From 43f03b8032087cc9b00ec7fdb818940e6c4d9677 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 26 Feb 2019 17:13:33 +0100 Subject: [PATCH 01/37] Incorporate individual support point radius. --- src/libslic3r/SLA/SLACommon.hpp | 6 +- src/libslic3r/SLA/SLASupportTree.cpp | 755 ++++++++++++------------ src/libslic3r/SLA/SLASupportTree.hpp | 20 +- src/libslic3r/SLA/SLASupportTreeIGL.cpp | 56 +- src/libslic3r/SLAPrint.cpp | 76 ++- 5 files changed, 462 insertions(+), 451 deletions(-) diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index f7c0acf332..b917db0d0e 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -2,6 +2,7 @@ #define SLACOMMON_HPP #include +#include // #define SLIC3R_SLA_NEEDS_WINDTREE @@ -36,7 +37,6 @@ struct SupportPoint { bool operator!=(const SupportPoint& sp) const { return !(sp == (*this)); } }; - /// An index-triangle structure for libIGL functions. Also serves as an /// alternative (raw) input format for the SLASupportTree /*struct EigenMesh3D { @@ -125,6 +125,8 @@ public: bool inside(const Vec3d& p) const; #endif /* SLIC3R_SLA_NEEDS_WINDTREE */ + + double squared_distance(const Vec3d& p, int& i, Vec3d& c) const; }; @@ -134,4 +136,4 @@ public: } // namespace Slic3r -#endif // SLASUPPORTTREE_HPP \ No newline at end of file +#endif // SLASUPPORTTREE_HPP diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 913e9bedab..9ba4afe427 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -551,18 +551,18 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers X, Y, Z }; -PointSet to_point_set(const std::vector &v) -{ - PointSet ret(v.size(), 3); - long i = 0; - for(const SupportPoint& support_point : v) { - ret.row(i)(0) = support_point.pos(0); - ret.row(i)(1) = support_point.pos(1); - ret.row(i)(2) = support_point.pos(2); - ++i; - } - return ret; -} +//PointSet to_point_set(const std::vector &v) +//{ +// PointSet ret(v.size(), 3); +// long i = 0; +// for(const SupportPoint& support_point : v) { +// ret.row(i)(X) = double(support_point.pos(X)); +// ret.row(i)(Y) = double(support_point.pos(Y)); +// ret.row(i)(Z) = double(support_point.pos(Z)); +// ++i; +// } +// return ret; +//} Vec3d model_coord(const ModelInstance& object, const Vec3f& mesh_coord) { return object.transform_vector(mesh_coord.cast()); @@ -751,9 +751,13 @@ double bridge_mesh_intersect(const Vec3d& s, return *mit; } -PointSet normals(const PointSet& points, const EigenMesh3D& mesh, +// Calculate the normals for the selected points (from 'points' set) on the +// mesh. This will call squared distance for each point. +PointSet normals(const PointSet& points, + const EigenMesh3D& mesh, double eps = 0.05, // min distance from edges - std::function throw_on_cancel = [](){}); + std::function throw_on_cancel = [](){}, + const std::vector& selected_points = {}); inline Vec2d to_vec2(const Vec3d& v3) { return {v3(X), v3(Y)}; @@ -763,11 +767,11 @@ bool operator==(const SpatElement& e1, const SpatElement& e2) { return e1.second == e2.second; } -// Clustering a set of points by the given criteria +// Clustering a set of points by the given criteria. ClusteredPoints cluster( const PointSet& points, std::function pred, - unsigned max_points = 0); + unsigned max_points = 0, const std::vector& indices = {}); // This class will hold the support tree meshes with some additional bookkeeping // as well. Various parts of the support geometry are stored separately and are @@ -783,7 +787,7 @@ ClusteredPoints cluster( // The support pad is considered an auxiliary geometry and is not part of the // merged mesh. It can be retrieved using a dedicated method (pad()) class SLASupportTree::Impl { - std::vector m_heads; + std::map m_heads; std::vector m_pillars; std::vector m_junctions; std::vector m_bridges; @@ -801,16 +805,19 @@ public: const Controller& ctl() const { return m_ctl; } - template Head& add_head(Args&&... args) { - m_heads.emplace_back(std::forward(args)...); - m_heads.back().id = long(m_heads.size() - 1); + template Head& add_head(unsigned id, Args&&... args) { + auto el = m_heads.emplace(std::piecewise_construct, + std::forward_as_tuple(id), + std::forward_as_tuple(std::forward(args)...)); + el.first->second.id = id; meshcache_valid = false; - return m_heads.back(); + return el.first->second; } - template Pillar& add_pillar(long headid, Args&&... args) { - assert(headid >= 0 && headid < m_heads.size()); - Head& head = m_heads[size_t(headid)]; + template Pillar& add_pillar(unsigned headid, Args&&... args) { + auto it = m_heads.find(headid); + assert(it != m_heads.end()); + Head& head = it->second; m_pillars.emplace_back(head, std::forward(args)...); Pillar& pillar = m_pillars.back(); pillar.id = long(m_pillars.size() - 1); @@ -824,14 +831,16 @@ public: const Head& pillar_head(long pillar_id) const { assert(pillar_id >= 0 && pillar_id < m_pillars.size()); const Pillar& p = m_pillars[size_t(pillar_id)]; - assert(p.starts_from_head && p.start_junction_id >= 0 && - p.start_junction_id < m_heads.size() ); - return m_heads[size_t(p.start_junction_id)]; + assert(p.starts_from_head && p.start_junction_id >= 0); + auto it = m_heads.find(unsigned(p.start_junction_id)); + assert(it != m_heads.end()); + return it->second; } - const Pillar& head_pillar(long headid) const { - assert(headid >= 0 && headid < m_heads.size()); - const Head& h = m_heads[size_t(headid)]; + const Pillar& head_pillar(unsigned headid) const { + auto it = m_heads.find(headid); + assert(it != m_heads.end()); + const Head& h = it->second; assert(h.pillar_id >= 0 && h.pillar_id < m_pillars.size()); return m_pillars[size_t(h.pillar_id)]; } @@ -858,8 +867,13 @@ public: return m_compact_bridges.back(); } - const std::vector& heads() const { return m_heads; } - Head& head(size_t idx) { meshcache_valid = false; return m_heads[idx]; } + const std::map& heads() const { return m_heads; } + Head& head(unsigned idx) { + meshcache_valid = false; + auto it = m_heads.find(idx); + assert(it != m_heads.end()); + return it->second; + } const std::vector& pillars() const { return m_pillars; } const std::vector& bridges() const { return m_bridges; } const std::vector& junctions() const { return m_junctions; } @@ -886,10 +900,10 @@ public: Contour3D merged; - for(auto& head : heads()) { + for(auto& headel : heads()) { if(m_ctl.stopcondition()) break; - if(head.is_valid()) - merged.merge(head.mesh); + if(headel.second.is_valid()) + merged.merge(headel.second.mesh); } for(auto& stick : pillars()) { @@ -1115,42 +1129,40 @@ Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { return (endp - startp).normalized(); } -/// Generation of the supports, entry point function. This is called from the -/// SLASupportTree constructor and throws an SLASupportsStoppedException if it -/// gets canceled by the ctl object's stopcondition functor. -bool SLASupportTree::generate(const PointSet &points, +bool SLASupportTree::generate(const std::vector &support_points, const EigenMesh3D& mesh, const SupportConfig &cfg, const Controller &ctl) { + // Prepare the support points in Eigen/IGL format as well, we will use it + // mostly in this form. + Eigen::MatrixXd points(support_points.size(), 3); long i = 0; + for(const SupportPoint& sp : support_points) { + points.row(i)(X) = double(sp.pos(X)); + points.row(i)(Y) = double(sp.pos(Y)); + points.row(i)(Z) = double(sp.pos(Z)); + ++i; + } + // If there are no input points there is no point in doing anything if(points.rows() == 0) return false; - PointSet filtered_points; // all valid support points - PointSet head_positions; // support points with pinhead - PointSet head_normals; // head normals - PointSet headless_positions; // headless support points - PointSet headless_normals; // headless support point normals + using PtIndices = std::vector; + const size_t pcount = size_t(points.rows()); - using IndexSet = std::vector; + PtIndices filtered_indices; // all valid support points + PtIndices head_indices; // support points with pinhead + PtIndices headless_indices; // headless support points + PtIndices onmodel_head_indices; // supp. pts. connecting to model - // Distances from head positions to ground or mesh touch points - std::vector head_heights; + PointSet support_normals(pcount, 3); // support point normals - // Indices of those who touch the ground - IndexSet ground_heads; + // Captures the height of the processed support points from the ground + // or the model surface + std::vector pt_heights(size_t(points.rows()), 0.0); - // Indices of those who don't touch the ground - IndexSet noground_heads; - - // Groups of the 'ground_head' indices that belong into one cluster. These - // are candidates to be connected to one pillar. - ClusteredPoints ground_connectors; - - // A help function to translate ground head index to the actual coordinates. - auto gnd_head_pt = [&ground_heads, &head_positions] (size_t idx) { - return Vec3d(head_positions.row(ground_heads[idx])); - }; + // Clusters of points which can reach the ground directly + std::vector pillar_clusters; // This algorithm uses the Impl class as its output stream. It will be // filled gradually with support elements (heads, pillars, bridges, ...) @@ -1176,49 +1188,44 @@ bool SLASupportTree::generate(const PointSet &points, // throw if canceled: It will be called many times so a shorthand will // come in handy. - auto& thr = ctl.cancelfn; + ThrowOnCancel thr = ctl.cancelfn; - // Filtering step: here we will discard inappropriate support points and - // decide the future of the appropriate ones. We will check if a pinhead - // is applicable and adjust its angle at each support point. - // We will also merge the support points that are just too close and can be - // considered as one. - auto filterfn = [thr] ( - const SupportConfig& cfg, - const PointSet& points, - const EigenMesh3D& mesh, - PointSet& filt_pts, - PointSet& head_norm, - PointSet& head_pos, - PointSet& headless_pos, - PointSet& headless_norm) + // Each step has a processing block in a form of a function. + + // Filtering step: here we will discard inappropriate support points + // and decide the future of the appropriate ones. We will check if a + // pinhead is applicable and adjust its angle at each support point. We + // will also merge the support points that are just too close and can + // be considered as one. + auto filter_fn = [thr](const SupportConfig& cfg, + const PointSet& points, + const EigenMesh3D& mesh, + PointSet& support_normals, + PtIndices& filtered_indices, + PtIndices& head_indices, + PtIndices& headless_indices) { // Get the points that are too close to each other and keep only the // first one - auto aliases = - cluster(points, - [thr](const SpatElement& p, const SpatElement& se) + auto aliases = cluster(points, + [thr](const SpatElement& p, const SpatElement& se) { - thr(); - return distance(p.first, se.first) < D_SP; + thr(); return distance(p.first, se.first) < D_SP; }, 2); - filt_pts.resize(Eigen::Index(aliases.size()), 3); - int count = 0; + + filtered_indices.resize(aliases.size()); + head_indices.reserve(aliases.size()); + headless_indices.reserve(aliases.size()); + unsigned count = 0; for(auto& a : aliases) { // Here we keep only the front point of the cluster. - filt_pts.row(count++) = points.row(a.front()); + filtered_indices[count++] = a.front(); } - thr(); - - // calculate the normals to the triangles belonging to filtered points - auto nmls = sla::normals(filt_pts, mesh, cfg.head_front_radius_mm, thr); - - head_norm.resize(count, 3); - head_pos.resize(count, 3); - headless_pos.resize(count, 3); - headless_norm.resize(count, 3); + // calculate the normals to the triangles for filtered points + auto nmls = sla::normals(points, mesh, cfg.head_front_radius_mm, + thr, filtered_indices); // Not all of the support points have to be a valid position for // support creation. The angle may be inappropriate or there may @@ -1231,8 +1238,7 @@ bool SLASupportTree::generate(const PointSet &points, using libnest2d::opt::StopCriteria; static const unsigned MAX_TRIES = 100; - int pcount = 0, hlcount = 0; - for(int i = 0; i < count; i++) { + for(unsigned i = 0; i < count; i++) { thr(); auto n = nmls.row(i); @@ -1255,7 +1261,7 @@ bool SLASupportTree::generate(const PointSet &points, polar = std::max(polar, 3*PI / 4); // save the head (pinpoint) position - Vec3d hp = filt_pts.row(i); + Vec3d hp = points.row(filtered_indices[i]); double w = cfg.head_width_mm + cfg.head_back_radius_mm + @@ -1276,9 +1282,10 @@ bool SLASupportTree::generate(const PointSet &points, mesh); if(t <= w) { - // Let's try to optimize this angle, there might be a viable - // normal that doesn't collide with the model geometry and - // its very close to the default. + + // Let's try to optimize this angle, there might be a + // viable normal that doesn't collide with the model + // geometry and its very close to the default. StopCriteria stc; stc.max_iterations = MAX_TRIES; @@ -1301,9 +1308,9 @@ bool SLASupportTree::generate(const PointSet &points, mesh); return score; }, - initvals(polar, azimuth), // let's start with what we have + initvals(polar, azimuth), // start with what we have bound(3*PI/4, PI), // Must not exceed the tilt limit - bound(-PI, PI) // azimuth can be a full range search + bound(-PI, PI) // azimuth can be a full search ); t = oresult.score; @@ -1314,221 +1321,122 @@ bool SLASupportTree::generate(const PointSet &points, std::cos(polar)).normalized(); } + // save the verified and corrected normal + support_normals.row(filtered_indices[i]) = nn; + if(t > w) { - head_pos.row(pcount) = hp; - - // save the verified and corrected normal - head_norm.row(pcount) = nn; - - ++pcount; + // mark the point for needing a head. + head_indices.emplace_back(filtered_indices[i]); } else if( polar >= 3*PI/4 ) { - // Headless supports do not tilt like the headed ones so // the normal should point almost to the ground. - headless_norm.row(hlcount) = nn; - headless_pos.row(hlcount++) = hp; + headless_indices.emplace_back(filtered_indices[i]); } } } - head_pos.conservativeResize(pcount, Eigen::NoChange); - head_norm.conservativeResize(pcount, Eigen::NoChange); - headless_pos.conservativeResize(hlcount, Eigen::NoChange); - headless_norm.conservativeResize(hlcount, Eigen::NoChange); + thr(); }; - // Pinhead creation: based on the filtering results, the Head objects will - // be constructed (together with their triangle meshes). - auto pinheadfn = [thr] ( - const SupportConfig& cfg, - PointSet& head_pos, - PointSet& nmls, - Result& result - ) + // Pinhead creation: based on the filtering results, the Head objects + // will be constructed (together with their triangle meshes). + auto pinheads_fn = [thr](const SupportConfig& cfg, + const std::vector support_points, + const PointSet& support_normals, + const PtIndices& head_indices, + Result& result) { - /* ******************************************************** */ /* Generating Pinheads */ /* ******************************************************** */ - for (int i = 0; i < head_pos.rows(); ++i) { + for (unsigned i : head_indices) { thr(); - result.add_head( + result.add_head(i, cfg.head_back_radius_mm, - cfg.head_front_radius_mm, + support_points[i].head_front_radius, cfg.head_width_mm, cfg.head_penetration_mm, - nmls.row(i), // dir - head_pos.row(i) // displacement + support_normals.row(i), // dir + support_points[i].pos.cast() // displacement ); } }; // Further classification of the support points with pinheads. If the - // ground is directly reachable through a vertical line parallel to the Z - // axis we consider a support point as pillar candidate. If touches the - // model geometry, it will be marked as non-ground facing and further steps - // will process it. Also, the pillars will be grouped into clusters that can - // be interconnected with bridges. Elements of these groups may or may not - // be interconnected. Here we only run the clustering algorithm. - auto classifyfn = [thr] ( - const SupportConfig& cfg, - const EigenMesh3D& mesh, - PointSet& head_pos, - IndexSet& gndidx, - IndexSet& nogndidx, - std::vector& gndheight, - ClusteredPoints& ground_clusters, - Result& result - ) + // ground is directly reachable through a vertical line parallel to the + // Z axis we consider a support point as pillar candidate. If touches + // the model geometry, it will be marked as non-ground facing and + // further steps will process it. Also, the pillars will be grouped + // into clusters that can be interconnected with bridges. Elements of + // these groups may or may not be interconnected. Here we only run the + // clustering algorithm. + auto classify_fn = [thr](const SupportConfig& cfg, + const PointSet& points, + const EigenMesh3D& mesh, + const PtIndices& head_indices, + PtIndices& onmodel_head_indices, + std::vector& pt_heights, + std::vector& pillar_clusters, + Result& result) { - /* ******************************************************** */ /* Classification */ /* ******************************************************** */ // We should first get the heads that reach the ground directly - gndheight.reserve(size_t(head_pos.rows())); - gndidx.reserve(size_t(head_pos.rows())); - nogndidx.reserve(size_t(head_pos.rows())); + pt_heights.reserve(head_indices.size()); + PtIndices ground_head_indices; + ground_head_indices.reserve(head_indices.size()); + onmodel_head_indices.reserve(head_indices.size()); // First we decide which heads reach the ground and can be full - // pillars and which shall be connected to the model surface (or search - // a suitable path around the surface that leads to the ground -- TODO) - for(unsigned i = 0; i < head_pos.rows(); i++) { + // pillars and which shall be connected to the model surface (or + // search a suitable path around the surface that leads to the + // ground -- TODO) + for(unsigned i : head_indices) { thr(); + auto& head = result.head(i); + Vec3d n(0, 0, -1); + double r = head.r_back_mm; + Vec3d headjp = head.junction_point(); - Vec3d dir(0, 0, -1); - bool accept = false; - int ri = 1; - double t = std::numeric_limits::infinity(); - double hw = head.width_mm; + // collision check + double t = bridge_mesh_intersect(headjp, n, r, mesh); + // Precise distance measurement + double tprec = ray_mesh_intersect(headjp, n, mesh); - { -// using libnest2d::opt::Method; -// using libnest2d::opt::bound; -// using libnest2d::opt::Optimizer; -// using libnest2d::opt::TOptimizer; -// using libnest2d::opt::StopCriteria; + // Save the distance from a surface in the Z axis downwards. It + // may be infinity but that is telling us that it touches the + // ground. + pt_heights.emplace_back(tprec); -// auto stopcond = [] () { return false; }; -// static const unsigned max_tries = 100; - -// auto objfunc = -// [&head](double polar, double azimuth, double width) -// { -// Vec3d nn(std::cos(azimuth) * std::sin(polar), -// std::sin(azimuth) * std::sin(polar), -// std::cos(polar)); - - -// }; - -// StopCriteria stc; -// stc.max_iterations = max_tries; -// stc.relative_score_difference = 1e-3; -// stc.stop_condition = stopcond; -// TOptimizer solver(stc); - } - - - // We will try to assign a pillar to all the pinheads. If a pillar - // would pierce the model surface, we will try to adjust slightly - // the head width so that the pillar can be deployed. - while(!accept && head.width_mm > 0) { - - Vec3d startpoint = head.junction_point(); - - // Collision detection - t = bridge_mesh_intersect(startpoint, dir, head.r_back_mm, mesh); - - // Precise distance measurement - double tprec = ray_mesh_intersect(startpoint, dir, mesh); - - if(std::isinf(tprec) && !std::isinf(t)) { - // This is a damned case where the pillar melts into the - // model but its center ray can reach the ground. We can - // not route this to the ground nor to the model surface. - head.width_mm = hw + (ri % 2? -1 : 1) * ri * head.r_back_mm; - } else { - if(!std::isinf(t) && !std::isinf(tprec) && - std::abs(tprec - t) > hw) - { - // In this case the head would scratch the model body - BOOST_LOG_TRIVIAL(warning) << "Head scratch detected."; - } - - accept = true; t = tprec; - - auto id = head.id; - // We need to regenerate the head geometry - head = Head(head.r_back_mm, - head.r_pin_mm, - head.width_mm, - head.penetration_mm, - head.dir, - head.tr); - head.id = id; - } - - ri++; - } - - // Save the distance from a surface in the Z axis downwards. It may - // be infinity but that is telling us that it touches the ground. - gndheight.emplace_back(t); - - if(accept) { - if(std::isinf(t)) gndidx.emplace_back(i); - else nogndidx.emplace_back(i); - } else { - // This is a serious issue. There was no way to deploy a pillar - // for the given pinhead. The whole thing has to be discarded - // leaving the model potentially unprintable. - // - // TODO: In the future this has to be solved by searching for - // a path in 3D space from this support point to a suitable - // pillar position or an existing pillar. - // As a workaround we could mark this head as "sidehead only" - // let it go trough the nearby pillar search in the next step. - BOOST_LOG_TRIVIAL(warning) << "A support point at " - << head.tr.transpose() - << " had to be discarded as there is" - << " nowhere to route it."; - head.invalidate(); - } + if(std::isinf(t)) ground_head_indices.emplace_back(i); + else onmodel_head_indices.emplace_back(i); } - // Transform the ground facing point indices top actual coordinates. - PointSet gnd(gndidx.size(), 3); - for(size_t i = 0; i < gndidx.size(); i++) - gnd.row(long(i)) = head_pos.row(gndidx[i]); - - // We want to search for clusters of points that are far enough 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. + // We want to search for clusters of points that are far enough + // 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 d_base = 2*cfg.base_radius_mm; - ground_clusters = - cluster( - gnd, - [d_base, thr](const SpatElement& p, const SpatElement& s) + pillar_clusters = cluster(points, + [thr, d_base](const SpatElement& p, const SpatElement& s) { thr(); return distance(Vec2d(p.first(X), p.first(Y)), Vec2d(s.first(X), s.first(Y))) < d_base; - }, 3); // max 3 heads to connect to one centroid + }, 3, ground_head_indices); // max 3 heads to connect to one pillar }; // Helper function for interconnecting two pillars with zig-zag bridges. // This is not an individual step. - auto interconnect = [&cfg]( - const Pillar& pillar, - const Pillar& nextpillar, - const EigenMesh3D& emesh, - Result& result) + auto interconnect = [&cfg](const Pillar& pillar, + const Pillar& nextpillar, + const EigenMesh3D& m, + Result& result) { const Head& phead = result.pillar_head(pillar.id); const Head& nextphead = result.pillar_head(nextpillar.id); @@ -1538,11 +1446,11 @@ bool SLASupportTree::generate(const PointSet &points, Vec3d ej = nextpillar.endpoint; double pillar_dist = distance(Vec2d{sj(X), sj(Y)}, Vec2d{ej(X), ej(Y)}); - double zstep = pillar_dist * std::tan(-cfg.tilt); + double zstep = pillar_dist * std::tan(-cfg.head_slope); ej(Z) = sj(Z) + zstep; - double chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r, emesh); - double bridge_distance = pillar_dist / std::cos(-cfg.tilt); + double chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r, m); + double bridge_distance = pillar_dist / std::cos(-cfg.head_slope); // If the pillars are so close that they touch each other, // there is no need to bridge them together. @@ -1569,8 +1477,7 @@ bool SLASupportTree::generate(const PointSet &points, // need to check collision for the cross stick double backchkd = bridge_mesh_intersect(bsj, dirv(bsj, bej), - pillar.r, - emesh); + pillar.r, m); if(backchkd >= bridge_distance) { @@ -1580,21 +1487,21 @@ bool SLASupportTree::generate(const PointSet &points, } sj.swap(ej); ej(Z) = sj(Z) + zstep; - chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r, emesh); + chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r, m); } }; - // Step: Routing the ground connected pinheads, and interconnecting them - // with additional (angled) bridges. Not all of these pinheads will be - // a full pillar (ground connected). Some will connect to a nearby pillar - // using a bridge. The max number of such side-heads for a central pillar - // is limited to avoid bad weight distribution. - auto routing_ground_fn = [gnd_head_pt, interconnect, thr]( - const SupportConfig& cfg, - const ClusteredPoints& gnd_clusters, - const IndexSet& gndidx, - const EigenMesh3D& emesh, - Result& result) + // Step: Routing the ground connected pinheads, and interconnecting + // them with additional (angled) bridges. Not all of these pinheads + // will be a full pillar (ground connected). Some will connect to a + // nearby pillar using a bridge. The max number of such side-heads for + // a central pillar is limited to avoid bad weight distribution. + auto routing_ground_fn = + [thr, interconnect](const SupportConfig& cfg, + const PointSet& points, + const std::vector& pillar_clusters, + const EigenMesh3D& emesh, + Result& result) { const double hbr = cfg.head_back_radius_mm; const double pradius = cfg.head_back_radius_mm; @@ -1602,20 +1509,23 @@ bool SLASupportTree::generate(const PointSet &points, const double gndlvl = result.ground_level; ClusterEl cl_centroids; - cl_centroids.reserve(gnd_clusters.size()); + cl_centroids.reserve(pillar_clusters.size()); SpatIndex pheadindex; // spatial index for the junctions - for(auto& cl : gnd_clusters) { thr(); - // place all the centroid head positions into the index. We will - // query for alternative pillar positions. If a sidehead cannot - // connect to the cluster centroid, we have to search for another - // head with a full pillar. Also when there are two elements in the - // cluster, the centroid is arbitrary and the sidehead is allowed to - // connect to a nearby pillar to increase structural stability. + for(auto& cl : pillar_clusters) { thr(); + // place all the centroid head positions into the index. We + // will query for alternative pillar positions. If a sidehead + // cannot connect to the cluster centroid, we have to search + // for another head with a full pillar. Also when there are two + // elements in the cluster, the centroid is arbitrary and the + // sidehead is allowed to connect to a nearby pillar to + // increase structural stability. + if(cl.empty()) continue; // get the current cluster centroid - long lcid = cluster_centroid(cl, gnd_head_pt, + long lcid = cluster_centroid(cl, + [&points](size_t idx) { return points.row(long(idx)); }, [thr](const Vec3d& p1, const Vec3d& p2) { thr(); @@ -1627,11 +1537,11 @@ bool SLASupportTree::generate(const PointSet &points, cl_centroids.push_back(unsigned(cid)); - unsigned hid = gndidx[cl[cid]]; // Head index + + unsigned hid = cl[cid]; // Head ID Head& h = result.head(hid); h.transform(); Vec3d p = h.junction_point(); p(Z) = gndlvl; - pheadindex.insert(p, hid); } @@ -1639,32 +1549,30 @@ bool SLASupportTree::generate(const PointSet &points, // sidepoints with the cluster centroid (which is a ground pillar) // or a nearby pillar if the centroid is unreachable. size_t ci = 0; - for(auto cl : gnd_clusters) { thr(); + for(auto cl : pillar_clusters) { thr(); auto cidx = cl_centroids[ci]; cl_centroids[ci++] = cl[cidx]; - size_t index_to_heads = gndidx[cl[cidx]]; - auto& head = result.head(index_to_heads); + auto& head = result.head(cl[cidx]); Vec3d startpoint = head.junction_point(); auto endpoint = startpoint; endpoint(Z) = gndlvl; // Create the central pillar of the cluster with its base on the // ground - result.add_pillar(long(index_to_heads), endpoint, pradius) + result.add_pillar(unsigned(head.id), endpoint, pradius) .add_base(cfg.base_height_mm, cfg.base_radius_mm); // Process side point in current cluster - cl.erase(cl.begin() + cidx); // delete the centroid before looping - - // TODO: dont consider the cluster centroid but calculate a central - // position where the pillar can be placed. this way the weight - // is distributed more effectively on the pillar. + cl.erase(cl.begin() + cidx); // delete the centroid + // TODO: don't consider the cluster centroid but calculate a + // central position where the pillar can be placed. this way + // the weight is distributed more effectively on the pillar. auto search_nearest = - [&thr, &cfg, &result, &emesh, maxbridgelen, gndlvl, pradius] - (SpatIndex& spindex, const Vec3d& jsh) + [thr, &cfg, &result, &emesh, maxbridgelen, gndlvl, pradius] + (SpatIndex& spindex, const Vec3d& jsh) { long nearest_id = -1; const double max_len = maxbridgelen / 2; @@ -1676,18 +1584,19 @@ bool SLASupportTree::generate(const PointSet &points, Vec3d qp(jsh(X), jsh(Y), gndlvl); auto ne = spindex.nearest(qp, 1).front(); - const Head& nearhead = result.heads()[ne.second]; + const Head& nearhead = result.head(ne.second); Vec3d jh = nearhead.junction_point(); Vec3d jp = jsh; double dist2d = distance(qp, ne.first); // Bridge endpoint on the main pillar - Vec3d jn(jh(X), jh(Y), jp(Z) + dist2d*std::tan(-cfg.tilt)); + Vec3d jn(jh(X), jh(Y), jp(Z) + + dist2d * std::tan(-cfg.head_slope)); if(jn(Z) > jh(Z)) { - // If the sidepoint cannot connect to the pillar from - // its head junction, then just skip this pillar. + // If the sidepoint cannot connect to the pillar + // from its head junction, just skip this pillar. spindex.remove(ne); continue; } @@ -1708,7 +1617,7 @@ bool SLASupportTree::generate(const PointSet &points, }; for(auto c : cl) { thr(); - auto& sidehead = result.head(gndidx[c]); + auto& sidehead = result.head(c); sidehead.transform(); Vec3d jsh = sidehead.junction_point(); @@ -1720,7 +1629,7 @@ bool SLASupportTree::generate(const PointSet &points, if(nearest_id < 0) { // Could not find a pillar, create one Vec3d jp = jsh; jp(Z) = gndlvl; - result.add_pillar(sidehead.id, jp, pradius). + result.add_pillar(unsigned(sidehead.id), jp, pradius). add_base(cfg.base_height_mm, cfg.base_radius_mm); // connects to ground, eligible for bridging @@ -1728,45 +1637,48 @@ bool SLASupportTree::generate(const PointSet &points, } else { // Creating the bridge to the nearest pillar - const Head& nearhead = result.heads()[size_t(nearest_id)]; + auto nearest_uid = unsigned(nearest_id); + const Head& nearhead = result.head(nearest_uid); Vec3d jp = jsh; Vec3d jh = nearhead.junction_point(); double d = distance(Vec2d{jp(X), jp(Y)}, Vec2d{jh(X), jh(Y)}); - Vec3d jn(jh(X), jh(Y), jp(Z) + d*std::tan(-cfg.tilt)); + Vec3d jn(jh(X), jh(Y), jp(Z) + + d * std::tan(-cfg.head_slope)); if(jn(Z) > jh(Z)) { double hdiff = jn(Z) - jh(Z); jp(Z) -= hdiff; jn(Z) -= hdiff; - // pillar without base, this does not connect to ground. - result.add_pillar(sidehead.id, jp, pradius); + // pillar without base, doesn't connect to ground. + result.add_pillar(nearest_uid, jp, pradius); } if(jp(Z) < jsh(Z)) result.add_junction(jp, hbr); if(jn(Z) >= jh(Z)) result.add_junction(jn, hbr); - double r_pillar = sidehead.request_pillar_radius(pradius); - result.add_bridge(jp, jn, r_pillar); + + result.add_bridge(jp, jn, + sidehead.request_pillar_radius(pradius)); } } } - // We will break down the pillar positions in 2D into concentric rings. - // Connecting the pillars belonging to the same ring will prevent - // bridges from crossing each other. After bridging the rings we can - // create bridges between the rings without the possibility of crossing - // bridges. Two pillars will be bridged with X shaped stick pairs. - // If they are really close to each other, than only one stick will be - // used in zig-zag mode. + // We will break down the pillar positions in 2D into concentric + // rings. Connecting the pillars belonging to the same ring will + // prevent bridges from crossing each other. After bridging the + // rings we can create bridges between the rings without the + // possibility of crossing bridges. Two pillars will be bridged + // with X shaped stick pairs. If they are really close to each + // other, than only one stick will be used in zig-zag mode. // Breaking down the points into rings will be done with a modified // convex hull algorithm (see pts_convex_hull()), that works for // collinear points as well. If the points are on the same surface, - // they can be part of an imaginary line segment for which the convex - // hull is not defined. I this case it is enough to sort the points - // spatially and create the bridge stick from the one endpoint to + // they can be part of an imaginary line segment for which the + // convex hull is not defined. I this case it is enough to sort the + // points spatially and create the bridge stick from one endpoint to // another. ClusterEl rem = cl_centroids; @@ -1777,8 +1689,8 @@ bool SLASupportTree::generate(const PointSet &points, std::sort(rem.begin(), rem.end()); auto newring = pts_convex_hull(rem, - [gnd_head_pt](unsigned i) { - auto&& p = gnd_head_pt(i); + [&points](unsigned i) { + auto&& p = points.row(i); return Vec2d(p(X), p(Y)); // project to 2D in along Z axis }); @@ -1786,7 +1698,7 @@ bool SLASupportTree::generate(const PointSet &points, // inner ring is now in 'newring' and outer ring is in 'ring' SpatIndex innerring; for(unsigned i : newring) { thr(); - const Pillar& pill = result.head_pillar(gndidx[i]); + const Pillar& pill = result.head_pillar(i); assert(pill.id >= 0); innerring.insert(pill.endpoint, unsigned(pill.id)); } @@ -1795,7 +1707,7 @@ bool SLASupportTree::generate(const PointSet &points, // inner ring and connect them. This will create the spider web // fashioned connections between pillars for(unsigned i : ring) { thr(); - const Pillar& outerpill = result.head_pillar(gndidx[i]); + const Pillar& outerpill = result.head_pillar(i); auto res = innerring.nearest(outerpill.endpoint, 1); if(res.empty()) continue; @@ -1821,8 +1733,8 @@ bool SLASupportTree::generate(const PointSet &points, ++it, ++next) { thr(); - const Pillar& pillar = result.head_pillar(gndidx[*it]); - const Pillar& nextpillar = result.head_pillar(gndidx[*next]); + const Pillar& pillar = result.head_pillar(*it); + const Pillar& nextpillar = result.head_pillar(*next); interconnect(pillar, nextpillar, emesh, result); } @@ -1830,9 +1742,10 @@ bool SLASupportTree::generate(const PointSet &points, std::sort(sring.begin(), sring.end()); std::set_difference(rem.begin(), rem.end(), sring.begin(), sring.end(), - std::back_inserter(tmp)); + std::back_inserter(tmp)); rem.swap(tmp); } + }; // Step: routing the pinheads that would connect to the model surface @@ -1842,17 +1755,71 @@ bool SLASupportTree::generate(const PointSet &points, // nearby pillar that can hold the supported weight. auto routing_nongnd_fn = [thr]( const SupportConfig& cfg, - const std::vector& gndheight, - const IndexSet& nogndidx, + const std::vector& pt_heights, + const PtIndices& nonground_head_indices, + const EigenMesh3D& mesh, Result& result) { // TODO: connect these to the ground pillars if possible - for(auto idx : nogndidx) { thr(); - double gh = gndheight[idx]; + for(auto idx : nonground_head_indices) { thr(); + double gh = pt_heights[idx]; double base_width = cfg.head_width_mm; auto& head = result.head(idx); + if(std::isinf(gh)) { // in this case the the pillar geometry + head.invalidate(); continue; +// // is partially inside the model geometry. We cannot go +// // straight down but at an angle. We will search for a suitable +// // direction with the optimizer, optimizing for the smallest +// // difference between the bridge body hit distance and the +// // bridge center hit distance. + +// // Get the spherical representation of the normal. its easier to +// // work with. +// double z = head.dir(Z); +// double r = 1.0; // for normalized vector +// double polar = std::acos(z / r); +// double azimuth = std::atan2(head.dir(Y), head.dir(X)); + +// using libnest2d::opt::bound; +// using libnest2d::opt::initvals; +// using libnest2d::opt::SimplexOptimizer; +// using libnest2d::opt::StopCriteria; + +// StopCriteria stc; +// stc.max_iterations = 100; +// stc.relative_score_difference = 1e-3; +// stc.stop_score = head.r_pin_mm; +// SimplexOptimizer solver(stc); + +// auto oresult = solver.optimize_max( +// [&head, &mesh](double plr, double azm) +// { +// auto n = Vec3d(std::cos(azm) * std::sin(plr), +// std::sin(azm) * std::sin(plr), +// std::cos(plr)).normalized(); + +// double score = bridge_mesh_intersect(head.junction_point(), +// n, +// head.r_back_mm, +// mesh); +// return score; +// }, +// initvals(polar, azimuth), // let's start with what we have +// bound(3*PI/4, PI), // Must not exceed the slope limit +// bound(-PI, PI) // azimuth can be a full range search +// ); + +// t = oresult.score; +// polar = std::get<0>(oresult.optimum); +// azimuth = std::get<1>(oresult.optimum); +// nn = Vec3d(std::cos(azimuth) * std::sin(polar), +// std::sin(azimuth) * std::sin(polar), +// std::cos(polar)).normalized(); + + } + // In this case there is no room for the base pinhead. if(gh < head.fullwidth()) { double min_l = @@ -1904,24 +1871,26 @@ bool SLASupportTree::generate(const PointSet &points, // Step: process the support points where there is not enough space for a // full pinhead. In this case we will use a rounded sphere as a touching // point and use a thinner bridge (let's call it a stick). - auto process_headless = [thr]( - const SupportConfig& cfg, - const PointSet& headless_pts, - const PointSet& headless_norm, + auto process_headless_fn = [thr]( + const std::vector& support_points, + const PointSet& support_normals, + const PtIndices& headless_indices, const EigenMesh3D& emesh, Result& result) { // For now we will just generate smaller headless sticks with a sharp // ending point that connects to the mesh surface. - const double R = cfg.headless_pillar_radius_mm; - const double HWIDTH_MM = R/3; // We will sink the pins into the model surface for a distance of 1/3 of // the pin radius - for(int i = 0; i < headless_pts.rows(); i++) { thr(); - Vec3d sph = headless_pts.row(i); // Exact support position - Vec3d n = headless_norm.row(i); // mesh outward normal + for(unsigned i : headless_indices) { thr(); + + const auto R = double(support_points[i].head_front_radius); + const double HWIDTH_MM = R/3; + + Vec3d sph = support_points[i].pos.cast(); // Exact support position + Vec3d n = support_normals.row(i); // mesh outward normal Vec3d sp = sph - n * HWIDTH_MM; // stick head start point Vec3d dir = {0, 0, -1}; @@ -1959,47 +1928,62 @@ bool SLASupportTree::generate(const PointSet &points, // Here we can easily track what goes in and what comes out of each step: // (see the cref-s as inputs and ref-s as outputs) std::array, NUM_STEPS> program = { - [] () { - // Begin... - // Potentially clear up the shared data (not needed for now) - }, + [] () { + // Begin... + // Potentially clear up the shared data (not needed for now) + }, - // Filtering unnecessary support points - bind(filterfn, cref(cfg), cref(points), cref(mesh), - ref(filtered_points), ref(head_normals), - ref(head_positions), ref(headless_positions), ref(headless_normals)), + bind(filter_fn, + // inputs: + cref(cfg), cref(points), cref(mesh), + // outputs: + ref(support_normals), ref(filtered_indices), ref(head_indices), + ref(headless_indices)), - // Pinhead generation - bind(pinheadfn, cref(cfg), - ref(head_positions), ref(head_normals), ref(result)), + bind(pinheads_fn, + // inputs: + cref(cfg), cref(support_points), cref(support_normals), + cref(head_indices), + // outputs: + ref(result)), - // Classification of support points - bind(classifyfn, cref(cfg), cref(mesh), - ref(head_positions), ref(ground_heads), ref(noground_heads), - ref(head_heights), ref(ground_connectors), ref(result)), + bind(classify_fn, + // inputs: + cref(cfg), cref(points), cref(mesh), cref(head_indices), + // outputs: + ref(onmodel_head_indices), ref(pt_heights), ref(pillar_clusters), + ref(result)), - // Routing ground connecting clusters - bind(routing_ground_fn, - cref(cfg), cref(ground_connectors), cref(ground_heads), cref(mesh), - ref(result)), + bind(routing_ground_fn, + // inputs: + cref(cfg), cref(points), cref(pillar_clusters), cref(mesh), + // outputs: + ref(result)), - // routing non ground connecting support points - bind(routing_nongnd_fn, cref(cfg), cref(head_heights), cref(noground_heads), - ref(result)), + bind(routing_nongnd_fn, + // inputs: + cref(cfg), cref(pt_heights), cref(onmodel_head_indices), cref(mesh), + // outputs: + ref(result)), - bind(process_headless, - cref(cfg), cref(headless_positions), - cref(headless_normals), cref(mesh), - ref(result)), - [] () { - // Done - }, - [] () { - // Halt - }, - [] () { - // Abort - } + bind(process_headless_fn, + // inputs: + cref(support_points), cref(support_normals), + cref(headless_indices), cref(mesh), + // outputs: + ref(result)), + + [] () { + // Done + }, + + [] () { + // Halt + }, + + [] () { + // Abort + } }; if(cfg.ground_facing_only) { // Delete the non-gnd steps if necessary @@ -2067,8 +2051,6 @@ bool SLASupportTree::generate(const PointSet &points, program[pc](); } - if(pc == ABORT) throw SLASupportsStoppedException(); - return pc == ABORT; } @@ -2131,7 +2113,7 @@ void SLASupportTree::remove_pad() m_impl->remove_pad(); } -SLASupportTree::SLASupportTree(const PointSet &points, +SLASupportTree::SLASupportTree(const std::vector &points, const EigenMesh3D& emesh, const SupportConfig &cfg, const Controller &ctl): @@ -2152,8 +2134,5 @@ SLASupportTree &SLASupportTree::operator=(const SLASupportTree &c) SLASupportTree::~SLASupportTree() {} -SLASupportsStoppedException::SLASupportsStoppedException(): - std::runtime_error("") {} - } } diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index c29b2a5712..d969519123 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -74,7 +74,7 @@ struct SupportConfig { double base_height_mm = 1.0; // The default angle for connecting support sticks and junctions. - double tilt = M_PI/4; + double head_slope = M_PI/4; // The max length of a bridge in mm double max_bridge_length_mm = 15.0; @@ -116,18 +116,11 @@ using PointSet = Eigen::MatrixXd; //EigenMesh3D to_eigenmesh(const ModelObject& model); // Simple conversion of 'vector of points' to an Eigen matrix -PointSet to_point_set(const std::vector&); +//PointSet to_point_set(const std::vector&); /* ************************************************************************** */ -/// Just a wrapper to the runtime error to be recognizable in try blocks -class SLASupportsStoppedException: public std::runtime_error { -public: - using std::runtime_error::runtime_error; - SLASupportsStoppedException(); -}; - /// The class containing mesh data for the generated supports. class SLASupportTree { class Impl; @@ -141,7 +134,12 @@ class SLASupportTree { const Controller&); /// Generate the 3D supports for a model intended for SLA print. - bool generate(const PointSet& pts, + bool generate(const std::vector& pts, + const EigenMesh3D& mesh, + const SupportConfig& cfg = {}, + const Controller& ctl = {}); + + bool _generate(const std::vector& pts, const EigenMesh3D& mesh, const SupportConfig& cfg = {}, const Controller& ctl = {}); @@ -149,7 +147,7 @@ public: SLASupportTree(); - SLASupportTree(const PointSet& pts, + SLASupportTree(const std::vector& pts, const EigenMesh3D& em, const SupportConfig& cfg = {}, const Controller& ctl = {}); diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index 25638fe692..65fa982861 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -17,6 +17,8 @@ #include #include +#include + #include "SLASpatIndex.hpp" #include "ClipperUtils.hpp" @@ -186,6 +188,15 @@ bool EigenMesh3D::inside(const Vec3d &p) const { } #endif /* SLIC3R_SLA_NEEDS_WINDTREE */ +double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { + double sqdst = 0; + Eigen::Matrix pp = p; + Eigen::Matrix cc; + sqdst = m_aabb->squared_distance(m_V, m_F, pp, i, cc); + c = cc; + return sqdst; +} + /* **************************************************************************** * Misc functions * ****************************************************************************/ @@ -208,21 +219,40 @@ template double distance(const Vec& pp1, const Vec& pp2) { PointSet normals(const PointSet& points, const EigenMesh3D& mesh, double eps, - std::function throw_on_cancel) + std::function throw_on_cancel, + const std::vector& pt_indices = {}) { if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0) return {}; - Eigen::VectorXd dists; - Eigen::VectorXi I; - PointSet C; + std::vector range = pt_indices; + if(range.empty()) { + range.resize(size_t(points.rows()), 0); + std::iota(range.begin(), range.end(), 0); + } - igl::point_mesh_squared_distance( points, mesh.V(), mesh.F(), dists, I, C); + std::vector dists(range.size()); + std::vector I(range.size()); + PointSet C(Eigen::Index(range.size()), 3); - PointSet ret(I.rows(), 3); - for(int i = 0; i < I.rows(); i++) { + tbb::parallel_for(size_t(0), range.size(), + [&range, &mesh, &points, &dists, &I, &C](size_t idx) + { + auto eidx = Eigen::Index(range[idx]); + int i = 0; + Vec3d c; + dists[idx] = mesh.squared_distance(points.row(eidx), i, c); + C.row(eidx) = c; + I[range[idx]] = i; + }); + +// igl::point_mesh_squared_distance( points, mesh.V(), mesh.F(), dists, I, C); + + + PointSet ret(I.size(), 3); + for(unsigned i = 0; i < I.size(); i++) { throw_on_cancel(); - auto idx = I(i); + auto idx = I[i]; auto trindex = mesh.F().row(idx); const Vec3d& p1 = mesh.V().row(trindex(0)); @@ -332,7 +362,7 @@ PointSet normals(const PointSet& points, ClusteredPoints cluster( const sla::PointSet& points, std::function pred, - unsigned max_points = 0) + unsigned max_points = 0, const std::vector& indices = {}) { namespace bgi = boost::geometry::index; @@ -342,8 +372,12 @@ ClusteredPoints cluster( Index3D sindex; // Build the index - for(unsigned idx = 0; idx < points.rows(); idx++) - sindex.insert( std::make_pair(points.row(idx), idx)); + if(indices.empty()) + for(unsigned idx = 0; idx < points.rows(); idx++) + sindex.insert( std::make_pair(points.row(idx), idx)); + else + for(unsigned idx : indices) + sindex.insert( std::make_pair(points.row(idx), idx)); using Elems = std::vector; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 827846b71e..4efff03dae 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -521,7 +521,7 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { scfg.head_penetration_mm = c.support_head_penetration.getFloat(); scfg.head_width_mm = c.support_head_width.getFloat(); scfg.object_elevation_mm = c.support_object_elevation.getFloat(); - scfg.tilt = c.support_critical_angle.getFloat() * PI / 180.0 ; + scfg.head_slope = c.support_critical_angle.getFloat() * PI / 180.0 ; scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat(); scfg.headless_pillar_radius_mm = 0.375*c.support_pillar_diameter.getFloat(); switch(c.support_pillar_connection_mode.getInt()) { @@ -684,54 +684,52 @@ void SLAPrint::process() return; } - try { - sla::SupportConfig scfg = make_support_cfg(po.m_config); - sla::Controller ctl; + sla::SupportConfig scfg = make_support_cfg(po.m_config); + sla::Controller ctl; - // some magic to scale the status values coming from the support - // tree creation into the whole print process - auto stfirst = OBJ_STEP_LEVELS.begin(); - auto stthis = stfirst + slaposSupportTree; - // we need to add up the status portions until this operation - int init = std::accumulate(stfirst, stthis, 0); - init = int(init * ostepd); // scale the init portion + // some magic to scale the status values coming from the support + // tree creation into the whole print process + auto stfirst = OBJ_STEP_LEVELS.begin(); + auto stthis = stfirst + slaposSupportTree; + // we need to add up the status portions until this operation + int init = std::accumulate(stfirst, stthis, 0); + init = int(init * ostepd); // scale the init portion - // scaling for the sub operations - double d = *stthis / (objcount * 100.0); + // scaling for the sub operations + double d = *stthis / (objcount * 100.0); - ctl.statuscb = [this, init, d](unsigned st, const std::string& msg) - { - //FIXME this status line scaling does not seem to be correct. - // How does it account for an increasing object index? - report_status(*this, int(init + st*d), msg); - }; + ctl.statuscb = [this, init, d](unsigned st, const std::string& msg) + { + //FIXME this status line scaling does not seem to be correct. + // How does it account for an increasing object index? + report_status(*this, int(init + st*d), msg); + }; - ctl.stopcondition = [this](){ return canceled(); }; - ctl.cancelfn = [this]() { throw_if_canceled(); }; + ctl.stopcondition = [this](){ return canceled(); }; + ctl.cancelfn = [this]() { throw_if_canceled(); }; - po.m_supportdata->support_tree_ptr.reset( - new SLASupportTree(sla::to_point_set(po.m_supportdata->support_points), - po.m_supportdata->emesh, scfg, ctl)); + po.m_supportdata->support_tree_ptr.reset( + new SLASupportTree(po.m_supportdata->support_points, + po.m_supportdata->emesh, scfg, ctl)); - // Create the unified mesh - auto rc = SlicingStatus::RELOAD_SCENE; + throw_if_canceled(); - // This is to prevent "Done." being displayed during merged_mesh() - report_status(*this, -1, L("Visualizing supports")); - po.m_supportdata->support_tree_ptr->merged_mesh(); + // Create the unified mesh + auto rc = SlicingStatus::RELOAD_SCENE; - BOOST_LOG_TRIVIAL(debug) << "Processed support point count " - << po.m_supportdata->support_points.size(); + // This is to prevent "Done." being displayed during merged_mesh() + report_status(*this, -1, L("Visualizing supports")); + po.m_supportdata->support_tree_ptr->merged_mesh(); - // Check the mesh for later troubleshooting. - if(po.support_mesh().empty()) - BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; + BOOST_LOG_TRIVIAL(debug) << "Processed support point count " + << po.m_supportdata->support_points.size(); + + // Check the mesh for later troubleshooting. + if(po.support_mesh().empty()) + BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; + + report_status(*this, -1, L("Visualizing supports"), rc); - report_status(*this, -1, L("Visualizing supports"), rc); - } catch(sla::SLASupportsStoppedException&) { - // no need to rethrow - // throw_if_canceled(); - } }; // This step generates the sla base pad From 359de84a05e4ff12465103d7109b53bd8d3cd5c8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 26 Feb 2019 18:09:33 +0100 Subject: [PATCH 02/37] Fixing issues from code cleanup --- src/libslic3r/SLA/SLASupportTree.cpp | 24 +++++++-------- src/libslic3r/SLA/SLASupportTreeIGL.cpp | 40 +++++++++---------------- 2 files changed, 25 insertions(+), 39 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 9ba4afe427..d0c33205ac 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -1150,7 +1150,6 @@ bool SLASupportTree::generate(const std::vector &support_points, using PtIndices = std::vector; const size_t pcount = size_t(points.rows()); - PtIndices filtered_indices; // all valid support points PtIndices head_indices; // support points with pinhead PtIndices headless_indices; // headless support points PtIndices onmodel_head_indices; // supp. pts. connecting to model @@ -1201,7 +1200,6 @@ bool SLASupportTree::generate(const std::vector &support_points, const PointSet& points, const EigenMesh3D& mesh, PointSet& support_normals, - PtIndices& filtered_indices, PtIndices& head_indices, PtIndices& headless_indices) { @@ -1213,14 +1211,13 @@ bool SLASupportTree::generate(const std::vector &support_points, thr(); return distance(p.first, se.first) < D_SP; }, 2); - - filtered_indices.resize(aliases.size()); + PtIndices filtered_indices; + filtered_indices.reserve(aliases.size()); head_indices.reserve(aliases.size()); headless_indices.reserve(aliases.size()); - unsigned count = 0; for(auto& a : aliases) { // Here we keep only the front point of the cluster. - filtered_indices[count++] = a.front(); + filtered_indices.emplace_back(a.front()); } // calculate the normals to the triangles for filtered points @@ -1238,7 +1235,9 @@ bool SLASupportTree::generate(const std::vector &support_points, using libnest2d::opt::StopCriteria; static const unsigned MAX_TRIES = 100; - for(unsigned i = 0; i < count; i++) { + for(unsigned i = 0, fidx = filtered_indices[0]; + i < filtered_indices.size(); ++i, fidx = filtered_indices[i]) + { thr(); auto n = nmls.row(i); @@ -1261,7 +1260,7 @@ bool SLASupportTree::generate(const std::vector &support_points, polar = std::max(polar, 3*PI / 4); // save the head (pinpoint) position - Vec3d hp = points.row(filtered_indices[i]); + Vec3d hp = points.row(i); double w = cfg.head_width_mm + cfg.head_back_radius_mm + @@ -1322,15 +1321,15 @@ bool SLASupportTree::generate(const std::vector &support_points, } // save the verified and corrected normal - support_normals.row(filtered_indices[i]) = nn; + support_normals.row(fidx) = nn; if(t > w) { // mark the point for needing a head. - head_indices.emplace_back(filtered_indices[i]); + head_indices.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. - headless_indices.emplace_back(filtered_indices[i]); + headless_indices.emplace_back(fidx); } } } @@ -1937,8 +1936,7 @@ bool SLASupportTree::generate(const std::vector &support_points, // inputs: cref(cfg), cref(points), cref(mesh), // outputs: - ref(support_normals), ref(filtered_indices), ref(head_indices), - ref(headless_indices)), + ref(support_normals), ref(head_indices), ref(headless_indices)), bind(pinheads_fn, // inputs: diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index 65fa982861..04b5a72070 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -219,7 +219,7 @@ template double distance(const Vec& pp1, const Vec& pp2) { PointSet normals(const PointSet& points, const EigenMesh3D& mesh, double eps, - std::function throw_on_cancel, + std::function thr, // throw on cancel const std::vector& pt_indices = {}) { if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0) @@ -231,29 +231,19 @@ PointSet normals(const PointSet& points, std::iota(range.begin(), range.end(), 0); } - std::vector dists(range.size()); - std::vector I(range.size()); - PointSet C(Eigen::Index(range.size()), 3); + PointSet ret(range.size(), 3); tbb::parallel_for(size_t(0), range.size(), - [&range, &mesh, &points, &dists, &I, &C](size_t idx) + [&ret, &range, &mesh, &points, thr, eps](size_t ridx) { - auto eidx = Eigen::Index(range[idx]); - int i = 0; - Vec3d c; - dists[idx] = mesh.squared_distance(points.row(eidx), i, c); - C.row(eidx) = c; - I[range[idx]] = i; - }); + thr(); + auto eidx = Eigen::Index(range[ridx]); + int faceid = 0; + Vec3d p; -// igl::point_mesh_squared_distance( points, mesh.V(), mesh.F(), dists, I, C); + mesh.squared_distance(points.row(eidx), faceid, p); - - PointSet ret(I.size(), 3); - for(unsigned i = 0; i < I.size(); i++) { - throw_on_cancel(); - auto idx = I[i]; - auto trindex = mesh.F().row(idx); + auto trindex = mesh.F().row(faceid); const Vec3d& p1 = mesh.V().row(trindex(0)); const Vec3d& p2 = mesh.V().row(trindex(1)); @@ -267,8 +257,6 @@ PointSet normals(const PointSet& points, // of its triangle. The procedure is the same, get the neighbor // triangles and calculate an average normal. - const Vec3d& p = C.row(i); - // mark the vertex indices of the edge. ia and ib marks and edge ic // will mark a single vertex. int ia = -1, ib = -1, ic = -1; @@ -296,7 +284,7 @@ PointSet normals(const PointSet& points, std::vector neigh; if(ic >= 0) { // The point is right on a vertex of the triangle for(int n = 0; n < mesh.F().rows(); ++n) { - throw_on_cancel(); + thr(); Vec3i ni = mesh.F().row(n); if((ni(X) == ic || ni(Y) == ic || ni(Z) == ic)) neigh.emplace_back(ni); @@ -305,7 +293,7 @@ PointSet normals(const PointSet& points, else if(ia >= 0 && ib >= 0) { // the point is on and edge // now get all the neigboring triangles for(int n = 0; n < mesh.F().rows(); ++n) { - throw_on_cancel(); + thr(); Vec3i ni = mesh.F().row(n); if((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) && (ni(X) == ib || ni(Y) == ib || ni(Z) == ib)) @@ -346,14 +334,14 @@ PointSet normals(const PointSet& points, Vec3d sumnorm(0, 0, 0); sumnorm = std::accumulate(neighnorms.begin(), lend, sumnorm); sumnorm.normalize(); - ret.row(i) = sumnorm; + ret.row(long(ridx)) = sumnorm; } else { // point lies safely within its triangle Eigen::Vector3d U = p2 - p1; Eigen::Vector3d V = p3 - p1; - ret.row(i) = U.cross(V).normalized(); + ret.row(long(ridx)) = U.cross(V).normalized(); } - } + }); return ret; } From 93c57612bf587f2647df1642142a3d7c1ef76228 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 27 Feb 2019 11:39:02 +0100 Subject: [PATCH 03/37] Grouping support generation algorithm into a separate class --- src/libslic3r/SLA/SLASupportTree.cpp | 978 +++++++++++++-------------- src/libslic3r/SLA/SLASupportTree.hpp | 25 +- src/libslic3r/SLAPrint.cpp | 3 +- 3 files changed, 472 insertions(+), 534 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index d0c33205ac..49ece2f1f8 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -551,206 +551,6 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers X, Y, Z }; -//PointSet to_point_set(const std::vector &v) -//{ -// PointSet ret(v.size(), 3); -// long i = 0; -// for(const SupportPoint& support_point : v) { -// ret.row(i)(X) = double(support_point.pos(X)); -// ret.row(i)(Y) = double(support_point.pos(Y)); -// ret.row(i)(Z) = double(support_point.pos(Z)); -// ++i; -// } -// return ret; -//} - -Vec3d model_coord(const ModelInstance& object, const Vec3f& mesh_coord) { - return object.transform_vector(mesh_coord.cast()); -} - -inline double ray_mesh_intersect(const Vec3d& s, - const Vec3d& dir, - const EigenMesh3D& m) -{ - return m.query_ray_hit(s, dir).distance(); -} - -// This function will test if a future pinhead would not collide with the model -// geometry. It does not take a 'Head' object because those are created after -// this test. -// Parameters: -// s: The touching point on the model surface. -// dir: This is the direction of the head from the pin to the back -// r_pin, r_back: the radiuses of the pin and the back sphere -// width: This is the full width from the pin center to the back center -// m: The object mesh -// -// Optional: -// samples: how many rays will be shot -// safety distance: This will be added to the radiuses to have a safety distance -// from the mesh. -double pinhead_mesh_intersect(const Vec3d& s, - const Vec3d& dir, - double r_pin, - double r_back, - double width, - const EigenMesh3D& m, - unsigned samples = 16, - double safety_distance = 0.001) -{ - // method based on: - // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space - - - // We will shoot multiple rays from the head pinpoint in the direction of - // the pinhead robe (side) surface. The result will be the smallest hit - // distance. - - // Move away slightly from the touching point to avoid raycasting on the - // inner surface of the mesh. - Vec3d v = dir; // Our direction (axis) - Vec3d c = s + width * dir; - const double& sd = safety_distance; - - // Two vectors that will be perpendicular to each other and to the axis. - // Values for a(X) and a(Y) are now arbitrary, a(Z) is just a placeholder. - Vec3d a(0, 1, 0), b; - - // The portions of the circle (the head-back circle) for which we will shoot - // rays. - std::vector phis(samples); - for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); - - // We have to address the case when the direction vector v (same as dir) - // is coincident with one of the world axes. In this case two of its - // components will be completely zero and one is 1.0. Our method becomes - // dangerous here due to division with zero. Instead, vector 'a' can be an - // element-wise rotated version of 'v' - auto chk1 = [] (double val) { return std::abs(std::abs(val) - 1) < 1e-20; }; - if(chk1(v(X)) || chk1(v(Y)) || chk1(v(Z))) { - a = {v(Z), v(X), v(Y)}; - b = {v(Y), v(Z), v(X)}; - } - else { - a(Z) = -(v(Y)*a(Y)) / v(Z); a.normalize(); - b = a.cross(v); - } - - // Now a and b vectors are perpendicular to v and to each other. Together - // they define the plane where we have to iterate with the given angles - // in the 'phis' vector - - tbb::parallel_for(size_t(0), phis.size(), - [&phis, &m, sd, r_pin, r_back, s, a, b, c](size_t i) - { - double& phi = phis[i]; - double sinphi = std::sin(phi); - double cosphi = std::cos(phi); - - // Let's have a safety coefficient for the radiuses. - double rpscos = (sd + r_pin) * cosphi; - double rpssin = (sd + r_pin) * sinphi; - double rpbcos = (sd + r_back) * cosphi; - double rpbsin = (sd + r_back) * sinphi; - - // Point on the circle on the pin sphere - Vec3d ps(s(X) + rpscos * a(X) + rpssin * b(X), - s(Y) + rpscos * a(Y) + rpssin * b(Y), - s(Z) + rpscos * a(Z) + rpssin * b(Z)); - - // Point ps is not on mesh but can be inside or outside as well. This - // would cause many problems with ray-casting. To detect the position we - // will use the ray-casting result (which has an is_inside predicate). - - // This is the point on the circle on the back sphere - Vec3d p(c(X) + rpbcos * a(X) + rpbsin * b(X), - c(Y) + rpbcos * a(Y) + rpbsin * b(Y), - c(Z) + rpbcos * a(Z) + rpbsin * b(Z)); - - Vec3d n = (p - ps).normalized(); - auto hr = m.query_ray_hit(ps + sd*n, n); - - if(hr.is_inside()) { // the hit is inside the model - if(hr.distance() > 2*r_pin) phi = 0; - else { - // re-cast the ray from the outside of the object - auto hr2 = m.query_ray_hit(ps + (hr.distance() + 2*sd)*n, n); - phi = hr2.distance(); - } - } else phi = hr.distance(); - }); - - auto mit = std::min_element(phis.begin(), phis.end()); - - return *mit; -} - - -// Checking bridge (pillar and stick as well) intersection with the model. If -// the function is used for headless sticks, the ins_check parameter have to be -// true as the beginning of the stick might be inside the model geometry. -double bridge_mesh_intersect(const Vec3d& s, - const Vec3d& dir, - double r, - const EigenMesh3D& m, - bool ins_check = false, - unsigned samples = 4, - double safety_distance = 0.001) -{ - // helper vector calculations - Vec3d a(0, 1, 0), b; - const double& sd = safety_distance; - - // INFO: for explanation of the method used here, see the previous method's - // comments. - - auto chk1 = [] (double val) { return std::abs(std::abs(val) - 1) < 1e-20; }; - if(chk1(dir(X)) || chk1(dir(Y)) || chk1(dir(Z))) { - a = {dir(Z), dir(X), dir(Y)}; - b = {dir(Y), dir(Z), dir(X)}; - } - else { - a(Z) = -(dir(Y)*a(Y)) / dir(Z); a.normalize(); - b = a.cross(dir); - } - - // circle portions - std::vector phis(samples); - for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); - - tbb::parallel_for(size_t(0), phis.size(), - [&phis, &m, a, b, sd, dir, r, s, ins_check](size_t i) - { - double& phi = phis[i]; - double sinphi = std::sin(phi); - double cosphi = std::cos(phi); - - // Let's have a safety coefficient for the radiuses. - double rcos = (sd + r) * cosphi; - double rsin = (sd + r) * sinphi; - - // Point on the circle on the pin sphere - Vec3d p (s(X) + rcos * a(X) + rsin * b(X), - s(Y) + rcos * a(Y) + rsin * b(Y), - s(Z) + rcos * a(Z) + rsin * b(Z)); - - auto hr = m.query_ray_hit(p + sd*dir, dir); - - if(ins_check && hr.is_inside()) { - if(hr.distance() > 2*r) phi = 0; - else { - // re-cast the ray from the outside of the object - auto hr2 = m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir); - phi = hr2.distance(); - } - } else phi = hr.distance(); - }); - - auto mit = std::min_element(phis.begin(), phis.end()); - - return *mit; -} - // Calculate the normals for the selected points (from 'points' set) on the // mesh. This will call squared distance for each point. PointSet normals(const PointSet& points, @@ -1129,100 +929,343 @@ Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { return (endp - startp).normalized(); } -bool SLASupportTree::generate(const std::vector &support_points, - const EigenMesh3D& mesh, - const SupportConfig &cfg, - const Controller &ctl) -{ - // Prepare the support points in Eigen/IGL format as well, we will use it - // mostly in this form. - Eigen::MatrixXd points(support_points.size(), 3); long i = 0; - for(const SupportPoint& sp : support_points) { - points.row(i)(X) = double(sp.pos(X)); - points.row(i)(Y) = double(sp.pos(Y)); - points.row(i)(Z) = double(sp.pos(Z)); - ++i; - } - - // If there are no input points there is no point in doing anything - if(points.rows() == 0) return false; +class SLASupportTree::Algorithm { + const SupportConfig& m_cfg; + const EigenMesh3D& m_mesh; + const std::vector& m_support_pts; using PtIndices = std::vector; - const size_t pcount = size_t(points.rows()); - PtIndices head_indices; // support points with pinhead - PtIndices headless_indices; // headless support points - PtIndices onmodel_head_indices; // supp. pts. connecting to model + PtIndices m_iheads; // support points with pinhead + PtIndices m_iheadless; // headless support points + PtIndices m_iheads_onmodel; // supp. pts. connecting to model - PointSet support_normals(pcount, 3); // support point normals + // normals for support points from model faces. + PointSet m_support_nmls; // Captures the height of the processed support points from the ground // or the model surface - std::vector pt_heights(size_t(points.rows()), 0.0); + std::vector m_ptheights; - // Clusters of points which can reach the ground directly - std::vector pillar_clusters; + // Clusters of points which can reach the ground directly and can be + // bridged to one central pillar + std::vector m_pillar_clusters; // This algorithm uses the Impl class as its output stream. It will be // filled gradually with support elements (heads, pillars, bridges, ...) using Result = SLASupportTree::Impl; - Result& result = *m_impl; - // Let's define the individual steps of the processing. We can experiment - // later with the ordering and the dependencies between them. - enum Steps { - BEGIN, - FILTER, - PINHEADS, - CLASSIFY, - ROUTING_GROUND, - ROUTING_NONGROUND, - HEADLESS, - DONE, - HALT, - ABORT, - NUM_STEPS - //... - }; + Result& m_result; + + // support points in Eigen/IGL format + PointSet m_points; // throw if canceled: It will be called many times so a shorthand will // come in handy. - ThrowOnCancel thr = ctl.cancelfn; + ThrowOnCancel m_thr; - // Each step has a processing block in a form of a function. + inline double ray_mesh_intersect(const Vec3d& s, + const Vec3d& dir) + { + return m_mesh.query_ray_hit(s, dir).distance(); + } + + // This function will test if a future pinhead would not collide with the + // model geometry. It does not take a 'Head' object because those are + // created after this test. Parameters: s: The touching point on the model + // surface. dir: This is the direction of the head from the pin to the back + // r_pin, r_back: the radiuses of the pin and the back sphere width: This + // is the full width from the pin center to the back center m: The object + // mesh + // + // Optional: + // samples: how many rays will be shot + // safety distance: This will be added to the radiuses to have a safety + // distance from the mesh. + double pinhead_mesh_intersect(const Vec3d& s, + const Vec3d& dir, + double r_pin, + double r_back, + double width, + unsigned samples = 8) + { + // method based on: + // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space + + // We will shoot multiple rays from the head pinpoint in the direction + // of the pinhead robe (side) surface. The result will be the smallest + // hit distance. + + // Move away slightly from the touching point to avoid raycasting on the + // inner surface of the mesh. + Vec3d v = dir; // Our direction (axis) + Vec3d c = s + width * dir; + const double& sd = m_cfg.safety_distance_mm; + + // Two vectors that will be perpendicular to each other and to the + // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a + // placeholder. + Vec3d a(0, 1, 0), b; + + // The portions of the circle (the head-back circle) for which we will + // shoot rays. + std::vector phis(samples); + for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); + + // We have to address the case when the direction vector v (same as + // dir) is coincident with one of the world axes. In this case two of + // its components will be completely zero and one is 1.0. Our method + // becomes dangerous here due to division with zero. Instead, vector + // 'a' can be an element-wise rotated version of 'v' + auto chk1 = [] (double val) { + return std::abs(std::abs(val) - 1) < 1e-20; + }; + + if(chk1(v(X)) || chk1(v(Y)) || chk1(v(Z))) { + a = {v(Z), v(X), v(Y)}; + b = {v(Y), v(Z), v(X)}; + } + else { + a(Z) = -(v(Y)*a(Y)) / v(Z); a.normalize(); + b = a.cross(v); + } + + // Now a and b vectors are perpendicular to v and to each other. + // Together they define the plane where we have to iterate with the + // given angles in the 'phis' vector + auto& m = m_mesh; + + tbb::parallel_for(size_t(0), phis.size(), + [&phis, &m, sd, r_pin, r_back, s, a, b, c](size_t i) + { + double& phi = phis[i]; + double sinphi = std::sin(phi); + double cosphi = std::cos(phi); + + // Let's have a safety coefficient for the radiuses. + double rpscos = (sd + r_pin) * cosphi; + double rpssin = (sd + r_pin) * sinphi; + double rpbcos = (sd + r_back) * cosphi; + double rpbsin = (sd + r_back) * sinphi; + + // Point on the circle on the pin sphere + Vec3d ps(s(X) + rpscos * a(X) + rpssin * b(X), + s(Y) + rpscos * a(Y) + rpssin * b(Y), + s(Z) + rpscos * a(Z) + rpssin * b(Z)); + + // Point ps is not on mesh but can be inside or outside as well. + // This would cause many problems with ray-casting. To detect the + // position we will use the ray-casting result (which has an + // is_inside predicate). + + // This is the point on the circle on the back sphere + Vec3d p(c(X) + rpbcos * a(X) + rpbsin * b(X), + c(Y) + rpbcos * a(Y) + rpbsin * b(Y), + c(Z) + rpbcos * a(Z) + rpbsin * b(Z)); + + Vec3d n = (p - ps).normalized(); + auto q = m.query_ray_hit(ps + sd*n, n); + + if(q.is_inside()) { // the hit is inside the model + if(q.distance() > 2*r_pin) phi = 0; + else { + // re-cast the ray from the outside of the object + auto q2 = m.query_ray_hit(ps + (q.distance() + 2*sd)*n, n); + phi = q2.distance(); + } + } else phi = q.distance(); + }); + + auto mit = std::min_element(phis.begin(), phis.end()); + + return *mit; + } + + // Checking bridge (pillar and stick as well) intersection with the model. + // If the function is used for headless sticks, the ins_check parameter + // have to be true as the beginning of the stick might be inside the model + // geometry. + double bridge_mesh_intersect(const Vec3d& s, + const Vec3d& dir, + double r, + bool ins_check = false, + unsigned samples = 4) + { + // helper vector calculations + Vec3d a(0, 1, 0), b; + const double& sd = m_cfg.safety_distance_mm; + + // INFO: for explanation of the method used here, see the previous + // method's comments. + + auto chk1 = [] (double val) { + return std::abs(std::abs(val) - 1) < 1e-20; + }; + + if(chk1(dir(X)) || chk1(dir(Y)) || chk1(dir(Z))) { + a = {dir(Z), dir(X), dir(Y)}; + b = {dir(Y), dir(Z), dir(X)}; + } + else { + a(Z) = -(dir(Y)*a(Y)) / dir(Z); a.normalize(); + b = a.cross(dir); + } + + // circle portions + std::vector phis(samples); + for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); + + auto& m = m_mesh; + + tbb::parallel_for(size_t(0), phis.size(), + [&m, &phis, a, b, sd, dir, r, s, ins_check](size_t i) + { + double& phi = phis[i]; + double sinphi = std::sin(phi); + double cosphi = std::cos(phi); + + // Let's have a safety coefficient for the radiuses. + double rcos = (sd + r) * cosphi; + double rsin = (sd + r) * sinphi; + + // Point on the circle on the pin sphere + Vec3d p (s(X) + rcos * a(X) + rsin * b(X), + s(Y) + rcos * a(Y) + rsin * b(Y), + s(Z) + rcos * a(Z) + rsin * b(Z)); + + auto hr = m.query_ray_hit(p + sd*dir, dir); + + if(ins_check && hr.is_inside()) { + if(hr.distance() > 2*r) phi = 0; + else { + // re-cast the ray from the outside of the object + auto hr2 = + m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir); + + phi = hr2.distance(); + } + } else phi = hr.distance(); + }); + + auto mit = std::min_element(phis.begin(), phis.end()); + + return *mit; + } + + // Helper function for interconnecting two pillars with zig-zag bridges. + // This is not an individual step. + void interconnect(const Pillar& pillar, const Pillar& nextpillar) + { + const Head& phead = m_result.pillar_head(pillar.id); + const Head& nextphead = m_result.pillar_head(nextpillar.id); + + Vec3d sj = phead.junction_point(); + sj(Z) = std::min(sj(Z), nextphead.junction_point()(Z)); + Vec3d ej = nextpillar.endpoint; + double pillar_dist = distance(Vec2d{sj(X), sj(Y)}, + Vec2d{ej(X), ej(Y)}); + double zstep = pillar_dist * std::tan(-m_cfg.bridge_slope); + ej(Z) = sj(Z) + zstep; + + double chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r); + + double bridge_distance = pillar_dist / std::cos(-m_cfg.bridge_slope); + + // If the pillars are so close that they touch each other, + // there is no need to bridge them together. + if(pillar_dist > 2*m_cfg.head_back_radius_mm && + bridge_distance < m_cfg.max_bridge_length_mm) + while(sj(Z) > pillar.endpoint(Z) + m_cfg.base_radius_mm && + ej(Z) > nextpillar.endpoint(Z) + m_cfg.base_radius_mm) + { + if(chkd >= bridge_distance) { + m_result.add_bridge(sj, ej, pillar.r); + + auto pcm = m_cfg.pillar_connection_mode; + + // double bridging: (crosses) + if( pcm == PillarConnectionMode::cross || + (pcm == PillarConnectionMode::dynamic && + pillar_dist > 2*m_cfg.base_radius_mm)) + { + // If the columns are close together, no need to + // double bridge them + Vec3d bsj(ej(X), ej(Y), sj(Z)); + Vec3d bej(sj(X), sj(Y), ej(Z)); + + // need to check collision for the cross stick + double backchkd = bridge_mesh_intersect( + bsj, dirv(bsj, bej), pillar.r); + + + if(backchkd >= bridge_distance) { + m_result.add_bridge(bsj, bej, pillar.r); + } + } + } + sj.swap(ej); + ej(Z) = sj(Z) + zstep; + chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r); + } + } + +public: + + Algorithm(const SupportConfig& config, + const EigenMesh3D& emesh, + const std::vector& support_pts, + Result& result, + ThrowOnCancel thr) : + m_cfg(config), + m_mesh(emesh), + m_support_pts(support_pts), + m_support_nmls(support_pts.size(), 3), + m_ptheights(support_pts.size(), 0.0), + m_result(result), + m_points(support_pts.size(), 3), + m_thr(thr) + { + // Prepare the support points in Eigen/IGL format as well, we will use + // it mostly in this form. + + long i = 0; + for(const SupportPoint& sp : m_support_pts) { + m_points.row(i)(X) = double(sp.pos(X)); + m_points.row(i)(Y) = double(sp.pos(Y)); + m_points.row(i)(Z) = double(sp.pos(Z)); + ++i; + } + } + + + // Now let's define the individual steps of the support generation algorithm // Filtering step: here we will discard inappropriate support points // and decide the future of the appropriate ones. We will check if a // pinhead is applicable and adjust its angle at each support point. We // will also merge the support points that are just too close and can // be considered as one. - auto filter_fn = [thr](const SupportConfig& cfg, - const PointSet& points, - const EigenMesh3D& mesh, - PointSet& support_normals, - PtIndices& head_indices, - PtIndices& headless_indices) - { + void filter() { // Get the points that are too close to each other and keep only the // first one - auto aliases = cluster(points, - [thr](const SpatElement& p, const SpatElement& se) - { - thr(); return distance(p.first, se.first) < D_SP; + auto aliases = cluster(m_points, + [this](const SpatElement& p, const SpatElement& se) { + m_thr(); + return distance(p.first, se.first) < D_SP; }, 2); PtIndices filtered_indices; filtered_indices.reserve(aliases.size()); - head_indices.reserve(aliases.size()); - headless_indices.reserve(aliases.size()); + m_iheads.reserve(aliases.size()); + m_iheadless.reserve(aliases.size()); for(auto& a : aliases) { // Here we keep only the front point of the cluster. filtered_indices.emplace_back(a.front()); } // calculate the normals to the triangles for filtered points - auto nmls = sla::normals(points, mesh, cfg.head_front_radius_mm, - thr, filtered_indices); + auto nmls = sla::normals(m_points, m_mesh, m_cfg.head_front_radius_mm, + m_thr, filtered_indices); // Not all of the support points have to be a valid position for // support creation. The angle may be inappropriate or there may @@ -1238,7 +1281,7 @@ bool SLASupportTree::generate(const std::vector &support_points, for(unsigned i = 0, fidx = filtered_indices[0]; i < filtered_indices.size(); ++i, fidx = filtered_indices[i]) { - thr(); + m_thr(); auto n = nmls.row(i); // for all normals we generate the spherical coordinates and @@ -1254,17 +1297,19 @@ bool SLASupportTree::generate(const std::vector &support_points, double azimuth = std::atan2(n(1), n(0)); // skip if the tilt is not sane - if(polar >= PI - cfg.normal_cutoff_angle) { + if(polar >= PI - m_cfg.normal_cutoff_angle) { // We saturate the polar angle to 3pi/4 polar = std::max(polar, 3*PI / 4); // save the head (pinpoint) position - Vec3d hp = points.row(i); + Vec3d hp = m_points.row(i); - double w = cfg.head_width_mm + - cfg.head_back_radius_mm + - 2*cfg.head_front_radius_mm; + double w = m_cfg.head_width_mm + + m_cfg.head_back_radius_mm + + 2*m_cfg.head_front_radius_mm; + + double pin_r = double(m_support_pts[fidx].head_front_radius); // Reassemble the now corrected normal auto nn = Vec3d(std::cos(azimuth) * std::sin(polar), @@ -1275,10 +1320,9 @@ bool SLASupportTree::generate(const std::vector &support_points, double t = pinhead_mesh_intersect( hp, // touching point nn, // normal - cfg.head_front_radius_mm, - cfg.head_back_radius_mm, - w, - mesh); + pin_r, + m_cfg.head_back_radius_mm, + w); if(t <= w) { @@ -1293,18 +1337,15 @@ bool SLASupportTree::generate(const std::vector &support_points, SimplexOptimizer solver(stc); auto oresult = solver.optimize_max( - [&mesh, &cfg, w, hp](double plr, double azm) + [this, pin_r, w, hp](double plr, double azm) { auto n = Vec3d(std::cos(azm) * std::sin(plr), std::sin(azm) * std::sin(plr), std::cos(plr)).normalized(); - double score = pinhead_mesh_intersect( - hp, n, - cfg.head_front_radius_mm, - cfg.head_back_radius_mm, - w, - mesh); + double score = pinhead_mesh_intersect( hp, n, pin_r, + m_cfg.head_back_radius_mm, + w); return score; }, initvals(polar, azimuth), // start with what we have @@ -1321,46 +1362,39 @@ bool SLASupportTree::generate(const std::vector &support_points, } // save the verified and corrected normal - support_normals.row(fidx) = nn; + m_support_nmls.row(fidx) = nn; if(t > w) { // mark the point for needing a head. - head_indices.emplace_back(fidx); + 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. - headless_indices.emplace_back(fidx); + m_iheadless.emplace_back(fidx); } } } - thr(); - }; + m_thr(); + } // Pinhead creation: based on the filtering results, the Head objects // will be constructed (together with their triangle meshes). - auto pinheads_fn = [thr](const SupportConfig& cfg, - const std::vector support_points, - const PointSet& support_normals, - const PtIndices& head_indices, - Result& result) + void add_pinheads() { - /* ******************************************************** */ - /* Generating Pinheads */ - /* ******************************************************** */ - - for (unsigned i : head_indices) { - thr(); - result.add_head(i, - cfg.head_back_radius_mm, - support_points[i].head_front_radius, - cfg.head_width_mm, - cfg.head_penetration_mm, - support_normals.row(i), // dir - support_points[i].pos.cast() // displacement + for (unsigned i : m_iheads) { + m_thr(); + m_result.add_head( + i, + m_cfg.head_back_radius_mm, + m_support_pts[i].head_front_radius, + m_cfg.head_width_mm, + m_cfg.head_penetration_mm, + m_support_nmls.row(i), // dir + m_support_pts[i].pos.cast() // displacement ); } - }; + } // Further classification of the support points with pinheads. If the // ground is directly reachable through a vertical line parallel to the @@ -1370,148 +1404,75 @@ bool SLASupportTree::generate(const std::vector &support_points, // into clusters that can be interconnected with bridges. Elements of // these groups may or may not be interconnected. Here we only run the // clustering algorithm. - auto classify_fn = [thr](const SupportConfig& cfg, - const PointSet& points, - const EigenMesh3D& mesh, - const PtIndices& head_indices, - PtIndices& onmodel_head_indices, - std::vector& pt_heights, - std::vector& pillar_clusters, - Result& result) + void classify() { - /* ******************************************************** */ - /* Classification */ - /* ******************************************************** */ - // We should first get the heads that reach the ground directly - pt_heights.reserve(head_indices.size()); + m_ptheights.reserve(m_iheads.size()); PtIndices ground_head_indices; - ground_head_indices.reserve(head_indices.size()); - onmodel_head_indices.reserve(head_indices.size()); + ground_head_indices.reserve(m_iheads.size()); + m_iheads_onmodel.reserve(m_iheads.size()); // First we decide which heads reach the ground and can be full // pillars and which shall be connected to the model surface (or // search a suitable path around the surface that leads to the // ground -- TODO) - for(unsigned i : head_indices) { - thr(); + for(unsigned i : m_iheads) { + m_thr(); - auto& head = result.head(i); + auto& head = m_result.head(i); Vec3d n(0, 0, -1); double r = head.r_back_mm; Vec3d headjp = head.junction_point(); // collision check - double t = bridge_mesh_intersect(headjp, n, r, mesh); + double t = bridge_mesh_intersect(headjp, n, r); // Precise distance measurement - double tprec = ray_mesh_intersect(headjp, n, mesh); + double tprec = ray_mesh_intersect(headjp, n); // Save the distance from a surface in the Z axis downwards. It // may be infinity but that is telling us that it touches the // ground. - pt_heights.emplace_back(tprec); + m_ptheights.emplace_back(tprec); if(std::isinf(t)) ground_head_indices.emplace_back(i); - else onmodel_head_indices.emplace_back(i); + else m_iheads_onmodel.emplace_back(i); } // We want to search for clusters of points that are far enough // 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 d_base = 2*cfg.base_radius_mm; - pillar_clusters = cluster(points, + auto d_base = 2*m_cfg.base_radius_mm; + auto& thr = m_thr; + m_pillar_clusters = cluster(m_points, [thr, d_base](const SpatElement& p, const SpatElement& s) { thr(); return distance(Vec2d(p.first(X), p.first(Y)), Vec2d(s.first(X), s.first(Y))) < d_base; }, 3, ground_head_indices); // max 3 heads to connect to one pillar - }; - - // Helper function for interconnecting two pillars with zig-zag bridges. - // This is not an individual step. - auto interconnect = [&cfg](const Pillar& pillar, - const Pillar& nextpillar, - const EigenMesh3D& m, - Result& result) - { - const Head& phead = result.pillar_head(pillar.id); - const Head& nextphead = result.pillar_head(nextpillar.id); - - Vec3d sj = phead.junction_point(); - sj(Z) = std::min(sj(Z), nextphead.junction_point()(Z)); - Vec3d ej = nextpillar.endpoint; - double pillar_dist = distance(Vec2d{sj(X), sj(Y)}, - Vec2d{ej(X), ej(Y)}); - double zstep = pillar_dist * std::tan(-cfg.head_slope); - ej(Z) = sj(Z) + zstep; - - double chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r, m); - double bridge_distance = pillar_dist / std::cos(-cfg.head_slope); - - // If the pillars are so close that they touch each other, - // there is no need to bridge them together. - if(pillar_dist > 2*cfg.head_back_radius_mm && - bridge_distance < cfg.max_bridge_length_mm) - while(sj(Z) > pillar.endpoint(Z) + cfg.base_radius_mm && - ej(Z) > nextpillar.endpoint(Z) + cfg.base_radius_mm) - { - if(chkd >= bridge_distance) { - result.add_bridge(sj, ej, pillar.r); - - auto pcm = cfg.pillar_connection_mode; - - // double bridging: (crosses) - if( pcm == PillarConnectionMode::cross || - (pcm == PillarConnectionMode::dynamic && - pillar_dist > 2*cfg.base_radius_mm)) - { - // If the columns are close together, no need to - // double bridge them - Vec3d bsj(ej(X), ej(Y), sj(Z)); - Vec3d bej(sj(X), sj(Y), ej(Z)); - - // need to check collision for the cross stick - double backchkd = bridge_mesh_intersect(bsj, - dirv(bsj, bej), - pillar.r, m); - - - if(backchkd >= bridge_distance) { - result.add_bridge(bsj, bej, pillar.r); - } - } - } - sj.swap(ej); - ej(Z) = sj(Z) + zstep; - chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r, m); - } - }; + } // Step: Routing the ground connected pinheads, and interconnecting // them with additional (angled) bridges. Not all of these pinheads // will be a full pillar (ground connected). Some will connect to a // nearby pillar using a bridge. The max number of such side-heads for // a central pillar is limited to avoid bad weight distribution. - auto routing_ground_fn = - [thr, interconnect](const SupportConfig& cfg, - const PointSet& points, - const std::vector& pillar_clusters, - const EigenMesh3D& emesh, - Result& result) + void routing_to_ground() { - const double hbr = cfg.head_back_radius_mm; - const double pradius = cfg.head_back_radius_mm; - const double maxbridgelen = cfg.max_bridge_length_mm; - const double gndlvl = result.ground_level; + const double hbr = m_cfg.head_back_radius_mm; + const double pradius = m_cfg.head_back_radius_mm; + const double maxbridgelen = m_cfg.max_bridge_length_mm; + const double gndlvl = m_result.ground_level; + + ClusterEl cl_centroids; - cl_centroids.reserve(pillar_clusters.size()); + cl_centroids.reserve(m_pillar_clusters.size()); SpatIndex pheadindex; // spatial index for the junctions - for(auto& cl : pillar_clusters) { thr(); + for(auto& cl : m_pillar_clusters) { m_thr(); // place all the centroid head positions into the index. We // will query for alternative pillar positions. If a sidehead // cannot connect to the cluster centroid, we have to search @@ -1523,6 +1484,7 @@ bool SLASupportTree::generate(const std::vector &support_points, if(cl.empty()) continue; // get the current cluster centroid + auto& thr = m_thr; const auto& points = m_points; long lcid = cluster_centroid(cl, [&points](size_t idx) { return points.row(long(idx)); }, [thr](const Vec3d& p1, const Vec3d& p2) @@ -1538,7 +1500,7 @@ bool SLASupportTree::generate(const std::vector &support_points, unsigned hid = cl[cid]; // Head ID - Head& h = result.head(hid); + Head& h = m_result.head(hid); h.transform(); Vec3d p = h.junction_point(); p(Z) = gndlvl; pheadindex.insert(p, hid); @@ -1548,20 +1510,20 @@ bool SLASupportTree::generate(const std::vector &support_points, // sidepoints with the cluster centroid (which is a ground pillar) // or a nearby pillar if the centroid is unreachable. size_t ci = 0; - for(auto cl : pillar_clusters) { thr(); + for(auto cl : m_pillar_clusters) { m_thr(); auto cidx = cl_centroids[ci]; cl_centroids[ci++] = cl[cidx]; - auto& head = result.head(cl[cidx]); + auto& head = m_result.head(cl[cidx]); Vec3d startpoint = head.junction_point(); auto endpoint = startpoint; endpoint(Z) = gndlvl; // Create the central pillar of the cluster with its base on the // ground - result.add_pillar(unsigned(head.id), endpoint, pradius) - .add_base(cfg.base_height_mm, cfg.base_radius_mm); + m_result.add_pillar(unsigned(head.id), endpoint, pradius) + .add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); // Process side point in current cluster cl.erase(cl.begin() + cidx); // delete the centroid @@ -1570,12 +1532,12 @@ bool SLASupportTree::generate(const std::vector &support_points, // central position where the pillar can be placed. this way // the weight is distributed more effectively on the pillar. auto search_nearest = - [thr, &cfg, &result, &emesh, maxbridgelen, gndlvl, pradius] + [this, maxbridgelen, gndlvl, pradius] (SpatIndex& spindex, const Vec3d& jsh) { long nearest_id = -1; const double max_len = maxbridgelen / 2; - while(nearest_id < 0 && !spindex.empty()) { thr(); + while(nearest_id < 0 && !spindex.empty()) { m_thr(); // loop until a suitable head is not found // if there is a pillar closer than the cluster center // (this may happen as the clustering is not perfect) @@ -1583,7 +1545,7 @@ bool SLASupportTree::generate(const std::vector &support_points, Vec3d qp(jsh(X), jsh(Y), gndlvl); auto ne = spindex.nearest(qp, 1).front(); - const Head& nearhead = result.head(ne.second); + const Head& nearhead = m_result.head(ne.second); Vec3d jh = nearhead.junction_point(); Vec3d jp = jsh; @@ -1591,7 +1553,7 @@ bool SLASupportTree::generate(const std::vector &support_points, // Bridge endpoint on the main pillar Vec3d jn(jh(X), jh(Y), jp(Z) + - dist2d * std::tan(-cfg.head_slope)); + dist2d * std::tan(-m_cfg.bridge_slope)); if(jn(Z) > jh(Z)) { // If the sidepoint cannot connect to the pillar @@ -1602,12 +1564,12 @@ bool SLASupportTree::generate(const std::vector &support_points, double d = distance(jp, jn); - if(jn(Z) <= gndlvl + 2*cfg.head_width_mm || d > max_len) + if(jn(Z) <= gndlvl + 2*m_cfg.head_width_mm || d > max_len) break; double chkd = bridge_mesh_intersect(jp, dirv(jp, jn), - pradius, - emesh); + pradius); + if(chkd >= d) nearest_id = ne.second; spindex.remove(ne); @@ -1615,8 +1577,8 @@ bool SLASupportTree::generate(const std::vector &support_points, return nearest_id; }; - for(auto c : cl) { thr(); - auto& sidehead = result.head(c); + for(auto c : cl) { m_thr(); + auto& sidehead = m_result.head(c); sidehead.transform(); Vec3d jsh = sidehead.junction_point(); @@ -1628,8 +1590,8 @@ bool SLASupportTree::generate(const std::vector &support_points, if(nearest_id < 0) { // Could not find a pillar, create one Vec3d jp = jsh; jp(Z) = gndlvl; - result.add_pillar(unsigned(sidehead.id), jp, pradius). - add_base(cfg.base_height_mm, cfg.base_radius_mm); + m_result.add_pillar(unsigned(sidehead.id), jp, pradius). + add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); // connects to ground, eligible for bridging cl_centroids.emplace_back(c); @@ -1637,14 +1599,14 @@ bool SLASupportTree::generate(const std::vector &support_points, // Creating the bridge to the nearest pillar auto nearest_uid = unsigned(nearest_id); - const Head& nearhead = result.head(nearest_uid); + const Head& nearhead = m_result.head(nearest_uid); Vec3d jp = jsh; Vec3d jh = nearhead.junction_point(); double d = distance(Vec2d{jp(X), jp(Y)}, Vec2d{jh(X), jh(Y)}); Vec3d jn(jh(X), jh(Y), jp(Z) + - d * std::tan(-cfg.head_slope)); + d * std::tan(-m_cfg.bridge_slope)); if(jn(Z) > jh(Z)) { double hdiff = jn(Z) - jh(Z); @@ -1652,14 +1614,14 @@ bool SLASupportTree::generate(const std::vector &support_points, jn(Z) -= hdiff; // pillar without base, doesn't connect to ground. - result.add_pillar(nearest_uid, jp, pradius); + m_result.add_pillar(nearest_uid, jp, pradius); } - if(jp(Z) < jsh(Z)) result.add_junction(jp, hbr); - if(jn(Z) >= jh(Z)) result.add_junction(jn, hbr); + if(jp(Z) < jsh(Z)) m_result.add_junction(jp, hbr); + if(jn(Z) >= jh(Z)) m_result.add_junction(jn, hbr); - result.add_bridge(jp, jn, - sidehead.request_pillar_radius(pradius)); + m_result.add_bridge(jp, jn, + sidehead.request_pillar_radius(pradius)); } } } @@ -1684,9 +1646,10 @@ bool SLASupportTree::generate(const std::vector &support_points, ClusterEl ring; while(!rem.empty()) { // loop until all the points belong to some ring - thr(); + m_thr(); std::sort(rem.begin(), rem.end()); + auto& points = m_points; auto newring = pts_convex_hull(rem, [&points](unsigned i) { auto&& p = points.row(i); @@ -1696,8 +1659,8 @@ bool SLASupportTree::generate(const std::vector &support_points, if(!ring.empty()) { // inner ring is now in 'newring' and outer ring is in 'ring' SpatIndex innerring; - for(unsigned i : newring) { thr(); - const Pillar& pill = result.head_pillar(i); + for(unsigned i : newring) { m_thr(); + const Pillar& pill = m_result.head_pillar(i); assert(pill.id >= 0); innerring.insert(pill.endpoint, unsigned(pill.id)); } @@ -1705,14 +1668,14 @@ bool SLASupportTree::generate(const std::vector &support_points, // For all pillars in the outer ring find the closest in the // inner ring and connect them. This will create the spider web // fashioned connections between pillars - for(unsigned i : ring) { thr(); - const Pillar& outerpill = result.head_pillar(i); + for(unsigned i : ring) { m_thr(); + const Pillar& outerpill = m_result.head_pillar(i); auto res = innerring.nearest(outerpill.endpoint, 1); if(res.empty()) continue; auto ne = res.front(); - const Pillar& innerpill = result.pillars()[ne.second]; - interconnect(outerpill, innerpill, emesh, result); + const Pillar& innerpill = m_result.pillars()[ne.second]; + interconnect(outerpill, innerpill); } } @@ -1731,10 +1694,10 @@ bool SLASupportTree::generate(const std::vector &support_points, next != ring.end(); ++it, ++next) { - thr(); - const Pillar& pillar = result.head_pillar(*it); - const Pillar& nextpillar = result.head_pillar(*next); - interconnect(pillar, nextpillar, emesh, result); + m_thr(); + const Pillar& pillar = m_result.head_pillar(*it); + const Pillar& nextpillar = m_result.head_pillar(*next); + interconnect(pillar, nextpillar); } auto sring = ring; ClusterEl tmp; @@ -1744,27 +1707,21 @@ bool SLASupportTree::generate(const std::vector &support_points, std::back_inserter(tmp)); rem.swap(tmp); } - - }; + } // Step: routing the pinheads that would connect to the model surface // along the Z axis downwards. For now these will actually be connected with // the model surface with a flipped pinhead. In the future here we could use // some smart algorithms to search for a safe path to the ground or to a // nearby pillar that can hold the supported weight. - auto routing_nongnd_fn = [thr]( - const SupportConfig& cfg, - const std::vector& pt_heights, - const PtIndices& nonground_head_indices, - const EigenMesh3D& mesh, - Result& result) + void routing_to_model() { // TODO: connect these to the ground pillars if possible - for(auto idx : nonground_head_indices) { thr(); - double gh = pt_heights[idx]; - double base_width = cfg.head_width_mm; + for(auto idx : m_iheads_onmodel) { m_thr(); + double gh = m_ptheights[idx]; + double base_width = m_cfg.head_width_mm; - auto& head = result.head(idx); + auto& head = m_result.head(idx); if(std::isinf(gh)) { // in this case the the pillar geometry head.invalidate(); continue; @@ -1822,8 +1779,9 @@ bool SLASupportTree::generate(const std::vector &support_points, // In this case there is no room for the base pinhead. if(gh < head.fullwidth()) { double min_l = - 2 * cfg.head_front_radius_mm + - 2 * cfg.head_back_radius_mm - cfg.head_penetration_mm; + 2 * m_cfg.head_front_radius_mm + + 2 * m_cfg.head_back_radius_mm - + m_cfg.head_penetration_mm; base_width = gh - min_l; } @@ -1840,10 +1798,10 @@ bool SLASupportTree::generate(const std::vector &support_points, Vec3d headend = head.junction_point(); - Head base_head(cfg.head_back_radius_mm, - cfg.head_front_radius_mm, + Head base_head(m_cfg.head_back_radius_mm, + m_cfg.head_front_radius_mm, base_width, - cfg.head_penetration_mm, + m_cfg.head_penetration_mm, {0.0, 0.0, 1.0}, {headend(X), headend(Y), headend(Z) - gh}); @@ -1860,44 +1818,39 @@ bool SLASupportTree::generate(const std::vector &support_points, double hl = base_head.fullwidth() - head.r_back_mm; - result.add_pillar(idx, + m_result.add_pillar(idx, Vec3d{headend(X), headend(Y), headend(Z) - gh + hl}, - cfg.head_back_radius_mm + m_cfg.head_back_radius_mm ).base = base_head.mesh; } - }; + } // Step: process the support points where there is not enough space for a // full pinhead. In this case we will use a rounded sphere as a touching // point and use a thinner bridge (let's call it a stick). - auto process_headless_fn = [thr]( - const std::vector& support_points, - const PointSet& support_normals, - const PtIndices& headless_indices, - const EigenMesh3D& emesh, - Result& result) + void routing_headless () { // For now we will just generate smaller headless sticks with a sharp // ending point that connects to the mesh surface. - // We will sink the pins into the model surface for a distance of 1/3 of // the pin radius - for(unsigned i : headless_indices) { thr(); + for(unsigned i : m_iheadless) { m_thr(); - const auto R = double(support_points[i].head_front_radius); + const auto R = double(m_support_pts[i].head_front_radius); const double HWIDTH_MM = R/3; - Vec3d sph = support_points[i].pos.cast(); // Exact support position - Vec3d n = support_normals.row(i); // mesh outward normal + // Exact support position + Vec3d sph = m_support_pts[i].pos.cast(); + Vec3d n = m_support_nmls.row(i); // mesh outward normal Vec3d sp = sph - n * HWIDTH_MM; // stick head start point Vec3d dir = {0, 0, -1}; Vec3d sj = sp + R * n; // stick start point // This is only for checking - double idist = bridge_mesh_intersect(sph, dir, R, emesh, true); - double dist = ray_mesh_intersect(sj, dir, emesh); + double idist = bridge_mesh_intersect(sph, dir, R, true); + double dist = ray_mesh_intersect(sj, dir); if(std::isinf(idist) || std::isnan(idist) || idist < 2*R || std::isinf(dist) || std::isnan(dist) || dist < 2*R) { @@ -1908,68 +1861,53 @@ bool SLASupportTree::generate(const std::vector &support_points, } Vec3d ej = sj + (dist + HWIDTH_MM)* dir; - result.add_compact_bridge(sp, ej, n, R); + m_result.add_compact_bridge(sp, ej, n, R); } + } +}; + +bool SLASupportTree::generate(const std::vector &support_points, + const EigenMesh3D& mesh, + const SupportConfig &cfg, + const Controller &ctl) +{ + Algorithm alg(cfg, mesh, support_points, *m_impl, ctl.cancelfn); + + // Let's define the individual steps of the processing. We can experiment + // later with the ordering and the dependencies between them. + enum Steps { + BEGIN, + FILTER, + PINHEADS, + CLASSIFY, + ROUTING_GROUND, + ROUTING_NONGROUND, + HEADLESS, + DONE, + HALT, + ABORT, + NUM_STEPS + //... }; - // Now that the individual blocks are defined, lets connect the wires. We - // will create an array of functions which represents a program. Place the - // step methods in the array and bind the right arguments to the methods - // This way the data dependencies will be easily traceable between - // individual steps. - // There will be empty steps as well like the begin step or the done or - // abort steps. These are slots for future initialization or cleanup. - - using std::cref; // Bind inputs with cref (read-only) - using std::ref; // Bind outputs with ref (writable) - using std::bind; - - // Here we can easily track what goes in and what comes out of each step: - // (see the cref-s as inputs and ref-s as outputs) + // Collect the algorithm steps into a nice sequence std::array, NUM_STEPS> program = { [] () { // Begin... // Potentially clear up the shared data (not needed for now) }, - bind(filter_fn, - // inputs: - cref(cfg), cref(points), cref(mesh), - // outputs: - ref(support_normals), ref(head_indices), ref(headless_indices)), + std::bind(&Algorithm::filter, &alg), - bind(pinheads_fn, - // inputs: - cref(cfg), cref(support_points), cref(support_normals), - cref(head_indices), - // outputs: - ref(result)), + std::bind(&Algorithm::add_pinheads, &alg), - bind(classify_fn, - // inputs: - cref(cfg), cref(points), cref(mesh), cref(head_indices), - // outputs: - ref(onmodel_head_indices), ref(pt_heights), ref(pillar_clusters), - ref(result)), + std::bind(&Algorithm::classify, &alg), - bind(routing_ground_fn, - // inputs: - cref(cfg), cref(points), cref(pillar_clusters), cref(mesh), - // outputs: - ref(result)), + std::bind(&Algorithm::routing_to_ground, &alg), - bind(routing_nongnd_fn, - // inputs: - cref(cfg), cref(pt_heights), cref(onmodel_head_indices), cref(mesh), - // outputs: - ref(result)), + std::bind(&Algorithm::routing_to_model, &alg), - bind(process_headless_fn, - // inputs: - cref(support_points), cref(support_normals), - cref(headless_indices), cref(mesh), - // outputs: - ref(result)), + std::bind(&Algorithm::routing_headless, &alg), [] () { // Done @@ -1984,19 +1922,19 @@ bool SLASupportTree::generate(const std::vector &support_points, } }; - if(cfg.ground_facing_only) { // Delete the non-gnd steps if necessary + Steps pc = BEGIN, pc_prev = BEGIN; + + if(cfg.ground_facing_only) { program[ROUTING_NONGROUND] = []() { - BOOST_LOG_TRIVIAL(info) << "Skipping non-ground facing supports as " - "requested."; + BOOST_LOG_TRIVIAL(info) + << "Skipping model-facing supports as requested."; }; - program[HEADLESS] = [](){ - BOOST_LOG_TRIVIAL(info) << "Skipping headless stick generation as " - "requested"; + program[HEADLESS] = []() { + BOOST_LOG_TRIVIAL(info) << "Skipping headless stick generation as" + " requested."; }; } - Steps pc = BEGIN, pc_prev = BEGIN; - // Let's define a simple automaton that will run our program. auto progress = [&ctl, &pc, &pc_prev] () { static const std::array stepstr { diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index d969519123..e1d5449a90 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -50,11 +50,6 @@ struct SupportConfig { // Width in mm from the back sphere center to the front sphere center. double head_width_mm = 1.0; - // Radius in mm of the support pillars. The actual radius of the pillars - // beginning with a head will not be higher than head_back_radius but the - // headless pillars will have half of this value. - double headless_pillar_radius_mm = 0.4; - // How to connect pillars PillarConnectionMode pillar_connection_mode = PillarConnectionMode::dynamic; @@ -74,7 +69,7 @@ struct SupportConfig { double base_height_mm = 1.0; // The default angle for connecting support sticks and junctions. - double head_slope = M_PI/4; + double bridge_slope = M_PI/4; // The max length of a bridge in mm double max_bridge_length_mm = 15.0; @@ -86,6 +81,8 @@ struct SupportConfig { // The max Z angle for a normal at which it will get completely ignored. double normal_cutoff_angle = 150.0 * M_PI / 180.0; + // The shortest distance of any support structure from the model surface + double safety_distance_mm = 0.001; }; struct PoolConfig; @@ -123,7 +120,7 @@ using PointSet = Eigen::MatrixXd; /// The class containing mesh data for the generated supports. class SLASupportTree { - class Impl; + class Impl; // persistent support data std::unique_ptr m_impl; Impl& get() { return *m_impl; } @@ -133,16 +130,20 @@ class SLASupportTree { const SupportConfig&, const Controller&); - /// Generate the 3D supports for a model intended for SLA print. + // The generation algorithm is quite long and will be captured in a separate + // class with private data, helper methods, etc... This data is only needed + // during the calculation whereas the Impl class contains the persistent + // data, mostly the meshes. + class Algorithm; + + // Generate the 3D supports for a model intended for SLA print. This + // will instantiate the Algorithm class and call its appropriate methods + // with status indication. bool generate(const std::vector& pts, const EigenMesh3D& mesh, const SupportConfig& cfg = {}, const Controller& ctl = {}); - bool _generate(const std::vector& pts, - const EigenMesh3D& mesh, - const SupportConfig& cfg = {}, - const Controller& ctl = {}); public: SLASupportTree(); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 4efff03dae..4358d8b945 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -521,9 +521,8 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { scfg.head_penetration_mm = c.support_head_penetration.getFloat(); scfg.head_width_mm = c.support_head_width.getFloat(); scfg.object_elevation_mm = c.support_object_elevation.getFloat(); - scfg.head_slope = c.support_critical_angle.getFloat() * PI / 180.0 ; + scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ; scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat(); - scfg.headless_pillar_radius_mm = 0.375*c.support_pillar_diameter.getFloat(); switch(c.support_pillar_connection_mode.getInt()) { case slapcmZigZag: scfg.pillar_connection_mode = sla::PillarConnectionMode::zigzag; break; From 450f817c09b0da65657b12c8bc3be275d6bd0ba3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 28 Feb 2019 19:05:11 +0100 Subject: [PATCH 04/37] Replacing old model routing with more advanced algorithm. Interconnection still missing. --- src/libslic3r/SLA/SLACommon.hpp | 22 +- src/libslic3r/SLA/SLASupportTree.cpp | 594 ++++++++++++++---------- src/libslic3r/SLA/SLASupportTreeIGL.cpp | 13 +- 3 files changed, 374 insertions(+), 255 deletions(-) diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index b917db0d0e..4fdb323784 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -70,24 +70,32 @@ public: // Result of a raycast class hit_result { - double m_t = std::numeric_limits::infinity(); + double m_t = std::nan(""); int m_face_id = -1; - const EigenMesh3D& m_mesh; + std::reference_wrapper m_mesh; Vec3d m_dir; - inline hit_result(const EigenMesh3D& em): m_mesh(em) {} + Vec3d m_source; friend class EigenMesh3D; public: + // A valid object of this class can only be obtained from + // EigenMesh3D::query_ray_hit method. + explicit inline hit_result(const EigenMesh3D& em): m_mesh(em) {} + inline double distance() const { return m_t; } inline const Vec3d& direction() const { return m_dir; } + inline Vec3d position() const { return m_source + m_dir * m_t; } inline int face() const { return m_face_id; } + // Hit_result can decay into a double as the hit distance. + inline operator double() const { return distance(); } + inline Vec3d normal() const { if(m_face_id < 0) return {}; - auto trindex = m_mesh.m_F.row(m_face_id); - const Vec3d& p1 = m_mesh.V().row(trindex(0)); - const Vec3d& p2 = m_mesh.V().row(trindex(1)); - const Vec3d& p3 = m_mesh.V().row(trindex(2)); + auto trindex = m_mesh.get().m_F.row(m_face_id); + const Vec3d& p1 = m_mesh.get().V().row(trindex(0)); + const Vec3d& p2 = m_mesh.get().V().row(trindex(1)); + const Vec3d& p3 = m_mesh.get().V().row(trindex(2)); Eigen::Vector3d U = p2 - p1; Eigen::Vector3d V = p3 - p1; return U.cross(V).normalized(); diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 49ece2f1f8..0a157fe3a4 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -15,6 +15,11 @@ #include #include #include +#include + +//! macro used to mark string used at localization, +//! return same string +#define L(s) Slic3r::I18N::translate(s) /** * Terminology: @@ -335,6 +340,11 @@ struct Head { return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm; } + static double fullwidth(const SupportConfig& cfg) { + return 2 * cfg.head_front_radius_mm + cfg.head_width_mm + + 2 * cfg.head_back_radius_mm - cfg.head_penetration_mm; + } + Vec3d junction_point() const { return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir; } @@ -381,13 +391,14 @@ struct Pillar { assert(steps > 0); double h = jp(Z) - endp(Z); - assert(h > 0); // Endpoint is below the starting point + if(h > 0) { // Endpoint is below the starting point - // We just create a bridge geometry with the pillar parameters and - // move the data. - Contour3D body = cylinder(radius, h, st, endp); - mesh.points.swap(body.points); - mesh.indices.swap(body.indices); + // We just create a bridge geometry with the pillar parameters and + // move the data. + Contour3D body = cylinder(radius, h, st, endp); + mesh.points.swap(body.points); + mesh.indices.swap(body.indices); + } } Pillar(const Junction& junc, const Vec3d& endp): @@ -569,9 +580,20 @@ bool operator==(const SpatElement& e1, const SpatElement& e2) { // Clustering a set of points by the given criteria. ClusteredPoints cluster( + const PointSet& points, const std::vector& indices, + std::function pred, + unsigned max_points = 0); + +inline ClusteredPoints cluster( const PointSet& points, std::function pred, - unsigned max_points = 0, const std::vector& indices = {}); + unsigned max_points = 0) +{ + std::vector indices(size_t(points.rows()), 0); + std::iota(indices.begin(), indices.end(), 0); + return cluster(points, indices, pred, max_points); +} + // This class will hold the support tree meshes with some additional bookkeeping // as well. Various parts of the support geometry are stored separately and are @@ -628,8 +650,20 @@ public: return m_pillars.back(); } + template Pillar& add_pillar(const Vec3d& startp, + const Vec3d& endp, + double r) + { + m_pillars.emplace_back(startp, endp, r); + Pillar& pillar = m_pillars.back(); + pillar.id = long(m_pillars.size() - 1); + pillar.starts_from_head = false; + meshcache_valid = false; + return m_pillars.back(); + } + const Head& pillar_head(long pillar_id) const { - assert(pillar_id >= 0 && pillar_id < m_pillars.size()); + assert(pillar_id >= 0 && pillar_id < long(m_pillars.size())); const Pillar& p = m_pillars[size_t(pillar_id)]; assert(p.starts_from_head && p.start_junction_id >= 0); auto it = m_heads.find(unsigned(p.start_junction_id)); @@ -641,7 +675,7 @@ public: auto it = m_heads.find(headid); assert(it != m_heads.end()); const Head& h = it->second; - assert(h.pillar_id >= 0 && h.pillar_id < m_pillars.size()); + assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size())); return m_pillars[size_t(h.pillar_id)]; } @@ -938,15 +972,13 @@ class SLASupportTree::Algorithm { PtIndices m_iheads; // support points with pinhead PtIndices m_iheadless; // headless support points - PtIndices m_iheads_onmodel; // supp. pts. connecting to model + + // supp. pts. connecting to model: point index and the ray hit data + std::vector> m_iheads_onmodel; // normals for support points from model faces. PointSet m_support_nmls; - // Captures the height of the processed support points from the ground - // or the model surface - std::vector m_ptheights; - // Clusters of points which can reach the ground directly and can be // bridged to one central pillar std::vector m_pillar_clusters; @@ -964,6 +996,9 @@ class SLASupportTree::Algorithm { // come in handy. ThrowOnCancel m_thr; + // A spatial index to easily find strong pillars to connect to. + SpatIndex m_pillar_index; + inline double ray_mesh_intersect(const Vec3d& s, const Vec3d& dir) { @@ -982,12 +1017,13 @@ class SLASupportTree::Algorithm { // samples: how many rays will be shot // safety distance: This will be added to the radiuses to have a safety // distance from the mesh. - double pinhead_mesh_intersect(const Vec3d& s, - const Vec3d& dir, - double r_pin, - double r_back, - double width, - unsigned samples = 8) + EigenMesh3D::hit_result pinhead_mesh_intersect( + const Vec3d& s, + const Vec3d& dir, + double r_pin, + double r_back, + double width, + unsigned samples = 8) { // method based on: // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space @@ -1012,6 +1048,12 @@ class SLASupportTree::Algorithm { std::vector phis(samples); for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); + auto& m = m_mesh; + using HitResult = EigenMesh3D::hit_result; + + // Hit results + std::vector hits(samples, HitResult(m)); + // We have to address the case when the direction vector v (same as // dir) is coincident with one of the world axes. In this case two of // its components will be completely zero and one is 1.0. Our method @@ -1033,10 +1075,10 @@ class SLASupportTree::Algorithm { // Now a and b vectors are perpendicular to v and to each other. // Together they define the plane where we have to iterate with the // given angles in the 'phis' vector - auto& m = m_mesh; tbb::parallel_for(size_t(0), phis.size(), - [&phis, &m, sd, r_pin, r_back, s, a, b, c](size_t i) + [&phis, &hits, &m, sd, r_pin, r_back, s, a, b, c] + (size_t i) { double& phi = phis[i]; double sinphi = std::sin(phi); @@ -1071,12 +1113,17 @@ class SLASupportTree::Algorithm { else { // re-cast the ray from the outside of the object auto q2 = m.query_ray_hit(ps + (q.distance() + 2*sd)*n, n); - phi = q2.distance(); + hits[i] = q2; } - } else phi = q.distance(); + } else hits[i] = q; }); - auto mit = std::min_element(phis.begin(), phis.end()); + auto mit = std::min_element(hits.begin(), hits.end(), + [](const HitResult& hr1, + const HitResult& hr2) + { + return hr1.distance() < hr2.distance(); + }); return *mit; } @@ -1085,11 +1132,12 @@ class SLASupportTree::Algorithm { // If the function is used for headless sticks, the ins_check parameter // have to be true as the beginning of the stick might be inside the model // geometry. - double bridge_mesh_intersect(const Vec3d& s, - const Vec3d& dir, - double r, - bool ins_check = false, - unsigned samples = 4) + EigenMesh3D::hit_result bridge_mesh_intersect( + const Vec3d& s, + const Vec3d& dir, + double r, + bool ins_check = false, + unsigned samples = 4) { // helper vector calculations Vec3d a(0, 1, 0), b; @@ -1116,9 +1164,14 @@ class SLASupportTree::Algorithm { for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); auto& m = m_mesh; + using HitResult = EigenMesh3D::hit_result; + + // Hit results + std::vector hits(samples, HitResult(m)); tbb::parallel_for(size_t(0), phis.size(), - [&m, &phis, a, b, sd, dir, r, s, ins_check](size_t i) + [&m, &phis, a, b, sd, dir, r, s, ins_check, &hits] + (size_t i) { double& phi = phis[i]; double sinphi = std::sin(phi); @@ -1142,12 +1195,17 @@ class SLASupportTree::Algorithm { auto hr2 = m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir); - phi = hr2.distance(); + hits[i] = hr2; } - } else phi = hr.distance(); + } else hits[i] = hr; }); - auto mit = std::min_element(phis.begin(), phis.end()); + auto mit = std::min_element(hits.begin(), hits.end(), + [](const HitResult& hr1, + const HitResult& hr2) + { + return hr1.distance() < hr2.distance(); + }); return *mit; } @@ -1209,6 +1267,88 @@ class SLASupportTree::Algorithm { } } + long search_nearest(const Vec3d& querypoint) + { + SpatIndex spindex = m_pillar_index; + + long nearest_id = -1; + const double gndlvl = m_result.ground_level; + + while(nearest_id < 0 && !spindex.empty()) { m_thr(); + // loop until a suitable head is not found + // if there is a pillar closer than the cluster center + // (this may happen as the clustering is not perfect) + // than we will bridge to this closer pillar + + Vec3d qp(querypoint(X), querypoint(Y), gndlvl); + auto ne = spindex.nearest(qp, 1).front(); + const Head& nearhead = m_result.head(ne.second); + + Vec3d nearhead_jp = nearhead.junction_point(); + double dist2d = distance(qp, ne.first); + + // Bridge endpoint on the main pillar + Vec3d bridge_ep(nearhead_jp(X), nearhead_jp(Y), querypoint(Z) + + dist2d * std::tan(-m_cfg.bridge_slope)); + + if(bridge_ep(Z) > nearhead_jp(Z)) { + // If the sidepoint cannot connect to the pillar + // from its head junction, just skip this pillar. + spindex.remove(ne); + continue; + } + + double d = distance(querypoint, bridge_ep); + + // There will be a minimum distance from the ground where the + // bridge is allowed to connect. This is an empiric value. + double minz = gndlvl + 2 * m_cfg.head_width_mm; + + // WARNING: previously, max_bridge_length was divided by two. + // I don't remember if this was intentional or by accident. There + // is no logical reason why it shouldn't be used directly. + if(bridge_ep(Z) <= minz || d > m_cfg.max_bridge_length_mm) break; + + double chkd = bridge_mesh_intersect(querypoint, + dirv(querypoint, bridge_ep), + m_cfg.head_back_radius_mm); + + if(chkd >= d) nearest_id = ne.second; + + spindex.remove(ne); + } + return nearest_id; + } + + void connect_to_nearhead(const Head& head, const Head& nearhead) { + Vec3d hjp = head.junction_point(); + Vec3d headjp = hjp; + Vec3d nearheadjp = nearhead.junction_point(); + double r = m_cfg.head_back_radius_mm; + + double d = distance(Vec2d{headjp(X), headjp(Y)}, + Vec2d{nearheadjp(X), nearheadjp(Y)}); + + Vec3d touchjp(nearheadjp(X), nearheadjp(Y), headjp(Z) + + d * std::tan(-m_cfg.bridge_slope)); + + if(touchjp(Z) > nearheadjp(Z)) { + double hdiff = touchjp(Z) - nearheadjp(Z); + headjp(Z) -= hdiff; + touchjp(Z) -= hdiff; + + // create a pillar without base, + // it doesn't connect to ground just to an existing + // shorter pillar + m_result.add_pillar(unsigned(nearhead.id), headjp, r); + } + + if(headjp(Z) < hjp(Z)) m_result.add_junction(headjp, r); + if(touchjp(Z) >= nearheadjp(Z)) m_result.add_junction(touchjp, r); + + m_result.add_bridge(headjp, touchjp, r); + } + public: Algorithm(const SupportConfig& config, @@ -1220,7 +1360,6 @@ public: m_mesh(emesh), m_support_pts(support_pts), m_support_nmls(support_pts.size(), 3), - m_ptheights(support_pts.size(), 0.0), m_result(result), m_points(support_pts.size(), 3), m_thr(thr) @@ -1407,7 +1546,6 @@ public: void classify() { // We should first get the heads that reach the ground directly - m_ptheights.reserve(m_iheads.size()); PtIndices ground_head_indices; ground_head_indices.reserve(m_iheads.size()); m_iheads_onmodel.reserve(m_iheads.size()); @@ -1425,18 +1563,10 @@ public: Vec3d headjp = head.junction_point(); // collision check - double t = bridge_mesh_intersect(headjp, n, r); + auto hit = bridge_mesh_intersect(headjp, n, r); - // Precise distance measurement - double tprec = ray_mesh_intersect(headjp, n); - - // Save the distance from a surface in the Z axis downwards. It - // may be infinity but that is telling us that it touches the - // ground. - m_ptheights.emplace_back(tprec); - - if(std::isinf(t)) ground_head_indices.emplace_back(i); - else m_iheads_onmodel.emplace_back(i); + if(std::isinf(hit)) ground_head_indices.emplace_back(i); + else m_iheads_onmodel.emplace_back(std::make_pair(i, hit)); } // We want to search for clusters of points that are far enough @@ -1445,13 +1575,13 @@ public: // possibly in their centroid support point. auto d_base = 2*m_cfg.base_radius_mm; auto& thr = m_thr; - m_pillar_clusters = cluster(m_points, + m_pillar_clusters = cluster(m_points, ground_head_indices, [thr, d_base](const SpatElement& p, const SpatElement& s) { thr(); return distance(Vec2d(p.first(X), p.first(Y)), Vec2d(s.first(X), s.first(Y))) < d_base; - }, 3, ground_head_indices); // max 3 heads to connect to one pillar + }, 3); // max 3 heads to connect to one pillar } // Step: Routing the ground connected pinheads, and interconnecting @@ -1461,17 +1591,12 @@ public: // a central pillar is limited to avoid bad weight distribution. void routing_to_ground() { - const double hbr = m_cfg.head_back_radius_mm; const double pradius = m_cfg.head_back_radius_mm; - const double maxbridgelen = m_cfg.max_bridge_length_mm; const double gndlvl = m_result.ground_level; - - ClusterEl cl_centroids; cl_centroids.reserve(m_pillar_clusters.size()); - SpatIndex pheadindex; // spatial index for the junctions for(auto& cl : m_pillar_clusters) { m_thr(); // place all the centroid head positions into the index. We // will query for alternative pillar positions. If a sidehead @@ -1503,7 +1628,7 @@ public: Head& h = m_result.head(hid); h.transform(); Vec3d p = h.junction_point(); p(Z) = gndlvl; - pheadindex.insert(p, hid); + m_pillar_index.insert(p, hid); } // now we will go through the clusters ones again and connect the @@ -1531,97 +1656,31 @@ public: // TODO: don't consider the cluster centroid but calculate a // central position where the pillar can be placed. this way // the weight is distributed more effectively on the pillar. - auto search_nearest = - [this, maxbridgelen, gndlvl, pradius] - (SpatIndex& spindex, const Vec3d& jsh) - { - long nearest_id = -1; - const double max_len = maxbridgelen / 2; - while(nearest_id < 0 && !spindex.empty()) { m_thr(); - // loop until a suitable head is not found - // if there is a pillar closer than the cluster center - // (this may happen as the clustering is not perfect) - // than we will bridge to this closer pillar - - Vec3d qp(jsh(X), jsh(Y), gndlvl); - auto ne = spindex.nearest(qp, 1).front(); - const Head& nearhead = m_result.head(ne.second); - - Vec3d jh = nearhead.junction_point(); - Vec3d jp = jsh; - double dist2d = distance(qp, ne.first); - - // Bridge endpoint on the main pillar - Vec3d jn(jh(X), jh(Y), jp(Z) + - dist2d * std::tan(-m_cfg.bridge_slope)); - - if(jn(Z) > jh(Z)) { - // If the sidepoint cannot connect to the pillar - // from its head junction, just skip this pillar. - spindex.remove(ne); - continue; - } - - double d = distance(jp, jn); - - if(jn(Z) <= gndlvl + 2*m_cfg.head_width_mm || d > max_len) - break; - - double chkd = bridge_mesh_intersect(jp, dirv(jp, jn), - pradius); - - if(chkd >= d) nearest_id = ne.second; - - spindex.remove(ne); - } - return nearest_id; - }; for(auto c : cl) { m_thr(); auto& sidehead = m_result.head(c); sidehead.transform(); - Vec3d jsh = sidehead.junction_point(); - SpatIndex spindex = pheadindex; - long nearest_id = search_nearest(spindex, jsh); + Vec3d sidehead_jp = sidehead.junction_point(); + long nearest_id = search_nearest(sidehead_jp); // at this point we either have our pillar index or we have // to connect the sidehead to the ground if(nearest_id < 0) { // Could not find a pillar, create one - Vec3d jp = jsh; jp(Z) = gndlvl; - m_result.add_pillar(unsigned(sidehead.id), jp, pradius). - add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); + m_result.add_pillar( + unsigned(sidehead.id), + Vec3d{sidehead_jp(X), sidehead_jp(Y), gndlvl}, + pradius).add_base(m_cfg.base_height_mm, + m_cfg.base_radius_mm); // connects to ground, eligible for bridging cl_centroids.emplace_back(c); } else { // Creating the bridge to the nearest pillar - auto nearest_uid = unsigned(nearest_id); const Head& nearhead = m_result.head(nearest_uid); - Vec3d jp = jsh; - Vec3d jh = nearhead.junction_point(); - - double d = distance(Vec2d{jp(X), jp(Y)}, - Vec2d{jh(X), jh(Y)}); - Vec3d jn(jh(X), jh(Y), jp(Z) + - d * std::tan(-m_cfg.bridge_slope)); - - if(jn(Z) > jh(Z)) { - double hdiff = jn(Z) - jh(Z); - jp(Z) -= hdiff; - jn(Z) -= hdiff; - - // pillar without base, doesn't connect to ground. - m_result.add_pillar(nearest_uid, jp, pradius); - } - - if(jp(Z) < jsh(Z)) m_result.add_junction(jp, hbr); - if(jn(Z) >= jh(Z)) m_result.add_junction(jn, hbr); - - m_result.add_bridge(jp, jn, - sidehead.request_pillar_radius(pradius)); + connect_to_nearhead(sidehead, nearhead); } } } @@ -1716,112 +1775,175 @@ public: // nearby pillar that can hold the supported weight. void routing_to_model() { + + // We need to check if there is an easy way out to the bed surface. + // If it can be routed there with a bridge shorter than + // min_bridge_distance. + + // First we want to index the available pillars. The best is to connect + // these points to the available pillars + + auto routedown = [this](Head& head, const Vec3d& dir, double dist) + { + head.transform(); + Vec3d hjp = head.junction_point(); + Vec3d endp = hjp + dist * dir; + 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; + m_result.add_pillar(endp, groundp, head.r_back_mm).add_base( + m_cfg.base_height_mm, m_cfg.base_radius_mm); + }; + // TODO: connect these to the ground pillars if possible - for(auto idx : m_iheads_onmodel) { m_thr(); - double gh = m_ptheights[idx]; - double base_width = m_cfg.head_width_mm; + for(auto item : m_iheads_onmodel) { m_thr(); + unsigned idx = item.first; + EigenMesh3D::hit_result hit = item.second; auto& head = m_result.head(idx); + Vec3d hjp = head.junction_point(); - if(std::isinf(gh)) { // in this case the the pillar geometry - head.invalidate(); continue; -// // is partially inside the model geometry. We cannot go -// // straight down but at an angle. We will search for a suitable -// // direction with the optimizer, optimizing for the smallest -// // difference between the bridge body hit distance and the -// // bridge center hit distance. + // ///////////////////////////////////////////////////////////////// + // Search nearby pillar + // ///////////////////////////////////////////////////////////////// -// // Get the spherical representation of the normal. its easier to -// // work with. -// double z = head.dir(Z); -// double r = 1.0; // for normalized vector -// double polar = std::acos(z / r); -// double azimuth = std::atan2(head.dir(Y), head.dir(X)); - -// using libnest2d::opt::bound; -// using libnest2d::opt::initvals; -// using libnest2d::opt::SimplexOptimizer; -// using libnest2d::opt::StopCriteria; - -// StopCriteria stc; -// stc.max_iterations = 100; -// stc.relative_score_difference = 1e-3; -// stc.stop_score = head.r_pin_mm; -// SimplexOptimizer solver(stc); - -// auto oresult = solver.optimize_max( -// [&head, &mesh](double plr, double azm) -// { -// auto n = Vec3d(std::cos(azm) * std::sin(plr), -// std::sin(azm) * std::sin(plr), -// std::cos(plr)).normalized(); - -// double score = bridge_mesh_intersect(head.junction_point(), -// n, -// head.r_back_mm, -// mesh); -// return score; -// }, -// initvals(polar, azimuth), // let's start with what we have -// bound(3*PI/4, PI), // Must not exceed the slope limit -// bound(-PI, PI) // azimuth can be a full range search -// ); - -// t = oresult.score; -// polar = std::get<0>(oresult.optimum); -// azimuth = std::get<1>(oresult.optimum); -// nn = Vec3d(std::cos(azimuth) * std::sin(polar), -// std::sin(azimuth) * std::sin(polar), -// std::cos(polar)).normalized(); - - } - - // In this case there is no room for the base pinhead. - if(gh < head.fullwidth()) { - double min_l = - 2 * m_cfg.head_front_radius_mm + - 2 * m_cfg.head_back_radius_mm - - m_cfg.head_penetration_mm; - - base_width = gh - min_l; - } - - if(base_width < 0) { - // There is really no space for even a reduced size head. We - // have to replace that with a small half sphere that touches - // the model surface. (TODO) - head.invalidate(); + long nearest_pillar_id = search_nearest(hjp); + if(nearest_pillar_id >= 0) { // successful search + auto nearest_uid = unsigned(nearest_pillar_id); + const Head& nearhead = m_result.head(nearest_uid); + head.transform(); // accept the head + connect_to_nearhead(head, nearhead); continue; } - head.transform(); + // ///////////////////////////////////////////////////////////////// + // Try straight path + // ///////////////////////////////////////////////////////////////// - Vec3d headend = head.junction_point(); + // Cannot connect to nearby pillar. We will try to search for + // a route to the ground. - Head base_head(m_cfg.head_back_radius_mm, - m_cfg.head_front_radius_mm, - base_width, - m_cfg.head_penetration_mm, - {0.0, 0.0, 1.0}, - {headend(X), headend(Y), headend(Z) - gh}); + double t = bridge_mesh_intersect(hjp, head.dir, head.r_back_mm); + double d = 0, tdown = 0; + Vec3d dirdown(0.0, 0.0, -1.0); - base_head.transform(); + while(d < t && !std::isinf(tdown = bridge_mesh_intersect( + hjp + d*head.dir, + dirdown, head.r_back_mm))) { + d += head.r_back_mm; + } - // Robustness check: - if(headend(Z) < base_head.junction_point()(Z)) { - // This should not happen it is against all assumptions - BOOST_LOG_TRIVIAL(warning) - << "Ignoring invalid supports connecting to model body"; - head.invalidate(); + if(std::isinf(tdown)) { // we heave found a route to the ground + routedown(head, head.dir, d); continue; + } + + // ///////////////////////////////////////////////////////////////// + // Optimize bridge direction + // ///////////////////////////////////////////////////////////////// + + // Straight path failed so we will try to search for a suitable + // direction out of the cavity. + + // Get the spherical representation of the normal. its easier to + // work with. + double z = head.dir(Z); + double r = 1.0; // for normalized vector + double polar = std::acos(z / r); + double azimuth = std::atan2(head.dir(Y), head.dir(X)); + + using libnest2d::opt::bound; + using libnest2d::opt::initvals; + using libnest2d::opt::SimplexOptimizer; + using libnest2d::opt::StopCriteria; + + StopCriteria stc; + stc.max_iterations = 100; + stc.relative_score_difference = 1e-3; + stc.stop_score = 1e6; + SimplexOptimizer solver(stc); + + double r_back = head.r_back_mm; + + auto oresult = solver.optimize_max( + [this, hjp, r_back](double plr, double azm) + { + Vec3d n = Vec3d(std::cos(azm) * std::sin(plr), + std::sin(azm) * std::sin(plr), + std::cos(plr)).normalized(); + + return bridge_mesh_intersect(hjp, n, r_back); + }, + initvals(polar, azimuth), // let's start with what we have + bound(3*PI/4, PI), // Must not exceed the slope limit + bound(-PI, PI) // azimuth can be a full range search + ); + + d = 0; t = oresult.score; + polar = std::get<0>(oresult.optimum); + azimuth = std::get<1>(oresult.optimum); + Vec3d bridgedir = Vec3d(std::cos(azimuth) * std::sin(polar), + std::sin(azimuth) * std::sin(polar), + std::cos(polar)).normalized(); + + while(d < t && !std::isinf(tdown = bridge_mesh_intersect( + hjp + d*bridgedir, + dirdown, + head.r_back_mm))) { + d += head.r_back_mm; + } + + if(std::isinf(tdown)) { // we heave found a route to the ground + routedown(head, bridgedir, d); continue; + } + + // ///////////////////////////////////////////////////////////////// + // Route to model body + // ///////////////////////////////////////////////////////////////// + + double zangle = std::asin(hit.direction()(Z)); + zangle = std::max(zangle, PI/4); + double h = std::sin(zangle) * head.fullwidth(); + + // The width of the tail head that we would like to have... + h = std::min(hit.distance() - head.r_back_mm, h); + + if(h > 0) { + Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h}; + auto center_hit = m_mesh.query_ray_hit(hjp, dirdown); + + double hitdiff = center_hit.distance() - hit.distance(); + Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm? + center_hit.position() : hit.position(); + + head.transform(); + + Pillar& pill = m_result.add_pillar(unsigned(head.id), + endp, + head.r_back_mm); + + Vec3d taildir = endp - hitp; + double dist = distance(endp, hitp) + m_cfg.head_penetration_mm; + double w = dist - 2 * head.r_pin_mm - head.r_back_mm; + + Head tailhead(head.r_back_mm, + head.r_pin_mm, + w, + m_cfg.head_penetration_mm, + taildir, + hitp); + + tailhead.transform(); + pill.base = tailhead.mesh; continue; } - double hl = base_head.fullwidth() - head.r_back_mm; - - m_result.add_pillar(idx, - Vec3d{headend(X), headend(Y), headend(Z) - gh + hl}, - m_cfg.head_back_radius_mm - ).base = base_head.mesh; + // We have failed to route this head. + BOOST_LOG_TRIVIAL(warning) + << "Failed to route model facing support point." + << " ID: " << idx; + head.invalidate(); } } @@ -1884,7 +2006,6 @@ bool SLASupportTree::generate(const std::vector &support_points, ROUTING_NONGROUND, HEADLESS, DONE, - HALT, ABORT, NUM_STEPS //... @@ -1913,16 +2034,12 @@ bool SLASupportTree::generate(const std::vector &support_points, // Done }, - [] () { - // Halt - }, - [] () { // Abort } }; - Steps pc = BEGIN, pc_prev = BEGIN; + Steps pc = BEGIN; if(cfg.ground_facing_only) { program[ROUTING_NONGROUND] = []() { @@ -1936,18 +2053,17 @@ bool SLASupportTree::generate(const std::vector &support_points, } // Let's define a simple automaton that will run our program. - auto progress = [&ctl, &pc, &pc_prev] () { + auto progress = [&ctl, &pc] () { static const std::array stepstr { - "Starting", - "Filtering", - "Generate pinheads", - "Classification", - "Routing to ground", - "Routing supports to model surface", - "Processing small holes", - "Done", - "Halt", - "Abort" + L("Starting"), + L("Filtering"), + L("Generate pinheads"), + L("Classification"), + L("Routing to ground"), + L("Routing supports to model surface"), + L("Processing small holes"), + L("Done"), + L("Abort") }; static const std::array stepstate { @@ -1959,7 +2075,6 @@ bool SLASupportTree::generate(const std::vector &support_points, 70, 80, 100, - 0, 0 }; @@ -1973,7 +2088,6 @@ bool SLASupportTree::generate(const std::vector &support_points, case ROUTING_GROUND: pc = ROUTING_NONGROUND; break; case ROUTING_NONGROUND: pc = HEADLESS; break; case HEADLESS: pc = DONE; break; - case HALT: pc = pc_prev; break; case DONE: case ABORT: break; default: ; @@ -1982,7 +2096,7 @@ bool SLASupportTree::generate(const std::vector &support_points, }; // Just here we run the computation... - while(pc < DONE || pc == HALT) { + while(pc < DONE) { progress(); program[pc](); } diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index 04b5a72070..da2bb1b798 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -169,6 +169,7 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const hit_result ret(*this); ret.m_t = double(hit.t); ret.m_dir = dir; + ret.m_source = s; if(!std::isinf(hit.t) && !std::isnan(hit.t)) ret.m_face_id = hit.id; return ret; @@ -348,9 +349,9 @@ PointSet normals(const PointSet& points, // Clustering a set of points by the given criteria ClusteredPoints cluster( - const sla::PointSet& points, + const sla::PointSet& points, const std::vector& indices, std::function pred, - unsigned max_points = 0, const std::vector& indices = {}) + unsigned max_points = 0) { namespace bgi = boost::geometry::index; @@ -360,12 +361,8 @@ ClusteredPoints cluster( Index3D sindex; // Build the index - if(indices.empty()) - for(unsigned idx = 0; idx < points.rows(); idx++) - sindex.insert( std::make_pair(points.row(idx), idx)); - else - for(unsigned idx : indices) - sindex.insert( std::make_pair(points.row(idx), idx)); + for(unsigned idx : indices) + sindex.insert( std::make_pair(points.row(idx), idx)); using Elems = std::vector; From 878ac7f1b09d20ee215738636129c77233c38f95 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 1 Mar 2019 17:45:29 +0100 Subject: [PATCH 05/37] Fixing many errors caused by the new changes. --- src/libslic3r/SLA/SLACommon.hpp | 22 ++++--- src/libslic3r/SLA/SLASupportTree.cpp | 86 ++++++++++++++++------------ src/libslic3r/SLA/SLASupportTree.hpp | 2 +- 3 files changed, 65 insertions(+), 45 deletions(-) diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index 4fdb323784..2dfc2b5e73 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -72,30 +72,36 @@ public: class hit_result { double m_t = std::nan(""); int m_face_id = -1; - std::reference_wrapper m_mesh; + const EigenMesh3D *m_mesh = nullptr; Vec3d m_dir; Vec3d m_source; friend class EigenMesh3D; - public: // A valid object of this class can only be obtained from // EigenMesh3D::query_ray_hit method. - explicit inline hit_result(const EigenMesh3D& em): m_mesh(em) {} + explicit inline hit_result(const EigenMesh3D& em): m_mesh(&em) {} + public: + + // This can create a placeholder object which is invalid (not created + // by a query_ray_hit call) but the distance can be preset to + // a specific value for distinguishing the placeholder. + inline hit_result(double val = std::nan("")): m_t(val) {} inline double distance() const { return m_t; } inline const Vec3d& direction() const { return m_dir; } inline Vec3d position() const { return m_source + m_dir * m_t; } inline int face() const { return m_face_id; } + inline bool is_valid() const { return m_mesh != nullptr; } // Hit_result can decay into a double as the hit distance. inline operator double() const { return distance(); } inline Vec3d normal() const { - if(m_face_id < 0) return {}; - auto trindex = m_mesh.get().m_F.row(m_face_id); - const Vec3d& p1 = m_mesh.get().V().row(trindex(0)); - const Vec3d& p2 = m_mesh.get().V().row(trindex(1)); - const Vec3d& p3 = m_mesh.get().V().row(trindex(2)); + if(m_face_id < 0 || !is_valid()) return {}; + auto trindex = m_mesh->m_F.row(m_face_id); + const Vec3d& p1 = m_mesh->V().row(trindex(0)); + const Vec3d& p2 = m_mesh->V().row(trindex(1)); + const Vec3d& p3 = m_mesh->V().row(trindex(2)); Eigen::Vector3d U = p2 - p1; Eigen::Vector3d V = p3 - p1; return U.cross(V).normalized(); diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 0a157fe3a4..d2c9a9f6b6 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -1011,20 +1011,20 @@ class SLASupportTree::Algorithm { // surface. dir: This is the direction of the head from the pin to the back // r_pin, r_back: the radiuses of the pin and the back sphere width: This // is the full width from the pin center to the back center m: The object - // mesh - // - // Optional: - // samples: how many rays will be shot - // safety distance: This will be added to the radiuses to have a safety - // distance from the mesh. + // mesh. + // The return value is the hit result from the ray casting. If the starting + // point was inside the model, an "invalid" hit_result will be returned + // with a zero distance value instead of a NAN. This way the result can + // be used safely for comparison with other distances. EigenMesh3D::hit_result pinhead_mesh_intersect( const Vec3d& s, const Vec3d& dir, double r_pin, double r_back, - double width, - unsigned samples = 8) + double width) { + static const size_t SAMPLES = 8; + // method based on: // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space @@ -1045,14 +1045,14 @@ class SLASupportTree::Algorithm { // The portions of the circle (the head-back circle) for which we will // shoot rays. - std::vector phis(samples); + std::array phis; for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); auto& m = m_mesh; using HitResult = EigenMesh3D::hit_result; // Hit results - std::vector hits(samples, HitResult(m)); + std::array hits; // We have to address the case when the direction vector v (same as // dir) is coincident with one of the world axes. In this case two of @@ -1109,21 +1109,27 @@ class SLASupportTree::Algorithm { auto q = m.query_ray_hit(ps + sd*n, n); if(q.is_inside()) { // the hit is inside the model - if(q.distance() > 2*r_pin) phi = 0; + if(q.distance() > 2*r_pin) { + // If we are inside the model and the hit distance is bigger + // than our pin circle diameter, it probably indicates that + // the support point was already inside the model, or there + // is really no space around the point. We will assign a + // zero hit distance to these cases which will enforce the + // function return value to be an invalid ray with zero hit + // distance. (see min_element at the end) + hits[i] = HitResult(0.0); + } else { - // re-cast the ray from the outside of the object + // re-cast the ray from the outside of the object. + // The starting point has an offset of 2*safety_distance + // because the original ray has also had an offset auto q2 = m.query_ray_hit(ps + (q.distance() + 2*sd)*n, n); hits[i] = q2; } } else hits[i] = q; }); - auto mit = std::min_element(hits.begin(), hits.end(), - [](const HitResult& hr1, - const HitResult& hr2) - { - return hr1.distance() < hr2.distance(); - }); + auto mit = std::min_element(hits.begin(), hits.end()); return *mit; } @@ -1132,13 +1138,18 @@ class SLASupportTree::Algorithm { // If the function is used for headless sticks, the ins_check parameter // have to be true as the beginning of the stick might be inside the model // geometry. + // The return value is the hit result from the ray casting. If the starting + // point was inside the model, an "invalid" hit_result will be returned + // with a zero distance value instead of a NAN. This way the result can + // be used safely for comparison with other distances. EigenMesh3D::hit_result bridge_mesh_intersect( const Vec3d& s, const Vec3d& dir, double r, - bool ins_check = false, - unsigned samples = 4) + bool ins_check = false) { + static const size_t SAMPLES = 8; + // helper vector calculations Vec3d a(0, 1, 0), b; const double& sd = m_cfg.safety_distance_mm; @@ -1160,14 +1171,14 @@ class SLASupportTree::Algorithm { } // circle portions - std::vector phis(samples); + std::array phis; for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); auto& m = m_mesh; using HitResult = EigenMesh3D::hit_result; // Hit results - std::vector hits(samples, HitResult(m)); + std::array hits; tbb::parallel_for(size_t(0), phis.size(), [&m, &phis, a, b, sd, dir, r, s, ins_check, &hits] @@ -1189,7 +1200,7 @@ class SLASupportTree::Algorithm { auto hr = m.query_ray_hit(p + sd*dir, dir); if(ins_check && hr.is_inside()) { - if(hr.distance() > 2*r) phi = 0; + if(hr.distance() > 2*r) hits[i] = HitResult(0.0); else { // re-cast the ray from the outside of the object auto hr2 = @@ -1200,12 +1211,7 @@ class SLASupportTree::Algorithm { } else hits[i] = hr; }); - auto mit = std::min_element(hits.begin(), hits.end(), - [](const HitResult& hr1, - const HitResult& hr2) - { - return hr1.distance() < hr2.distance(); - }); + auto mit = std::min_element(hits.begin(), hits.end()); return *mit; } @@ -1442,7 +1448,7 @@ public: polar = std::max(polar, 3*PI / 4); // save the head (pinpoint) position - Vec3d hp = m_points.row(i); + Vec3d hp = m_points.row(fidx); double w = m_cfg.head_width_mm + m_cfg.head_back_radius_mm + @@ -1492,12 +1498,14 @@ public: bound(-PI, PI) // azimuth can be a full search ); - t = oresult.score; - polar = std::get<0>(oresult.optimum); - azimuth = std::get<1>(oresult.optimum); - nn = Vec3d(std::cos(azimuth) * std::sin(polar), - std::sin(azimuth) * std::sin(polar), - std::cos(polar)).normalized(); + if(oresult.score > w) { + polar = std::get<0>(oresult.optimum); + azimuth = std::get<1>(oresult.optimum); + nn = Vec3d(std::cos(azimuth) * std::sin(polar), + std::sin(azimuth) * std::sin(polar), + std::cos(polar)).normalized(); + t = oresult.score; + } } // save the verified and corrected normal @@ -1829,6 +1837,8 @@ public: double d = 0, tdown = 0; Vec3d dirdown(0.0, 0.0, -1.0); + t = std::min(t, m_cfg.max_bridge_length_mm); + while(d < t && !std::isinf(tdown = bridge_mesh_intersect( hjp + d*head.dir, dirdown, head.r_back_mm))) { @@ -1881,12 +1891,16 @@ public: ); d = 0; t = oresult.score; + + polar = std::get<0>(oresult.optimum); azimuth = std::get<1>(oresult.optimum); Vec3d bridgedir = Vec3d(std::cos(azimuth) * std::sin(polar), std::sin(azimuth) * std::sin(polar), std::cos(polar)).normalized(); + t = std::min(t, m_cfg.max_bridge_length_mm); + while(d < t && !std::isinf(tdown = bridge_mesh_intersect( hjp + d*bridgedir, dirdown, diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index e1d5449a90..54e934f3a5 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -82,7 +82,7 @@ struct SupportConfig { double normal_cutoff_angle = 150.0 * M_PI / 180.0; // The shortest distance of any support structure from the model surface - double safety_distance_mm = 0.001; + double safety_distance_mm = 0.1; }; struct PoolConfig; From f2f513dd3e1077c8b36c0a3f5c083ff2e4ea7eb8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 1 Mar 2019 19:19:05 +0100 Subject: [PATCH 06/37] Trying to improve pillar connectivity. --- src/libslic3r/SLA/SLASupportTree.cpp | 63 +++++++++++++++------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index d2c9a9f6b6..d264c30122 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -1218,7 +1218,7 @@ class SLASupportTree::Algorithm { // Helper function for interconnecting two pillars with zig-zag bridges. // This is not an individual step. - void interconnect(const Pillar& pillar, const Pillar& nextpillar) + bool interconnect(const Pillar& pillar, const Pillar& nextpillar) { const Head& phead = m_result.pillar_head(pillar.id); const Head& nextphead = m_result.pillar_head(nextpillar.id); @@ -1234,43 +1234,48 @@ class SLASupportTree::Algorithm { double chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r); double bridge_distance = pillar_dist / std::cos(-m_cfg.bridge_slope); + bool was_connected = false; // If the pillars are so close that they touch each other, // there is no need to bridge them together. if(pillar_dist > 2*m_cfg.head_back_radius_mm && - bridge_distance < m_cfg.max_bridge_length_mm) + bridge_distance < m_cfg.max_bridge_length_mm) { while(sj(Z) > pillar.endpoint(Z) + m_cfg.base_radius_mm && ej(Z) > nextpillar.endpoint(Z) + m_cfg.base_radius_mm) - { - if(chkd >= bridge_distance) { - m_result.add_bridge(sj, ej, pillar.r); + { + if(chkd >= bridge_distance) { + m_result.add_bridge(sj, ej, pillar.r); + was_connected = true; - auto pcm = m_cfg.pillar_connection_mode; + auto pcm = m_cfg.pillar_connection_mode; - // double bridging: (crosses) - if( pcm == PillarConnectionMode::cross || - (pcm == PillarConnectionMode::dynamic && - pillar_dist > 2*m_cfg.base_radius_mm)) - { - // If the columns are close together, no need to - // double bridge them - Vec3d bsj(ej(X), ej(Y), sj(Z)); - Vec3d bej(sj(X), sj(Y), ej(Z)); + // double bridging: (crosses) + if( pcm == PillarConnectionMode::cross || + (pcm == PillarConnectionMode::dynamic && + pillar_dist > 2*m_cfg.base_radius_mm)) + { + // If the columns are close together, no need to + // double bridge them + Vec3d bsj(ej(X), ej(Y), sj(Z)); + Vec3d bej(sj(X), sj(Y), ej(Z)); - // need to check collision for the cross stick - double backchkd = bridge_mesh_intersect( - bsj, dirv(bsj, bej), pillar.r); + // need to check collision for the cross stick + double backchkd = bridge_mesh_intersect( + bsj, dirv(bsj, bej), pillar.r); - if(backchkd >= bridge_distance) { - m_result.add_bridge(bsj, bej, pillar.r); + if(backchkd >= bridge_distance) { + m_result.add_bridge(bsj, bej, pillar.r); + } } } + sj.swap(ej); + ej(Z) = sj(Z) + zstep; + chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r); } - sj.swap(ej); - ej(Z) = sj(Z) + zstep; - chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r); } + + return was_connected; } long search_nearest(const Vec3d& querypoint) @@ -1737,12 +1742,14 @@ public: // fashioned connections between pillars for(unsigned i : ring) { m_thr(); const Pillar& outerpill = m_result.head_pillar(i); - auto res = innerring.nearest(outerpill.endpoint, 1); - if(res.empty()) continue; - auto ne = res.front(); - const Pillar& innerpill = m_result.pillars()[ne.second]; - interconnect(outerpill, innerpill); + auto res = innerring.nearest(outerpill.endpoint, + unsigned(innerring.size())); + + for(auto& ne : res) { + const Pillar& innerpill = m_result.pillars()[ne.second]; + if(interconnect(outerpill, innerpill)) break; + } } } From c2d5a8d03b4974fe2793eb666f296b7d318347db Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 4 Mar 2019 18:32:28 +0100 Subject: [PATCH 07/37] Working on improved interconnection strategy --- src/libslic3r/SLA/SLASupportTree.cpp | 296 +++++++++++++++--------- src/libslic3r/SLA/SLASupportTreeIGL.cpp | 89 +++++++ 2 files changed, 277 insertions(+), 108 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index d264c30122..7ffa2274a4 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -594,6 +594,10 @@ inline ClusteredPoints cluster( return cluster(points, indices, pred, max_points); } +ClusteredPoints cluster_nearest2d( + const PointSet& points, const std::vector& indices, + double dist, + unsigned max_points = 0); // This class will hold the support tree meshes with some additional bookkeeping // as well. Various parts of the support geometry are stored separately and are @@ -818,6 +822,9 @@ long cluster_centroid(const ClusterEl& clust, // distance for the two points and add the distance to the averages. // The point with the smallest average than wins. + // The complexity should be O(n^2) but we will mostly apply this function + // for small clusters only (cca 3 elements) + std::vector sel(clust.size(), false); // create full zero bitmask std::fill(sel.end() - 2, sel.end(), true); // insert the two ones std::vector avgs(clust.size(), 0.0); // store the average distances @@ -1217,68 +1224,109 @@ class SLASupportTree::Algorithm { } // Helper function for interconnecting two pillars with zig-zag bridges. - // This is not an individual step. bool interconnect(const Pillar& pillar, const Pillar& nextpillar) { + // Get the starting heads corresponding to the given pillars const Head& phead = m_result.pillar_head(pillar.id); const Head& nextphead = m_result.pillar_head(nextpillar.id); - Vec3d sj = phead.junction_point(); - sj(Z) = std::min(sj(Z), nextphead.junction_point()(Z)); - Vec3d ej = nextpillar.endpoint; - double pillar_dist = distance(Vec2d{sj(X), sj(Y)}, - Vec2d{ej(X), ej(Y)}); - double zstep = pillar_dist * std::tan(-m_cfg.bridge_slope); - ej(Z) = sj(Z) + zstep; - - double chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r); - - double bridge_distance = pillar_dist / std::cos(-m_cfg.bridge_slope); + // We need to get the starting point of the zig-zag pattern. We have to + // be aware that the two head junctions are at different heights. We + // may start from the lowest junction and call it a day but this + // strategy would leave unconnected a lot of pillar duos where the + // shorter pillar is too short to start a new bridge but the taller + // pillar could still be bridged with the shorter one. bool was_connected = false; - // If the pillars are so close that they touch each other, - // there is no need to bridge them together. - if(pillar_dist > 2*m_cfg.head_back_radius_mm && - bridge_distance < m_cfg.max_bridge_length_mm) { - while(sj(Z) > pillar.endpoint(Z) + m_cfg.base_radius_mm && - ej(Z) > nextpillar.endpoint(Z) + m_cfg.base_radius_mm) + Vec3d supper = phead.junction_point(); + Vec3d slower = nextphead.junction_point(); + Vec3d eupper = pillar.endpoint; + Vec3d elower = nextpillar.endpoint; + + double zmin = m_result.ground_level + m_cfg.base_height_mm; + eupper(Z) = std::max(eupper(Z), zmin); + elower(Z) = std::max(elower(Z), zmin); + + // The usable length of both pillars should be positive + if(slower(Z) - elower(Z) < 0) return false; + if(supper(Z) - eupper(Z) < 0) return false; + + double pillar_dist = distance(Vec2d{slower(X), slower(Y)}, + Vec2d{supper(X), supper(Y)}); + double bridge_distance = pillar_dist / std::cos(-m_cfg.bridge_slope); + double zstep = pillar_dist * std::tan(-m_cfg.bridge_slope); + + if(pillar_dist < 2*m_cfg.head_back_radius_mm) return false; + if(bridge_distance > m_cfg.max_bridge_length_mm) return false; + + if(supper(Z) < slower(Z)) supper.swap(slower); + if(eupper(Z) < elower(Z)) eupper.swap(elower); + + double startz = 0, endz = 0; + + startz = slower(Z) - zstep < supper(Z) ? slower(Z) - zstep : slower(Z); + endz = eupper(Z) + zstep > elower(Z) ? eupper(Z) + zstep : eupper(Z); + + if(slower(Z) - eupper(Z) < std::abs(zstep)) { // no space for even one cross + + // Get max available space + startz = std::min(supper(Z), slower(Z) - zstep); + endz = std::max(eupper(Z) + zstep, elower(Z)); + + // Align to center + double available_dist = (startz - endz); + double rounds = std::floor(available_dist / std::abs(zstep)); + startz -= 0.5 * (available_dist - rounds * std::abs(zstep));; + } + + auto pcm = m_cfg.pillar_connection_mode; + bool docrosses = + pcm == PillarConnectionMode::cross || + (pcm == PillarConnectionMode::dynamic && + pillar_dist > 2*m_cfg.base_radius_mm); + + // 'sj' means starting junction, 'ej' is the end junction of a bridge. + // They will be swapped in every iteration thus the zig-zag pattern. + // According to a config parameter, a second bridge may be added which + // results in a cross connection between the pillars. + Vec3d sj = supper, ej = slower; sj(Z) = startz; ej(Z) = sj(Z) + zstep; + + while(ej(Z) >= endz) { + if(bridge_mesh_intersect(sj, + dirv(sj, ej), + pillar.r) >= bridge_distance) { - if(chkd >= bridge_distance) { - m_result.add_bridge(sj, ej, pillar.r); - was_connected = true; - - auto pcm = m_cfg.pillar_connection_mode; - - // double bridging: (crosses) - if( pcm == PillarConnectionMode::cross || - (pcm == PillarConnectionMode::dynamic && - pillar_dist > 2*m_cfg.base_radius_mm)) - { - // If the columns are close together, no need to - // double bridge them - Vec3d bsj(ej(X), ej(Y), sj(Z)); - Vec3d bej(sj(X), sj(Y), ej(Z)); - - // need to check collision for the cross stick - double backchkd = bridge_mesh_intersect( - bsj, dirv(bsj, bej), pillar.r); - - - if(backchkd >= bridge_distance) { - m_result.add_bridge(bsj, bej, pillar.r); - } - } - } - sj.swap(ej); - ej(Z) = sj(Z) + zstep; - chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r); + m_result.add_bridge(sj, ej, pillar.r); + was_connected = true; } + + // double bridging: (crosses) + if(docrosses) { + Vec3d sjback(ej(X), ej(Y), sj(Z)); + Vec3d ejback(sj(X), sj(Y), ej(Z)); + if(sjback(Z) <= slower(Z) && ejback(Z) >= eupper(Z) && + bridge_mesh_intersect(sjback, + dirv(sjback, ejback), + pillar.r) >= bridge_distance) + { + // need to check collision for the cross stick + m_result.add_bridge(sjback, ejback, pillar.r); + was_connected = true; + } + } + + sj.swap(ej); + ej(Z) = sj(Z) + zstep; } return was_connected; } - long search_nearest(const Vec3d& querypoint) + // Search for the nearest pillar to the given query point which is not + // further than max_dist. The result is the pillar ID or -1 if nothing was + // found. The pillar search is carried out using the m_pillar_index + // structure. + long search_nearest(const Vec3d& querypoint, double max_dist) { SpatIndex spindex = m_pillar_index; @@ -1318,7 +1366,7 @@ class SLASupportTree::Algorithm { // WARNING: previously, max_bridge_length was divided by two. // I don't remember if this was intentional or by accident. There // is no logical reason why it shouldn't be used directly. - if(bridge_ep(Z) <= minz || d > m_cfg.max_bridge_length_mm) break; + if(bridge_ep(Z) <= minz || d > max_dist) break; double chkd = bridge_mesh_intersect(querypoint, dirv(querypoint, bridge_ep), @@ -1331,6 +1379,10 @@ class SLASupportTree::Algorithm { return nearest_id; } + inline long search_nearest(const Vec3d& qp) { + return search_nearest(qp, m_cfg.max_bridge_length_mm); + } + void connect_to_nearhead(const Head& head, const Head& nearhead) { Vec3d hjp = head.junction_point(); Vec3d headjp = hjp; @@ -1579,7 +1631,10 @@ public: auto hit = bridge_mesh_intersect(headjp, n, r); if(std::isinf(hit)) ground_head_indices.emplace_back(i); - else m_iheads_onmodel.emplace_back(std::make_pair(i, hit)); + else { + if(m_cfg.ground_facing_only) head.invalidate(); + m_iheads_onmodel.emplace_back(std::make_pair(i, hit)); + } } // We want to search for clusters of points that are far enough @@ -1675,7 +1730,8 @@ public: sidehead.transform(); Vec3d sidehead_jp = sidehead.junction_point(); - long nearest_id = search_nearest(sidehead_jp); + long nearest_id = search_nearest(sidehead_jp, + m_cfg.max_bridge_length_mm/2); // at this point we either have our pillar index or we have // to connect the sidehead to the ground @@ -1714,59 +1770,15 @@ public: // points spatially and create the bridge stick from one endpoint to // another. - ClusterEl rem = cl_centroids; - ClusterEl ring; - - while(!rem.empty()) { // loop until all the points belong to some ring - m_thr(); - std::sort(rem.begin(), rem.end()); - - auto& points = m_points; - auto newring = pts_convex_hull(rem, - [&points](unsigned i) { - auto&& p = points.row(i); - return Vec2d(p(X), p(Y)); // project to 2D in along Z axis - }); - - if(!ring.empty()) { - // inner ring is now in 'newring' and outer ring is in 'ring' - SpatIndex innerring; - for(unsigned i : newring) { m_thr(); - const Pillar& pill = m_result.head_pillar(i); - assert(pill.id >= 0); - innerring.insert(pill.endpoint, unsigned(pill.id)); - } - - // For all pillars in the outer ring find the closest in the - // inner ring and connect them. This will create the spider web - // fashioned connections between pillars - for(unsigned i : ring) { m_thr(); - const Pillar& outerpill = m_result.head_pillar(i); - - auto res = innerring.nearest(outerpill.endpoint, - unsigned(innerring.size())); - - for(auto& ne : res) { - const Pillar& innerpill = m_result.pillars()[ne.second]; - if(interconnect(outerpill, innerpill)) break; - } - } - } - - // no need for newring anymore in the current iteration - ring.swap(newring); - - /*std::cout << "ring: \n"; - for(auto ri : ring) { - std::cout << ri << " " << " X = " << gnd_head_pt(ri)(X) - << " Y = " << gnd_head_pt(ri)(Y) << std::endl; - } - std::cout << std::endl;*/ + double d = std::cos(m_cfg.bridge_slope) * m_cfg.max_bridge_length_mm; + auto pclusters = cluster_nearest2d(m_points, cl_centroids, d, 3); + for(auto& ring : pclusters) { // now the ring has to be connected with bridge sticks - for(auto it = ring.begin(), next = std::next(it); - next != ring.end(); - ++it, ++next) + if(ring.size() > 1) + for(auto it = ring.begin(), next = std::next(it); + next != ring.end(); + ++it, ++next) { m_thr(); const Pillar& pillar = m_result.head_pillar(*it); @@ -1774,13 +1786,81 @@ public: interconnect(pillar, nextpillar); } - auto sring = ring; ClusterEl tmp; - std::sort(sring.begin(), sring.end()); - std::set_difference(rem.begin(), rem.end(), - sring.begin(), sring.end(), - std::back_inserter(tmp)); - rem.swap(tmp); + if(ring.size() > 2) { + const Pillar& firstpillar = m_result.head_pillar(ring.front()); + const Pillar& lastpillar = m_result.head_pillar(ring.back()); + interconnect(firstpillar, lastpillar); + } + } + +// ClusterEl rem = cl_centroids; +// ClusterEl ring; + +// while(!rem.empty()) { // loop until all the points belong to some ring +// m_thr(); +// std::sort(rem.begin(), rem.end()); + +// auto& points = m_points; +// auto newring = pts_convex_hull(rem, +// [&points](unsigned i) { +// auto&& p = points.row(i); +// return Vec2d(p(X), p(Y)); // project to 2D in along Z axis +// }); + +// if(!ring.empty()) { +// // inner ring is now in 'newring' and outer ring is in 'ring' +// SpatIndex innerring; +// for(unsigned i : newring) { m_thr(); +// const Pillar& pill = m_result.head_pillar(i); +// assert(pill.id >= 0); +// innerring.insert(pill.endpoint, unsigned(pill.id)); +// } + +// // For all pillars in the outer ring find the closest in the +// // inner ring and connect them. This will create the spider web +// // fashioned connections between pillars +// for(unsigned i : ring) { m_thr(); +// const Pillar& outerpill = m_result.head_pillar(i); + +// auto res = innerring.nearest(outerpill.endpoint, +// unsigned(innerring.size())); + +// for(auto& ne : res) { +// const Pillar& innerpill = m_result.pillars()[ne.second]; +// if(interconnect(outerpill, innerpill)) break; +// } +// } +// } + +// // no need for newring anymore in the current iteration +// ring.swap(newring); + +// /*std::cout << "ring: \n"; +// for(auto ri : ring) { +// std::cout << ri << " " << " X = " << gnd_head_pt(ri)(X) +// << " Y = " << gnd_head_pt(ri)(Y) << std::endl; +// } +// std::cout << std::endl;*/ + +// // now the ring has to be connected with bridge sticks +// for(auto it = ring.begin(), next = std::next(it); +// next != ring.end(); +// ++it, ++next) +// { +// m_thr(); +// const Pillar& pillar = m_result.head_pillar(*it); +// const Pillar& nextpillar = m_result.head_pillar(*next); +// interconnect(pillar, nextpillar); +// } + +// auto sring = ring; ClusterEl tmp; +// std::sort(sring.begin(), sring.end()); +// std::set_difference(rem.begin(), rem.end(), +// sring.begin(), sring.end(), +// std::back_inserter(tmp)); +// rem.swap(tmp); +// } } // Step: routing the pinheads that would connect to the model surface diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index da2bb1b798..bd2d162156 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -347,6 +347,95 @@ PointSet normals(const PointSet& points, return ret; } +//template double distance(const Vec& p) { +// return std::sqrt(p.transpose() * p); +//} + +//template double distance(const Vec& pp1, const Vec& pp2) { +// auto p = pp2 - pp1; +// return distance(p); +//} + +// Clustering a set of points by the given criteria +ClusteredPoints cluster_nearest2d( + const sla::PointSet& points, const std::vector& indices, + double dist, + unsigned max_points = 0) +{ + + namespace bgi = boost::geometry::index; + using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; + + // A spatial index for querying the nearest points + Index3D sindex; + + // Build the index + for(unsigned idx : indices) { + Vec3d p = points.row(idx); p(Z) = 0; + sindex.insert( std::make_pair(points.row(idx), idx)); + } + + using Elems = std::vector; + + // Recursive function for visiting all the points in a given distance to + // each other + std::function group = + [&sindex, &group, max_points, dist](Elems& pts, Elems& cluster) + { + for(auto& p : pts) { + std::vector tmp; + + sindex.query( + bgi::nearest(p.first, max_points), + std::back_inserter(tmp) + ); + + for(auto it = tmp.begin(); it < tmp.end(); ++it) + if(distance(p.first, it->first) > dist) it = tmp.erase(it); + + auto cmp = [](const SpatElement& e1, const SpatElement& e2){ + return e1.second < e2.second; + }; + + std::sort(tmp.begin(), tmp.end(), cmp); + + Elems newpts; + std::set_difference(tmp.begin(), tmp.end(), + cluster.begin(), cluster.end(), + std::back_inserter(newpts), cmp); + + int c = max_points && newpts.size() + cluster.size() > max_points? + int(max_points - cluster.size()) : int(newpts.size()); + + cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c); + std::sort(cluster.begin(), cluster.end(), cmp); + + if(!newpts.empty() && (!max_points || cluster.size() < max_points)) + group(newpts, cluster); + } + }; + + std::vector clusters; + for(auto it = sindex.begin(); it != sindex.end();) { + Elems cluster = {}; + Elems pts = {*it}; + group(pts, cluster); + + for(auto& c : cluster) sindex.remove(c); + it = sindex.begin(); + + clusters.emplace_back(cluster); + } + + ClusteredPoints result; + for(auto& cluster : clusters) { + result.emplace_back(); + for(auto c : cluster) result.back().emplace_back(c.second); + } + + return result; +} + // Clustering a set of points by the given criteria ClusteredPoints cluster( const sla::PointSet& points, const std::vector& indices, From 75525569985c4ef871d764d3228dfc509504ee76 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 5 Mar 2019 16:28:18 +0100 Subject: [PATCH 08/37] New interconnection strategy --- src/libslic3r/SLA/SLASpatIndex.hpp | 2 + src/libslic3r/SLA/SLASupportTree.cpp | 342 +++++++++++++----------- src/libslic3r/SLA/SLASupportTree.hpp | 4 + src/libslic3r/SLA/SLASupportTreeIGL.cpp | 114 ++------ 4 files changed, 216 insertions(+), 246 deletions(-) diff --git a/src/libslic3r/SLA/SLASpatIndex.hpp b/src/libslic3r/SLA/SLASpatIndex.hpp index 792e86c859..e5fbfa7d4b 100644 --- a/src/libslic3r/SLA/SLASpatIndex.hpp +++ b/src/libslic3r/SLA/SLASpatIndex.hpp @@ -43,6 +43,8 @@ public: // For testing size_t size() const; bool empty() const { return size() == 0; } + + void foreach(std::function fn); }; } diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 7ffa2274a4..204a951db1 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -377,6 +377,7 @@ struct Pillar { double r = 1; size_t steps = 0; Vec3d endpoint; + double height = 0; long id = -1; @@ -390,12 +391,12 @@ struct Pillar { { assert(steps > 0); - double h = jp(Z) - endp(Z); - if(h > 0) { // Endpoint is below the starting point + height = jp(Z) - endp(Z); + if(height > 0) { // Endpoint is below the starting point // We just create a bridge geometry with the pillar parameters and // move the data. - Contour3D body = cylinder(radius, h, st, endp); + Contour3D body = cylinder(radius, height, st, endp); mesh.points.swap(body.points); mesh.indices.swap(body.indices); } @@ -410,6 +411,10 @@ struct Pillar { { } + Vec3d startpoint() const { + return {endpoint(X), endpoint(Y), endpoint(Z) + height}; + } + void add_base(double height = 3, double radius = 2) { if(height <= 0) return; @@ -419,23 +424,23 @@ struct Pillar { if(radius < r ) radius = r; double a = 2*PI/steps; - double z = endpoint(2) + height; + double z = endpoint(Z) + height; for(size_t i = 0; i < steps; ++i) { double phi = i*a; - double x = endpoint(0) + r*std::cos(phi); - double y = endpoint(1) + r*std::sin(phi); + double x = endpoint(X) + r*std::cos(phi); + double y = endpoint(Y) + r*std::sin(phi); base.points.emplace_back(x, y, z); } for(size_t i = 0; i < steps; ++i) { double phi = i*a; - double x = endpoint(0) + radius*std::cos(phi); - double y = endpoint(1) + radius*std::sin(phi); + double x = endpoint(X) + radius*std::cos(phi); + double y = endpoint(Y) + radius*std::sin(phi); base.points.emplace_back(x, y, z - height); } - auto ep = endpoint; ep(2) += height; + auto ep = endpoint; ep(Z) += height; base.points.emplace_back(endpoint); base.points.emplace_back(ep); @@ -578,26 +583,15 @@ bool operator==(const SpatElement& e1, const SpatElement& e2) { return e1.second == e2.second; } -// Clustering a set of points by the given criteria. -ClusteredPoints cluster( - const PointSet& points, const std::vector& indices, - std::function pred, - unsigned max_points = 0); +// Clustering a set of points by the given distance. +ClusteredPoints cluster(const std::vector& indices, + std::function pointfn, + double dist, + unsigned max_points); -inline ClusteredPoints cluster( - const PointSet& points, - std::function pred, - unsigned max_points = 0) -{ - std::vector indices(size_t(points.rows()), 0); - std::iota(indices.begin(), indices.end(), 0); - return cluster(points, indices, pred, max_points); -} - -ClusteredPoints cluster_nearest2d( - const PointSet& points, const std::vector& indices, - double dist, - unsigned max_points = 0); +ClusteredPoints cluster(const PointSet& points, + double dist, + unsigned max_points); // This class will hold the support tree meshes with some additional bookkeeping // as well. Various parts of the support geometry are stored separately and are @@ -802,6 +796,8 @@ public: }; +// This function returns the position of the centroid in the input 'clust' +// vector of point indices. template long cluster_centroid(const ClusterEl& clust, std::function pointfn, @@ -1340,10 +1336,16 @@ class SLASupportTree::Algorithm { // than we will bridge to this closer pillar Vec3d qp(querypoint(X), querypoint(Y), gndlvl); - auto ne = spindex.nearest(qp, 1).front(); - const Head& nearhead = m_result.head(ne.second); + auto qres = spindex.nearest(qp, 1); + if(qres.empty()) continue; + auto ne = qres.front(); + const Head& nearhead = m_result.head(ne.second); Vec3d nearhead_jp = nearhead.junction_point(); +// const Pillar& nearpillar = m_result.pillars()[ne.second]; + +// Vec3d nearhead_jp = nearpillar.startpoint(); + double dist2d = distance(qp, ne.first); // Bridge endpoint on the main pillar @@ -1412,6 +1414,149 @@ class SLASupportTree::Algorithm { m_result.add_bridge(headjp, touchjp, r); } + // Interconnection strategy. Pillars with height exceeding H1 will require + // at least one neighbor to connect with. Height exceeding H2 require two + // neighbors. A connection only counts if the height ratio is bigger + // than 20% + void connect_pillars_nearest(unsigned neighbors = 3, + double H1 = 4.0, // min 1 neighbor required + double H2 = 35.0, // min 2 neighbor required + double min_height_ratio = 0.2) + { + // Now comes the algorithm that connects ground pillars with each other. + // Ideally every pillar should be connected with at least one of its + // neighbors if that neighbor is within sufficient distance (a bridge to + // it would not be longer than max_bridge_distance) + + double d = std::cos(m_cfg.bridge_slope) * m_cfg.max_bridge_length_mm; + + std::set pairs; + + m_pillar_index.foreach( + [this, d, &pairs, neighbors, min_height_ratio, H1, H2] + (const SpatElement& el) + { + Vec3d qp = el.first; + + // Query all remaining points within reach + auto qres = m_pillar_index.query([qp, d](const SpatElement& 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){ + return distance(e1.first, qp) < distance(e2.first, qp); + }); + + const Pillar& pillar = m_result.head_pillar(el.second); + unsigned ncount = 0; + for(auto& re : qres) { + if(re.second == el.second) continue; + + auto hashval = m_pillar_index.size() * el.second + re.second; + if(pairs.find(hashval) != pairs.end()) continue; + + const Pillar& neighborpillar = m_result.head_pillar(re.second); + if(interconnect(pillar, neighborpillar)) { + pairs.insert(hashval); + + // If the interconnection length between the two pillars is + // less than 20% of the longer pillar's height, don't count + if(std::min(pillar.height, neighborpillar.height) / + std::max(pillar.height, neighborpillar.height) > + min_height_ratio) ++ncount; + } + + // 3 connections are enough for one pillar + if(ncount == neighbors) break; + } + + if(ncount < 1 && pillar.height > H1) { + // No neighbors could be found and the pillar is too long. + BOOST_LOG_TRIVIAL(warning) << "Pillar is too long and has no " + "neighbors. Head ID: " + << pillar.start_junction_id; + } else if(ncount < 2 && pillar.height > H2) { + // Not enough neighbors to support this pillar + BOOST_LOG_TRIVIAL(warning) << "Pillar is too long and has too " + "few neighbors. Head ID: " + << pillar.start_junction_id; + } + }); + } + + // This is the old interconnection strategy which has a lot of imperfections + void connect_pillars_spiderweb() { + std::vector rem; rem.reserve(m_pillar_index.size()); + std::vector ring; + + // Could use an unordered_map instead. Points can be quite many. + std::vector pts(size_t(m_points.rows())); + + m_pillar_index.foreach([&rem, &pts](const SpatElement& e) { + rem.emplace_back(e.second); + pts[e.second] = {e.first(X), e.first(Y)}; + }); + + while(!rem.empty()) { // loop until all the points belong to some ring + m_thr(); + + std::sort(rem.begin(), rem.end()); + + auto newring = pts_convex_hull(rem, + [&pts](unsigned i) { + return pts[i]; // project to 2D in along Z axis + }); + + if(!ring.empty()) { + // inner ring is now in 'newring' and outer ring is in 'ring' + SpatIndex innerring; + for(unsigned i : newring) { m_thr(); + const Pillar& pill = m_result.head_pillar(i); + assert(pill.id >= 0); + innerring.insert(pill.endpoint, unsigned(pill.id)); + } + + // For all pillars in the outer ring find the closest in the + // inner ring and connect them. This will create the spider web + // fashioned connections between pillars + for(unsigned i : ring) { m_thr(); + const Pillar& outerpill = m_result.head_pillar(i); + + auto res = innerring.nearest(outerpill.endpoint, + unsigned(innerring.size())); + + for(auto& ne : res) { + const Pillar& innerpill = m_result.pillars()[ne.second]; + if(interconnect(outerpill, innerpill)) break; + } + } + } + + // no need for newring anymore in the current iteration + ring.swap(newring); + + // now the ring has to be connected with bridge sticks + for(auto it = ring.begin(), next = std::next(it); + next != ring.end(); + ++it, ++next) + { + m_thr(); + const Pillar& pillar = m_result.head_pillar(*it); + const Pillar& nextpillar = m_result.head_pillar(*next); + interconnect(pillar, nextpillar); + } + + auto sring = ring; ClusterEl tmp; + std::sort(sring.begin(), sring.end()); + std::set_difference(rem.begin(), rem.end(), + sring.begin(), sring.end(), + std::back_inserter(tmp)); + rem.swap(tmp); + } + } + public: Algorithm(const SupportConfig& config, @@ -1450,11 +1595,7 @@ public: void filter() { // Get the points that are too close to each other and keep only the // first one - auto aliases = cluster(m_points, - [this](const SpatElement& p, const SpatElement& se) { - m_thr(); - return distance(p.first, se.first) < D_SP; - }, 2); + auto aliases = cluster(m_points, D_SP, 2); PtIndices filtered_indices; filtered_indices.reserve(aliases.size()); @@ -1642,14 +1783,10 @@ public: // These clusters of support points will join in one pillar, // possibly in their centroid support point. auto d_base = 2*m_cfg.base_radius_mm; - auto& thr = m_thr; - m_pillar_clusters = cluster(m_points, ground_head_indices, - [thr, d_base](const SpatElement& p, const SpatElement& s) - { - thr(); - return distance(Vec2d(p.first(X), p.first(Y)), - Vec2d(s.first(X), s.first(Y))) < d_base; - }, 3); // max 3 heads to connect to one pillar + m_pillar_clusters = cluster(ground_head_indices, + [this](unsigned i) { + return m_points.row(i); + }, d_base, 3); } // Step: Routing the ground connected pinheads, and interconnecting @@ -1691,7 +1828,6 @@ public: cl_centroids.push_back(unsigned(cid)); - unsigned hid = cl[cid]; // Head ID Head& h = m_result.head(hid); h.transform(); @@ -1705,8 +1841,7 @@ public: size_t ci = 0; for(auto cl : m_pillar_clusters) { m_thr(); - auto cidx = cl_centroids[ci]; - cl_centroids[ci++] = cl[cidx]; + auto cidx = cl_centroids[ci++]; auto& head = m_result.head(cl[cidx]); @@ -1736,15 +1871,15 @@ public: // at this point we either have our pillar index or we have // to connect the sidehead to the ground if(nearest_id < 0) { + Vec3d pend = Vec3d{sidehead_jp(X), sidehead_jp(Y), gndlvl}; // Could not find a pillar, create one m_result.add_pillar( - unsigned(sidehead.id), - Vec3d{sidehead_jp(X), sidehead_jp(Y), gndlvl}, + unsigned(sidehead.id), pend, pradius).add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); // connects to ground, eligible for bridging - cl_centroids.emplace_back(c); + m_pillar_index.insert(pend, c); } else { // Creating the bridge to the nearest pillar auto nearest_uid = unsigned(nearest_id); @@ -1754,113 +1889,9 @@ public: } } - // We will break down the pillar positions in 2D into concentric - // rings. Connecting the pillars belonging to the same ring will - // prevent bridges from crossing each other. After bridging the - // rings we can create bridges between the rings without the - // possibility of crossing bridges. Two pillars will be bridged - // with X shaped stick pairs. If they are really close to each - // other, than only one stick will be used in zig-zag mode. + // connect_pillars_spiderweb(); + connect_pillars_nearest(); - // Breaking down the points into rings will be done with a modified - // convex hull algorithm (see pts_convex_hull()), that works for - // collinear points as well. If the points are on the same surface, - // they can be part of an imaginary line segment for which the - // convex hull is not defined. I this case it is enough to sort the - // points spatially and create the bridge stick from one endpoint to - // another. - - double d = std::cos(m_cfg.bridge_slope) * m_cfg.max_bridge_length_mm; - auto pclusters = cluster_nearest2d(m_points, cl_centroids, d, 3); - - for(auto& ring : pclusters) { - // now the ring has to be connected with bridge sticks - if(ring.size() > 1) - for(auto it = ring.begin(), next = std::next(it); - next != ring.end(); - ++it, ++next) - { - m_thr(); - const Pillar& pillar = m_result.head_pillar(*it); - const Pillar& nextpillar = m_result.head_pillar(*next); - interconnect(pillar, nextpillar); - } - - if(ring.size() > 2) { - const Pillar& firstpillar = m_result.head_pillar(ring.front()); - const Pillar& lastpillar = m_result.head_pillar(ring.back()); - interconnect(firstpillar, lastpillar); - } - - } - -// ClusterEl rem = cl_centroids; -// ClusterEl ring; - -// while(!rem.empty()) { // loop until all the points belong to some ring -// m_thr(); -// std::sort(rem.begin(), rem.end()); - -// auto& points = m_points; -// auto newring = pts_convex_hull(rem, -// [&points](unsigned i) { -// auto&& p = points.row(i); -// return Vec2d(p(X), p(Y)); // project to 2D in along Z axis -// }); - -// if(!ring.empty()) { -// // inner ring is now in 'newring' and outer ring is in 'ring' -// SpatIndex innerring; -// for(unsigned i : newring) { m_thr(); -// const Pillar& pill = m_result.head_pillar(i); -// assert(pill.id >= 0); -// innerring.insert(pill.endpoint, unsigned(pill.id)); -// } - -// // For all pillars in the outer ring find the closest in the -// // inner ring and connect them. This will create the spider web -// // fashioned connections between pillars -// for(unsigned i : ring) { m_thr(); -// const Pillar& outerpill = m_result.head_pillar(i); - -// auto res = innerring.nearest(outerpill.endpoint, -// unsigned(innerring.size())); - -// for(auto& ne : res) { -// const Pillar& innerpill = m_result.pillars()[ne.second]; -// if(interconnect(outerpill, innerpill)) break; -// } -// } -// } - -// // no need for newring anymore in the current iteration -// ring.swap(newring); - -// /*std::cout << "ring: \n"; -// for(auto ri : ring) { -// std::cout << ri << " " << " X = " << gnd_head_pt(ri)(X) -// << " Y = " << gnd_head_pt(ri)(Y) << std::endl; -// } -// std::cout << std::endl;*/ - -// // now the ring has to be connected with bridge sticks -// for(auto it = ring.begin(), next = std::next(it); -// next != ring.end(); -// ++it, ++next) -// { -// m_thr(); -// const Pillar& pillar = m_result.head_pillar(*it); -// const Pillar& nextpillar = m_result.head_pillar(*next); -// interconnect(pillar, nextpillar); -// } - -// auto sring = ring; ClusterEl tmp; -// std::sort(sring.begin(), sring.end()); -// std::set_difference(rem.begin(), rem.end(), -// sring.begin(), sring.end(), -// std::back_inserter(tmp)); -// rem.swap(tmp); -// } } // Step: routing the pinheads that would connect to the model surface @@ -1890,6 +1921,7 @@ public: groundp(Z) = m_result.ground_level; 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(head.id)); }; // TODO: connect these to the ground pillars if possible diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 54e934f3a5..d275c7963d 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -83,6 +83,10 @@ struct SupportConfig { // The shortest distance of any support structure from the model surface double safety_distance_mm = 0.1; + + double max_solo_pillar_height_mm = 5.0; + + double max_dual_pillar_height_mm = 35.0; }; struct PoolConfig; diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index bd2d162156..d422aae24b 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -91,6 +91,11 @@ size_t SpatIndex::size() const return m_impl->m_store.size(); } +void SpatIndex::foreach(std::function fn) +{ + for(auto& el : m_impl->m_store) fn(el); +} + /* **************************************************************************** * EigenMesh3D implementation * ****************************************************************************/ @@ -346,35 +351,11 @@ PointSet normals(const PointSet& points, return ret; } +namespace bgi = boost::geometry::index; +using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; -//template double distance(const Vec& p) { -// return std::sqrt(p.transpose() * p); -//} - -//template double distance(const Vec& pp1, const Vec& pp2) { -// auto p = pp2 - pp1; -// return distance(p); -//} - -// Clustering a set of points by the given criteria -ClusteredPoints cluster_nearest2d( - const sla::PointSet& points, const std::vector& indices, - double dist, - unsigned max_points = 0) +ClusteredPoints cluster(Index3D& sindex, double dist, unsigned max_points) { - - namespace bgi = boost::geometry::index; - using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; - - // A spatial index for querying the nearest points - Index3D sindex; - - // Build the index - for(unsigned idx : indices) { - Vec3d p = points.row(idx); p(Z) = 0; - sindex.insert( std::make_pair(points.row(idx), idx)); - } - using Elems = std::vector; // Recursive function for visiting all the points in a given distance to @@ -438,79 +419,30 @@ ClusteredPoints cluster_nearest2d( // Clustering a set of points by the given criteria ClusteredPoints cluster( - const sla::PointSet& points, const std::vector& indices, - std::function pred, - unsigned max_points = 0) + const std::vector& indices, + std::function pointfn, + double dist, + unsigned max_points) { - - namespace bgi = boost::geometry::index; - using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; - // A spatial index for querying the nearest points Index3D sindex; // Build the index - for(unsigned idx : indices) - sindex.insert( std::make_pair(points.row(idx), idx)); + for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); - using Elems = std::vector; + return cluster(sindex, dist, max_points); +} - // Recursive function for visiting all the points in a given distance to - // each other - std::function group = - [&sindex, &group, pred, max_points](Elems& pts, Elems& cluster) - { - for(auto& p : pts) { - std::vector tmp; +ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) +{ + // A spatial index for querying the nearest points + Index3D sindex; - sindex.query( - bgi::satisfies([p, pred](const SpatElement& se) { - return pred(p, se); - }), - std::back_inserter(tmp) - ); + // Build the index + for(Eigen::Index i = 0; i < pts.rows(); i++) + sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i))); - auto cmp = [](const SpatElement& e1, const SpatElement& e2){ - return e1.second < e2.second; - }; - - std::sort(tmp.begin(), tmp.end(), cmp); - - Elems newpts; - std::set_difference(tmp.begin(), tmp.end(), - cluster.begin(), cluster.end(), - std::back_inserter(newpts), cmp); - - int c = max_points && newpts.size() + cluster.size() > max_points? - int(max_points - cluster.size()) : int(newpts.size()); - - cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c); - std::sort(cluster.begin(), cluster.end(), cmp); - - if(!newpts.empty() && (!max_points || cluster.size() < max_points)) - group(newpts, cluster); - } - }; - - std::vector clusters; - for(auto it = sindex.begin(); it != sindex.end();) { - Elems cluster = {}; - Elems pts = {*it}; - group(pts, cluster); - - for(auto& c : cluster) sindex.remove(c); - it = sindex.begin(); - - clusters.emplace_back(cluster); - } - - ClusteredPoints result; - for(auto& cluster : clusters) { - result.emplace_back(); - for(auto c : cluster) result.back().emplace_back(c.second); - } - - return result; + return cluster(sindex, dist, max_points); } } From 34e0b6917951803974754c9cb8826ce5b0afba81 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 5 Mar 2019 18:21:20 +0100 Subject: [PATCH 09/37] WIP: sidehead routedown when pillar is too long --- src/libslic3r/SLA/SLASupportTree.cpp | 47 ++++++++++++++-- src/libslic3r/SLA/SLASupportTreeIGL.cpp | 71 ++++++++++++++++++++----- 2 files changed, 100 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 204a951db1..525f86e11c 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -415,8 +415,8 @@ struct Pillar { return {endpoint(X), endpoint(Y), endpoint(Z) + height}; } - void add_base(double height = 3, double radius = 2) { - if(height <= 0) return; + Pillar& add_base(double height = 3, double radius = 2) { + if(height <= 0) return *this; assert(steps >= 0); auto last = int(steps - 1); @@ -459,7 +459,7 @@ struct Pillar { indices.emplace_back(last, offs + last, offs); indices.emplace_back(hcenter, last, 0); indices.emplace_back(offs, offs + last, lcenter); - + return *this; } bool has_base() const { return !base.points.empty(); } @@ -1472,17 +1472,56 @@ class SLASupportTree::Algorithm { if(ncount == neighbors) break; } + unsigned needpillars = 0; if(ncount < 1 && pillar.height > H1) { - // No neighbors could be found and the pillar is too long. + // No neighbors could not be found and the pillar is too long. BOOST_LOG_TRIVIAL(warning) << "Pillar is too long and has no " "neighbors. Head ID: " << pillar.start_junction_id; + +// double D = 2*m_cfg.base_radius_mm; +// Vec3d jp = pillar.startpoint(); +// double h = D / std::cos(m_cfg.bridge_slope); +// bool found = false; +// double phi = 0; + +// // Search for a suitable angle for the two pillars +// while(!found && phi < 2*PI) { + +// } + needpillars = 1; } else if(ncount < 2 && pillar.height > H2) { // Not enough neighbors to support this pillar BOOST_LOG_TRIVIAL(warning) << "Pillar is too long and has too " "few neighbors. Head ID: " << pillar.start_junction_id; + needpillars = 2 - ncount; } + + // WIP: + // note: sideheads ARE tested to reach the ground! + + +// if(needpillars > 0) { +// if(pillar.starts_from_head) { +// // search for a sidehead for this head. We will route that +// // to the ground. +// const Head& head = m_result.head(unsigned(pillar.start_junction_id)); +// for(auto cl : m_pillar_clusters) { +// auto it = std::find(cl.begin(), cl.end(), head.id); +// if(it != cl.end()) { +// cl.erase(it); +// for(size_t j = 0; j < cl.size() && j < needpillars; j++) { +// unsigned hid = cl[j]; + +// m_result.add_pillar(hid, endpoint, ) +// .add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); +// } +// } +// } +// } +// } + }); } diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index d422aae24b..c368b8604d 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -354,26 +354,18 @@ PointSet normals(const PointSet& points, namespace bgi = boost::geometry::index; using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; -ClusteredPoints cluster(Index3D& sindex, double dist, unsigned max_points) +ClusteredPoints cluster(Index3D& sindex, unsigned max_points, + std::function(const Index3D&, const SpatElement&)> qfn) { using Elems = std::vector; // Recursive function for visiting all the points in a given distance to // each other std::function group = - [&sindex, &group, max_points, dist](Elems& pts, Elems& cluster) + [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) { for(auto& p : pts) { - std::vector tmp; - - sindex.query( - bgi::nearest(p.first, max_points), - std::back_inserter(tmp) - ); - - for(auto it = tmp.begin(); it < tmp.end(); ++it) - if(distance(p.first, it->first) > dist) it = tmp.erase(it); - + std::vector tmp = qfn(sindex, p); auto cmp = [](const SpatElement& e1, const SpatElement& e2){ return e1.second < e2.second; }; @@ -417,6 +409,25 @@ ClusteredPoints cluster(Index3D& sindex, double dist, unsigned max_points) return result; } +namespace { +std::vector distance_queryfn(const Index3D& sindex, + const SpatElement& p, + double dist, + unsigned max_points) +{ + std::vector tmp; tmp.reserve(max_points); + sindex.query( + bgi::nearest(p.first, max_points), + std::back_inserter(tmp) + ); + + for(auto it = tmp.begin(); it < tmp.end(); ++it) + if(distance(p.first, it->first) > dist) it = tmp.erase(it); + + return tmp; +} +} + // Clustering a set of points by the given criteria ClusteredPoints cluster( const std::vector& indices, @@ -430,7 +441,35 @@ ClusteredPoints cluster( // Build the index for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); - return cluster(sindex, dist, max_points); + return cluster(sindex, max_points, + [dist, max_points](const Index3D& sidx, const SpatElement& p) + { + return distance_queryfn(sidx, p, dist, max_points); + }); +} + +// Clustering a set of points by the given criteria +ClusteredPoints cluster( + const std::vector& indices, + std::function pointfn, + std::function predicate, + unsigned max_points) +{ + // A spatial index for querying the nearest points + Index3D sindex; + + // Build the index + 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) + { + std::vector tmp; tmp.reserve(max_points); + sidx.query(bgi::satisfies([p, predicate](const SpatElement& e){ + return predicate(p, e); + }), std::back_inserter(tmp)); + return tmp; + }); } ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) @@ -442,7 +481,11 @@ ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) for(Eigen::Index i = 0; i < pts.rows(); i++) sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i))); - return cluster(sindex, dist, max_points); + return cluster(sindex, max_points, + [dist, max_points](const Index3D& sidx, const SpatElement& p) + { + return distance_queryfn(sidx, p, dist, max_points); + }); } } From 0a2ef07ca05865d0027013a56f22d5f6fe6f1e64 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 6 Mar 2019 15:21:07 +0100 Subject: [PATCH 10/37] Reworking sidehead to pillar connections. --- src/libslic3r/SLA/SLASupportTree.cpp | 307 +++++++++++++++------------ 1 file changed, 171 insertions(+), 136 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 525f86e11c..64c30d5921 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -376,7 +376,7 @@ struct Pillar { Contour3D base; double r = 1; size_t steps = 0; - Vec3d endpoint; + Vec3d endpt; double height = 0; long id = -1; @@ -387,7 +387,7 @@ struct Pillar { Pillar(const Vec3d& jp, const Vec3d& endp, double radius = 1, size_t st = 45): - r(radius), steps(st), endpoint(endp), starts_from_head(false) + r(radius), steps(st), endpt(endp), starts_from_head(false) { assert(steps > 0); @@ -411,10 +411,12 @@ struct Pillar { { } - Vec3d startpoint() const { - return {endpoint(X), endpoint(Y), endpoint(Z) + height}; + inline Vec3d startpoint() const { + return {endpt(X), endpt(Y), endpt(Z) + height}; } + inline const Vec3d& endpoint() const { return endpt; } + Pillar& add_base(double height = 3, double radius = 2) { if(height <= 0) return *this; @@ -424,24 +426,24 @@ struct Pillar { if(radius < r ) radius = r; double a = 2*PI/steps; - double z = endpoint(Z) + height; + double z = endpt(Z) + height; for(size_t i = 0; i < steps; ++i) { double phi = i*a; - double x = endpoint(X) + r*std::cos(phi); - double y = endpoint(Y) + r*std::sin(phi); + double x = endpt(X) + r*std::cos(phi); + double y = endpt(Y) + r*std::sin(phi); base.points.emplace_back(x, y, z); } for(size_t i = 0; i < steps; ++i) { double phi = i*a; - double x = endpoint(X) + radius*std::cos(phi); - double y = endpoint(Y) + radius*std::sin(phi); + double x = endpt(X) + radius*std::cos(phi); + double y = endpt(Y) + radius*std::sin(phi); base.points.emplace_back(x, y, z - height); } - auto ep = endpoint; ep(Z) += height; - base.points.emplace_back(endpoint); + auto ep = endpt; ep(Z) += height; + base.points.emplace_back(endpt); base.points.emplace_back(ep); auto& indices = base.indices; @@ -593,6 +595,12 @@ ClusteredPoints cluster(const PointSet& points, double dist, unsigned max_points); +ClusteredPoints cluster( + const std::vector& indices, + std::function pointfn, + std::function predicate, + unsigned max_points); + // This class will hold the support tree meshes with some additional bookkeeping // as well. Various parts of the support geometry are stored separately and are // merged when the caller queries the merged mesh. The merged result is cached @@ -962,7 +970,7 @@ ClusterEl pts_convex_hull(const ClusterEl& inpts, return hull; } -Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { +inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { return (endp - startp).normalized(); } @@ -1236,8 +1244,8 @@ class SLASupportTree::Algorithm { Vec3d supper = phead.junction_point(); Vec3d slower = nextphead.junction_point(); - Vec3d eupper = pillar.endpoint; - Vec3d elower = nextpillar.endpoint; + Vec3d eupper = pillar.endpt; + Vec3d elower = nextpillar.endpt; double zmin = m_result.ground_level + m_cfg.base_height_mm; eupper(Z) = std::max(eupper(Z), zmin); @@ -1318,16 +1326,110 @@ class SLASupportTree::Algorithm { return was_connected; } - // Search for the nearest pillar to the given query point which is not - // further than max_dist. The result is the pillar ID or -1 if nothing was - // found. The pillar search is carried out using the m_pillar_index - // structure. - long search_nearest(const Vec3d& querypoint, double max_dist) - { + // For connecting a head to a nearby pillar. + bool connect_to_nearpillar(const Head& head, const Pillar& nearpillar) { +// Vec3d hjp = head.junction_point(); +// Vec3d headjp = hjp; +// Vec3d nearheadjp = nearhead.junction_point(); +// double r = m_cfg.head_back_radius_mm; + +// double d = distance(Vec2d{headjp(X), headjp(Y)}, +// Vec2d{nearheadjp(X), nearheadjp(Y)}); + +// // Touching point on the nearby pillar +// Vec3d touchjp(nearheadjp(X), nearheadjp(Y), headjp(Z) + +// d * std::tan(-m_cfg.bridge_slope)); + +// // If the touching point is too high, we can add a partial pillar from +// // under the higher head to reach the nearby pillar with a bridge +// if(touchjp(Z) > nearheadjp(Z)) { +// double hdiff = touchjp(Z) - nearheadjp(Z); +// headjp(Z) -= hdiff; +// touchjp(Z) -= hdiff; + +// // create a pillar without base, +// // it doesn't connect to ground just to an existing shorter pillar +// m_result.add_pillar(unsigned(nearhead.id), headjp, r); +// } + +// if(headjp(Z) < hjp(Z)) m_result.add_junction(headjp, r); +// if(touchjp(Z) >= nearheadjp(Z)) m_result.add_junction(touchjp, r); + +// m_result.add_bridge(headjp, touchjp, r); + + Vec3d headjp = head.junction_point(); + Vec3d nearjp_u = nearpillar.startpoint(); + Vec3d nearjp_l = nearpillar.endpoint(); + + double r = head.r_back_mm; + double d2d = distance(to_2d(headjp), to_2d(nearjp_u)); + double d3d = distance(headjp, nearjp_u); + + double hdiff = nearjp_u(Z) - headjp(Z); + double slope = std::atan2(hdiff, d2d); + + Vec3d bridgestart = headjp; + Vec3d bridgeend = nearjp_u; + double max_len = m_cfg.max_bridge_length_mm; + double max_slope = m_cfg.bridge_slope; + double zdiff = 0.0; + + // check the default situation if feasible for a bridge + if(d3d > max_len || slope > -max_slope) { + // not feasible to connect the two head junctions. We have to search + // for a suitable touch point. + + double Zdown = headjp(Z) + d2d * std::tan(-max_slope); + Vec3d touchjp = bridgeend; touchjp(Z) = Zdown; + double D = distance(headjp, touchjp); + double zdiff = Zdown - nearjp_u(Z); + + if(zdiff > 0) { + Zdown -= zdiff; + bridgestart(Z) -= zdiff; + touchjp(Z) = Zdown; + + double t = bridge_mesh_intersect(bridgestart, {0,0,-1}, r); + + // We can't insert a pillar under the source head to connect + // with the nearby pillar's starting junction + if(t < zdiff) return false; + } + + if(Zdown <= nearjp_u(Z) && Zdown >= nearjp_l(Z) && D < max_len) + bridgeend(Z) = Zdown; + else + return false; + } + + // There will be a minimum distance from the ground where the + // bridge is allowed to connect. This is an empiric value. + double minz = m_result.ground_level + 2 * m_cfg.head_width_mm; + if(bridgeend(Z) < minz) return false; + + double t = bridge_mesh_intersect(bridgestart, + dirv(bridgestart, bridgeend), r); + + // Cannot insert the bridge. (further search might not worth the hassle) + if(t < distance(bridgestart, bridgeend)) return false; + + // A partial pillar is needed under the starting head. + if(zdiff > 0) { + m_result.add_pillar(unsigned(head.id), bridgestart, r); + m_result.add_junction(bridgestart, r); + } + + m_result.add_bridge(bridgestart, bridgeend, r); + + return true; + } + + bool search_pillar_and_connect(const Head& head) { SpatIndex spindex = m_pillar_index; long nearest_id = -1; - const double gndlvl = m_result.ground_level; + + Vec3d querypoint = head.junction_point(); while(nearest_id < 0 && !spindex.empty()) { m_thr(); // loop until a suitable head is not found @@ -1335,83 +1437,25 @@ class SLASupportTree::Algorithm { // (this may happen as the clustering is not perfect) // than we will bridge to this closer pillar - Vec3d qp(querypoint(X), querypoint(Y), gndlvl); + Vec3d qp(querypoint(X), querypoint(Y), m_result.ground_level); auto qres = spindex.nearest(qp, 1); - if(qres.empty()) continue; + if(qres.empty()) break; auto ne = qres.front(); - const Head& nearhead = m_result.head(ne.second); - Vec3d nearhead_jp = nearhead.junction_point(); -// const Pillar& nearpillar = m_result.pillars()[ne.second]; + nearest_id = ne.second; -// Vec3d nearhead_jp = nearpillar.startpoint(); - - double dist2d = distance(qp, ne.first); - - // Bridge endpoint on the main pillar - Vec3d bridge_ep(nearhead_jp(X), nearhead_jp(Y), querypoint(Z) + - dist2d * std::tan(-m_cfg.bridge_slope)); - - if(bridge_ep(Z) > nearhead_jp(Z)) { - // If the sidepoint cannot connect to the pillar - // from its head junction, just skip this pillar. - spindex.remove(ne); - continue; + assert(nearest_id >= 0); + if(nearest_id >= 0) { + auto nearheadid = unsigned(nearest_id); + const Pillar& nearpillar = m_result.head_pillar(nearheadid); + if(!connect_to_nearpillar(head, nearpillar)) { + nearest_id = -1; // continue searching + spindex.remove(ne); // without the current pillar + } } - - double d = distance(querypoint, bridge_ep); - - // There will be a minimum distance from the ground where the - // bridge is allowed to connect. This is an empiric value. - double minz = gndlvl + 2 * m_cfg.head_width_mm; - - // WARNING: previously, max_bridge_length was divided by two. - // I don't remember if this was intentional or by accident. There - // is no logical reason why it shouldn't be used directly. - if(bridge_ep(Z) <= minz || d > max_dist) break; - - double chkd = bridge_mesh_intersect(querypoint, - dirv(querypoint, bridge_ep), - m_cfg.head_back_radius_mm); - - if(chkd >= d) nearest_id = ne.second; - - spindex.remove(ne); - } - return nearest_id; - } - - inline long search_nearest(const Vec3d& qp) { - return search_nearest(qp, m_cfg.max_bridge_length_mm); - } - - void connect_to_nearhead(const Head& head, const Head& nearhead) { - Vec3d hjp = head.junction_point(); - Vec3d headjp = hjp; - Vec3d nearheadjp = nearhead.junction_point(); - double r = m_cfg.head_back_radius_mm; - - double d = distance(Vec2d{headjp(X), headjp(Y)}, - Vec2d{nearheadjp(X), nearheadjp(Y)}); - - Vec3d touchjp(nearheadjp(X), nearheadjp(Y), headjp(Z) + - d * std::tan(-m_cfg.bridge_slope)); - - if(touchjp(Z) > nearheadjp(Z)) { - double hdiff = touchjp(Z) - nearheadjp(Z); - headjp(Z) -= hdiff; - touchjp(Z) -= hdiff; - - // create a pillar without base, - // it doesn't connect to ground just to an existing - // shorter pillar - m_result.add_pillar(unsigned(nearhead.id), headjp, r); } - if(headjp(Z) < hjp(Z)) m_result.add_junction(headjp, r); - if(touchjp(Z) >= nearheadjp(Z)) m_result.add_junction(touchjp, r); - - m_result.add_bridge(headjp, touchjp, r); + return nearest_id >= 0; } // Interconnection strategy. Pillars with height exceeding H1 will require @@ -1554,7 +1598,7 @@ class SLASupportTree::Algorithm { for(unsigned i : newring) { m_thr(); const Pillar& pill = m_result.head_pillar(i); assert(pill.id >= 0); - innerring.insert(pill.endpoint, unsigned(pill.id)); + innerring.insert(pill.endpt, unsigned(pill.id)); } // For all pillars in the outer ring find the closest in the @@ -1563,7 +1607,7 @@ class SLASupportTree::Algorithm { for(unsigned i : ring) { m_thr(); const Pillar& outerpill = m_result.head_pillar(i); - auto res = innerring.nearest(outerpill.endpoint, + auto res = innerring.nearest(outerpill.endpt, unsigned(innerring.size())); for(auto& ne : res) { @@ -1821,11 +1865,16 @@ 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 d_base = 2*m_cfg.base_radius_mm; - m_pillar_clusters = cluster(ground_head_indices, - [this](unsigned i) { - return m_points.row(i); - }, d_base, 3); + auto pointfn = [this](unsigned i) { + return m_result.head(i).junction_point(); + }; + 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; + }; + m_pillar_clusters = cluster(ground_head_indices, pointfn, predicate, 3); } // Step: Routing the ground connected pinheads, and interconnecting @@ -1863,15 +1912,16 @@ public: }); assert(lcid >= 0); - auto cid = unsigned(lcid); + unsigned hid = cl[size_t(lcid)]; // Head ID - cl_centroids.push_back(unsigned(cid)); + cl_centroids.emplace_back(hid); - unsigned hid = cl[cid]; // Head ID Head& h = m_result.head(hid); h.transform(); Vec3d p = h.junction_point(); p(Z) = gndlvl; - m_pillar_index.insert(p, hid); + m_result.add_pillar(hid, p, h.r_back_mm) + .add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); + m_pillar_index.insert(p, hid); // TODO: replace hid with pillarID } // now we will go through the clusters ones again and connect the @@ -1882,35 +1932,23 @@ public: auto cidx = cl_centroids[ci++]; - auto& head = m_result.head(cl[cidx]); - - Vec3d startpoint = head.junction_point(); - auto endpoint = startpoint; endpoint(Z) = gndlvl; - - // Create the central pillar of the cluster with its base on the - // ground - m_result.add_pillar(unsigned(head.id), endpoint, pradius) - .add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); - - // Process side point in current cluster - cl.erase(cl.begin() + cidx); // delete the centroid - // TODO: don't consider the cluster centroid but calculate a // central position where the pillar can be placed. this way // the weight is distributed more effectively on the pillar. + const Pillar& centerpillar = m_result.head_pillar(cidx); + for(auto c : cl) { m_thr(); + if(c == cidx) continue; + auto& sidehead = m_result.head(c); sidehead.transform(); - Vec3d sidehead_jp = sidehead.junction_point(); - long nearest_id = search_nearest(sidehead_jp, - m_cfg.max_bridge_length_mm/2); - - // at this point we either have our pillar index or we have - // to connect the sidehead to the ground - if(nearest_id < 0) { - Vec3d pend = Vec3d{sidehead_jp(X), sidehead_jp(Y), gndlvl}; + if(!connect_to_nearpillar(sidehead, centerpillar) && + !search_pillar_and_connect(sidehead)) + { + Vec3d pstart = sidehead.junction_point(); + Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; // Could not find a pillar, create one m_result.add_pillar( unsigned(sidehead.id), pend, @@ -1919,11 +1957,6 @@ public: // connects to ground, eligible for bridging m_pillar_index.insert(pend, c); - } else { - // Creating the bridge to the nearest pillar - auto nearest_uid = unsigned(nearest_id); - const Head& nearhead = m_result.head(nearest_uid); - connect_to_nearhead(sidehead, nearhead); } } } @@ -1975,14 +2008,16 @@ public: // Search nearby pillar // ///////////////////////////////////////////////////////////////// - long nearest_pillar_id = search_nearest(hjp); - if(nearest_pillar_id >= 0) { // successful search - auto nearest_uid = unsigned(nearest_pillar_id); - const Head& nearhead = m_result.head(nearest_uid); - head.transform(); // accept the head - connect_to_nearhead(head, nearhead); - continue; - } + if(search_pillar_and_connect(head)) continue; + +// long nearest_pillar_id = search_nearest(hjp); +// if(nearest_pillar_id >= 0) { // successful search +// auto nearest_uid = unsigned(nearest_pillar_id); +// const Pillar& nearpillar = m_result.head_pillar(nearest_uid); +// head.transform(); // accept the head +// connect_to_nearpillar(head, nearpillar); +// continue; +// } // ///////////////////////////////////////////////////////////////// // Try straight path From efd3d274251a3e0ca3fe39630c2a8b60177ba703 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 6 Mar 2019 18:00:34 +0100 Subject: [PATCH 11/37] Experimental working version of onmodel pillar cascading. --- src/libslic3r/SLA/SLASupportTree.cpp | 314 ++++----------------------- 1 file changed, 46 insertions(+), 268 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 64c30d5921..de2bb274b4 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -854,122 +854,6 @@ long cluster_centroid(const ClusterEl& clust, return long(minit - avgs.begin()); } -/** - * This function will calculate the convex hull of the input point set and - * return the indices of those points belonging to the chull in the right - * (counter clockwise) order. The input is also the set of indices and a - * functor to get the actual point form the index. - * - * I've adapted this algorithm from here: - * https://www.geeksforgeeks.org/convex-hull-set-1-jarviss-algorithm-or-wrapping/ - * and modified it so that it starts with the leftmost lower vertex. Also added - * support for floating point coordinates. - * - * This function is a modded version of the standard convex hull. If the points - * are all collinear with each other, it will return their indices in spatially - * subsequent order (the order they appear on the screen). - */ -ClusterEl pts_convex_hull(const ClusterEl& inpts, - std::function pfn) -{ - using Point = Vec2d; - using std::vector; - - static const double ERR = 1e-3; - - auto orientation = [](const Point& p, const Point& q, const Point& r) - { - double val = (q(Y) - p(Y)) * (r(X) - q(X)) - - (q(X) - p(X)) * (r(Y) - q(Y)); - - if (std::abs(val) < ERR) return 0; // collinear - return (val > ERR)? 1: 2; // clock or counterclockwise - }; - - size_t n = inpts.size(); - - if (n < 3) return inpts; - - // Initialize Result - ClusterEl hull; - vector points; points.reserve(n); - for(auto i : inpts) { - points.emplace_back(pfn(i)); - } - - // Check if the triplet of points is collinear. The standard convex hull - // algorithms are not capable of handling such input properly. - bool collinear = true; - for(auto one = points.begin(), two = std::next(one), three = std::next(two); - three != points.end() && collinear; - ++one, ++two, ++three) - { - // check if the points are collinear - if(orientation(*one, *two, *three) != 0) collinear = false; - } - - // Find the leftmost (bottom) point - size_t l = 0; - for (size_t i = 1; i < n; i++) { - if(std::abs(points[i](X) - points[l](X)) < ERR) { - if(points[i](Y) < points[l](Y)) l = i; - } - else if (points[i](X) < points[l](X)) l = i; - } - - if(collinear) { - // fill the output with the spatially ordered set of points. - - // find the direction - hull = inpts; - auto& lp = points[l]; - std::sort(hull.begin(), hull.end(), - [&lp, points](unsigned i1, unsigned i2) { - // compare the distance from the leftmost point - return distance(lp, points[i1]) < distance(lp, points[i2]); - }); - - return hull; - } - - // TODO: this algorithm is O(m*n) and O(n^2) in the worst case so it needs - // to be replaced with a graham scan or something O(nlogn) - - // Start from leftmost point, keep moving counterclockwise - // until reach the start point again. This loop runs O(h) - // times where h is number of points in result or output. - size_t p = l; - do - { - // Add current point to result - hull.push_back(inpts[p]); - - // Search for a point 'q' such that orientation(p, x, - // q) is counterclockwise for all points 'x'. The idea - // is to keep track of last visited most counterclock- - // wise point in q. If any point 'i' is more counterclock- - // wise than q, then update q. - size_t q = (p + 1) % n; - for (size_t i = 0; i < n; i++) - { - // If i is more counterclockwise than current q, then - // update q - if (orientation(points[p], points[i], points[q]) == 2) q = i; - } - - // Now q is the most counterclockwise with respect to p - // Set p as q for next iteration, so that q is added to - // result 'hull' - p = q; - - } while (p != l); // While we don't come to first point - - auto first = hull.front(); - hull.emplace_back(first); - - return hull; -} - inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { return (endp - startp).normalized(); } @@ -1230,10 +1114,6 @@ class SLASupportTree::Algorithm { // Helper function for interconnecting two pillars with zig-zag bridges. bool interconnect(const Pillar& pillar, const Pillar& nextpillar) { - // Get the starting heads corresponding to the given pillars - const Head& phead = m_result.pillar_head(pillar.id); - const Head& nextphead = m_result.pillar_head(nextpillar.id); - // We need to get the starting point of the zig-zag pattern. We have to // be aware that the two head junctions are at different heights. We // may start from the lowest junction and call it a day but this @@ -1242,8 +1122,8 @@ class SLASupportTree::Algorithm { // pillar could still be bridged with the shorter one. bool was_connected = false; - Vec3d supper = phead.junction_point(); - Vec3d slower = nextphead.junction_point(); + Vec3d supper = pillar.startpoint(); + Vec3d slower = nextpillar.startpoint(); Vec3d eupper = pillar.endpt; Vec3d elower = nextpillar.endpt; @@ -1328,35 +1208,6 @@ class SLASupportTree::Algorithm { // For connecting a head to a nearby pillar. bool connect_to_nearpillar(const Head& head, const Pillar& nearpillar) { -// Vec3d hjp = head.junction_point(); -// Vec3d headjp = hjp; -// Vec3d nearheadjp = nearhead.junction_point(); -// double r = m_cfg.head_back_radius_mm; - -// double d = distance(Vec2d{headjp(X), headjp(Y)}, -// Vec2d{nearheadjp(X), nearheadjp(Y)}); - -// // Touching point on the nearby pillar -// Vec3d touchjp(nearheadjp(X), nearheadjp(Y), headjp(Z) + -// d * std::tan(-m_cfg.bridge_slope)); - -// // If the touching point is too high, we can add a partial pillar from -// // under the higher head to reach the nearby pillar with a bridge -// if(touchjp(Z) > nearheadjp(Z)) { -// double hdiff = touchjp(Z) - nearheadjp(Z); -// headjp(Z) -= hdiff; -// touchjp(Z) -= hdiff; - -// // create a pillar without base, -// // it doesn't connect to ground just to an existing shorter pillar -// m_result.add_pillar(unsigned(nearhead.id), headjp, r); -// } - -// if(headjp(Z) < hjp(Z)) m_result.add_junction(headjp, r); -// if(touchjp(Z) >= nearheadjp(Z)) m_result.add_junction(touchjp, r); - -// m_result.add_bridge(headjp, touchjp, r); - Vec3d headjp = head.junction_point(); Vec3d nearjp_u = nearpillar.startpoint(); Vec3d nearjp_l = nearpillar.endpoint(); @@ -1382,14 +1233,14 @@ class SLASupportTree::Algorithm { double Zdown = headjp(Z) + d2d * std::tan(-max_slope); Vec3d touchjp = bridgeend; touchjp(Z) = Zdown; double D = distance(headjp, touchjp); - double zdiff = Zdown - nearjp_u(Z); + zdiff = Zdown - nearjp_u(Z); if(zdiff > 0) { Zdown -= zdiff; bridgestart(Z) -= zdiff; touchjp(Z) = Zdown; - double t = bridge_mesh_intersect(bridgestart, {0,0,-1}, r); + double t = bridge_mesh_intersect(headjp, {0,0,-1}, r); // We can't insert a pillar under the source head to connect // with the nearby pillar's starting junction @@ -1446,8 +1297,8 @@ class SLASupportTree::Algorithm { assert(nearest_id >= 0); if(nearest_id >= 0) { - auto nearheadid = unsigned(nearest_id); - const Pillar& nearpillar = m_result.head_pillar(nearheadid); + auto nearpillarID = unsigned(nearest_id); + const Pillar& nearpillar = m_result.pillars()[nearpillarID]; if(!connect_to_nearpillar(head, nearpillar)) { nearest_id = -1; // continue searching spindex.remove(ne); // without the current pillar @@ -1462,7 +1313,7 @@ class SLASupportTree::Algorithm { // at least one neighbor to connect with. Height exceeding H2 require two // neighbors. A connection only counts if the height ratio is bigger // than 20% - void connect_pillars_nearest(unsigned neighbors = 3, + void connect_pillars_nearest(unsigned neighbors = 1, double H1 = 4.0, // min 1 neighbor required double H2 = 35.0, // min 2 neighbor required double min_height_ratio = 0.2) @@ -1493,7 +1344,7 @@ class SLASupportTree::Algorithm { return distance(e1.first, qp) < distance(e2.first, qp); }); - const Pillar& pillar = m_result.head_pillar(el.second); + const Pillar& pillar = m_result.pillars()[el.second]; unsigned ncount = 0; for(auto& re : qres) { if(re.second == el.second) continue; @@ -1501,7 +1352,7 @@ class SLASupportTree::Algorithm { auto hashval = m_pillar_index.size() * el.second + re.second; if(pairs.find(hashval) != pairs.end()) continue; - const Pillar& neighborpillar = m_result.head_pillar(re.second); + const Pillar& neighborpillar = m_result.pillars()[re.second]; if(interconnect(pillar, neighborpillar)) { pairs.insert(hashval); @@ -1522,17 +1373,6 @@ class SLASupportTree::Algorithm { BOOST_LOG_TRIVIAL(warning) << "Pillar is too long and has no " "neighbors. Head ID: " << pillar.start_junction_id; - -// double D = 2*m_cfg.base_radius_mm; -// Vec3d jp = pillar.startpoint(); -// double h = D / std::cos(m_cfg.bridge_slope); -// bool found = false; -// double phi = 0; - -// // Search for a suitable angle for the two pillars -// while(!found && phi < 2*PI) { - -// } needpillars = 1; } else if(ncount < 2 && pillar.height > H2) { // Not enough neighbors to support this pillar @@ -1545,7 +1385,6 @@ class SLASupportTree::Algorithm { // WIP: // note: sideheads ARE tested to reach the ground! - // if(needpillars > 0) { // if(pillar.starts_from_head) { // // search for a sidehead for this head. We will route that @@ -1569,77 +1408,6 @@ class SLASupportTree::Algorithm { }); } - // This is the old interconnection strategy which has a lot of imperfections - void connect_pillars_spiderweb() { - std::vector rem; rem.reserve(m_pillar_index.size()); - std::vector ring; - - // Could use an unordered_map instead. Points can be quite many. - std::vector pts(size_t(m_points.rows())); - - m_pillar_index.foreach([&rem, &pts](const SpatElement& e) { - rem.emplace_back(e.second); - pts[e.second] = {e.first(X), e.first(Y)}; - }); - - while(!rem.empty()) { // loop until all the points belong to some ring - m_thr(); - - std::sort(rem.begin(), rem.end()); - - auto newring = pts_convex_hull(rem, - [&pts](unsigned i) { - return pts[i]; // project to 2D in along Z axis - }); - - if(!ring.empty()) { - // inner ring is now in 'newring' and outer ring is in 'ring' - SpatIndex innerring; - for(unsigned i : newring) { m_thr(); - const Pillar& pill = m_result.head_pillar(i); - assert(pill.id >= 0); - innerring.insert(pill.endpt, unsigned(pill.id)); - } - - // For all pillars in the outer ring find the closest in the - // inner ring and connect them. This will create the spider web - // fashioned connections between pillars - for(unsigned i : ring) { m_thr(); - const Pillar& outerpill = m_result.head_pillar(i); - - auto res = innerring.nearest(outerpill.endpt, - unsigned(innerring.size())); - - for(auto& ne : res) { - const Pillar& innerpill = m_result.pillars()[ne.second]; - if(interconnect(outerpill, innerpill)) break; - } - } - } - - // no need for newring anymore in the current iteration - ring.swap(newring); - - // now the ring has to be connected with bridge sticks - for(auto it = ring.begin(), next = std::next(it); - next != ring.end(); - ++it, ++next) - { - m_thr(); - const Pillar& pillar = m_result.head_pillar(*it); - const Pillar& nextpillar = m_result.head_pillar(*next); - interconnect(pillar, nextpillar); - } - - auto sring = ring; ClusterEl tmp; - std::sort(sring.begin(), sring.end()); - std::set_difference(rem.begin(), rem.end(), - sring.begin(), sring.end(), - std::back_inserter(tmp)); - rem.swap(tmp); - } - } - public: Algorithm(const SupportConfig& config, @@ -1919,9 +1687,12 @@ public: Head& h = m_result.head(hid); h.transform(); Vec3d p = h.junction_point(); p(Z) = gndlvl; - m_result.add_pillar(hid, p, h.r_back_mm) - .add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); - m_pillar_index.insert(p, hid); // TODO: replace hid with pillarID + 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)); } // now we will go through the clusters ones again and connect the @@ -1950,20 +1721,16 @@ public: Vec3d pstart = sidehead.junction_point(); Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; // Could not find a pillar, create one - m_result.add_pillar( - unsigned(sidehead.id), pend, - pradius).add_base(m_cfg.base_height_mm, - m_cfg.base_radius_mm); + 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, c); + m_pillar_index.insert(pend, unsigned(pillar.id)); } } } - - // connect_pillars_spiderweb(); - connect_pillars_nearest(); - } // Step: routing the pinheads that would connect to the model surface @@ -1991,11 +1758,14 @@ public: auto groundp = endp; groundp(Z) = m_result.ground_level; - 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(head.id)); + 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)); }; + std::vector modelpillars; + // TODO: connect these to the ground pillars if possible for(auto item : m_iheads_onmodel) { m_thr(); unsigned idx = item.first; @@ -2008,16 +1778,7 @@ public: // Search nearby pillar // ///////////////////////////////////////////////////////////////// - if(search_pillar_and_connect(head)) continue; - -// long nearest_pillar_id = search_nearest(hjp); -// if(nearest_pillar_id >= 0) { // successful search -// auto nearest_uid = unsigned(nearest_pillar_id); -// const Pillar& nearpillar = m_result.head_pillar(nearest_uid); -// head.transform(); // accept the head -// connect_to_nearpillar(head, nearpillar); -// continue; -// } + if(search_pillar_and_connect(head)) { head.transform(); continue; } // ///////////////////////////////////////////////////////////////// // Try straight path @@ -2085,7 +1846,6 @@ public: d = 0; t = oresult.score; - polar = std::get<0>(oresult.optimum); azimuth = std::get<1>(oresult.optimum); Vec3d bridgedir = Vec3d(std::cos(azimuth) * std::sin(polar), @@ -2143,6 +1903,9 @@ public: tailhead.transform(); pill.base = tailhead.mesh; + + // Experimental: add the pillar to the index for cascading + modelpillars.emplace_back(unsigned(pill.id)); continue; } @@ -2152,6 +1915,15 @@ public: << " ID: " << idx; head.invalidate(); } + + for(auto pillid : modelpillars) { + auto& pillar = m_result.pillars()[pillid]; + m_pillar_index.insert(pillar.endpoint(), pillid); + } + } + + void cascade_pillars() { + connect_pillars_nearest(); } // Step: process the support points where there is not enough space for a @@ -2211,6 +1983,7 @@ bool SLASupportTree::generate(const std::vector &support_points, CLASSIFY, ROUTING_GROUND, ROUTING_NONGROUND, + CASCADE_PILLARS, HEADLESS, DONE, ABORT, @@ -2235,6 +2008,8 @@ bool SLASupportTree::generate(const std::vector &support_points, std::bind(&Algorithm::routing_to_model, &alg), + std::bind(&Algorithm::cascade_pillars, &alg), + std::bind(&Algorithm::routing_headless, &alg), [] () { @@ -2268,6 +2043,7 @@ bool SLASupportTree::generate(const std::vector &support_points, L("Classification"), L("Routing to ground"), L("Routing supports to model surface"), + L("Cascading pillars"), L("Processing small holes"), L("Done"), L("Abort") @@ -2281,6 +2057,7 @@ bool SLASupportTree::generate(const std::vector &support_points, 60, 70, 80, + 90, 100, 0 }; @@ -2293,7 +2070,8 @@ bool SLASupportTree::generate(const std::vector &support_points, case PINHEADS: pc = CLASSIFY; break; case CLASSIFY: pc = ROUTING_GROUND; break; case ROUTING_GROUND: pc = ROUTING_NONGROUND; break; - case ROUTING_NONGROUND: pc = HEADLESS; break; + case ROUTING_NONGROUND: pc = CASCADE_PILLARS; break; + case CASCADE_PILLARS: pc = HEADLESS; break; case HEADLESS: pc = DONE; break; case DONE: case ABORT: break; From 9131b1658aefd0495343fc70f92ea33d92d37054 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 7 Mar 2019 12:01:21 +0100 Subject: [PATCH 12/37] Replacing simplex optimizers with more intelligent genetic ones. --- src/libslic3r/SLA/SLASupportTree.cpp | 235 ++++++++++++++------------- src/libslic3r/SLA/SLASupportTree.hpp | 16 +- 2 files changed, 132 insertions(+), 119 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index de2bb274b4..547ad8527f 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include #include @@ -63,6 +63,19 @@ namespace Slic3r { namespace sla { +// Compile time configuration value definitions: + +// The max Z angle for a normal at which it will get completely ignored. +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.1; + +const double SupportConfig::max_solo_pillar_height_mm = 5.0; +const double SupportConfig::max_dual_pillar_height_mm = 35.0; +const double SupportConfig::optimizer_rel_score_diff = 1e-6; +const unsigned SupportConfig::optimizer_max_iterations = 500; + using Coordf = double; using Portion = std::tuple; @@ -1309,105 +1322,6 @@ class SLASupportTree::Algorithm { return nearest_id >= 0; } - // Interconnection strategy. Pillars with height exceeding H1 will require - // at least one neighbor to connect with. Height exceeding H2 require two - // neighbors. A connection only counts if the height ratio is bigger - // than 20% - void connect_pillars_nearest(unsigned neighbors = 1, - double H1 = 4.0, // min 1 neighbor required - double H2 = 35.0, // min 2 neighbor required - double min_height_ratio = 0.2) - { - // Now comes the algorithm that connects ground pillars with each other. - // Ideally every pillar should be connected with at least one of its - // neighbors if that neighbor is within sufficient distance (a bridge to - // it would not be longer than max_bridge_distance) - - double d = std::cos(m_cfg.bridge_slope) * m_cfg.max_bridge_length_mm; - - std::set pairs; - - m_pillar_index.foreach( - [this, d, &pairs, neighbors, min_height_ratio, H1, H2] - (const SpatElement& el) - { - Vec3d qp = el.first; - - // Query all remaining points within reach - auto qres = m_pillar_index.query([qp, d](const SpatElement& 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){ - return distance(e1.first, qp) < distance(e2.first, qp); - }); - - const Pillar& pillar = m_result.pillars()[el.second]; - unsigned ncount = 0; - for(auto& re : qres) { - if(re.second == el.second) continue; - - auto hashval = m_pillar_index.size() * el.second + re.second; - if(pairs.find(hashval) != pairs.end()) continue; - - const Pillar& neighborpillar = m_result.pillars()[re.second]; - if(interconnect(pillar, neighborpillar)) { - pairs.insert(hashval); - - // If the interconnection length between the two pillars is - // less than 20% of the longer pillar's height, don't count - if(std::min(pillar.height, neighborpillar.height) / - std::max(pillar.height, neighborpillar.height) > - min_height_ratio) ++ncount; - } - - // 3 connections are enough for one pillar - if(ncount == neighbors) break; - } - - unsigned needpillars = 0; - if(ncount < 1 && pillar.height > H1) { - // No neighbors could not be found and the pillar is too long. - BOOST_LOG_TRIVIAL(warning) << "Pillar is too long and has no " - "neighbors. Head ID: " - << pillar.start_junction_id; - needpillars = 1; - } else if(ncount < 2 && pillar.height > H2) { - // Not enough neighbors to support this pillar - BOOST_LOG_TRIVIAL(warning) << "Pillar is too long and has too " - "few neighbors. Head ID: " - << pillar.start_junction_id; - needpillars = 2 - ncount; - } - - // WIP: - // note: sideheads ARE tested to reach the ground! - -// if(needpillars > 0) { -// if(pillar.starts_from_head) { -// // search for a sidehead for this head. We will route that -// // to the ground. -// const Head& head = m_result.head(unsigned(pillar.start_junction_id)); -// for(auto cl : m_pillar_clusters) { -// auto it = std::find(cl.begin(), cl.end(), head.id); -// if(it != cl.end()) { -// cl.erase(it); -// for(size_t j = 0; j < cl.size() && j < needpillars; j++) { -// unsigned hid = cl[j]; - -// m_result.add_pillar(hid, endpoint, ) -// .add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); -// } -// } -// } -// } -// } - - }); - } - public: Algorithm(const SupportConfig& config, @@ -1468,9 +1382,8 @@ public: using libnest2d::opt::bound; using libnest2d::opt::initvals; - using libnest2d::opt::SimplexOptimizer; + using libnest2d::opt::GeneticOptimizer; using libnest2d::opt::StopCriteria; - static const unsigned MAX_TRIES = 100; for(unsigned i = 0, fidx = filtered_indices[0]; i < filtered_indices.size(); ++i, fidx = filtered_indices[i]) @@ -1525,10 +1438,10 @@ public: // geometry and its very close to the default. StopCriteria stc; - stc.max_iterations = MAX_TRIES; - stc.relative_score_difference = 1e-3; + stc.max_iterations = m_cfg.optimizer_max_iterations; + stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; stc.stop_score = w; // space greater than w is enough - SimplexOptimizer solver(stc); + GeneticOptimizer solver(stc); auto oresult = solver.optimize_max( [this, pin_r, w, hp](double plr, double azm) @@ -1538,8 +1451,8 @@ public: std::cos(plr)).normalized(); double score = pinhead_mesh_intersect( hp, n, pin_r, - m_cfg.head_back_radius_mm, - w); + m_cfg.head_back_radius_mm, w); + return score; }, initvals(polar, azimuth), // start with what we have @@ -1639,7 +1552,7 @@ public: 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 && + return d2d < 2 * m_cfg.base_radius_mm && d3d < m_cfg.max_bridge_length_mm; }; m_pillar_clusters = cluster(ground_head_indices, pointfn, predicate, 3); @@ -1819,14 +1732,14 @@ public: using libnest2d::opt::bound; using libnest2d::opt::initvals; - using libnest2d::opt::SimplexOptimizer; + using libnest2d::opt::GeneticOptimizer; using libnest2d::opt::StopCriteria; StopCriteria stc; - stc.max_iterations = 100; - stc.relative_score_difference = 1e-3; + stc.max_iterations = m_cfg.optimizer_max_iterations; + stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; stc.stop_score = 1e6; - SimplexOptimizer solver(stc); + GeneticOptimizer solver(stc); double r_back = head.r_back_mm; @@ -1836,7 +1749,6 @@ public: Vec3d n = Vec3d(std::cos(azm) * std::sin(plr), std::sin(azm) * std::sin(plr), std::cos(plr)).normalized(); - return bridge_mesh_intersect(hjp, n, r_back); }, initvals(polar, azimuth), // let's start with what we have @@ -1923,7 +1835,102 @@ public: } void cascade_pillars() { - connect_pillars_nearest(); + // Now comes the algorithm that connects ground pillars with each other. + // Ideally every pillar should be connected with at least one of its + // neighbors if that neighbor is within sufficient distance (a bridge to + // it would not be longer than max_bridge_distance) + + // Pillars with height exceeding H1 will require at least one neighbor + // to connect with. Height exceeding H2 require two neighbors. + double H1 = m_cfg.max_solo_pillar_height_mm; + double H2 = m_cfg.max_dual_pillar_height_mm; + unsigned neighbors = m_cfg.pillar_cascade_neighbors; + double d = std::cos(m_cfg.bridge_slope) * m_cfg.max_bridge_length_mm; + + //A connection between two pillars only counts if the height ratio is + // bigger than 20% + double min_height_ratio = 0.2; + + std::set pairs; + + m_pillar_index.foreach( + [this, d, &pairs, neighbors, min_height_ratio, H1, H2] + (const SpatElement& el) + { + Vec3d qp = el.first; + + // Query all remaining points within reach + auto qres = m_pillar_index.query([qp, d](const SpatElement& 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){ + return distance(e1.first, qp) < distance(e2.first, qp); + }); + + const Pillar& pillar = m_result.pillars()[el.second]; + unsigned ncount = 0; + for(auto& re : qres) { + if(re.second == el.second) continue; + + auto hashval = m_pillar_index.size() * el.second + re.second; + if(pairs.find(hashval) != pairs.end()) continue; + + const Pillar& neighborpillar = m_result.pillars()[re.second]; + if(interconnect(pillar, neighborpillar)) { + pairs.insert(hashval); + + // If the interconnection length between the two pillars is + // less than 20% of the longer pillar's height, don't count + if(neighborpillar.height / pillar.height > min_height_ratio) + ++ncount; + } + + // 3 connections are enough for one pillar + if(ncount == neighbors) break; + } + + unsigned needpillars = 0; + if(ncount < 1 && pillar.height > H1) { + // No neighbors could not be found and the pillar is too long. + BOOST_LOG_TRIVIAL(warning) << "Pillar is too long and has no " + "neighbors. Head ID: " + << pillar.start_junction_id; + needpillars = 1; + } else if(ncount < 2 && pillar.height > H2) { + // Not enough neighbors to support this pillar + BOOST_LOG_TRIVIAL(warning) << "Pillar is too long and has too " + "few neighbors. Head ID: " + << pillar.start_junction_id; + needpillars = 2 - ncount; + } + + // WIP: + // note: sideheads ARE tested to reach the ground! + +// if(needpillars > 0) { +// if(pillar.starts_from_head) { +// // search for a sidehead for this head. We will route that +// // to the ground. +// const Head& head = m_result.head(unsigned(pillar.start_junction_id)); +// for(auto cl : m_pillar_clusters) { +// auto it = std::find(cl.begin(), cl.end(), head.id); +// if(it != cl.end()) { +// cl.erase(it); +// for(size_t j = 0; j < cl.size() && j < needpillars; j++) { +// unsigned hid = cl[j]; + +// m_result.add_pillar(hid, endpoint, ) +// .add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); +// } +// } +// } +// } +// } + + }); } // Step: process the support points where there is not enough space for a diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index d275c7963d..4017985824 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -79,14 +79,20 @@ struct SupportConfig { double object_elevation_mm = 10; // The max Z angle for a normal at which it will get completely ignored. - double normal_cutoff_angle = 150.0 * M_PI / 180.0; + static const double normal_cutoff_angle; + + // ///////////////////////////////////////////////////////////////////////// + // Compile time configuration values (candidates for runtime) + // ///////////////////////////////////////////////////////////////////////// // The shortest distance of any support structure from the model surface - double safety_distance_mm = 0.1; + static const double safety_distance_mm; - double max_solo_pillar_height_mm = 5.0; - - double max_dual_pillar_height_mm = 35.0; + static const double max_solo_pillar_height_mm; + static const double max_dual_pillar_height_mm; + static const double optimizer_rel_score_diff; + static const unsigned optimizer_max_iterations; + static const unsigned pillar_cascade_neighbors; }; struct PoolConfig; From 28a516b6dc31346b590bbf1c7366ff6532c8bf39 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 7 Mar 2019 12:48:17 +0100 Subject: [PATCH 13/37] Fix a missing definition issue --- src/libslic3r/SLA/SLASupportTree.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index b65f99466c..ff1547e279 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -75,6 +75,7 @@ const double SupportConfig::max_solo_pillar_height_mm = 5.0; const double SupportConfig::max_dual_pillar_height_mm = 35.0; const double SupportConfig::optimizer_rel_score_diff = 1e-6; const unsigned SupportConfig::optimizer_max_iterations = 500; +const unsigned SupportConfig::pillar_cascade_neighbors = 2; using Coordf = double; using Portion = std::tuple; From 0d594331784951666e372cafbf1d82152a65d7ed Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 7 Mar 2019 13:01:28 +0100 Subject: [PATCH 14/37] Fix build on Mac --- src/libslic3r/SLA/SLASupportTree.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index ff1547e279..1cd9ffdcc3 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -1536,7 +1536,7 @@ public: // collision check auto hit = bridge_mesh_intersect(headjp, n, r); - if(std::isinf(hit)) ground_head_indices.emplace_back(i); + if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i); else { if(m_cfg.ground_facing_only) head.invalidate(); m_iheads_onmodel.emplace_back(std::make_pair(i, hit)); @@ -1980,6 +1980,8 @@ bool SLASupportTree::generate(const std::vector &support_points, const SupportConfig &cfg, const Controller &ctl) { + if(support_points.empty()) return false; + Algorithm alg(cfg, mesh, support_points, *m_impl, ctl.cancelfn); // Let's define the individual steps of the processing. We can experiment From 3bce99bd235d62fa711d63b7d8ad183559f3c138 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 7 Mar 2019 17:17:47 +0100 Subject: [PATCH 15/37] Fixes for interconnection issues. --- src/libslic3r/SLA/SLASupportTree.cpp | 160 ++++++++++++++++++--------- src/libslic3r/SLA/SLASupportTree.hpp | 7 +- 2 files changed, 113 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 1cd9ffdcc3..e145cf64a3 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -76,6 +76,7 @@ const double SupportConfig::max_dual_pillar_height_mm = 35.0; const double SupportConfig::optimizer_rel_score_diff = 1e-6; const unsigned SupportConfig::optimizer_max_iterations = 500; const unsigned SupportConfig::pillar_cascade_neighbors = 2; +const unsigned SupportConfig::max_bridges_on_pillar = 3; using Coordf = double; using Portion = std::tuple; @@ -399,6 +400,12 @@ struct Pillar { bool starts_from_head = true; // Could start from a junction as well long start_junction_id = -1; + // How many bridges are connected to this pillar + unsigned bridges = 0; + + // How many pillars are cascaded with this one + unsigned links = 0; + Pillar(const Vec3d& jp, const Vec3d& endp, double radius = 1, size_t st = 45): r(radius), steps(st), endpt(endp), starts_from_head(false) @@ -670,11 +677,19 @@ public: return m_pillars.back(); } - template Pillar& add_pillar(const Vec3d& startp, - const Vec3d& endp, - double r) + void increment_bridges(const Pillar& pillar) { + if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) + m_pillars[size_t(pillar.id)].bridges++; + } + + void increment_links(const Pillar& pillar) { + if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) + m_pillars[size_t(pillar.id)].links++; + } + + template Pillar& add_pillar(Args&&...args) { - m_pillars.emplace_back(startp, endp, r); + m_pillars.emplace_back(std::forward(args)...); Pillar& pillar = m_pillars.back(); pillar.id = long(m_pillars.size() - 1); pillar.starts_from_head = false; @@ -1138,8 +1153,8 @@ class SLASupportTree::Algorithm { Vec3d supper = pillar.startpoint(); Vec3d slower = nextpillar.startpoint(); - Vec3d eupper = pillar.endpt; - Vec3d elower = nextpillar.endpt; + Vec3d eupper = pillar.endpoint(); + Vec3d elower = nextpillar.endpoint(); double zmin = m_result.ground_level + m_cfg.base_height_mm; eupper(Z) = std::max(eupper(Z), zmin); @@ -1165,7 +1180,8 @@ class SLASupportTree::Algorithm { startz = slower(Z) - zstep < supper(Z) ? slower(Z) - zstep : slower(Z); endz = eupper(Z) + zstep > elower(Z) ? eupper(Z) + zstep : eupper(Z); - if(slower(Z) - eupper(Z) < std::abs(zstep)) { // no space for even one cross + if(slower(Z) - eupper(Z) < std::abs(zstep)) { + // no space for even one cross // Get max available space startz = std::min(supper(Z), slower(Z) - zstep); @@ -1189,7 +1205,8 @@ class SLASupportTree::Algorithm { // results in a cross connection between the pillars. Vec3d sj = supper, ej = slower; sj(Z) = startz; ej(Z) = sj(Z) + zstep; - while(ej(Z) >= endz) { + // TODO: This is a workaround to not have a faulty last bridge + while(ej(Z) >= eupper(Z) /*endz*/) { if(bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r) >= bridge_distance) @@ -1222,6 +1239,8 @@ class SLASupportTree::Algorithm { // For connecting a head to a nearby pillar. bool connect_to_nearpillar(const Head& head, const Pillar& nearpillar) { + if(nearpillar.bridges > m_cfg.max_bridges_on_pillar) return false; + Vec3d headjp = head.junction_point(); Vec3d nearjp_u = nearpillar.startpoint(); Vec3d nearjp_l = nearpillar.endpoint(); @@ -1285,6 +1304,7 @@ class SLASupportTree::Algorithm { } m_result.add_bridge(bridgestart, bridgeend, r); + m_result.increment_bridges(nearpillar); return true; } @@ -1309,13 +1329,14 @@ class SLASupportTree::Algorithm { auto ne = qres.front(); nearest_id = ne.second; - assert(nearest_id >= 0); if(nearest_id >= 0) { auto nearpillarID = unsigned(nearest_id); - const Pillar& nearpillar = m_result.pillars()[nearpillarID]; - if(!connect_to_nearpillar(head, nearpillar)) { - nearest_id = -1; // continue searching - spindex.remove(ne); // without the current pillar + if(nearpillarID < m_result.pillars().size()) { + const Pillar& nearpillar = m_result.pillars()[nearpillarID]; + if(!connect_to_nearpillar(head, nearpillar)) { + nearest_id = -1; // continue searching + spindex.remove(ne); // without the current pillar + } } } } @@ -1556,7 +1577,8 @@ public: return d2d < 2 * m_cfg.base_radius_mm && d3d < m_cfg.max_bridge_length_mm; }; - m_pillar_clusters = cluster(ground_head_indices, pointfn, predicate, 3); + m_pillar_clusters = cluster(ground_head_indices, pointfn, predicate, + m_cfg.max_bridges_on_pillar); } // Step: Routing the ground connected pinheads, and interconnecting @@ -1850,12 +1872,12 @@ public: //A connection between two pillars only counts if the height ratio is // bigger than 20% - double min_height_ratio = 0.2; + double min_height_ratio = 0.5; std::set pairs; m_pillar_index.foreach( - [this, d, &pairs, neighbors, min_height_ratio, H1, H2] + [this, d, &pairs, neighbors, min_height_ratio] (const SpatElement& el) { Vec3d qp = el.first; @@ -1872,7 +1894,6 @@ public: }); const Pillar& pillar = m_result.pillars()[el.second]; - unsigned ncount = 0; for(auto& re : qres) { if(re.second == el.second) continue; @@ -1886,52 +1907,89 @@ public: // If the interconnection length between the two pillars is // less than 20% of the longer pillar's height, don't count if(neighborpillar.height / pillar.height > min_height_ratio) - ++ncount; + m_result.increment_links(pillar); + + if(pillar.height / neighborpillar.height > min_height_ratio) + m_result.increment_links(neighborpillar); + } // 3 connections are enough for one pillar - if(ncount == neighbors) break; + if(pillar.links == neighbors) break; } + }); + + size_t pillarcount = m_result.pillars().size(); + + for(size_t pid = 0; pid < pillarcount; pid++) { + const Pillar& pillar = m_result.pillars()[pid]; unsigned needpillars = 0; - if(ncount < 1 && pillar.height > H1) { - // No neighbors could not be found and the pillar is too long. - BOOST_LOG_TRIVIAL(warning) << "Pillar is too long and has no " - "neighbors. Head ID: " - << pillar.start_junction_id; - needpillars = 1; - } else if(ncount < 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 - BOOST_LOG_TRIVIAL(warning) << "Pillar is too long and has too " - "few neighbors. Head ID: " - << pillar.start_junction_id; - needpillars = 2 - ncount; + needpillars = 2 - pillar.links; + } + else if(pillar.links < 1 && pillar.height > H1) { + // No neighbors could be found and the pillar is too long. + needpillars = 1; } - // WIP: - // note: sideheads ARE tested to reach the ground! + // 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(); + Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r); + std::vector tv(needpillars, false); + std::vector spts(needpillars); -// if(needpillars > 0) { -// if(pillar.starts_from_head) { -// // search for a sidehead for this head. We will route that -// // to the ground. -// const Head& head = m_result.head(unsigned(pillar.start_junction_id)); -// for(auto cl : m_pillar_clusters) { -// auto it = std::find(cl.begin(), cl.end(), head.id); -// if(it != cl.end()) { -// cl.erase(it); -// for(size_t j = 0; j < cl.size() && j < needpillars; j++) { -// unsigned hid = cl[j]; + while(!found && alpha < 2*PI) { -// m_result.add_pillar(hid, endpoint, ) -// .add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); -// } -// } -// } -// } -// } + 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; + auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar.r); + tv[n] = std::isinf(hr.distance()); + } - }); + found = std::all_of(tv.begin(), tv.end(), [](bool v){return v;}); + + // 20 angles will be tried... + alpha += 0.1 * PI; + } + + std::vector> newpills; + newpills.reserve(needpillars); + + if(found) for(unsigned n = 0; n < needpillars; n++) { + Vec3d s = spts[n]; double gnd = m_result.ground_level; + Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar.r); + p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); + + if(interconnect(pillar, p)) { + Pillar& pp = m_result.add_pillar(p); + m_result.add_junction(s, pillar.r); + double t = bridge_mesh_intersect(pillarsp, + dirv(pillarsp, s), + pillar.r); + if(distance(pillarsp, s) < t) + m_result.add_bridge(pillarsp, s, pillar.r); + + if(pillar.endpoint()(Z) > m_result.ground_level) + m_result.add_junction(pillar.endpoint(), pillar.r); + + newpills.emplace_back(pp); + } + } + + if(!newpills.empty()) + for(auto it = newpills.begin(), nx = std::next(it); + nx != newpills.end(); ++it, ++nx) interconnect(*it, *nx); + } } // Step: process the support points where there is not enough space for a diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 4017985824..fb54252502 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -78,13 +78,13 @@ struct SupportConfig { // and the model object's bounding box bottom. double object_elevation_mm = 10; - // The max Z angle for a normal at which it will get completely ignored. - static const double normal_cutoff_angle; - // ///////////////////////////////////////////////////////////////////////// // Compile time configuration values (candidates for runtime) // ///////////////////////////////////////////////////////////////////////// + // The max Z angle for a normal at which it will get completely ignored. + static const double normal_cutoff_angle; + // The shortest distance of any support structure from the model surface static const double safety_distance_mm; @@ -93,6 +93,7 @@ struct SupportConfig { static const double optimizer_rel_score_diff; static const unsigned optimizer_max_iterations; static const unsigned pillar_cascade_neighbors; + static const unsigned max_bridges_on_pillar; }; struct PoolConfig; From 1cb1c12812316b3950af395bccbc1c9b403a0522 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 8 Mar 2019 11:39:34 +0100 Subject: [PATCH 16/37] Fine tuning the new parameters and adding max_pillar_link_distance --- src/libslic3r/PrintConfig.cpp | 9 +++++++++ src/libslic3r/PrintConfig.hpp | 4 ++++ src/libslic3r/SLA/SLASupportTree.cpp | 23 +++++++++++++++-------- src/libslic3r/SLA/SLASupportTree.hpp | 5 ++++- src/libslic3r/SLAPrint.cpp | 2 ++ src/libslic3r/SLAPrint.hpp | 7 +++++-- src/slic3r/GUI/Preset.cpp | 1 + src/slic3r/GUI/Tab.cpp | 1 + 8 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 1c78a3e310..d6e66cd678 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2672,6 +2672,15 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->default_value = new ConfigOptionFloat(15.0); + def = this->add("support_max_pillar_link_distance", coFloat); + def->label = L("Max pillar linking distance"); + def->category = L("Supports"); + def->tooltip = L("The max distance of two pillars to get linked with each other."); + def->sidetext = L("mm"); + def->cli = ""; + def->min = 0; // 0 means no linking + def->default_value = new ConfigOptionFloat(10.0); + def = this->add("support_object_elevation", coFloat); def->label = L("Object elevation"); def->category = L("Supports"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index f4d9053a6b..7ddc3b8d6d 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1006,6 +1006,9 @@ public: // The max length of a bridge in mm ConfigOptionFloat support_max_bridge_length /*= 15.0*/; + // The max distance of two pillars to get cross linked. + ConfigOptionFloat support_max_pillar_link_distance; + // 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*/; @@ -1053,6 +1056,7 @@ protected: OPT_PTR(support_base_height); OPT_PTR(support_critical_angle); OPT_PTR(support_max_bridge_length); + OPT_PTR(support_max_pillar_link_distance); OPT_PTR(support_points_density_relative); OPT_PTR(support_points_minimal_distance); OPT_PTR(support_object_elevation); diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index e145cf64a3..c52f82654d 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -71,7 +71,7 @@ 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.1; -const double SupportConfig::max_solo_pillar_height_mm = 5.0; +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; const unsigned SupportConfig::optimizer_max_iterations = 500; @@ -678,11 +678,15 @@ public: } void increment_bridges(const Pillar& pillar) { + assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); + if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) m_pillars[size_t(pillar.id)].bridges++; } void increment_links(const Pillar& pillar) { + assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); + if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) m_pillars[size_t(pillar.id)].links++; } @@ -1169,8 +1173,8 @@ class SLASupportTree::Algorithm { double bridge_distance = pillar_dist / std::cos(-m_cfg.bridge_slope); double zstep = pillar_dist * std::tan(-m_cfg.bridge_slope); - if(pillar_dist < 2*m_cfg.head_back_radius_mm) return false; - if(bridge_distance > m_cfg.max_bridge_length_mm) return false; + if(pillar_dist < 2 * m_cfg.head_back_radius_mm || + pillar_dist > m_cfg.max_pillar_link_distance_mm) return false; if(supper(Z) < slower(Z)) supper.swap(slower); if(eupper(Z) < elower(Z)) eupper.swap(elower); @@ -1659,8 +1663,8 @@ public: // 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); + .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)); @@ -1962,7 +1966,7 @@ public: alpha += 0.1 * PI; } - std::vector> newpills; + std::vector newpills; newpills.reserve(needpillars); if(found) for(unsigned n = 0; n < needpillars; n++) { @@ -1982,13 +1986,16 @@ public: if(pillar.endpoint()(Z) > m_result.ground_level) m_result.add_junction(pillar.endpoint(), pillar.r); - newpills.emplace_back(pp); + newpills.emplace_back(pp.id); } } if(!newpills.empty()) for(auto it = newpills.begin(), nx = std::next(it); - nx != newpills.end(); ++it, ++nx) interconnect(*it, *nx); + nx != newpills.end(); ++it, ++nx) { + interconnect(m_result.pillars()[size_t(*it)], + m_result.pillars()[size_t(*nx)]); + } } } diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index fb54252502..74d7da9cad 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -72,7 +72,10 @@ struct SupportConfig { double bridge_slope = M_PI/4; // The max length of a bridge in mm - double max_bridge_length_mm = 15.0; + double max_bridge_length_mm = 10.0; + + // The max distance of a pillar to pillar link. + double max_pillar_link_distance_mm = 10.0; // The elevation in Z direction upwards. This is the space between the pad // and the model object's bounding box bottom. diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 469e7a8fff..82b499ec75 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -546,6 +546,7 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { scfg.object_elevation_mm = c.support_object_elevation.getFloat(); scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ; scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat(); + scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat(); switch(c.support_pillar_connection_mode.getInt()) { case slapcmZigZag: scfg.pillar_connection_mode = sla::PillarConnectionMode::zigzag; break; @@ -1386,6 +1387,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector &opt_keys); + std::vector calculate_heights(const BoundingBoxf3& bb, + float elevation, + float initial_layer_height, + float layer_height) const; + void fill_statistics(); SLAPrintConfig m_print_config; @@ -270,8 +275,6 @@ private: lref(std::cref(lyr)), copies(std::cref(cp)) {} }; - std::vector calculate_heights(const BoundingBoxf3& bb, float elevation, float initial_layer_height, float layer_height) const; - // One level may contain multiple slices from multiple objects and their // supports using LayerRefs = std::vector; diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 37b495a363..4902b5552e 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -457,6 +457,7 @@ const std::vector& Preset::sla_print_options() "support_base_height", "support_critical_angle", "support_max_bridge_length", + "support_max_pillar_link_distance", "support_object_elevation", "support_points_density_relative", "support_points_minimal_distance", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 060eb13833..a7b1af75de 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3277,6 +3277,7 @@ void TabSLAPrint::build() optgroup = page->new_optgroup(_(L("Connection of the support sticks and junctions"))); optgroup->append_single_option_line("support_critical_angle"); optgroup->append_single_option_line("support_max_bridge_length"); + optgroup->append_single_option_line("support_max_pillar_link_distance"); optgroup = page->new_optgroup(_(L("Automatic generation"))); optgroup->append_single_option_line("support_points_density_relative"); From 7c09b841bc4bd01d4f8bf0500d709d90bf43c659 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 8 Mar 2019 14:18:22 +0100 Subject: [PATCH 17/37] Fix for the application of the new pillar link distance parameter --- src/libslic3r/SLA/SLASupportTree.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index c52f82654d..eab0ddba78 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -1862,17 +1862,16 @@ public: } void cascade_pillars() { - // Now comes the algorithm that connects ground pillars with each other. + // Now comes the algorithm that connects pillars with each other. // Ideally every pillar should be connected with at least one of its - // neighbors if that neighbor is within sufficient distance (a bridge to - // it would not be longer than max_bridge_distance) + // neighbors if that neighbor is within max_pillar_link_distance // Pillars with height exceeding H1 will require at least one neighbor // to connect with. Height exceeding H2 require two neighbors. double H1 = m_cfg.max_solo_pillar_height_mm; double H2 = m_cfg.max_dual_pillar_height_mm; unsigned neighbors = m_cfg.pillar_cascade_neighbors; - double d = std::cos(m_cfg.bridge_slope) * m_cfg.max_bridge_length_mm; + double d = m_cfg.max_pillar_link_distance_mm; //A connection between two pillars only counts if the height ratio is // bigger than 20% From 7857206442bfd09fd268d776de0e1918c4103bf4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 8 Mar 2019 16:06:21 +0100 Subject: [PATCH 18/37] Fixes for cascading logic. --- src/libslic3r/SLA/SLASupportTree.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index eab0ddba78..2516d265ea 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -74,7 +74,7 @@ const double SupportConfig::safety_distance_mm = 0.1; 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; -const unsigned SupportConfig::optimizer_max_iterations = 500; +const unsigned SupportConfig::optimizer_max_iterations = 1000; const unsigned SupportConfig::pillar_cascade_neighbors = 2; const unsigned SupportConfig::max_bridges_on_pillar = 3; @@ -1870,17 +1870,16 @@ public: // to connect with. Height exceeding H2 require two neighbors. double H1 = m_cfg.max_solo_pillar_height_mm; double H2 = m_cfg.max_dual_pillar_height_mm; - unsigned neighbors = m_cfg.pillar_cascade_neighbors; double d = m_cfg.max_pillar_link_distance_mm; //A connection between two pillars only counts if the height ratio is // bigger than 20% - double min_height_ratio = 0.5; + double min_height_ratio = 0.2; std::set pairs; m_pillar_index.foreach( - [this, d, &pairs, neighbors, min_height_ratio] + [this, d, &pairs, min_height_ratio] (const SpatElement& el) { Vec3d qp = el.first; @@ -1897,13 +1896,23 @@ public: }); const Pillar& pillar = m_result.pillars()[el.second]; + + unsigned neighbors = m_cfg.pillar_cascade_neighbors; + for(auto& re : qres) { + // connections are enough for one pillar + if(pillar.links >= neighbors) break; + if(re.second == el.second) continue; auto hashval = m_pillar_index.size() * el.second + re.second; if(pairs.find(hashval) != pairs.end()) continue; const Pillar& neighborpillar = m_result.pillars()[re.second]; + + // this neighbor is occupied + if(neighborpillar.links >= neighbors) continue; + if(interconnect(pillar, neighborpillar)) { pairs.insert(hashval); @@ -1916,9 +1925,6 @@ public: m_result.increment_links(neighborpillar); } - - // 3 connections are enough for one pillar - if(pillar.links == neighbors) break; } }); From 140c717c044b3809a55b6fd6723d6e372f680ac1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 11 Mar 2019 14:55:28 +0100 Subject: [PATCH 19/37] Enable imgui support point size. Fine tuning of support params. --- src/libslic3r/PrintConfig.cpp | 38 +++++++++++++++++++++++++--- src/libslic3r/SLA/SLASupportTree.cpp | 31 +++++++++++++++-------- src/slic3r/GUI/GLGizmo.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 3 ++- 4 files changed, 59 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d6e66cd678..ea2657bde9 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2450,6 +2450,7 @@ void PrintConfigDef::init_sla_params() def->enum_values.push_back("portrait"); def->enum_labels.push_back(L("Landscape")); def->enum_labels.push_back(L("Portrait")); + def->mode = comExpert; def->default_value = new ConfigOptionEnum(sladoPortrait); def = this->add("fast_tilt_time", coFloat); @@ -2563,6 +2564,7 @@ void PrintConfigDef::init_sla_params() def->category = L("Supports"); def->tooltip = L("Generate supports for the models"); def->cli = ""; + def->mode = comSimple; def->default_value = new ConfigOptionBool(true); def = this->add("support_head_front_diameter", coFloat); @@ -2580,6 +2582,7 @@ void PrintConfigDef::init_sla_params() def->tooltip = L("How much the pinhead has to penetrate the model surface"); def->sidetext = L("mm"); def->cli = ""; + def->mode = comAdvanced; def->min = 0; def->default_value = new ConfigOptionFloat(0.2); @@ -2590,6 +2593,8 @@ void PrintConfigDef::init_sla_params() def->sidetext = L("mm"); def->cli = ""; def->min = 0; + def->max = 20; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(1.0); def = this->add("support_pillar_diameter", coFloat); @@ -2599,6 +2604,8 @@ void PrintConfigDef::init_sla_params() def->sidetext = L("mm"); def->cli = ""; def->min = 0; + def->max = 15; + def->mode = comSimple; def->default_value = new ConfigOptionFloat(1.0); def = this->add("support_pillar_connection_mode", coEnum); @@ -2615,6 +2622,7 @@ void PrintConfigDef::init_sla_params() def->enum_labels.push_back(L("Zig-Zag")); def->enum_labels.push_back(L("Cross")); def->enum_labels.push_back(L("Dynamic")); + def->mode = comAdvanced; def->default_value = new ConfigOptionEnum(slapcmDynamic); def = this->add("support_buildplate_only", coBool); @@ -2634,6 +2642,7 @@ void PrintConfigDef::init_sla_params() def->cli = ""; def->min = 0; def->max = 1; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0.0); def = this->add("support_base_diameter", coFloat); @@ -2643,6 +2652,8 @@ void PrintConfigDef::init_sla_params() def->sidetext = L("mm"); def->cli = ""; def->min = 0; + def->max = 30; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(4.0); def = this->add("support_base_height", coFloat); @@ -2652,6 +2663,7 @@ void PrintConfigDef::init_sla_params() def->sidetext = L("mm"); def->cli = ""; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(1.0); def = this->add("support_critical_angle", coFloat); @@ -2661,6 +2673,8 @@ void PrintConfigDef::init_sla_params() def->sidetext = L("°"); def->cli = ""; def->min = 0; + def->max = 90; + def->mode = comSimple; def->default_value = new ConfigOptionFloat(45); def = this->add("support_max_bridge_length", coFloat); @@ -2670,15 +2684,18 @@ void PrintConfigDef::init_sla_params() def->sidetext = L("mm"); def->cli = ""; def->min = 0; + def->mode = comSimple; def->default_value = new ConfigOptionFloat(15.0); def = this->add("support_max_pillar_link_distance", coFloat); def->label = L("Max pillar linking distance"); def->category = L("Supports"); - def->tooltip = L("The max distance of two pillars to get linked with each other."); + def->tooltip = L("The max distance of two pillars to get linked with each other." + " A zero value will prohibit pillar cascading."); def->sidetext = L("mm"); def->cli = ""; def->min = 0; // 0 means no linking + def->mode = comSimple; def->default_value = new ConfigOptionFloat(10.0); def = this->add("support_object_elevation", coFloat); @@ -2688,6 +2705,8 @@ void PrintConfigDef::init_sla_params() def->sidetext = L("mm"); def->cli = ""; def->min = 0; + def->max = 150; // This is the max height of print on SL1 + def->mode = comSimple; def->default_value = new ConfigOptionFloat(5.0); def = this->add("support_points_density_relative", coInt); @@ -2713,35 +2732,46 @@ void PrintConfigDef::init_sla_params() def->category = L("Pad"); def->tooltip = L("Add a pad underneath the supported model"); def->cli = ""; + def->mode = comSimple; def->default_value = new ConfigOptionBool(true); def = this->add("pad_wall_thickness", coFloat); def->label = L("Pad wall thickness"); def->category = L("Pad"); -// def->tooltip = L(""); + def->tooltip = L("The thickness of the pad and its optional cavity walls."); def->sidetext = L("mm"); def->cli = ""; def->min = 0; + def->max = 30; + def->mode = comSimple; def->default_value = new ConfigOptionFloat(2.0); def = this->add("pad_wall_height", coFloat); def->label = L("Pad wall height"); + def->tooltip = L("Defines the cavity depth. Set to zero to disable the cavity."); def->category = L("Pad"); // def->tooltip = L(""); def->sidetext = L("mm"); def->cli = ""; def->min = 0; + def->max = 30; + def->mode = comSimple; def->default_value = new ConfigOptionFloat(5.0); def = this->add("pad_max_merge_distance", coFloat); def->label = L("Max merge distance"); def->category = L("Pad"); -// def->tooltip = L(""); + def->tooltip = L("Some objects can get along with a few smaller pads " + "instead of a single big one. This parameter defines " + "how far the center of two smaller pads should be. If they" + "are closer, they will get merged into one pad."); def->sidetext = L("mm"); def->cli = ""; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(50.0); + // This is disabled on the UI. I hope it will never be enabled. def = this->add("pad_edge_radius", coFloat); def->label = L("Pad edge radius"); def->category = L("Pad"); @@ -2749,6 +2779,7 @@ void PrintConfigDef::init_sla_params() def->sidetext = L("mm"); def->cli = ""; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(1.0); def = this->add("pad_wall_slope", coFloat); @@ -2760,6 +2791,7 @@ void PrintConfigDef::init_sla_params() def->cli = ""; def->min = 45; def->max = 90; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(45.0); } diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 2516d265ea..4dc82fcb5f 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -69,7 +69,7 @@ namespace sla { 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.1; +const double SupportConfig::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; @@ -438,8 +438,9 @@ struct Pillar { inline const Vec3d& endpoint() const { return endpt; } - Pillar& add_base(double height = 3, double radius = 2) { - if(height <= 0) return *this; + Pillar& add_base(double baseheight = 3, double radius = 2) { + if(baseheight <= 0) return *this; + if(baseheight > height) baseheight = height; assert(steps >= 0); auto last = int(steps - 1); @@ -447,7 +448,7 @@ struct Pillar { if(radius < r ) radius = r; double a = 2*PI/steps; - double z = endpt(Z) + height; + double z = endpt(Z) + baseheight; for(size_t i = 0; i < steps; ++i) { double phi = i*a; @@ -460,10 +461,10 @@ struct Pillar { double phi = i*a; double x = endpt(X) + radius*std::cos(phi); double y = endpt(Y) + radius*std::sin(phi); - base.points.emplace_back(x, y, z - height); + base.points.emplace_back(x, y, z - baseheight); } - auto ep = endpt; ep(Z) += height; + auto ep = endpt; ep(Z) += baseheight; base.points.emplace_back(endpt); base.points.emplace_back(ep); @@ -1004,9 +1005,12 @@ class SLASupportTree::Algorithm { // Together they define the plane where we have to iterate with the // given angles in the 'phis' vector +// std::cout << "Head check begin: " << std::endl; + tbb::parallel_for(size_t(0), phis.size(), [&phis, &hits, &m, sd, r_pin, r_back, s, a, b, c] (size_t i) +// for(size_t i = 0; i < phis.size(); ++i) { double& phi = phis[i]; double sinphi = std::sin(phi); @@ -1037,7 +1041,10 @@ class SLASupportTree::Algorithm { auto q = m.query_ray_hit(ps + sd*n, n); if(q.is_inside()) { // the hit is inside the model - if(q.distance() > 2*r_pin) { + if(q.distance() > r_pin + sd) { + +// std::cout << "Fatal inside hit. Phi: " << phi << " distance: " << q.distance() << std::endl; + // If we are inside the model and the hit distance is bigger // than our pin circle diameter, it probably indicates that // the support point was already inside the model, or there @@ -1048,17 +1055,21 @@ class SLASupportTree::Algorithm { hits[i] = HitResult(0.0); } else { +// std::cout << "Recoverable inside hit. Phi: " << phi << " distance: " << q.distance() << " re-cast dist: " ; // re-cast the ray from the outside of the object. // The starting point has an offset of 2*safety_distance // because the original ray has also had an offset auto q2 = m.query_ray_hit(ps + (q.distance() + 2*sd)*n, n); hits[i] = q2; +// std::cout << q2.distance() << std::endl; } } else hits[i] = q; }); auto mit = std::min_element(hits.begin(), hits.end()); +// std::cout << "Head check end. Result: " << mit->distance() << std::endl; + return *mit; } @@ -1128,7 +1139,7 @@ class SLASupportTree::Algorithm { auto hr = m.query_ray_hit(p + sd*dir, dir); if(ins_check && hr.is_inside()) { - if(hr.distance() > 2*r) hits[i] = HitResult(0.0); + if(hr.distance() > r + sd) hits[i] = HitResult(0.0); else { // re-cast the ray from the outside of the object auto hr2 = @@ -1457,7 +1468,7 @@ public: m_cfg.head_back_radius_mm, w); - if(t <= w) { + if(t <= w || (hp(Z) + nn(Z) * w) < m_result.ground_level) { // Let's try to optimize this angle, there might be a // viable normal that doesn't collide with the model @@ -1499,7 +1510,7 @@ public: // save the verified and corrected normal m_support_nmls.row(fidx) = nn; - if(t > w) { + if(t > w && (hp(Z) + nn(Z) * w) > m_result.ground_level) { // mark the point for needing a head. m_iheads.emplace_back(fidx); } else if( polar >= 3*PI/4 ) { diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp index 22023825f3..0efba4b461 100644 --- a/src/slic3r/GUI/GLGizmo.cpp +++ b/src/slic3r/GUI/GLGizmo.cpp @@ -2296,7 +2296,7 @@ RENDER_AGAIN: bool old_combo_state = m_combo_box_open; // The combo is commented out for now, until the feature is supported by backend. - // m_combo_box_open = m_imgui->combo(_(L("Head diameter")), options_str, selection); + m_combo_box_open = m_imgui->combo(_(L("Head diameter")), options_str, selection); force_refresh |= (old_combo_state != m_combo_box_open); // float current_number = atof(str); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a7b1af75de..779f0ca6fc 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3269,7 +3269,8 @@ void TabSLAPrint::build() optgroup->append_single_option_line("support_pillar_diameter"); optgroup->append_single_option_line("support_pillar_connection_mode"); optgroup->append_single_option_line("support_buildplate_only"); - optgroup->append_single_option_line("support_pillar_widening_factor"); + // TODO: This parameter is not used at the moment. + // 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_object_elevation"); From 2da3f22fbc53de996dec06a2935956c69a8131d5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 11 Mar 2019 16:27:54 +0100 Subject: [PATCH 20/37] Some constraints applied to support parameters. --- src/libslic3r/PrintConfig.cpp | 9 +++++---- src/slic3r/GUI/Tab.cpp | 36 ++++++++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ea2657bde9..b54a7a1fd9 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2574,6 +2574,7 @@ void PrintConfigDef::init_sla_params() def->sidetext = L("mm"); def->cli = ""; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0.4); def = this->add("support_head_penetration", coFloat); @@ -2674,7 +2675,7 @@ void PrintConfigDef::init_sla_params() def->cli = ""; def->min = 0; def->max = 90; - def->mode = comSimple; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(45); def = this->add("support_max_bridge_length", coFloat); @@ -2684,7 +2685,7 @@ void PrintConfigDef::init_sla_params() def->sidetext = L("mm"); def->cli = ""; def->min = 0; - def->mode = comSimple; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(15.0); def = this->add("support_max_pillar_link_distance", coFloat); @@ -2695,7 +2696,7 @@ void PrintConfigDef::init_sla_params() def->sidetext = L("mm"); def->cli = ""; def->min = 0; // 0 means no linking - def->mode = comSimple; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(10.0); def = this->add("support_object_elevation", coFloat); @@ -2706,7 +2707,7 @@ void PrintConfigDef::init_sla_params() def->cli = ""; def->min = 0; def->max = 150; // This is the max height of print on SL1 - def->mode = comSimple; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(5.0); def = this->add("support_points_density_relative", coInt); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 779f0ca6fc..aa7b7ded4b 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3337,11 +3337,37 @@ void TabSLAPrint::update() return; // #ys_FIXME // #ys_FIXME -// m_update_cnt++; -// ! something to update -// m_update_cnt--; -// -// if (m_update_cnt == 0) + 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)); + } + + load_config(new_conf); + } + + 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)); + } + + load_config(new_conf); + } + + m_update_cnt--; + + if (m_update_cnt == 0) wxGetApp().mainframe->on_config_changed(m_config); } From 8b23740d3052dfdfc107b630ec220bd5573034ae Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 11 Mar 2019 18:17:26 +0100 Subject: [PATCH 21/37] Fixing issue with cascading pair has function. --- src/libslic3r/SLA/SLASupportTree.cpp | 42 ++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 4dc82fcb5f..cbd27eb7c8 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -1884,14 +1884,13 @@ public: double d = m_cfg.max_pillar_link_distance_mm; //A connection between two pillars only counts if the height ratio is - // bigger than 20% - double min_height_ratio = 0.2; + // bigger than 50% + double min_height_ratio = 0.5; std::set pairs; - m_pillar_index.foreach( - [this, d, &pairs, min_height_ratio] - (const SpatElement& el) + auto cascadefn = + [this, d, &pairs, min_height_ratio, H1] (const SpatElement& el) { Vec3d qp = el.first; @@ -1916,7 +1915,12 @@ public: if(re.second == el.second) continue; - auto hashval = m_pillar_index.size() * el.second + re.second; + 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; + if(pairs.find(hashval) != pairs.end()) continue; const Pillar& neighborpillar = m_result.pillars()[re.second]; @@ -1929,15 +1933,19 @@ public: // If the interconnection length between the two pillars is // less than 20% of the longer pillar's height, don't count - if(neighborpillar.height / pillar.height > min_height_ratio) + if(pillar.height < H1 || + neighborpillar.height / pillar.height > min_height_ratio) m_result.increment_links(pillar); - if(pillar.height / neighborpillar.height > min_height_ratio) + if(neighborpillar.height < H1 || + pillar.height / neighborpillar.height > min_height_ratio) m_result.increment_links(neighborpillar); } } - }); + }; + + m_pillar_index.foreach(cascadefn); size_t pillarcount = m_result.pillars().size(); @@ -1992,6 +2000,8 @@ public: if(interconnect(pillar, p)) { Pillar& pp = m_result.add_pillar(p); + m_pillar_index.insert(pp.endpoint(), unsigned(pp.id)); + m_result.add_junction(s, pillar.r); double t = bridge_mesh_intersect(pillarsp, dirv(pillarsp, s), @@ -2003,15 +2013,23 @@ public: m_result.add_junction(pillar.endpoint(), pillar.r); newpills.emplace_back(pp.id); + m_result.increment_links(pillar); } } - if(!newpills.empty()) + if(!newpills.empty()) { for(auto it = newpills.begin(), nx = std::next(it); nx != newpills.end(); ++it, ++nx) { - interconnect(m_result.pillars()[size_t(*it)], - m_result.pillars()[size_t(*nx)]); + const Pillar& itpll = m_result.pillars()[size_t(*it)]; + const Pillar& nxpll = m_result.pillars()[size_t(*nx)]; + if(interconnect(itpll, nxpll)) { + m_result.increment_links(itpll); + m_result.increment_links(nxpll); + } } + + m_pillar_index.foreach(cascadefn); + } } } From 9522cd1d4f255aac752da1640a874e2a3e04ab4e Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 11 Mar 2019 11:49:24 +0100 Subject: [PATCH 22/37] OnInit: Move preset loading out of EVT_IDLE/once, remove temp workaround of sidebar scrollbar --- src/slic3r/GUI/GUI_App.cpp | 16 +++------------- src/slic3r/GUI/GUI_ObjectList.cpp | 5 +---- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 73ff19a9d5..afbf6b5685 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -167,17 +167,6 @@ bool GUI_App::OnInit() if (app_config->dirty() && app_config->get("autosave") == "1") app_config->save(); - // ! Temporary workaround for the correct behavior of the Scrolled sidebar panel - // Do this "manipulations" only once ( after (re)create of the application ) - if (sidebar().obj_list()->GetMinHeight() > 15 * wxGetApp().em_unit()) - { - wxWindowUpdateLocker noUpdates_sidebar(&sidebar()); - sidebar().obj_list()->SetMinSize(wxSize(-1, 15 * wxGetApp().em_unit())); - - // !!! to correct later layouts - update_mode(); // update view mode after fix of the object_list size - } - this->obj_manipul()->update_if_dirty(); // Preset updating & Configwizard are done after the above initializations, @@ -205,11 +194,12 @@ bool GUI_App::OnInit() } preset_updater->sync(preset_bundle); }); - - load_current_presets(); } }); + load_current_presets(); + update_mode(); // update view mode after fix of the object_list size + mainframe->Show(true); m_initialized = true; return true; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index eb3744befe..6a605a6678 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -115,10 +115,7 @@ ObjectList::~ObjectList() void ObjectList::create_objects_ctrl() { - // temporary workaround for the correct behavior of the Scrolled sidebar panel: - // 1. set a height of the list to some big value - // 2. change it to the normal min value (200) after first whole App updating/layouting - SetMinSize(wxSize(-1, 3000)); // #ys_FIXME + SetMinSize(wxSize(-1, 15 * wxGetApp().em_unit())); m_sizer = new wxBoxSizer(wxVERTICAL); m_sizer->Add(this, 1, wxGROW); From 4e510dc3e71d3eb96045c90cdf944396d0ca7f61 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 11 Mar 2019 09:48:24 +0100 Subject: [PATCH 23/37] PostProcessor on Unix: Execute using default shell #1908 escape gcode path, collect stderr --- src/libslic3r/GCode/PostProcessor.cpp | 76 ++++++++++++++++++--------- src/slic3r/GUI/Plater.cpp | 9 ++-- 2 files changed, 57 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/GCode/PostProcessor.cpp b/src/libslic3r/GCode/PostProcessor.cpp index e86463b847..df4acc1bf8 100644 --- a/src/libslic3r/GCode/PostProcessor.cpp +++ b/src/libslic3r/GCode/PostProcessor.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #ifdef WIN32 @@ -88,7 +89,7 @@ static DWORD execute_process_winapi(const std::wstring &command_line) // Run the script. If it is a perl script, run it through the bundled perl interpreter. // If it is a batch file, run it through the cmd.exe. // Otherwise run it directly. -static int run_script_win32(const std::string &script, const std::string &gcode) +static int run_script(const std::string &script, const std::string &gcode, std::string &/*std_err*/) { // Unpack the argument list provided by the user. int nArgs; @@ -132,9 +133,46 @@ static int run_script_win32(const std::string &script, const std::string &gcode) } #else - #include //for getting filesystem UID/GID - #include //for getting current UID/GID - #include + // POSIX + +#include // getenv() +#include +#include + +namespace process = boost::process; + +static int run_script(const std::string &script, const std::string &gcode, std::string &std_err) +{ + // Try to obtain user's default shell + const char *shell = ::getenv("SHELL"); + if (shell == nullptr) { shell = "sh"; } + + // Quote and escape the gcode path argument + std::string command { script }; + command.append(" '"); + for (char c : gcode) { + if (c == '\'') { command.append("'\\''"); } + else { command.push_back(c); } + } + command.push_back('\''); + + BOOST_LOG_TRIVIAL(debug) << boost::format("Executing script, shell: %1%, command: %2%") % shell % command; + + process::ipstream istd_err; + process::child child(shell, "-c", command, process::std_err > istd_err); + + std_err.clear(); + std::string line; + + while (child.running() && std::getline(istd_err, line)) { + std_err.append(line); + std_err.push_back('\n'); + } + + child.wait(); + return child.exit_code(); +} + #endif namespace Slic3r { @@ -158,27 +196,15 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config if (script.empty()) continue; BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path; -#ifdef WIN32 - int result = run_script_win32(script, gcode_file.string()); -#else - //FIXME testing existence of a script is risky, as the script line may contain the script and some additional command line parameters. - // We would have to process the script line into parameters before testing for the existence of the command, the command may be looked up - // in the PATH etc. - if (! boost::filesystem::exists(boost::filesystem::path(script))) - throw std::runtime_error(std::string("The configured post-processing script does not exist: ") + script); - struct stat info; - if (stat(script.c_str(), &info)) - throw std::runtime_error(std::string("Cannot read information for post-processing script: ") + script); - boost::filesystem::perms script_perms = boost::filesystem::status(script).permissions(); - //if UID matches, check UID perm. else if GID matches, check GID perm. Otherwise check other perm. - if (!(script_perms & ((info.st_uid == geteuid()) ? boost::filesystem::perms::owner_exe - : ((info.st_gid == getegid()) ? boost::filesystem::perms::group_exe - : boost::filesystem::perms::others_exe)))) - throw std::runtime_error(std::string("The configured post-processing script is not executable: check permissions. ") + script); - int result = boost::process::system(script, gcode_file); - if (result < 0) - BOOST_LOG_TRIVIAL(error) << "Script " << script << " on file " << path << " failed. Negative error code returned."; -#endif + + std::string std_err; + const int result = run_script(script, gcode_file.string(), std_err); + if (result != 0) { + const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str() + : (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str(); + BOOST_LOG_TRIVIAL(error) << msg; + throw std::runtime_error(msg); + } } } } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index eac611115b..aac21e7db8 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2463,14 +2463,17 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) this->statusbar()->reset_cancel_callback(); this->statusbar()->stop_busy(); - bool canceled = evt.GetInt() < 0; - bool success = evt.GetInt() > 0; + const bool canceled = evt.GetInt() < 0; + const bool error = evt.GetInt() == 0; + const bool success = evt.GetInt() > 0; // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. this->background_process.reset_export(); - if (! success) { + + if (error) { wxString message = evt.GetString(); if (message.IsEmpty()) message = _(L("Export failed")); + show_error(q, message); this->statusbar()->set_status_text(message); } if (canceled) From 09c9f567f23c8c9248759ee71adcb902fbbdba38 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 11 Mar 2019 13:59:58 +0100 Subject: [PATCH 24/37] Fix crash on exit --- src/slic3r/GUI/Tab.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 0d53c3fd9c..1d7a9a0c2d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -751,6 +751,10 @@ void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bo void Tab::on_value_change(const std::string& opt_key, const boost::any& value) { + if (wxGetApp().plater() == nullptr) { + return; + } + const bool is_fff = supports_printer_technology(ptFFF); ConfigOptionsGroup* og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); if (opt_key == "fill_density" || opt_key == "pad_enable") From 077b4cbfbc73941bcd802645e7f14a7aaf657a96 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 12 Mar 2019 11:39:16 +0100 Subject: [PATCH 25/37] Fixed layout for the action buttons + Tried to use DoubleBuffered for the controls drawing on the Sidebar (under MSW only) --- src/slic3r/GUI/Plater.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index aac21e7db8..2fddd4784b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -555,8 +555,6 @@ void Sidebar::priv::show_preset_comboboxes() { const bool showSLA = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA; - wxWindowUpdateLocker noUpdates_scrolled(scrolled->GetParent()); - for (size_t i = 0; i < 4; ++i) sizer_presets->Show(i, !showSLA); @@ -580,6 +578,10 @@ Sidebar::Sidebar(Plater *parent) p->scrolled = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(40 * wxGetApp().em_unit(), -1)); p->scrolled->SetScrollbars(0, 20, 1, 2); +#ifdef __WINDOWS__ + p->scrolled->SetDoubleBuffered(true); +#endif //__WINDOWS__ + // Sizer in the scrolled area auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL); p->scrolled->SetSizer(scrolled_sizer); @@ -645,9 +647,7 @@ Sidebar::Sidebar(Plater *parent) p->object_settings->Hide(); p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); - wxBitmap arrow_up(GUI::from_u8(Slic3r::var("brick_go.png")), wxBITMAP_TYPE_PNG); p->btn_send_gcode = new wxButton(this, wxID_ANY, _(L("Send to printer"))); - p->btn_send_gcode->SetBitmap(arrow_up); p->btn_send_gcode->SetFont(wxGetApp().bold_font()); p->btn_send_gcode->Hide(); @@ -757,6 +757,8 @@ void Sidebar::update_presets(Preset::Type preset_type) case Preset::TYPE_PRINTER: { + wxWindowUpdateLocker noUpdates_scrolled(p->scrolled); + // Update the print choosers to only contain the compatible presets, update the dirty flags. if (print_tech == ptFFF) preset_bundle.prints.update_platter_ui(p->combo_print); @@ -2893,6 +2895,7 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const sidebar->show_export(!is_ready_to_slice); sidebar->show_send(send_gcode_shown && !is_ready_to_slice); } + sidebar->Layout(); } void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const From f5b5e48ad78342bffa9b806c838f7bd038226750 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 12 Mar 2019 11:57:39 +0100 Subject: [PATCH 26/37] Added cancel mechanism to GCodeAnalyzer::calc_gcode_preview_data() --- src/libslic3r/GCode.cpp | 2 +- src/libslic3r/GCode/Analyzer.cpp | 51 ++++++++++++++++++++++++++------ src/libslic3r/GCode/Analyzer.hpp | 12 ++++---- 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 2907415bfc..94be37b218 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -487,7 +487,7 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ // starts analyzer calculations if (m_enable_analyzer) { BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data"; - m_analyzer.calc_gcode_preview_data(*preview_data); + m_analyzer.calc_gcode_preview_data(*preview_data, [print]() { print->throw_if_canceled(); }); m_analyzer.reset(); } diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index e1fb140bb0..f1f828776f 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -137,22 +137,22 @@ const std::string& GCodeAnalyzer::process_gcode(const std::string& gcode) return m_process_output; } -void GCodeAnalyzer::calc_gcode_preview_data(GCodePreviewData& preview_data) +void GCodeAnalyzer::calc_gcode_preview_data(GCodePreviewData& preview_data, std::function cancel_callback) { // resets preview data preview_data.reset(); // calculates extrusion layers - _calc_gcode_preview_extrusion_layers(preview_data); + _calc_gcode_preview_extrusion_layers(preview_data, cancel_callback); // calculates travel - _calc_gcode_preview_travel(preview_data); + _calc_gcode_preview_travel(preview_data, cancel_callback); // calculates retractions - _calc_gcode_preview_retractions(preview_data); + _calc_gcode_preview_retractions(preview_data, cancel_callback); // calculates unretractions - _calc_gcode_preview_unretractions(preview_data); + _calc_gcode_preview_unretractions(preview_data, cancel_callback); } bool GCodeAnalyzer::is_valid_extrusion_role(ExtrusionRole role) @@ -676,7 +676,7 @@ bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const return ((int)erNone <= value) && (value <= (int)erMixed); } -void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data) +void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data, std::function cancel_callback) { struct Helper { @@ -725,9 +725,18 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ GCodePreviewData::Range feedrate_range; GCodePreviewData::Range volumetric_rate_range; + // to avoid to call the callback too often + unsigned int cancel_callback_threshold = (unsigned int)extrude_moves->second.size() / 25; + unsigned int cancel_callback_curr = 0; + // constructs the polylines while traversing the moves for (const GCodeMove& move : extrude_moves->second) { + // to avoid to call the callback too often + cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold; + if (cancel_callback_curr == 0) + cancel_callback(); + if ((data != move.data) || (z != move.start_position.z()) || (position != move.start_position) || (volumetric_rate != move.data.feedrate * (float)move.data.mm3_per_mm)) { // store current polyline @@ -769,7 +778,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ preview_data.ranges.volumetric_rate.update_from(volumetric_rate_range); } -void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) +void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data, std::function cancel_callback) { struct Helper { @@ -797,9 +806,17 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) GCodePreviewData::Range width_range; GCodePreviewData::Range feedrate_range; + // to avoid to call the callback too often + unsigned int cancel_callback_threshold = (unsigned int)travel_moves->second.size() / 25; + unsigned int cancel_callback_curr = 0; + // constructs the polylines while traversing the moves for (const GCodeMove& move : travel_moves->second) { + cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold; + if (cancel_callback_curr == 0) + cancel_callback(); + GCodePreviewData::Travel::EType move_type = (move.delta_extruder < 0.0f) ? GCodePreviewData::Travel::Retract : ((move.delta_extruder > 0.0f) ? GCodePreviewData::Travel::Extrude : GCodePreviewData::Travel::Move); GCodePreviewData::Travel::Polyline::EDirection move_direction = ((move.start_position.x() != move.end_position.x()) || (move.start_position.y() != move.end_position.y())) ? GCodePreviewData::Travel::Polyline::Generic : GCodePreviewData::Travel::Polyline::Vertical; @@ -840,28 +857,44 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) preview_data.ranges.feedrate.update_from(feedrate_range); } -void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data) +void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data, std::function cancel_callback) { TypeToMovesMap::iterator retraction_moves = m_moves_map.find(GCodeMove::Retract); if (retraction_moves == m_moves_map.end()) return; + // to avoid to call the callback too often + unsigned int cancel_callback_threshold = (unsigned int)retraction_moves->second.size() / 25; + unsigned int cancel_callback_curr = 0; + for (const GCodeMove& move : retraction_moves->second) { + cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold; + if (cancel_callback_curr == 0) + cancel_callback(); + // store position Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())); preview_data.retraction.positions.emplace_back(position, move.data.width, move.data.height); } } -void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_data) +void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_data, std::function cancel_callback) { TypeToMovesMap::iterator unretraction_moves = m_moves_map.find(GCodeMove::Unretract); if (unretraction_moves == m_moves_map.end()) return; + // to avoid to call the callback too often + unsigned int cancel_callback_threshold = (unsigned int)unretraction_moves->second.size() / 25; + unsigned int cancel_callback_curr = 0; + for (const GCodeMove& move : unretraction_moves->second) { + cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold; + if (cancel_callback_curr == 0) + cancel_callback(); + // store position Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())); preview_data.unretraction.positions.emplace_back(position, move.data.width, move.data.height); diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp index c74a4558cb..4c201c6403 100644 --- a/src/libslic3r/GCode/Analyzer.hpp +++ b/src/libslic3r/GCode/Analyzer.hpp @@ -122,7 +122,8 @@ public: const std::string& process_gcode(const std::string& gcode); // Calculates all data needed for gcode visualization - void calc_gcode_preview_data(GCodePreviewData& preview_data); + // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). + void calc_gcode_preview_data(GCodePreviewData& preview_data, std::function cancel_callback = std::function()); // Return an estimate of the memory consumed by the time estimator. size_t memory_used() const; @@ -237,10 +238,11 @@ private: // Checks if the given int is a valid extrusion role (contained into enum ExtrusionRole) bool _is_valid_extrusion_role(int value) const; - void _calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data); - void _calc_gcode_preview_travel(GCodePreviewData& preview_data); - void _calc_gcode_preview_retractions(GCodePreviewData& preview_data); - void _calc_gcode_preview_unretractions(GCodePreviewData& preview_data); + // All the following methods throw CanceledException through print->throw_if_canceled() (sent by the caller as callback). + void _calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data, std::function cancel_callback); + void _calc_gcode_preview_travel(GCodePreviewData& preview_data, std::function cancel_callback); + void _calc_gcode_preview_retractions(GCodePreviewData& preview_data, std::function cancel_callback); + void _calc_gcode_preview_unretractions(GCodePreviewData& preview_data, std::function cancel_callback); }; } // namespace Slic3r From 1778c51af1711b5d5d1621155042fc41ed118afd Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 12 Mar 2019 15:21:55 +0100 Subject: [PATCH 27/37] Fixed seed for the genetic optimizer for support generation --- src/libnest2d/include/libnest2d/optimizers/nlopt/genetic.hpp | 2 ++ src/libslic3r/SLA/SLASupportTree.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/libnest2d/include/libnest2d/optimizers/nlopt/genetic.hpp b/src/libnest2d/include/libnest2d/optimizers/nlopt/genetic.hpp index 731ead554e..b92251ebdf 100644 --- a/src/libnest2d/include/libnest2d/optimizers/nlopt/genetic.hpp +++ b/src/libnest2d/include/libnest2d/optimizers/nlopt/genetic.hpp @@ -14,6 +14,8 @@ public: localmethod_ = m; return *this; } + + inline void seed(unsigned long val) { nlopt::srand(val); } }; template<> diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index cbd27eb7c8..4f449523a9 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -1479,6 +1479,7 @@ public: stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; stc.stop_score = w; // space greater than w is enough GeneticOptimizer solver(stc); + solver.seed(0); // we want deterministic behavior auto oresult = solver.optimize_max( [this, pin_r, w, hp](double plr, double azm) @@ -1778,6 +1779,7 @@ public: stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; stc.stop_score = 1e6; GeneticOptimizer solver(stc); + solver.seed(0); // we want deterministic behavior double r_back = head.r_back_mm; From 63b65bb3c88e74fa8ce2bdc2245a1fd07116503c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 13 Mar 2019 10:46:50 +0100 Subject: [PATCH 28/37] Wipe tower brim lines are now printed with overlaps as they should be --- src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 3 +-- src/slic3r/GUI/GLCanvas3D.cpp | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 031fd9a28a..829fe1bb93 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -709,12 +709,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded. // Extrude 4 rounds of a brim around the future wipe tower. box_coordinates box(wipeTower_box); - box.expand(m_perimeter_width); for (size_t i = 0; i < 4; ++ i) { + box.expand(m_perimeter_width - m_layer_height*(1.f-M_PI_4)); // the brim shall have 'normal' spacing with no extra void space writer.travel (box.ld, 7000) .extrude(box.lu, 2100).extrude(box.ru) .extrude(box.rd ).extrude(box.ld); - box.expand(m_perimeter_width); } writer.travel(wipeTower_box.ld, 7000); // Move to the front left corner. diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a3a523d941..8178c9cf6a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4564,11 +4564,18 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re const Print *print = m_process->fff_print(); float depth = print->get_wipe_tower_depth(); + + // Calculate wipe tower brim spacing. + const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + double layer_height = print_config.opt_float("layer_height"); + double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height); + float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); + if (!print->is_step_done(psWipeTower)) depth = (900.f/w) * (float)(extruders_count - 1) ; int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( 1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), - print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); + brim_spacing * 4.5f); if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; } @@ -7852,10 +7859,17 @@ void GLCanvas3D::_load_shells_fff() unsigned int extruders_count = config.nozzle_diameter.size(); if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { float depth = print->get_wipe_tower_depth(); + + // Calculate wipe tower brim spacing. + const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + double layer_height = print_config.opt_float("layer_height"); + double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height); + float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); + if (!print->is_step_done(psWipeTower)) depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, - m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); + m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), brim_spacing * 4.5f); } } } From 03beb1870b799184ed7a1ea9b94640924da4cdcf Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 13 Mar 2019 12:02:33 +0100 Subject: [PATCH 29/37] SLA gizmo - combo replaced by slider, autogeneration uses config value for head_diameter --- src/libslic3r/SLA/SLAAutoSupports.cpp | 2 +- src/libslic3r/SLA/SLAAutoSupports.hpp | 1 + src/libslic3r/SLAPrint.cpp | 1 + src/slic3r/GUI/GLGizmo.cpp | 51 +++++++++++++-------------- src/slic3r/GUI/GLGizmo.hpp | 4 +-- 5 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/libslic3r/SLA/SLAAutoSupports.cpp b/src/libslic3r/SLA/SLAAutoSupports.cpp index e99ed111ee..9e9f07b6cb 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.cpp +++ b/src/libslic3r/SLA/SLAAutoSupports.cpp @@ -496,7 +496,7 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end()); } for (const Vec2f &pt : poisson_samples) { - m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, 0.2f, is_new_island); + m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, m_config.head_diameter/2.f, is_new_island); structure.supports_force_this_layer += m_config.support_force(); grid3d.insert(pt, &structure); } diff --git a/src/libslic3r/SLA/SLAAutoSupports.hpp b/src/libslic3r/SLA/SLAAutoSupports.hpp index 5a758c2a6f..038b505dac 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.hpp +++ b/src/libslic3r/SLA/SLAAutoSupports.hpp @@ -17,6 +17,7 @@ public: struct Config { float density_relative; float minimal_distance; + float head_diameter; /////////////// inline float support_force() const { return 10.f / density_relative; } // a force one point can support (arbitrary force unit) inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 82b499ec75..35750dad3b 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -673,6 +673,7 @@ void SLAPrint::process() // the density config value is in percents: config.density_relative = float(cfg.support_points_density_relative / 100.f); config.minimal_distance = float(cfg.support_points_minimal_distance); + config.head_diameter = float(cfg.support_head_front_diameter); // Construction of this object does the calculation. this->throw_if_canceled(); diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp index 8b21833c47..fc91dff2d2 100644 --- a/src/slic3r/GUI/GLGizmo.cpp +++ b/src/slic3r/GUI/GLGizmo.cpp @@ -2227,15 +2227,15 @@ void GLGizmoSlaSupports::render_tooltip_texture() const { #endif // not ENABLE_IMGUI -std::vector GLGizmoSlaSupports::get_config_options(const std::vector& keys) const +std::vector GLGizmoSlaSupports::get_config_options(const std::vector& keys) const { - std::vector out; + std::vector out; if (!m_model_object) return out; - DynamicPrintConfig& object_cfg = m_model_object->config; - DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + const DynamicPrintConfig& object_cfg = m_model_object->config; + const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; std::unique_ptr default_cfg = nullptr; for (const std::string& key : keys) { @@ -2268,7 +2268,7 @@ RENDER_AGAIN: m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); const float scaling = m_imgui->get_style_scaling(); - const ImVec2 window_size(285.f * scaling, 260.f * scaling); + const ImVec2 window_size(285.f * scaling, 300.f * scaling); ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) )); ImGui::SetNextWindowSize(ImVec2(window_size)); @@ -2287,30 +2287,19 @@ RENDER_AGAIN: m_imgui->text(_(L("Shift + Left (+ drag) - select point(s)"))); m_imgui->text(" "); // vertical gap - static const std::vector options = {0.2f, 0.4f, 0.6f, 0.8f, 1.0f}; - static const std::vector options_str = {"0.2", "0.4", "0.6", "0.8", "1.0"}; - int selection = -1; - for (size_t i = 0; i < options.size(); i++) { - if (options[i] == m_new_point_head_diameter) { selection = (int)i; } - } + float diameter_upper_cap = static_cast(wxGetApp().preset_bundle->sla_prints.get_edited_preset().config.option("support_pillar_diameter"))->value; + if (m_new_point_head_diameter > diameter_upper_cap) + m_new_point_head_diameter = diameter_upper_cap; - bool old_combo_state = m_combo_box_open; - // The combo is commented out for now, until the feature is supported by backend. - m_combo_box_open = m_imgui->combo(_(L("Head diameter")), options_str, selection); - force_refresh |= (old_combo_state != m_combo_box_open); - - // float current_number = atof(str); - const float current_number = selection < options.size() ? options[selection] : m_new_point_head_diameter; - if (old_combo_state && !m_combo_box_open) // closing the combo must always change the sizes (even if the selection did not change) + m_imgui->text(_(L("Head diameter: "))); + ImGui::SameLine(); + if (ImGui::SliderFloat("", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f")) { + // value was changed for (auto& point_and_selection : m_editing_mode_cache) if (point_and_selection.second) { - point_and_selection.first.head_front_radius = current_number / 2.f; + point_and_selection.first.head_front_radius = m_new_point_head_diameter / 2.f; m_unsaved_changes = true; } - - if (std::abs(current_number - m_new_point_head_diameter) > 0.001) { - force_refresh = true; - m_new_point_head_diameter = current_number; } bool changed = m_lock_unique_islands; @@ -2343,9 +2332,9 @@ RENDER_AGAIN: m_imgui->text(_(L("Minimal points distance: "))); ImGui::SameLine(); - std::vector opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"}); - float density = static_cast(opts[0])->value; - float minimal_point_distance = static_cast(opts[1])->value; + std::vector opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"}); + float density = static_cast(opts[0])->value; + float minimal_point_distance = static_cast(opts[1])->value; bool value_changed = ImGui::SliderFloat("", &minimal_point_distance, 0.f, 20.f, "%.f mm"); if (value_changed) @@ -2451,6 +2440,10 @@ void GLGizmoSlaSupports::on_set_state() m_parent.toggle_model_objects_visibility(false); if (m_model_object) m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); + + // Set default head diameter from config. + const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; } if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off if (m_model_object) { @@ -2489,10 +2482,14 @@ void GLGizmoSlaSupports::select_point(int i) for (auto& point_and_selection : m_editing_mode_cache) point_and_selection.second = ( i == AllPoints ); m_selection_empty = (i == NoPoints); + + if (i == AllPoints) + m_new_point_head_diameter = m_editing_mode_cache[0].first.head_front_radius * 2.f; } else { m_editing_mode_cache[i].second = true; m_selection_empty = false; + m_new_point_head_diameter = m_editing_mode_cache[i].first.head_front_radius * 2.f; } } diff --git a/src/slic3r/GUI/GLGizmo.hpp b/src/slic3r/GUI/GLGizmo.hpp index fc8d40a659..c0690b8bce 100644 --- a/src/slic3r/GUI/GLGizmo.hpp +++ b/src/slic3r/GUI/GLGizmo.hpp @@ -520,7 +520,7 @@ private: bool m_lock_unique_islands = false; bool m_editing_mode = false; // Is editing mode active? bool m_old_editing_state = false; // To keep track of whether the user toggled between the modes (needed for imgui refreshes). - float m_new_point_head_diameter = 0.4f; // Size of a new point. + float m_new_point_head_diameter; // Size of a new point. float m_minimal_point_distance = 20.f; float m_density = 100.f; std::vector> m_editing_mode_cache; // a support point and whether it is currently selected @@ -536,7 +536,7 @@ private: int m_canvas_width; int m_canvas_height; - std::vector get_config_options(const std::vector& keys) const; + std::vector get_config_options(const std::vector& keys) const; // Methods that do the model_object and editing cache synchronization, // editing mode selection, etc: From a6dcbc8791aad14a0b0dde169cce66dd6b231b17 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 13 Mar 2019 13:13:18 +0100 Subject: [PATCH 30/37] Set DoubleBuffered state for the Tabs (under MSW) + Some improvements/experiments for the presets selection from the Plater + Some fix of a sidebar layout --- src/slic3r/GUI/GUI_App.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 48 ++++++++++++++++++++++----------- src/slic3r/GUI/Plater.hpp | 3 +++ src/slic3r/GUI/Preset.cpp | 6 ++++- src/slic3r/GUI/PresetBundle.cpp | 5 +++- src/slic3r/GUI/Tab.cpp | 39 +++++++-------------------- 6 files changed, 54 insertions(+), 49 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index afbf6b5685..7d448fbe47 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -198,9 +198,9 @@ bool GUI_App::OnInit() }); load_current_presets(); - update_mode(); // update view mode after fix of the object_list size mainframe->Show(true); + update_mode(); // update view mode after fix of the object_list size m_initialized = true; return true; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2fddd4784b..6fb21e17b1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -524,6 +524,7 @@ struct Sidebar::priv Plater *plater; wxScrolledWindow *scrolled; + wxPanel* presets_panel; // Used for MSW better layouts PrusaModeSizer *mode_sizer; wxFlexGridSizer *sizer_presets; @@ -578,9 +579,6 @@ Sidebar::Sidebar(Plater *parent) p->scrolled = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(40 * wxGetApp().em_unit(), -1)); p->scrolled->SetScrollbars(0, 20, 1, 2); -#ifdef __WINDOWS__ - p->scrolled->SetDoubleBuffered(true); -#endif //__WINDOWS__ // Sizer in the scrolled area auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL); @@ -593,12 +591,25 @@ Sidebar::Sidebar(Plater *parent) p->sizer_presets = new wxFlexGridSizer(10, 1, 1, 2); p->sizer_presets->AddGrowableCol(0, 1); p->sizer_presets->SetFlexibleDirection(wxBOTH); + + bool is_msw = false; +#ifdef __WINDOWS__ + p->scrolled->SetDoubleBuffered(true); + + p->presets_panel = new wxPanel(p->scrolled, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + p->presets_panel->SetSizer(p->sizer_presets); + + is_msw = true; +#else + presets_panel = p->scrolled; +#endif //__WINDOWS__ + p->sizer_filaments = new wxBoxSizer(wxVERTICAL); auto init_combo = [this](PresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) { - auto *text = new wxStaticText(p->scrolled, wxID_ANY, label+" :"); + auto *text = new wxStaticText(p->presets_panel, wxID_ANY, label + " :"); text->SetFont(wxGetApp().small_font()); - *combo = new PresetComboBox(p->scrolled, preset_type); + *combo = new PresetComboBox(p->presets_panel, preset_type); auto *sizer_presets = this->p->sizer_presets; auto *sizer_filaments = this->p->sizer_filaments; @@ -657,7 +668,9 @@ Sidebar::Sidebar(Plater *parent) // Sizer in the scrolled area scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL/*RIGHT | wxBOTTOM | wxRIGHT, 5*/); - scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5); + is_msw ? + scrolled_sizer->Add(p->presets_panel, 0, wxEXPAND | wxLEFT, margin_5) : + scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5); scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND | wxLEFT, margin_5); scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5); scrolled_sizer->Add(p->sliced_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5); @@ -695,7 +708,7 @@ Sidebar::Sidebar(Plater *parent) Sidebar::~Sidebar() {} void Sidebar::init_filament_combo(PresetComboBox **combo, const int extr_idx) { - *combo = new PresetComboBox(p->scrolled, Slic3r::Preset::TYPE_FILAMENT); + *combo = new PresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT); // # copy icons from first choice // $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; @@ -757,7 +770,7 @@ void Sidebar::update_presets(Preset::Type preset_type) case Preset::TYPE_PRINTER: { - wxWindowUpdateLocker noUpdates_scrolled(p->scrolled); +// wxWindowUpdateLocker noUpdates_scrolled(p->scrolled); // Update the print choosers to only contain the compatible presets, update the dirty flags. if (print_tech == ptFFF) @@ -819,6 +832,11 @@ wxScrolledWindow* Sidebar::scrolled_panel() return p->scrolled; } +wxPanel* Sidebar::presets_panel() +{ + return p->presets_panel; +} + ConfigOptionsGroup* Sidebar::og_freq_chng_params(const bool is_fff) { return p->frequently_changed_parameters->get_og(is_fff); @@ -2392,7 +2410,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) //! instead of //! combo->GetStringSelection().ToUTF8().data()); - std::string selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data(); + const std::string& selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data(); if (preset_type == Preset::TYPE_FILAMENT) { wxGetApp().preset_bundle->set_filament_preset(idx, selected_string); @@ -2404,12 +2422,8 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) wxGetApp().preset_bundle->update_platter_filament_ui(idx, combo); } else { - for (Tab* tab : wxGetApp().tabs_list) { - if (tab->type() == preset_type) { - tab->select_preset(selected_string); - break; - } - } + wxWindowUpdateLocker noUpdates(sidebar->presets_panel()); + wxGetApp().get_tab(preset_type)->select_preset(selected_string); } // update plater with new config @@ -3313,8 +3327,10 @@ void Plater::on_extruders_change(int num_extruders) { auto& choices = sidebar().combos_filament(); + if (num_extruders == choices.size()) + return; + wxWindowUpdateLocker noUpdates_scrolled_panel(&sidebar()/*.scrolled_panel()*/); -// sidebar().scrolled_panel()->Freeze(); int i = choices.size(); while ( i < num_extruders ) diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 78035d0ab2..4261dbae2d 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -51,6 +51,8 @@ public: int get_extruder_idx() const { return extruder_idx; } void check_selection(); + std::string selected_preset_name; + private: typedef std::size_t Marker; enum { LABEL_ITEM_MARKER = 0x4d }; @@ -81,6 +83,7 @@ public: ObjectList* obj_list(); ObjectSettings* obj_settings(); wxScrolledWindow* scrolled_panel(); + wxPanel* presets_panel(); ConfigOptionsGroup* og_freq_chng_params(const bool is_fff); wxButton* get_wiping_dialog_button(); diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 4902b5552e..86fdde44b1 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -879,8 +879,10 @@ size_t PresetCollection::update_compatible_internal(const Preset &active_printer // Hide the void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) { - if (ui == nullptr) + if (ui == nullptr || + ui->selected_preset_name == this->get_selected_preset().name) return; + // Otherwise fill in the list from scratch. ui->Freeze(); ui->Clear(); @@ -949,6 +951,8 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) ui->SetSelection(selected_preset_item); ui->SetToolTip(ui->GetString(selected_preset_item)); ui->Thaw(); + + ui->selected_preset_name = this->get_selected_preset().name; } size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible) diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 0ee7a5c6c2..6ec8eb6e38 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -1436,7 +1436,8 @@ bool PresetBundle::parse_color(const std::string &scolor, unsigned char *rgb_out void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui) { if (ui == nullptr || this->printers.get_edited_preset().printer_technology() == ptSLA || - this->filament_presets.size() <= idx_extruder ) + this->filament_presets.size() <= idx_extruder || + ui->selected_preset_name == this->filaments.find_preset(this->filament_presets[idx_extruder])->name) return; unsigned char rgb[3]; @@ -1525,6 +1526,8 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr ui->SetSelection(selected_preset_item); ui->SetToolTip(ui->GetString(selected_preset_item)); ui->Thaw(); + + ui->selected_preset_name = this->filaments.find_preset(this->filament_presets[idx_extruder])->name; } void PresetBundle::set_default_suppressed(bool default_suppressed) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 1400f75a68..c756181321 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -73,6 +73,10 @@ void Tab::set_type() // sub new void Tab::create_preset_tab() { +#ifdef __WINDOWS__ + SetDoubleBuffered(true); +#endif //__WINDOWS__ + m_preset_bundle = wxGetApp().preset_bundle; // Vertical sizer to hold the choice menu and the rest of the page. @@ -787,25 +791,6 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) wxGetApp().plater()->on_extruders_change(boost::any_cast(value)); update(); - - // #ys_FIXME_to_delete - // Post event to the Plater after updating of the all dirty options - // It helps to avoid needless schedule_background_processing -// if (update_completed()) -// if (m_update_stack.empty()) -// { -// // wxCommandEvent event(EVT_TAB_VALUE_CHANGED); -// // event.SetEventObject(this); -// // event.SetString(opt_key); -// // if (opt_key == "extruders_count") -// // { -// // const int val = boost::any_cast(value); -// // event.SetInt(val); -// // } -// // -// // wxPostEvent(this, event); -// wxGetApp().mainframe->on_value_changed(m_config); -// } } // Show/hide the 'purging volumes' button @@ -828,9 +813,13 @@ void Tab::update_wiping_button_visibility() { // To update the content of the selection boxes, // to update the filament colors of the selection boxes, // to update the "dirty" flags of the selection boxes, -// to uddate number of "filament" selection boxes when the number of extruders change. +// to update number of "filament" selection boxes when the number of extruders change. void Tab::on_presets_changed() { + // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets + wxGetApp().plater()->sidebar().update_presets(m_type); + update_preset_description_line(); + // Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. for (auto t: m_dependent_tabs) { @@ -841,16 +830,6 @@ void Tab::on_presets_changed() // clear m_dependent_tabs after first update from select_preset() // to avoid needless preset loading from update() function m_dependent_tabs.clear(); - - // #ys_FIXME_to_delete -// wxCommandEvent event(EVT_TAB_PRESETS_CHANGED); -// event.SetEventObject(this); -// wxPostEvent(this, event); - - // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets - wxGetApp().plater()->sidebar().update_presets(m_type); - - update_preset_description_line(); } void Tab::update_preset_description_line() From dec1c6ecfb2936610b00f20b9ece0a5350e87db7 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 13 Mar 2019 13:37:36 +0100 Subject: [PATCH 31/37] Typo fixed --- src/slic3r/GUI/Plater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6fb21e17b1..64d1898595 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -601,7 +601,7 @@ Sidebar::Sidebar(Plater *parent) is_msw = true; #else - presets_panel = p->scrolled; + p->presets_panel = p->scrolled; #endif //__WINDOWS__ p->sizer_filaments = new wxBoxSizer(wxVERTICAL); From 77964de9f24dab24c811ae293d9775e0c080d59a Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 13 Mar 2019 14:04:59 +0100 Subject: [PATCH 32/37] Update of ModelVolume::m_is_splittable moved inside ModelVolume itself --- src/libslic3r/Model.cpp | 9 +++++++++ src/libslic3r/Model.hpp | 5 ++--- src/slic3r/GUI/GUI_ObjectList.cpp | 7 +------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 9c5f8c826a..606bcd6f02 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1456,6 +1456,15 @@ int ModelVolume::extruder_id() const return extruder_id; } +bool ModelVolume::is_splittable() const +{ + // the call mesh.has_multiple_patches() is expensive, so cache the value to calculate it only once + if (m_is_splittable == -1) + m_is_splittable = (int)mesh.has_multiple_patches(); + + return m_is_splittable == 1; +} + void ModelVolume::center_geometry() { #if ENABLE_VOLUMES_CENTERING_FIXES diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index a4b32d93f8..432e49220b 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -336,8 +336,7 @@ public: // Extruder ID is only valid for FFF. Returns -1 for SLA or if the extruder ID is not applicable (support volumes). int extruder_id() const; - void set_splittable(const int val) { m_is_splittable = val; } - int is_splittable() const { return m_is_splittable; } + bool is_splittable() const; // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. @@ -417,7 +416,7 @@ private: // -1 -> is unknown value (before first cheking) // 0 -> is not splittable // 1 -> is splittable - int m_is_splittable {-1}; + mutable int m_is_splittable{ -1 }; ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(ModelVolumeType::MODEL_PART), object(object) { diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 6a605a6678..494467f79d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1519,12 +1519,7 @@ bool ObjectList::is_splittable() if (!get_volume_by_item(item, volume) || !volume) return false; - int splittable = volume->is_splittable(); - if (splittable == -1) { - splittable = (int)volume->mesh.has_multiple_patches(); - volume->set_splittable(splittable); - } - return splittable != 0; + return volume->is_splittable(); } bool ObjectList::selected_instances_of_same_object() From 973fafab2fa76660e3103277fc944bac930501ee Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 13 Mar 2019 14:35:09 +0100 Subject: [PATCH 33/37] Removed unused members from View3D and unused parameters from Preview::Init() --- src/slic3r/GUI/GUI_Preview.cpp | 12 +++--------- src/slic3r/GUI/GUI_Preview.hpp | 6 +----- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 57ede0a965..6f177d8b45 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -33,9 +33,6 @@ namespace GUI { #if !ENABLE_IMGUI , m_gizmo_widget(nullptr) #endif // !ENABLE_IMGUI - , m_model(nullptr) - , m_config(nullptr) - , m_process(nullptr) { init(parent, bed, camera, view_toolbar, model, config, process); } @@ -202,18 +199,15 @@ Preview::Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_t , m_enabled(false) , m_schedule_background_process(schedule_background_process_func) { - if (init(parent, bed, camera, view_toolbar, config, process, gcode_preview_data)) + if (init(parent, bed, camera, view_toolbar)) { show_hide_ui_elements("none"); load_print(); } } -bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data) +bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) { - if ((config == nullptr) || (process == nullptr) || (gcode_preview_data == nullptr)) - return false; - if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) return false; @@ -222,7 +216,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view m_canvas = _3DScene::get_canvas(this->m_canvas_widget); m_canvas->allow_multisample(GLCanvas3DManager::can_multisample()); m_canvas->set_config(m_config); - m_canvas->set_process(process); + m_canvas->set_process(m_process); m_canvas->enable_legend_texture(true); m_canvas->enable_dynamic_background(true); diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 05f9fc2ca8..c15aad3b3a 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -39,10 +39,6 @@ class View3D : public wxPanel wxPanel* m_gizmo_widget; #endif // !ENABLE_IMGUI - Model* m_model; - DynamicPrintConfig* m_config; - BackgroundSlicingProcess* m_process; - public: View3D(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); virtual ~View3D(); @@ -124,7 +120,7 @@ public: void refresh_print(); private: - bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data); + bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar); void bind_event_handlers(); void unbind_event_handlers(); From 3c62c0d595d763a1d3358689f911dcaf827bb169 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 13 Mar 2019 14:44:59 +0100 Subject: [PATCH 34/37] imgui: Fix mouse button state while dragging --- src/slic3r/GUI/ImGuiWrapper.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 9e75eb2ff7..aeb74d2f09 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -107,11 +107,11 @@ bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt) ImGuiIO& io = ImGui::GetIO(); io.MousePos = ImVec2((float)evt.GetX(), (float)evt.GetY()); - io.MouseDown[0] = evt.LeftDown(); - io.MouseDown[1] = evt.RightDown(); - io.MouseDown[2] = evt.MiddleDown(); + io.MouseDown[0] = evt.LeftIsDown(); + io.MouseDown[1] = evt.RightIsDown(); + io.MouseDown[2] = evt.MiddleIsDown(); - unsigned buttons = (evt.LeftDown() ? 1 : 0) | (evt.RightDown() ? 2 : 0) | (evt.MiddleDown() ? 4 : 0); + unsigned buttons = (evt.LeftIsDown() ? 1 : 0) | (evt.RightIsDown() ? 2 : 0) | (evt.MiddleIsDown() ? 4 : 0); m_mouse_buttons = buttons; new_frame(); From 88f93a0b448744de2904fc251fcbaccc041f71a7 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 13 Mar 2019 14:50:32 +0100 Subject: [PATCH 35/37] imgui: Slider style --- src/slic3r/GUI/ImGuiWrapper.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index aeb74d2f09..0050b64632 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -441,6 +441,10 @@ void ImGuiWrapper::init_style() set_color(ImGuiCol_Header, COL_ORANGE_DARK); set_color(ImGuiCol_HeaderHovered, COL_ORANGE_LIGHT); set_color(ImGuiCol_HeaderActive, COL_ORANGE_LIGHT); + + // Slider + set_color(ImGuiCol_SliderGrab, COL_ORANGE_DARK); + set_color(ImGuiCol_SliderGrabActive, COL_ORANGE_LIGHT); } void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) From 46d19aa52dc4923ccbb87c4c5e7e550605c928a4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 13 Mar 2019 15:34:27 +0100 Subject: [PATCH 36/37] Added function for the updating of the Error icon after a fix through the NetFabb --- src/slic3r/GUI/GUI_ObjectList.cpp | 42 +++++++++++++++++++++++-------- src/slic3r/GUI/GUI_ObjectList.hpp | 1 + src/slic3r/GUI/Tab.cpp | 4 +-- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 494467f79d..04fb9b7ca3 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -427,10 +427,8 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) else if (title == _("Name") && pt.x >15 && m_objects_model->GetBitmap(item).GetRefData() == m_bmp_manifold_warning.GetRefData()) { - if (is_windows10()) { - const auto obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); - wxGetApp().plater()->fix_through_netfabb(obj_idx); - } + if (is_windows10()) + fix_through_netfabb(); } #ifndef __WXMSW__ GetMainWindow()->SetToolTip(""); // hide tooltip @@ -2272,13 +2270,37 @@ void ObjectList::fix_through_netfabb() const if (!item) return; - ItemType type = m_objects_model->GetItemType(item); + const ItemType type = m_objects_model->GetItemType(item); + + const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : + type & itVolume ? m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)) : -1; + + const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; + + wxGetApp().plater()->fix_through_netfabb(obj_idx, vol_idx); - if (type & itObject) - wxGetApp().plater()->fix_through_netfabb(m_objects_model->GetIdByItem(item)); - else if (type & itVolume) - wxGetApp().plater()->fix_through_netfabb(m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)), - m_objects_model->GetVolumeIdByItem(item)); + update_item_error_icon(obj_idx, vol_idx); +} + +void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) const +{ + const wxDataViewItem item = vol_idx <0 ? m_objects_model->GetItemById(obj_idx) : + m_objects_model->GetItemByVolumeId(obj_idx, vol_idx); + if (!item) + return; + + auto model_object = (*m_objects)[obj_idx]; + + const stl_stats& stats = model_object->volumes[vol_idx<0 ? 0 : vol_idx]->mesh.stl.stats; + const int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + + stats.facets_added + stats.facets_reversed + stats.backwards_edges; + + if (errors == 0) { + // delete Error_icon if all errors are fixed + wxVariant variant; + variant << PrusaDataViewBitmapText(from_u8(model_object->name), wxNullBitmap); + m_objects_model->SetValue(variant, item, 0); + } } void ObjectList::ItemValueChanged(wxDataViewEvent &event) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 762020bda7..a4df674977 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -270,6 +270,7 @@ public: void split_instances(); void rename_item(); void fix_through_netfabb() const; + void update_item_error_icon(const int obj_idx, int vol_idx) const ; private: void OnChar(wxKeyEvent& event); void OnContextMenu(wxDataViewEvent &event); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index c756181321..56af8b4f95 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2455,7 +2455,7 @@ void Tab::load_current_preset() //Regerenerate content of the page tree. void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/) { - Freeze(); +// Freeze(); // get label of the currently selected item const auto sel_item = m_treectrl->GetSelection(); @@ -2481,7 +2481,7 @@ void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/) // this is triggered on first load, so we don't disable the sel change event m_treectrl->SelectItem(m_treectrl->GetFirstVisibleItem());//! (treectrl->GetFirstChild(rootItem)); } - Thaw(); +// Thaw(); } void Tab::update_page_tree_visibility() From 2750a8113da30d582aff830d02845a99b3619fbd Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 13 Mar 2019 14:32:09 +0100 Subject: [PATCH 37/37] SLA gizmo now shows arrows pointing at the points so they are easy to see --- src/slic3r/GUI/GLGizmo.cpp | 101 +++++++++++++++++++++++++------------ src/slic3r/GUI/GLGizmo.hpp | 15 +++++- 2 files changed, 83 insertions(+), 33 deletions(-) diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp index fc91dff2d2..a87cb68fb7 100644 --- a/src/slic3r/GUI/GLGizmo.cpp +++ b/src/slic3r/GUI/GLGizmo.cpp @@ -1873,8 +1873,8 @@ void GLGizmoSlaSupports::render_points(const GLCanvas3D::Selection& selection, b float render_color[3]; for (int i = 0; i < (int)m_editing_mode_cache.size(); ++i) { - const sla::SupportPoint& support_point = m_editing_mode_cache[i].first; - const bool& point_selected = m_editing_mode_cache[i].second; + const sla::SupportPoint& support_point = m_editing_mode_cache[i].support_point; + const bool& point_selected = m_editing_mode_cache[i].selected; // First decide about the color of the point. if (picking) { @@ -1889,7 +1889,7 @@ void GLGizmoSlaSupports::render_points(const GLCanvas3D::Selection& selection, b render_color[2] = 1.0f; } else { // neigher hover nor picking - bool supports_new_island = m_lock_unique_islands && m_editing_mode_cache[i].first.is_new_island; + bool supports_new_island = m_lock_unique_islands && m_editing_mode_cache[i].support_point.is_new_island; if (m_editing_mode) { render_color[0] = point_selected ? 1.0f : (supports_new_island ? 0.3f : 0.7f); render_color[1] = point_selected ? 0.3f : (supports_new_island ? 0.3f : 0.7f); @@ -1903,12 +1903,32 @@ void GLGizmoSlaSupports::render_points(const GLCanvas3D::Selection& selection, b float render_color_emissive[4] = { 0.5f * render_color[0], 0.5f * render_color[1], 0.5f * render_color[2], 1.f}; ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive); - // Now render the sphere. Inverse matrix of the instance scaling is applied so that the - // sphere does not scale with the object. + // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. ::glPushMatrix(); ::glTranslated(support_point.pos(0), support_point.pos(1), support_point.pos(2)); ::glMultMatrixd(instance_scaling_matrix_inverse.data()); - ::gluSphere(m_quadric, m_editing_mode_cache[i].first.head_front_radius * RenderPointScale, 64, 36); + + // Matrices set, we can render the point mark now. + // If in editing mode, we'll also render a cone pointing to the sphere. + if (m_editing_mode) { + if (m_editing_mode_cache[i].normal == Vec3f::Zero()) + update_cache_entry_normal(i); // in case the normal is not yet cached, find and cache it + + Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_mode_cache[i].normal.cast()); + Eigen::AngleAxisd aa(q); + ::glRotated(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2)); + + const float cone_radius = 0.25f; // mm + const float cone_height = 0.75f; + ::glPushMatrix(); + ::glTranslatef(0.f, 0.f, m_editing_mode_cache[i].support_point.head_front_radius * RenderPointScale); + ::gluCylinder(m_quadric, 0.f, cone_radius, cone_height, 36, 1); + ::glTranslatef(0.f, 0.f, cone_height); + ::gluDisk(m_quadric, 0.0, cone_radius, 36, 1); + ::glPopMatrix(); + } + ::gluSphere(m_quadric, m_editing_mode_cache[i].support_point.head_front_radius * RenderPointScale, 64, 36); ::glPopMatrix(); } @@ -1957,7 +1977,7 @@ void GLGizmoSlaSupports::update_mesh() m_AABB.init(m_V, m_F); } -Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) +std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) { // if the gizmo doesn't have the V, F structures for igl, calculate them first: if (m_V.size() == 0) @@ -1992,9 +2012,16 @@ Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) if (!m_AABB.intersect_ray(m_V, m_F, point1.cast(), (point2-point1).cast(), hit)) throw std::invalid_argument("unproject_on_mesh(): No intersection found."); - int fid = hit.id; - Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); - return bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)); + int fid = hit.id; // facet id + Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit + Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); + Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); + + // Calculate and return both the point and the facet normal. + return std::make_pair( + bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)), + a.cross(b) + ); } // Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. @@ -2046,10 +2073,9 @@ bool GLGizmoSlaSupports::mouse_event(SLAGizmoEventType action, const Vec2d& mous // If there is some selection, don't add new point and deselect everything instead. if (m_selection_empty) { - Vec3f new_pos; try { - new_pos = unproject_on_mesh(mouse_position); // this can throw - we don't want to create a new point in that case - m_editing_mode_cache.emplace_back(std::make_pair(sla::SupportPoint(new_pos, m_new_point_head_diameter/2.f, false), false)); + std::pair pos_and_normal = unproject_on_mesh(mouse_position); // don't create anything if this throws + m_editing_mode_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); m_unsaved_changes = true; } catch (...) { // not clicked on object @@ -2090,7 +2116,7 @@ bool GLGizmoSlaSupports::mouse_event(SLAGizmoEventType action, const Vec2d& mous // Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh: for (unsigned int i=0; i() * support_point.pos; pos(2) += z_offset; GLdouble out_x, out_y, out_z; @@ -2166,7 +2192,7 @@ bool GLGizmoSlaSupports::mouse_event(SLAGizmoEventType action, const Vec2d& mous void GLGizmoSlaSupports::delete_selected_points(bool force) { for (unsigned int idx=0; idx pos_and_normal; try { - new_pos = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1))); + pos_and_normal = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1))); } catch (...) { return; } - m_editing_mode_cache[m_hover_id].first.pos = new_pos; - m_editing_mode_cache[m_hover_id].first.is_new_island = false; + m_editing_mode_cache[m_hover_id].support_point.pos = pos_and_normal.first; + m_editing_mode_cache[m_hover_id].support_point.is_new_island = false; + m_editing_mode_cache[m_hover_id].normal = pos_and_normal.second; m_unsaved_changes = true; // Do not update immediately, wait until the mouse is released. // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); @@ -2255,6 +2282,18 @@ std::vector GLGizmoSlaSupports::get_config_options(const st } +void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const +{ + int idx = 0; + Eigen::Matrix pp = m_editing_mode_cache[i].support_point.pos; + Eigen::Matrix cc; + m_AABB.squared_distance(m_V, m_F, pp, idx, cc); + Vec3f a = (m_V.row(m_F(idx, 1)) - m_V.row(m_F(idx, 0))); + Vec3f b = (m_V.row(m_F(idx, 2)) - m_V.row(m_F(idx, 0))); + m_editing_mode_cache[i].normal = a.cross(b); +} + + #if ENABLE_IMGUI void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) @@ -2295,9 +2334,9 @@ RENDER_AGAIN: ImGui::SameLine(); if (ImGui::SliderFloat("", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f")) { // value was changed - for (auto& point_and_selection : m_editing_mode_cache) - if (point_and_selection.second) { - point_and_selection.first.head_front_radius = m_new_point_head_diameter / 2.f; + for (auto& cache_entry : m_editing_mode_cache) + if (cache_entry.selected) { + cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; m_unsaved_changes = true; } } @@ -2480,16 +2519,16 @@ void GLGizmoSlaSupports::select_point(int i) { if (i == AllPoints || i == NoPoints) { for (auto& point_and_selection : m_editing_mode_cache) - point_and_selection.second = ( i == AllPoints ); + point_and_selection.selected = ( i == AllPoints ); m_selection_empty = (i == NoPoints); if (i == AllPoints) - m_new_point_head_diameter = m_editing_mode_cache[0].first.head_front_radius * 2.f; + m_new_point_head_diameter = m_editing_mode_cache[0].support_point.head_front_radius * 2.f; } else { - m_editing_mode_cache[i].second = true; + m_editing_mode_cache[i].selected = true; m_selection_empty = false; - m_new_point_head_diameter = m_editing_mode_cache[i].first.head_front_radius * 2.f; + m_new_point_head_diameter = m_editing_mode_cache[i].support_point.head_front_radius * 2.f; } } @@ -2499,7 +2538,7 @@ void GLGizmoSlaSupports::editing_mode_discard_changes() { m_editing_mode_cache.clear(); for (const sla::SupportPoint& point : m_model_object->sla_support_points) - m_editing_mode_cache.push_back(std::make_pair(point, false)); + m_editing_mode_cache.emplace_back(point, false); m_editing_mode = false; m_unsaved_changes = false; } @@ -2513,8 +2552,8 @@ void GLGizmoSlaSupports::editing_mode_apply_changes() if (m_unsaved_changes) { m_model_object->sla_points_status = sla::PointsStatus::UserModified; m_model_object->sla_support_points.clear(); - for (const std::pair& point_and_selection : m_editing_mode_cache) - m_model_object->sla_support_points.push_back(point_and_selection.first); + for (const CacheEntry& cache_entry : m_editing_mode_cache) + m_model_object->sla_support_points.push_back(cache_entry.support_point); // Recalculate support structures once the editing mode is left. // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); @@ -2531,7 +2570,7 @@ void GLGizmoSlaSupports::editing_mode_reload_cache() { m_editing_mode_cache.clear(); for (const sla::SupportPoint& point : m_model_object->sla_support_points) - m_editing_mode_cache.push_back(std::make_pair(point, false)); + m_editing_mode_cache.emplace_back(point, false); m_unsaved_changes = false; } diff --git a/src/slic3r/GUI/GLGizmo.hpp b/src/slic3r/GUI/GLGizmo.hpp index c0690b8bce..a872a161e5 100644 --- a/src/slic3r/GUI/GLGizmo.hpp +++ b/src/slic3r/GUI/GLGizmo.hpp @@ -471,7 +471,7 @@ private: ModelObject* m_old_model_object = nullptr; int m_active_instance = -1; int m_old_instance_id = -1; - Vec3f unproject_on_mesh(const Vec2d& mouse_pos); + std::pair unproject_on_mesh(const Vec2d& mouse_pos); const float RenderPointScale = 1.f; @@ -484,6 +484,16 @@ private: Geometry::Transformation transformation; }; + class CacheEntry { + public: + CacheEntry(const sla::SupportPoint& point, bool sel, const Vec3f& norm = Vec3f::Zero()) : + support_point(point), selected(sel), normal(norm) {} + + sla::SupportPoint support_point; + bool selected; // whether the point is selected + Vec3f normal; + }; + // This holds information to decide whether recalculation is necessary: SourceDataSummary m_source_data; @@ -510,6 +520,7 @@ private: void render_points(const GLCanvas3D::Selection& selection, bool picking = false) const; bool is_mesh_update_necessary() const; void update_mesh(); + void update_cache_entry_normal(unsigned int i) const; #if !ENABLE_IMGUI void render_tooltip_texture() const; @@ -523,7 +534,7 @@ private: float m_new_point_head_diameter; // Size of a new point. float m_minimal_point_distance = 20.f; float m_density = 100.f; - std::vector> m_editing_mode_cache; // a support point and whether it is currently selected + mutable std::vector m_editing_mode_cache; // a support point and whether it is currently selected bool m_selection_rectangle_active = false; Vec2d m_selection_rectangle_start_corner;