Replacing simplex optimizers with more intelligent genetic ones.

This commit is contained in:
tamasmeszaros 2019-03-07 12:01:21 +01:00
parent efd3d27425
commit 9131b1658a
2 changed files with 132 additions and 119 deletions

View File

@ -12,7 +12,7 @@
#include <libslic3r/ClipperUtils.hpp> #include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/Model.hpp> #include <libslic3r/Model.hpp>
#include <libnest2d/optimizers/nlopt/simplex.hpp> #include <libnest2d/optimizers/nlopt/genetic.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <tbb/parallel_for.h> #include <tbb/parallel_for.h>
#include <libslic3r/I18N.hpp> #include <libslic3r/I18N.hpp>
@ -63,6 +63,19 @@
namespace Slic3r { namespace Slic3r {
namespace sla { 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 Coordf = double;
using Portion = std::tuple<double, double>; using Portion = std::tuple<double, double>;
@ -1309,105 +1322,6 @@ class SLASupportTree::Algorithm {
return nearest_id >= 0; 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<unsigned long> 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: public:
Algorithm(const SupportConfig& config, Algorithm(const SupportConfig& config,
@ -1468,9 +1382,8 @@ public:
using libnest2d::opt::bound; using libnest2d::opt::bound;
using libnest2d::opt::initvals; using libnest2d::opt::initvals;
using libnest2d::opt::SimplexOptimizer; using libnest2d::opt::GeneticOptimizer;
using libnest2d::opt::StopCriteria; using libnest2d::opt::StopCriteria;
static const unsigned MAX_TRIES = 100;
for(unsigned i = 0, fidx = filtered_indices[0]; for(unsigned i = 0, fidx = filtered_indices[0];
i < filtered_indices.size(); ++i, fidx = filtered_indices[i]) i < filtered_indices.size(); ++i, fidx = filtered_indices[i])
@ -1525,10 +1438,10 @@ public:
// geometry and its very close to the default. // geometry and its very close to the default.
StopCriteria stc; StopCriteria stc;
stc.max_iterations = MAX_TRIES; stc.max_iterations = m_cfg.optimizer_max_iterations;
stc.relative_score_difference = 1e-3; stc.relative_score_difference = m_cfg.optimizer_rel_score_diff;
stc.stop_score = w; // space greater than w is enough stc.stop_score = w; // space greater than w is enough
SimplexOptimizer solver(stc); GeneticOptimizer solver(stc);
auto oresult = solver.optimize_max( auto oresult = solver.optimize_max(
[this, pin_r, w, hp](double plr, double azm) [this, pin_r, w, hp](double plr, double azm)
@ -1538,8 +1451,8 @@ public:
std::cos(plr)).normalized(); std::cos(plr)).normalized();
double score = pinhead_mesh_intersect( hp, n, pin_r, double score = pinhead_mesh_intersect( hp, n, pin_r,
m_cfg.head_back_radius_mm, m_cfg.head_back_radius_mm, w);
w);
return score; return score;
}, },
initvals(polar, azimuth), // start with what we have initvals(polar, azimuth), // start with what we have
@ -1639,7 +1552,7 @@ public:
auto predicate = [this](const SpatElement& e1, const SpatElement& e2) { auto predicate = [this](const SpatElement& e1, const SpatElement& e2) {
double d2d = distance(to_2d(e1.first), to_2d(e2.first)); double d2d = distance(to_2d(e1.first), to_2d(e2.first));
double d3d = distance(e1.first, 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; 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, 3);
@ -1819,14 +1732,14 @@ public:
using libnest2d::opt::bound; using libnest2d::opt::bound;
using libnest2d::opt::initvals; using libnest2d::opt::initvals;
using libnest2d::opt::SimplexOptimizer; using libnest2d::opt::GeneticOptimizer;
using libnest2d::opt::StopCriteria; using libnest2d::opt::StopCriteria;
StopCriteria stc; StopCriteria stc;
stc.max_iterations = 100; stc.max_iterations = m_cfg.optimizer_max_iterations;
stc.relative_score_difference = 1e-3; stc.relative_score_difference = m_cfg.optimizer_rel_score_diff;
stc.stop_score = 1e6; stc.stop_score = 1e6;
SimplexOptimizer solver(stc); GeneticOptimizer solver(stc);
double r_back = head.r_back_mm; double r_back = head.r_back_mm;
@ -1836,7 +1749,6 @@ public:
Vec3d n = Vec3d(std::cos(azm) * std::sin(plr), Vec3d n = Vec3d(std::cos(azm) * std::sin(plr),
std::sin(azm) * std::sin(plr), std::sin(azm) * std::sin(plr),
std::cos(plr)).normalized(); std::cos(plr)).normalized();
return bridge_mesh_intersect(hjp, n, r_back); return bridge_mesh_intersect(hjp, n, r_back);
}, },
initvals(polar, azimuth), // let's start with what we have initvals(polar, azimuth), // let's start with what we have
@ -1923,7 +1835,102 @@ public:
} }
void cascade_pillars() { 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<unsigned long> 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 // Step: process the support points where there is not enough space for a

View File

@ -79,14 +79,20 @@ struct SupportConfig {
double object_elevation_mm = 10; double object_elevation_mm = 10;
// The max Z angle for a normal at which it will get completely ignored. // 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 // 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; static const double max_solo_pillar_height_mm;
static const double max_dual_pillar_height_mm;
double max_dual_pillar_height_mm = 35.0; static const double optimizer_rel_score_diff;
static const unsigned optimizer_max_iterations;
static const unsigned pillar_cascade_neighbors;
}; };
struct PoolConfig; struct PoolConfig;