Replacing simplex optimizers with more intelligent genetic ones.
This commit is contained in:
parent
efd3d27425
commit
9131b1658a
@ -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
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user