From 9131b1658aefd0495343fc70f92ea33d92d37054 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 7 Mar 2019 12:01:21 +0100 Subject: [PATCH] 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 de2bb274b..547ad8527 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 d275c7963..401798582 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;