Merge branch 'tm_branching_tree_fixes_SPE-1224'
This commit is contained in:
commit
b87561a7ff
@ -72,9 +72,9 @@ public:
|
||||
double m_t = infty();
|
||||
int m_face_id = -1;
|
||||
const AABBMesh *m_mesh = nullptr;
|
||||
Vec3d m_dir;
|
||||
Vec3d m_source;
|
||||
Vec3d m_normal;
|
||||
Vec3d m_dir = Vec3d::Zero();
|
||||
Vec3d m_source = Vec3d::Zero();
|
||||
Vec3d m_normal = Vec3d::Zero();
|
||||
friend class AABBMesh;
|
||||
|
||||
// A valid object of this class can only be obtained from
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include <optional>
|
||||
#include <algorithm>
|
||||
|
||||
#include "libslic3r/SLA/SupportTreeUtils.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
namespace Slic3r { namespace branchingtree {
|
||||
|
||||
@ -76,18 +76,20 @@ void build_tree(PointCloud &nodes, Builder &builder)
|
||||
switch (type) {
|
||||
case BED: {
|
||||
closest_node.weight = w;
|
||||
if (closest_it->dst_branching > nodes.properties().max_branch_length()) {
|
||||
auto hl_br_len = float(nodes.properties().max_branch_length()) / 2.f;
|
||||
Node new_node {{node.pos.x(), node.pos.y(), node.pos.z() - hl_br_len}, node.Rmin};
|
||||
new_node.id = int(nodes.next_junction_id());
|
||||
new_node.weight = nodes.get(node_id).weight + hl_br_len;
|
||||
new_node.left = node.id;
|
||||
double max_br_len = nodes.properties().max_branch_length();
|
||||
if (closest_it->dst_branching > max_br_len) {
|
||||
std::optional<Vec3f> avo = builder.suggest_avoidance(node, max_br_len);
|
||||
if (!avo)
|
||||
break;
|
||||
|
||||
Node new_node {*avo, node.Rmin};
|
||||
new_node.weight = nodes.get(node_id).weight + (node.pos - *avo).norm();
|
||||
new_node.left = node.id;
|
||||
if ((routed = builder.add_bridge(node, new_node))) {
|
||||
size_t new_idx = nodes.insert_junction(new_node);
|
||||
ptsqueue.push(new_idx);
|
||||
}
|
||||
}
|
||||
else if ((routed = builder.add_ground_bridge(node, closest_node))) {
|
||||
} else if ((routed = builder.add_ground_bridge(node, closest_node))) {
|
||||
closest_node.left = closest_node.right = node_id;
|
||||
nodes.get(closest_node_id) = closest_node;
|
||||
nodes.mark_unreachable(closest_node_id);
|
||||
|
@ -5,7 +5,6 @@
|
||||
#include <admesh/stl.h>
|
||||
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
|
||||
namespace Slic3r { namespace branchingtree {
|
||||
|
||||
@ -21,6 +20,7 @@ class Properties
|
||||
ExPolygons m_bed_shape;
|
||||
|
||||
public:
|
||||
|
||||
// Maximum slope for bridges of the tree
|
||||
Properties &max_slope(double val) noexcept
|
||||
{
|
||||
@ -77,6 +77,11 @@ struct Node
|
||||
{}
|
||||
};
|
||||
|
||||
inline bool is_occupied(const Node &n)
|
||||
{
|
||||
return n.left != Node::ID_NONE && n.right != Node::ID_NONE;
|
||||
}
|
||||
|
||||
// An output interface for the branching tree generator function. Consider each
|
||||
// method as a callback and implement the actions that need to be done.
|
||||
class Builder
|
||||
@ -100,6 +105,12 @@ public:
|
||||
// Add an anchor bridge to the model body
|
||||
virtual bool add_mesh_bridge(const Node &from, const Node &to) = 0;
|
||||
|
||||
virtual std::optional<Vec3f> suggest_avoidance(const Node &from,
|
||||
float max_bridge_len) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
// Report nodes that can not be routed to an endpoint (model or ground)
|
||||
virtual void report_unroutable(const Node &j) = 0;
|
||||
|
||||
|
@ -1,82 +1,15 @@
|
||||
#include "PointCloud.hpp"
|
||||
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Tesselate.hpp"
|
||||
#include "libslic3r/SLA/SupportTreeUtils.hpp"
|
||||
|
||||
#include <igl/random_points_on_mesh.h>
|
||||
|
||||
namespace Slic3r { namespace branchingtree {
|
||||
|
||||
std::optional<Vec3f> find_merge_pt(const Vec3f &A,
|
||||
const Vec3f &B,
|
||||
float critical_angle)
|
||||
std::optional<Vec3f> find_merge_pt(const Vec3f &A, const Vec3f &B, float max_slope)
|
||||
{
|
||||
// The idea is that A and B both have their support cones. But searching
|
||||
// for the intersection of these support cones is difficult and its enough
|
||||
// to reduce this problem to 2D and search for the intersection of two
|
||||
// rays that merge somewhere between A and B. The 2D plane is a vertical
|
||||
// slice of the 3D scene where the 2D Y axis is equal to the 3D Z axis and
|
||||
// the 2D X axis is determined by the XY direction of the AB vector.
|
||||
//
|
||||
// Z^
|
||||
// | A *
|
||||
// | . . B *
|
||||
// | . . . .
|
||||
// | . . . .
|
||||
// | . x .
|
||||
// -------------------> XY
|
||||
|
||||
// Determine the transformation matrix for the 2D projection:
|
||||
Vec3f diff = {B.x() - A.x(), B.y() - A.y(), 0.f};
|
||||
Vec3f dir = diff.normalized(); // TODO: avoid normalization
|
||||
|
||||
Eigen::Matrix<float, 2, 3> tr2D;
|
||||
tr2D.row(0) = Vec3f{dir.x(), dir.y(), dir.z()};
|
||||
tr2D.row(1) = Vec3f{0.f, 0.f, 1.f};
|
||||
|
||||
// Transform the 2 vectors A and B into 2D vector 'a' and 'b'. Here we can
|
||||
// omit 'a', pretend that its the origin and use BA as the vector b.
|
||||
Vec2f b = tr2D * (B - A);
|
||||
|
||||
// Get the square sine of the ray emanating from 'a' towards 'b'. This ray might
|
||||
// exceed the allowed angle but that is corrected subsequently.
|
||||
// The sign of the original sine is also needed, hence b.y is multiplied by
|
||||
// abs(b.y)
|
||||
float b_sqn = b.squaredNorm();
|
||||
float sin2sig_a = b_sqn > EPSILON ? (b.y() * std::abs(b.y())) / b_sqn : 0.f;
|
||||
|
||||
// sine2 from 'b' to 'a' is the opposite of sine2 from a to b
|
||||
float sin2sig_b = -sin2sig_a;
|
||||
|
||||
// Derive the allowed angles from the given critical angle.
|
||||
// critical_angle is measured from the horizontal X axis.
|
||||
// The rays need to go downwards which corresponds to negative angles
|
||||
|
||||
float sincrit = std::sin(critical_angle); // sine of the critical angle
|
||||
float sin2crit = -sincrit * sincrit; // signed sine squared
|
||||
sin2sig_a = std::min(sin2sig_a, sin2crit); // Do the angle saturation of both rays
|
||||
sin2sig_b = std::min(sin2sig_b, sin2crit); //
|
||||
float sin2_a = std::abs(sin2sig_a); // Get cosine squared values
|
||||
float sin2_b = std::abs(sin2sig_b);
|
||||
float cos2_a = 1.f - sin2_a;
|
||||
float cos2_b = 1.f - sin2_b;
|
||||
|
||||
// Derive the new direction vectors. This is by square rooting the sin2
|
||||
// and cos2 values and restoring the original signs
|
||||
Vec2f Da = {std::copysign(std::sqrt(cos2_a), b.x()), std::copysign(std::sqrt(sin2_a), sin2sig_a)};
|
||||
Vec2f Db = {-std::copysign(std::sqrt(cos2_b), b.x()), std::copysign(std::sqrt(sin2_b), sin2sig_b)};
|
||||
|
||||
// Determine where two rays ([0, 0], Da), (b, Db) intersect.
|
||||
// Based on
|
||||
// https://stackoverflow.com/questions/27459080/given-two-points-and-two-direction-vectors-find-the-point-where-they-intersect
|
||||
// One ray is emanating from (0, 0) so the formula is simplified
|
||||
double t1 = (Db.y() * b.x() - b.y() * Db.x()) /
|
||||
(Da.x() * Db.y() - Da.y() * Db.x());
|
||||
|
||||
Vec2f mp = t1 * Da;
|
||||
Vec3f Mp = A + tr2D.transpose() * mp;
|
||||
|
||||
return t1 >= 0.f ? Mp : Vec3f{};
|
||||
return sla::find_merge_pt(A, B, max_slope);
|
||||
}
|
||||
|
||||
void to_eigen_mesh(const indexed_triangle_set &its,
|
||||
@ -141,8 +74,6 @@ std::vector<Node> sample_mesh(const indexed_triangle_set &its, double radius)
|
||||
|
||||
std::vector<Node> sample_bed(const ExPolygons &bed, float z, double radius)
|
||||
{
|
||||
std::vector<Vec3f> ret;
|
||||
|
||||
auto triangles = triangulate_expolygons_3d(bed, z);
|
||||
indexed_triangle_set its;
|
||||
its.vertices.reserve(triangles.size());
|
||||
@ -198,7 +129,10 @@ PointCloud::PointCloud(std::vector<Node> meshpts,
|
||||
|
||||
for (size_t i = 0; i < m_leafs.size(); ++i) {
|
||||
Node &n = m_leafs[i];
|
||||
n.id = int(LEAFS_BEGIN + i);
|
||||
n.id = int(LEAFS_BEGIN + i);
|
||||
n.left = Node::ID_NONE;
|
||||
n.right = Node::ID_NONE;
|
||||
|
||||
m_ktree.insert({n.pos, n.id});
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
#include "BranchingTree.hpp"
|
||||
|
||||
#include "libslic3r/Execution/Execution.hpp"
|
||||
//#include "libslic3r/Execution/Execution.hpp"
|
||||
#include "libslic3r/MutablePriorityQueue.hpp"
|
||||
|
||||
#include "libslic3r/BoostAdapter.hpp"
|
||||
@ -78,14 +78,6 @@ private:
|
||||
rtree<PointIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>
|
||||
m_ktree;
|
||||
|
||||
bool is_outside_support_cone(const Vec3f &supp, const Vec3f &pt) const
|
||||
{
|
||||
Vec3d D = (pt - supp).cast<double>();
|
||||
double dot_sq = -D.z() * std::abs(-D.z());
|
||||
|
||||
return dot_sq < D.squaredNorm() * cos2bridge_slope;
|
||||
}
|
||||
|
||||
template<class PC>
|
||||
static auto *get_node(PC &&pc, size_t id)
|
||||
{
|
||||
@ -104,6 +96,14 @@ private:
|
||||
|
||||
public:
|
||||
|
||||
bool is_outside_support_cone(const Vec3f &supp, const Vec3f &pt) const
|
||||
{
|
||||
Vec3d D = (pt - supp).cast<double>();
|
||||
double dot_sq = -D.z() * std::abs(-D.z());
|
||||
|
||||
return dot_sq < D.squaredNorm() * cos2bridge_slope;
|
||||
}
|
||||
|
||||
static constexpr auto Unqueued = size_t(-1);
|
||||
|
||||
struct ZCompareFn
|
||||
@ -255,18 +255,42 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template<class Fn> constexpr bool IsTraverseFn = std::is_invocable_v<Fn, Node&>;
|
||||
|
||||
struct TraverseReturnT
|
||||
{
|
||||
bool to_left : 1; // if true, continue traversing to the left
|
||||
bool to_right : 1; // if true, continue traversing to the right
|
||||
};
|
||||
|
||||
template<class PC, class Fn> void traverse(PC &&pc, size_t root, Fn &&fn)
|
||||
{
|
||||
if (auto nodeptr = pc.find(root); nodeptr != nullptr) {
|
||||
auto &nroot = *nodeptr;
|
||||
fn(nroot);
|
||||
if (nroot.left >= 0) traverse(pc, nroot.left, fn);
|
||||
if (nroot.right >= 0) traverse(pc, nroot.right, fn);
|
||||
TraverseReturnT ret{true, true};
|
||||
|
||||
if constexpr (std::is_same_v<std::invoke_result_t<Fn, decltype(nroot)>, void>) {
|
||||
// Our fn has no return value
|
||||
fn(nroot);
|
||||
} else {
|
||||
// Fn returns instructions about how to continue traversing
|
||||
ret = fn(nroot);
|
||||
}
|
||||
|
||||
if (ret.to_left && nroot.left >= 0)
|
||||
traverse(pc, nroot.left, fn);
|
||||
if (ret.to_right && nroot.right >= 0)
|
||||
traverse(pc, nroot.right, fn);
|
||||
}
|
||||
}
|
||||
|
||||
void build_tree(PointCloud &pcloud, Builder &builder);
|
||||
|
||||
inline void build_tree(PointCloud &&pc, Builder &builder)
|
||||
{
|
||||
build_tree(pc, builder);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::branchingtree
|
||||
|
||||
#endif // POINTCLOUD_HPP
|
||||
|
@ -323,6 +323,7 @@ set(SLIC3R_SOURCES
|
||||
SLA/SupportTreeMesher.hpp
|
||||
SLA/SupportTreeMesher.cpp
|
||||
SLA/SupportTreeUtils.hpp
|
||||
SLA/SupportTreeUtilsLegacy.hpp
|
||||
SLA/SupportTreeBuilder.cpp
|
||||
SLA/SupportTree.hpp
|
||||
SLA/SupportTree.cpp
|
||||
|
@ -13,7 +13,9 @@ template<size_t N>
|
||||
long num_iter(const std::array<size_t, N> &idx, size_t gridsz)
|
||||
{
|
||||
long ret = 0;
|
||||
for (size_t i = 0; i < N; ++i) ret += idx[i] * std::pow(gridsz, i);
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
ret += idx[i] * std::pow(gridsz, i);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <libslic3r/Optimize/Optimizer.hpp>
|
||||
#include "Optimizer.hpp"
|
||||
|
||||
namespace Slic3r { namespace opt {
|
||||
|
||||
@ -40,69 +40,163 @@ struct IsNLoptAlg<NLoptAlgComb<a1, a2>> {
|
||||
static const constexpr bool value = true;
|
||||
};
|
||||
|
||||
// NLopt can wrap any of its algorithms to use the augmented lagrangian method
|
||||
// for deriving an object function from all equality and inequality constraints
|
||||
// This way one can use algorithms that do not support these constraints natively
|
||||
template<class Alg> struct NLoptAUGLAG {};
|
||||
|
||||
template<nlopt_algorithm a1, nlopt_algorithm a2>
|
||||
struct IsNLoptAlg<NLoptAUGLAG<NLoptAlgComb<a1, a2>>> {
|
||||
static const constexpr bool value = true;
|
||||
};
|
||||
|
||||
template<nlopt_algorithm a> struct IsNLoptAlg<NLoptAUGLAG<NLoptAlg<a>>> {
|
||||
static const constexpr bool value = true;
|
||||
};
|
||||
|
||||
template<class M, class T = void>
|
||||
using NLoptOnly = std::enable_if_t<IsNLoptAlg<M>::value, T>;
|
||||
|
||||
template<class M> struct GetNLoptAlg_ {
|
||||
static constexpr nlopt_algorithm Local = NLOPT_NUM_ALGORITHMS;
|
||||
static constexpr nlopt_algorithm Global = NLOPT_NUM_ALGORITHMS;
|
||||
static constexpr bool IsAUGLAG = false;
|
||||
};
|
||||
|
||||
template<nlopt_algorithm a> struct GetNLoptAlg_<NLoptAlg<a>> {
|
||||
static constexpr nlopt_algorithm Local = NLOPT_NUM_ALGORITHMS;
|
||||
static constexpr nlopt_algorithm Global = a;
|
||||
static constexpr bool IsAUGLAG = false;
|
||||
};
|
||||
|
||||
template<nlopt_algorithm g, nlopt_algorithm l>
|
||||
struct GetNLoptAlg_<NLoptAlgComb<g, l>> {
|
||||
static constexpr nlopt_algorithm Local = l;
|
||||
static constexpr nlopt_algorithm Global = g;
|
||||
static constexpr bool IsAUGLAG = false;
|
||||
};
|
||||
|
||||
template<class M> constexpr nlopt_algorithm GetNLoptAlg_Global = GetNLoptAlg_<remove_cvref_t<M>>::Global;
|
||||
template<class M> constexpr nlopt_algorithm GetNLoptAlg_Local = GetNLoptAlg_<remove_cvref_t<M>>::Local;
|
||||
template<class M> constexpr bool IsAUGLAG = GetNLoptAlg_<remove_cvref_t<M>>::IsAUGLAG;
|
||||
|
||||
template<class M> struct GetNLoptAlg_<NLoptAUGLAG<M>> {
|
||||
static constexpr nlopt_algorithm Local = GetNLoptAlg_Local<M>;
|
||||
static constexpr nlopt_algorithm Global = GetNLoptAlg_Global<M>;
|
||||
static constexpr bool IsAUGLAG = true;
|
||||
};
|
||||
|
||||
enum class OptDir { MIN, MAX }; // Where to optimize
|
||||
|
||||
struct NLopt { // Helper RAII class for nlopt_opt
|
||||
struct NLoptRAII { // Helper RAII class for nlopt_opt
|
||||
nlopt_opt ptr = nullptr;
|
||||
|
||||
template<class...A> explicit NLopt(A&&...a)
|
||||
template<class...A> explicit NLoptRAII(A&&...a)
|
||||
{
|
||||
ptr = nlopt_create(std::forward<A>(a)...);
|
||||
}
|
||||
|
||||
NLopt(const NLopt&) = delete;
|
||||
NLopt(NLopt&&) = delete;
|
||||
NLopt& operator=(const NLopt&) = delete;
|
||||
NLopt& operator=(NLopt&&) = delete;
|
||||
NLoptRAII(const NLoptRAII&) = delete;
|
||||
NLoptRAII(NLoptRAII&&) = delete;
|
||||
NLoptRAII& operator=(const NLoptRAII&) = delete;
|
||||
NLoptRAII& operator=(NLoptRAII&&) = delete;
|
||||
|
||||
~NLopt() { nlopt_destroy(ptr); }
|
||||
~NLoptRAII() { nlopt_destroy(ptr); }
|
||||
};
|
||||
|
||||
template<class Method> class NLoptOpt {};
|
||||
// Map a generic function to each argument following the mapping function
|
||||
template<class Fn, class...Args>
|
||||
Fn for_each_argument(Fn &&fn, Args&&...args)
|
||||
{
|
||||
// see https://www.fluentcpp.com/2019/03/05/for_each_arg-applying-a-function-to-each-argument-of-a-function-in-cpp/
|
||||
(fn(std::forward<Args>(args)),...);
|
||||
|
||||
// Optimizers based on NLopt.
|
||||
template<nlopt_algorithm alg> class NLoptOpt<NLoptAlg<alg>> {
|
||||
protected:
|
||||
return fn;
|
||||
}
|
||||
|
||||
// Call fn on each element of the input tuple tup.
|
||||
template<class Fn, class Tup>
|
||||
Fn for_each_in_tuple(Fn fn, Tup &&tup)
|
||||
{
|
||||
auto mpfn = [&fn](auto&...pack) {
|
||||
for_each_argument(fn, pack...);
|
||||
};
|
||||
|
||||
std::apply(mpfn, tup);
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
// Wrap each element of the tuple tup into a wrapper class W and return
|
||||
// a new tuple with each element being of type W<T_i> where T_i is the type of
|
||||
// i-th element of tup.
|
||||
template<template<class> class W, class...Args>
|
||||
auto wrap_tup(const std::tuple<Args...> &tup)
|
||||
{
|
||||
return std::tuple<W<Args>...>(tup);
|
||||
}
|
||||
|
||||
template<class M, class = NLoptOnly<M>>
|
||||
class NLoptOpt {
|
||||
StopCriteria m_stopcr;
|
||||
OptDir m_dir;
|
||||
StopCriteria m_loc_stopcr;
|
||||
OptDir m_dir = OptDir::MIN;
|
||||
|
||||
template<class Fn> using TOptData =
|
||||
std::tuple<std::remove_reference_t<Fn>*, NLoptOpt*, nlopt_opt>;
|
||||
static constexpr double ConstraintEps = 1e-6;
|
||||
|
||||
template<class Fn> struct OptData {
|
||||
Fn fn;
|
||||
NLoptOpt *self = nullptr;
|
||||
nlopt_opt opt_raw = nullptr;
|
||||
|
||||
OptData(const Fn &f): fn{f} {}
|
||||
|
||||
OptData(const Fn &f, NLoptOpt *s, nlopt_opt nlopt_raw)
|
||||
: fn{f}, self{s}, opt_raw{nlopt_raw} {}
|
||||
};
|
||||
|
||||
template<class Fn, size_t N>
|
||||
static double optfunc(unsigned n, const double *params,
|
||||
double *gradient,
|
||||
void *data)
|
||||
double *gradient, void *data)
|
||||
{
|
||||
assert(n >= N);
|
||||
assert(n == N);
|
||||
|
||||
auto tdata = static_cast<TOptData<Fn>*>(data);
|
||||
auto tdata = static_cast<OptData<Fn>*>(data);
|
||||
|
||||
if (std::get<1>(*tdata)->m_stopcr.stop_condition())
|
||||
nlopt_force_stop(std::get<2>(*tdata));
|
||||
if (tdata->self->m_stopcr.stop_condition())
|
||||
nlopt_force_stop(tdata->opt_raw);
|
||||
|
||||
auto fnptr = std::get<0>(*tdata);
|
||||
auto funval = to_arr<N>(params);
|
||||
|
||||
double scoreval = 0.;
|
||||
using RetT = decltype((*fnptr)(funval));
|
||||
using RetT = decltype(tdata->fn(funval));
|
||||
if constexpr (std::is_convertible_v<RetT, ScoreGradient<N>>) {
|
||||
ScoreGradient<N> score = (*fnptr)(funval);
|
||||
ScoreGradient<N> score = tdata->fn(funval);
|
||||
for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i];
|
||||
scoreval = score.score;
|
||||
} else {
|
||||
scoreval = (*fnptr)(funval);
|
||||
scoreval = tdata->fn(funval);
|
||||
}
|
||||
|
||||
return scoreval;
|
||||
}
|
||||
|
||||
template<class Fn, size_t N>
|
||||
static double constrain_func(unsigned n, const double *params,
|
||||
double *gradient, void *data)
|
||||
{
|
||||
assert(n == N);
|
||||
|
||||
auto tdata = static_cast<OptData<Fn>*>(data);
|
||||
auto funval = to_arr<N>(params);
|
||||
|
||||
return tdata->fn(funval);
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
void set_up(NLopt &nl, const Bounds<N>& bounds)
|
||||
static void set_up(NLoptRAII &nl,
|
||||
const Bounds<N> &bounds,
|
||||
const StopCriteria &stopcr)
|
||||
{
|
||||
std::array<double, N> lb, ub;
|
||||
|
||||
@ -114,23 +208,45 @@ protected:
|
||||
nlopt_set_lower_bounds(nl.ptr, lb.data());
|
||||
nlopt_set_upper_bounds(nl.ptr, ub.data());
|
||||
|
||||
double abs_diff = m_stopcr.abs_score_diff();
|
||||
double rel_diff = m_stopcr.rel_score_diff();
|
||||
double stopval = m_stopcr.stop_score();
|
||||
double abs_diff = stopcr.abs_score_diff();
|
||||
double rel_diff = stopcr.rel_score_diff();
|
||||
double stopval = stopcr.stop_score();
|
||||
if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff);
|
||||
if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff);
|
||||
if(!std::isnan(stopval)) nlopt_set_stopval(nl.ptr, stopval);
|
||||
|
||||
if(m_stopcr.max_iterations() > 0)
|
||||
nlopt_set_maxeval(nl.ptr, m_stopcr.max_iterations());
|
||||
if(stopcr.max_iterations() > 0)
|
||||
nlopt_set_maxeval(nl.ptr, stopcr.max_iterations());
|
||||
}
|
||||
|
||||
template<class Fn, size_t N>
|
||||
Result<N> optimize(NLopt &nl, Fn &&fn, const Input<N> &initvals)
|
||||
template<class Fn, size_t N, class...EqFns, class...IneqFns>
|
||||
Result<N> optimize(NLoptRAII &nl, Fn &&fn, const Input<N> &initvals,
|
||||
const std::tuple<EqFns...> &equalities,
|
||||
const std::tuple<IneqFns...> &inequalities)
|
||||
{
|
||||
Result<N> r;
|
||||
|
||||
TOptData<Fn> data = std::make_tuple(&fn, this, nl.ptr);
|
||||
OptData<Fn> data {fn, this, nl.ptr};
|
||||
|
||||
auto do_for_each_eq = [this, &nl](auto &arg) {
|
||||
arg.self = this;
|
||||
arg.opt_raw = nl.ptr;
|
||||
using F = decltype(arg.fn);
|
||||
nlopt_add_equality_constraint (nl.ptr, constrain_func<F, N>, &arg, ConstraintEps);
|
||||
};
|
||||
|
||||
auto do_for_each_ineq = [this, &nl](auto &arg) {
|
||||
arg.self = this;
|
||||
arg.opt_raw = nl.ptr;
|
||||
using F = decltype(arg.fn);
|
||||
nlopt_add_inequality_constraint (nl.ptr, constrain_func<F, N>, &arg, ConstraintEps);
|
||||
};
|
||||
|
||||
auto eq_data = wrap_tup<OptData>(equalities);
|
||||
for_each_in_tuple(do_for_each_eq, eq_data);
|
||||
|
||||
auto ineq_data = wrap_tup<OptData>(inequalities);
|
||||
for_each_in_tuple(do_for_each_ineq, ineq_data);
|
||||
|
||||
switch(m_dir) {
|
||||
case OptDir::MIN:
|
||||
@ -147,51 +263,81 @@ protected:
|
||||
|
||||
public:
|
||||
|
||||
template<class Func, size_t N>
|
||||
Result<N> optimize(Func&& func,
|
||||
template<class Fn, size_t N, class...EqFns, class...IneqFns>
|
||||
Result<N> optimize(Fn&& f,
|
||||
const Input<N> &initvals,
|
||||
const Bounds<N>& bounds)
|
||||
const Bounds<N>& bounds,
|
||||
const std::tuple<EqFns...> &equalities,
|
||||
const std::tuple<IneqFns...> &inequalities)
|
||||
{
|
||||
NLopt nl{alg, N};
|
||||
set_up(nl, bounds);
|
||||
if constexpr (IsAUGLAG<M>) {
|
||||
NLoptRAII nl_wrap{NLOPT_AUGLAG, N};
|
||||
set_up(nl_wrap, bounds, get_criteria());
|
||||
|
||||
return optimize(nl, std::forward<Func>(func), initvals);
|
||||
NLoptRAII nl_glob{GetNLoptAlg_Global<M>, N};
|
||||
set_up(nl_glob, bounds, get_criteria());
|
||||
nlopt_set_local_optimizer(nl_wrap.ptr, nl_glob.ptr);
|
||||
|
||||
if constexpr (GetNLoptAlg_Local<M> < NLOPT_NUM_ALGORITHMS) {
|
||||
NLoptRAII nl_loc{GetNLoptAlg_Local<M>, N};
|
||||
set_up(nl_loc, bounds, m_loc_stopcr);
|
||||
nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr);
|
||||
|
||||
return optimize(nl_wrap, std::forward<Fn>(f), initvals,
|
||||
equalities, inequalities);
|
||||
} else {
|
||||
return optimize(nl_wrap, std::forward<Fn>(f), initvals,
|
||||
equalities, inequalities);
|
||||
}
|
||||
} else {
|
||||
NLoptRAII nl_glob{GetNLoptAlg_Global<M>, N};
|
||||
set_up(nl_glob, bounds, get_criteria());
|
||||
|
||||
if constexpr (GetNLoptAlg_Local<M> < NLOPT_NUM_ALGORITHMS) {
|
||||
NLoptRAII nl_loc{GetNLoptAlg_Local<M>, N};
|
||||
set_up(nl_loc, bounds, m_loc_stopcr);
|
||||
nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr);
|
||||
|
||||
return optimize(nl_glob, std::forward<Fn>(f), initvals,
|
||||
equalities, inequalities);
|
||||
} else {
|
||||
return optimize(nl_glob, std::forward<Fn>(f), initvals,
|
||||
equalities, inequalities);
|
||||
}
|
||||
}
|
||||
|
||||
assert(false);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {}
|
||||
explicit NLoptOpt(const StopCriteria &stopcr_glob = {})
|
||||
: m_stopcr(stopcr_glob)
|
||||
{}
|
||||
|
||||
void set_criteria(const StopCriteria &cr) { m_stopcr = cr; }
|
||||
const StopCriteria &get_criteria() const noexcept { return m_stopcr; }
|
||||
void set_dir(OptDir dir) noexcept { m_dir = dir; }
|
||||
|
||||
void set_loc_criteria(const StopCriteria &cr) { m_loc_stopcr = cr; }
|
||||
const StopCriteria &get_loc_criteria() const noexcept { return m_loc_stopcr; }
|
||||
|
||||
void set_dir(OptDir dir) noexcept { m_dir = dir; }
|
||||
void seed(long s) { nlopt_srand(s); }
|
||||
};
|
||||
|
||||
template<nlopt_algorithm glob, nlopt_algorithm loc>
|
||||
class NLoptOpt<NLoptAlgComb<glob, loc>>: public NLoptOpt<NLoptAlg<glob>>
|
||||
{
|
||||
using Base = NLoptOpt<NLoptAlg<glob>>;
|
||||
public:
|
||||
|
||||
template<class Fn, size_t N>
|
||||
Result<N> optimize(Fn&& f,
|
||||
const Input<N> &initvals,
|
||||
const Bounds<N>& bounds)
|
||||
{
|
||||
NLopt nl_glob{glob, N}, nl_loc{loc, N};
|
||||
|
||||
Base::set_up(nl_glob, bounds);
|
||||
Base::set_up(nl_loc, bounds);
|
||||
nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr);
|
||||
|
||||
return Base::optimize(nl_glob, std::forward<Fn>(f), initvals);
|
||||
}
|
||||
|
||||
explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {}
|
||||
template<class Alg> struct AlgFeatures_ {
|
||||
static constexpr bool SupportsInequalities = false;
|
||||
static constexpr bool SupportsEqualities = false;
|
||||
};
|
||||
|
||||
} // namespace detail;
|
||||
|
||||
template<class Alg> constexpr bool SupportsEqualities =
|
||||
detail::AlgFeatures_<remove_cvref_t<Alg>>::SupportsEqualities;
|
||||
|
||||
template<class Alg> constexpr bool SupportsInequalities =
|
||||
detail::AlgFeatures_<remove_cvref_t<Alg>>::SupportsInequalities;
|
||||
|
||||
// Optimizers based on NLopt.
|
||||
template<class M> class Optimizer<M, detail::NLoptOnly<M>> {
|
||||
detail::NLoptOpt<M> m_opt;
|
||||
@ -201,12 +347,24 @@ public:
|
||||
Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; }
|
||||
Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; }
|
||||
|
||||
template<class Func, size_t N>
|
||||
template<class Func, size_t N, class...EqFns, class...IneqFns>
|
||||
Result<N> optimize(Func&& func,
|
||||
const Input<N> &initvals,
|
||||
const Bounds<N>& bounds)
|
||||
const Bounds<N>& bounds,
|
||||
const std::tuple<EqFns...> &eq_constraints = {},
|
||||
const std::tuple<IneqFns...> &ineq_constraint = {})
|
||||
{
|
||||
return m_opt.optimize(std::forward<Func>(func), initvals, bounds);
|
||||
static_assert(std::tuple_size_v<std::tuple<EqFns...>> == 0
|
||||
|| SupportsEqualities<M>,
|
||||
"Equality constraints are not supported.");
|
||||
|
||||
static_assert(std::tuple_size_v<std::tuple<IneqFns...>> == 0
|
||||
|| SupportsInequalities<M>,
|
||||
"Inequality constraints are not supported.");
|
||||
|
||||
return m_opt.optimize(std::forward<Func>(func), initvals, bounds,
|
||||
eq_constraints,
|
||||
ineq_constraint);
|
||||
}
|
||||
|
||||
explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {}
|
||||
@ -219,14 +377,56 @@ public:
|
||||
const StopCriteria &get_criteria() const { return m_opt.get_criteria(); }
|
||||
|
||||
void seed(long s) { m_opt.seed(s); }
|
||||
|
||||
void set_loc_criteria(const StopCriteria &cr) { m_opt.set_loc_criteria(cr); }
|
||||
const StopCriteria &get_loc_criteria() const noexcept { return m_opt.get_loc_criteria(); }
|
||||
};
|
||||
|
||||
// Predefinded NLopt algorithms
|
||||
using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
|
||||
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
|
||||
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
|
||||
using AlgNLoptDIRECT = detail::NLoptAlg<NLOPT_GN_DIRECT>;
|
||||
using AlgNLoptMLSL = detail::NLoptAlg<NLOPT_GN_MLSL>;
|
||||
using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
|
||||
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
|
||||
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
|
||||
using AlgNLoptCobyla = detail::NLoptAlg<NLOPT_LN_COBYLA>;
|
||||
using AlgNLoptDIRECT = detail::NLoptAlg<NLOPT_GN_DIRECT>;
|
||||
using AlgNLoptORIG_DIRECT = detail::NLoptAlg<NLOPT_GN_ORIG_DIRECT>;
|
||||
using AlgNLoptISRES = detail::NLoptAlg<NLOPT_GN_ISRES>;
|
||||
using AlgNLoptAGS = detail::NLoptAlg<NLOPT_GN_AGS>;
|
||||
|
||||
using AlgNLoptMLSL_Subplx = detail::NLoptAlgComb<NLOPT_GN_MLSL_LDS, NLOPT_LN_SBPLX>;
|
||||
using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb<NLOPT_GN_MLSL, NLOPT_LN_COBYLA>;
|
||||
using AlgNLoptGenetic_Subplx = detail::NLoptAlgComb<NLOPT_GN_ESCH, NLOPT_LN_SBPLX>;
|
||||
|
||||
// To craft auglag algorithms (constraint support through object function transformation)
|
||||
using detail::NLoptAUGLAG;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<> struct AlgFeatures_<AlgNLoptCobyla> {
|
||||
static constexpr bool SupportsInequalities = true;
|
||||
static constexpr bool SupportsEqualities = true;
|
||||
};
|
||||
|
||||
template<> struct AlgFeatures_<AlgNLoptISRES> {
|
||||
static constexpr bool SupportsInequalities = true;
|
||||
static constexpr bool SupportsEqualities = false;
|
||||
};
|
||||
|
||||
template<> struct AlgFeatures_<AlgNLoptORIG_DIRECT> {
|
||||
static constexpr bool SupportsInequalities = true;
|
||||
static constexpr bool SupportsEqualities = false;
|
||||
};
|
||||
|
||||
template<> struct AlgFeatures_<AlgNLoptAGS> {
|
||||
static constexpr bool SupportsInequalities = true;
|
||||
static constexpr bool SupportsEqualities = true;
|
||||
};
|
||||
|
||||
template<class M> struct AlgFeatures_<NLoptAUGLAG<M>> {
|
||||
static constexpr bool SupportsInequalities = true;
|
||||
static constexpr bool SupportsEqualities = true;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
}} // namespace Slic3r::opt
|
||||
|
||||
|
@ -12,6 +12,15 @@
|
||||
|
||||
namespace Slic3r { namespace opt {
|
||||
|
||||
template<class T, class O = T>
|
||||
using FloatingOnly = std::enable_if_t<std::is_floating_point<T>::value, O>;
|
||||
|
||||
template<class T, class = FloatingOnly<T>>
|
||||
constexpr T NaN = std::numeric_limits<T>::quiet_NaN();
|
||||
|
||||
constexpr float NaNf = NaN<float>;
|
||||
constexpr double NaNd = NaN<double>;
|
||||
|
||||
// A type to hold the complete result of the optimization.
|
||||
template<size_t N> struct Result {
|
||||
int resultcode; // Method dependent
|
||||
@ -79,7 +88,7 @@ public:
|
||||
|
||||
double stop_score() const { return m_stop_score; }
|
||||
|
||||
StopCriteria & max_iterations(double val)
|
||||
StopCriteria & max_iterations(unsigned val)
|
||||
{
|
||||
m_max_iterations = val; return *this;
|
||||
}
|
||||
@ -137,16 +146,25 @@ public:
|
||||
// For each dimension an interval (Bound) has to be given marking the bounds
|
||||
// for that dimension.
|
||||
//
|
||||
// Optionally, some constraints can be given in the form of double(Input<N>)
|
||||
// functors. The parameters eq_constraints and ineq_constraints can be used
|
||||
// to add equality and inequality (<= 0) constraints to the optimization.
|
||||
// Note that it is up the the particular method if it accepts these
|
||||
// constraints.
|
||||
//
|
||||
// initvals have to be within the specified bounds, otherwise its undefined
|
||||
// behavior.
|
||||
//
|
||||
// Func can return a score of type double or optionally a ScoreGradient
|
||||
// class to indicate the function gradient for a optimization methods that
|
||||
// make use of the gradient.
|
||||
template<class Func, size_t N>
|
||||
template<class Func, size_t N, class...EqConstraints, class...IneqConstraints>
|
||||
Result<N> optimize(Func&& /*func*/,
|
||||
const Input<N> &/*initvals*/,
|
||||
const Bounds<N>& /*bounds*/) { return {}; }
|
||||
const Bounds<N>& /*bounds*/,
|
||||
const std::tuple<EqConstraints...> &eq_constraints = {},
|
||||
const std::tuple<IneqConstraints...> &ineq_constraint = {}
|
||||
) { return {}; }
|
||||
|
||||
// optional for randomized methods:
|
||||
void seed(long /*s*/) {}
|
||||
|
@ -495,12 +495,14 @@ static std::vector<std::string> s_Preset_sla_print_options {
|
||||
"faded_layers",
|
||||
"supports_enable",
|
||||
"support_tree_type",
|
||||
|
||||
"support_head_front_diameter",
|
||||
"support_head_penetration",
|
||||
"support_head_width",
|
||||
"support_pillar_diameter",
|
||||
"support_small_pillar_diameter_percent",
|
||||
"support_max_bridges_on_pillar",
|
||||
"support_max_weight_on_model",
|
||||
"support_pillar_connection_mode",
|
||||
"support_buildplate_only",
|
||||
"support_pillar_widening_factor",
|
||||
@ -511,6 +513,25 @@ static std::vector<std::string> s_Preset_sla_print_options {
|
||||
"support_max_bridge_length",
|
||||
"support_max_pillar_link_distance",
|
||||
"support_object_elevation",
|
||||
|
||||
"branchingsupport_head_front_diameter",
|
||||
"branchingsupport_head_penetration",
|
||||
"branchingsupport_head_width",
|
||||
"branchingsupport_pillar_diameter",
|
||||
"branchingsupport_small_pillar_diameter_percent",
|
||||
"branchingsupport_max_bridges_on_pillar",
|
||||
"branchingsupport_max_weight_on_model",
|
||||
"branchingsupport_pillar_connection_mode",
|
||||
"branchingsupport_buildplate_only",
|
||||
"branchingsupport_pillar_widening_factor",
|
||||
"branchingsupport_base_diameter",
|
||||
"branchingsupport_base_height",
|
||||
"branchingsupport_base_safety_distance",
|
||||
"branchingsupport_critical_angle",
|
||||
"branchingsupport_max_bridge_length",
|
||||
"branchingsupport_max_pillar_link_distance",
|
||||
"branchingsupport_object_elevation",
|
||||
|
||||
"support_points_density_relative",
|
||||
"support_points_minimal_distance",
|
||||
"slice_closing_radius",
|
||||
|
@ -181,7 +181,8 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLAMaterialSpeed);
|
||||
|
||||
static inline const t_config_enum_values s_keys_map_SLASupportTreeType = {
|
||||
{"default", int(sla::SupportTreeType::Default)},
|
||||
{"branching", int(sla::SupportTreeType::Branching)}
|
||||
{"branching", int(sla::SupportTreeType::Branching)},
|
||||
//TODO: {"organic", int(sla::SupportTreeType::Organic)}
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLASupportTreeType);
|
||||
|
||||
@ -3272,6 +3273,219 @@ void PrintConfigDef::init_extruder_option_keys()
|
||||
assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end()));
|
||||
}
|
||||
|
||||
void PrintConfigDef::init_sla_support_params(const std::string &prefix)
|
||||
{
|
||||
ConfigOptionDef* def;
|
||||
|
||||
constexpr const char * pretext_unavailable = L("Unavailable for this method.\n");
|
||||
std::string pretext;
|
||||
|
||||
def = this->add(prefix + "support_head_front_diameter", coFloat);
|
||||
def->label = L("Pinhead front diameter");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("Diameter of the pointing side of the head");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0.4));
|
||||
|
||||
def = this->add(prefix + "support_head_penetration", coFloat);
|
||||
def->label = L("Head penetration");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("How much the pinhead has to penetrate the model surface");
|
||||
def->sidetext = L("mm");
|
||||
def->mode = comAdvanced;
|
||||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionFloat(0.2));
|
||||
|
||||
def = this->add(prefix + "support_head_width", coFloat);
|
||||
def->label = L("Pinhead width");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("Width from the back sphere center to the front sphere center");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 20;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(1.0));
|
||||
|
||||
def = this->add(prefix + "support_pillar_diameter", coFloat);
|
||||
def->label = L("Pillar diameter");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("Diameter in mm of the support pillars");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 15;
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionFloat(1.0));
|
||||
|
||||
def = this->add(prefix + "support_small_pillar_diameter_percent", coPercent);
|
||||
def->label = L("Small pillar diameter percent");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter "
|
||||
"which are used in problematic areas where a normal pilla cannot fit.");
|
||||
def->sidetext = L("%");
|
||||
def->min = 1;
|
||||
def->max = 100;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionPercent(50));
|
||||
|
||||
pretext = "";
|
||||
if (prefix == "branching")
|
||||
pretext = pretext_unavailable;
|
||||
|
||||
def = this->add(prefix + "support_max_bridges_on_pillar", coInt);
|
||||
def->label = L("Max bridges on a pillar");
|
||||
def->tooltip = pretext + L(
|
||||
"Maximum number of bridges that can be placed on a pillar. Bridges "
|
||||
"hold support point pinheads and connect to pillars as small branches.");
|
||||
def->min = 0;
|
||||
def->max = 50;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionInt(prefix == "branching" ? 2 : 3));
|
||||
|
||||
pretext = "";
|
||||
if (prefix.empty())
|
||||
pretext = pretext_unavailable;
|
||||
|
||||
def = this->add(prefix + "support_max_weight_on_model", coFloat);
|
||||
def->label = L("Max weight on model");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = pretext + L(
|
||||
"Maximum weight of sub-trees that terminate on the model instead of the print bed. The weight is the sum of the lenghts of all "
|
||||
"branches emanating from the endpoint.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(10.));
|
||||
|
||||
pretext = "";
|
||||
if (prefix == "branching")
|
||||
pretext = pretext_unavailable;
|
||||
|
||||
def = this->add(prefix + "support_pillar_connection_mode", coEnum);
|
||||
def->label = L("Pillar connection mode");
|
||||
def->tooltip = pretext + L("Controls the bridge type between two neighboring pillars."
|
||||
" Can be zig-zag, cross (double zig-zag) or dynamic which"
|
||||
" will automatically switch between the first two depending"
|
||||
" on the distance of the two pillars.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_values();
|
||||
def->enum_keys_map = &ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_values();
|
||||
def->enum_values = ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_names();
|
||||
def->enum_labels = ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_names();
|
||||
def->enum_labels[0] = L("Zig-Zag");
|
||||
def->enum_labels[1] = L("Cross");
|
||||
def->enum_labels[2] = L("Dynamic");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum(SLAPillarConnectionMode::dynamic));
|
||||
|
||||
def = this->add(prefix + "support_buildplate_only", coBool);
|
||||
def->label = L("Support on build plate only");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("Only create support if it lies on a build plate. Don't create support on a print.");
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add(prefix + "support_pillar_widening_factor", coFloat);
|
||||
def->label = L("Pillar widening factor");
|
||||
def->category = L("Supports");
|
||||
|
||||
pretext = "";
|
||||
if (prefix.empty())
|
||||
pretext = pretext_unavailable;
|
||||
|
||||
def->tooltip = pretext +
|
||||
L("Merging bridges or pillars into another pillars can "
|
||||
"increase the radius. Zero means no increase, one means "
|
||||
"full increase. The exact amount of increase is unspecified and can "
|
||||
"change in the future.");
|
||||
|
||||
def->min = 0;
|
||||
def->max = 1;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(0.5));
|
||||
|
||||
def = this->add(prefix + "support_base_diameter", coFloat);
|
||||
def->label = L("Support base diameter");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("Diameter in mm of the pillar base");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 30;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(4.0));
|
||||
|
||||
def = this->add(prefix + "support_base_height", coFloat);
|
||||
def->label = L("Support base height");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("The height of the pillar base cone");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(1.0));
|
||||
|
||||
def = this->add(prefix + "support_base_safety_distance", coFloat);
|
||||
def->label = L("Support base safety distance");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L(
|
||||
"The minimum distance of the pillar base from the model in mm. "
|
||||
"Makes sense in zero elevation mode where a gap according "
|
||||
"to this parameter is inserted between the model and the pad.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 10;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(1));
|
||||
|
||||
def = this->add(prefix + "support_critical_angle", coFloat);
|
||||
def->label = L("Critical angle");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("The default angle for connecting support sticks and junctions.");
|
||||
def->sidetext = L("°");
|
||||
def->min = 0;
|
||||
def->max = 90;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(45));
|
||||
|
||||
def = this->add(prefix + "support_max_bridge_length", coFloat);
|
||||
def->label = L("Max bridge length");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("The max length of a bridge");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
|
||||
double default_val = 15.0;
|
||||
if (prefix == "branching")
|
||||
default_val = 5.0;
|
||||
|
||||
def->set_default_value(new ConfigOptionFloat(default_val));
|
||||
|
||||
pretext = "";
|
||||
if (prefix == "branching")
|
||||
pretext = pretext_unavailable;
|
||||
|
||||
def = this->add(prefix + "support_max_pillar_link_distance", coFloat);
|
||||
def->label = L("Max pillar linking distance");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = pretext + 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->min = 0; // 0 means no linking
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(10.0));
|
||||
|
||||
def = this->add(prefix + "support_object_elevation", coFloat);
|
||||
def->label = L("Object elevation");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("How much the supports should lift up the supported object. "
|
||||
"If \"Pad around object\" is enabled, this value is ignored.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 150; // This is the max height of print on SL1
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(5.0));
|
||||
}
|
||||
|
||||
void PrintConfigDef::init_sla_params()
|
||||
{
|
||||
ConfigOptionDef* def;
|
||||
@ -3614,177 +3828,12 @@ void PrintConfigDef::init_sla_params()
|
||||
def->enum_labels = ConfigOptionEnum<sla::SupportTreeType>::get_enum_names();
|
||||
def->enum_labels[0] = L("Default");
|
||||
def->enum_labels[1] = L("Branching");
|
||||
def->mode = comAdvanced;
|
||||
// TODO: def->enum_labels[2] = L("Organic");
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionEnum(sla::SupportTreeType::Default));
|
||||
|
||||
def = this->add("support_head_front_diameter", coFloat);
|
||||
def->label = L("Pinhead front diameter");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("Diameter of the pointing side of the head");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0.4));
|
||||
|
||||
def = this->add("support_head_penetration", coFloat);
|
||||
def->label = L("Head penetration");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("How much the pinhead has to penetrate the model surface");
|
||||
def->sidetext = L("mm");
|
||||
def->mode = comAdvanced;
|
||||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionFloat(0.2));
|
||||
|
||||
def = this->add("support_head_width", coFloat);
|
||||
def->label = L("Pinhead width");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("Width from the back sphere center to the front sphere center");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 20;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(1.0));
|
||||
|
||||
def = this->add("support_pillar_diameter", coFloat);
|
||||
def->label = L("Pillar diameter");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("Diameter in mm of the support pillars");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 15;
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionFloat(1.0));
|
||||
|
||||
def = this->add("support_small_pillar_diameter_percent", coPercent);
|
||||
def->label = L("Small pillar diameter percent");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter "
|
||||
"which are used in problematic areas where a normal pilla cannot fit.");
|
||||
def->sidetext = L("%");
|
||||
def->min = 1;
|
||||
def->max = 100;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionPercent(50));
|
||||
|
||||
def = this->add("support_max_bridges_on_pillar", coInt);
|
||||
def->label = L("Max bridges on a pillar");
|
||||
def->tooltip = L(
|
||||
"Maximum number of bridges that can be placed on a pillar. Bridges "
|
||||
"hold support point pinheads and connect to pillars as small branches.");
|
||||
def->min = 0;
|
||||
def->max = 50;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionInt(3));
|
||||
|
||||
def = this->add("support_pillar_connection_mode", coEnum);
|
||||
def->label = L("Pillar connection mode");
|
||||
def->tooltip = L("Controls the bridge type between two neighboring pillars."
|
||||
" Can be zig-zag, cross (double zig-zag) or dynamic which"
|
||||
" will automatically switch between the first two depending"
|
||||
" on the distance of the two pillars.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_values();
|
||||
def->enum_keys_map = &ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_values();
|
||||
def->enum_values = ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_names();
|
||||
def->enum_labels = ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_names();
|
||||
def->enum_labels[0] = L("Zig-Zag");
|
||||
def->enum_labels[1] = L("Cross");
|
||||
def->enum_labels[2] = L("Dynamic");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum(SLAPillarConnectionMode::dynamic));
|
||||
|
||||
def = this->add("support_buildplate_only", coBool);
|
||||
def->label = L("Support on build plate only");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("Only create support if it lies on a build plate. Don't create support on a print.");
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("support_pillar_widening_factor", coFloat);
|
||||
def->label = L("Pillar widening factor");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L(
|
||||
"Merging bridges or pillars into another pillars can "
|
||||
"increase the radius. Zero means no increase, one means "
|
||||
"full increase. The exact amount of increase is unspecified and can "
|
||||
"change in the future. What is garanteed is that thickness will not "
|
||||
"exceed \"support_base_diameter\"");
|
||||
|
||||
def->min = 0;
|
||||
def->max = 1;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(0.15));
|
||||
|
||||
def = this->add("support_base_diameter", coFloat);
|
||||
def->label = L("Support base diameter");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("Diameter in mm of the pillar base");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 30;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(4.0));
|
||||
|
||||
def = this->add("support_base_height", coFloat);
|
||||
def->label = L("Support base height");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("The height of the pillar base cone");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(1.0));
|
||||
|
||||
def = this->add("support_base_safety_distance", coFloat);
|
||||
def->label = L("Support base safety distance");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L(
|
||||
"The minimum distance of the pillar base from the model in mm. "
|
||||
"Makes sense in zero elevation mode where a gap according "
|
||||
"to this parameter is inserted between the model and the pad.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 10;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(1));
|
||||
|
||||
def = this->add("support_critical_angle", coFloat);
|
||||
def->label = L("Critical angle");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("The default angle for connecting support sticks and junctions.");
|
||||
def->sidetext = L("°");
|
||||
def->min = 0;
|
||||
def->max = 90;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(45));
|
||||
|
||||
def = this->add("support_max_bridge_length", coFloat);
|
||||
def->label = L("Max bridge length");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("The max length of a bridge");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_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."
|
||||
" A zero value will prohibit pillar cascading.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0; // 0 means no linking
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(10.0));
|
||||
|
||||
def = this->add("support_object_elevation", coFloat);
|
||||
def->label = L("Object elevation");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("How much the supports should lift up the supported object. "
|
||||
"If \"Pad around object\" is enabled, this value is ignored.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 150; // This is the max height of print on SL1
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(5.0));
|
||||
init_sla_support_params("");
|
||||
init_sla_support_params("branching");
|
||||
|
||||
def = this->add("support_points_density_relative", coInt);
|
||||
def->label = L("Support points density");
|
||||
@ -4753,6 +4802,23 @@ Points get_bed_shape(const PrintConfig &cfg)
|
||||
|
||||
Points get_bed_shape(const SLAPrinterConfig &cfg) { return to_points(cfg.bed_shape.values); }
|
||||
|
||||
std::string get_sla_suptree_prefix(const DynamicPrintConfig &config)
|
||||
{
|
||||
const auto *suptreetype = config.option<ConfigOptionEnum<sla::SupportTreeType>>("support_tree_type");
|
||||
std::string slatree = "";
|
||||
if (suptreetype) {
|
||||
auto ttype = static_cast<sla::SupportTreeType>(suptreetype->getInt());
|
||||
switch (ttype) {
|
||||
case sla::SupportTreeType::Branching: slatree = "branching"; break;
|
||||
case sla::SupportTreeType::Organic: slatree = "organic"; break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
return slatree;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#include <cereal/types/polymorphic.hpp>
|
||||
|
@ -185,6 +185,7 @@ private:
|
||||
void init_fff_params();
|
||||
void init_extruder_option_keys();
|
||||
void init_sla_params();
|
||||
void init_sla_support_params(const std::string &method_prefix);
|
||||
|
||||
std::vector<std::string> m_extruder_option_keys;
|
||||
std::vector<std::string> m_extruder_retract_keys;
|
||||
@ -861,6 +862,8 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
// Generate only ground facing supports
|
||||
((ConfigOptionBool, support_buildplate_only))
|
||||
|
||||
((ConfigOptionFloat, support_max_weight_on_model))
|
||||
|
||||
// TODO: unimplemented at the moment. This coefficient will have an impact
|
||||
// when bridges and pillars are merged. The resulting pillar should be a bit
|
||||
// thicker than the ones merging into it. How much thicker? I don't know
|
||||
@ -889,6 +892,62 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
// and the model object's bounding box bottom. Units in mm.
|
||||
((ConfigOptionFloat, support_object_elevation))/*= 5.0*/
|
||||
|
||||
|
||||
// Branching tree
|
||||
|
||||
// Diameter in mm of the pointing side of the head.
|
||||
((ConfigOptionFloat, branchingsupport_head_front_diameter))/*= 0.2*/
|
||||
|
||||
// How much the pinhead has to penetrate the model surface
|
||||
((ConfigOptionFloat, branchingsupport_head_penetration))/*= 0.2*/
|
||||
|
||||
// Width in mm from the back sphere center to the front sphere center.
|
||||
((ConfigOptionFloat, branchingsupport_head_width))/*= 1.0*/
|
||||
|
||||
// Radius in mm of the support pillars.
|
||||
((ConfigOptionFloat, branchingsupport_pillar_diameter))/*= 0.8*/
|
||||
|
||||
// The percentage of smaller pillars compared to the normal pillar diameter
|
||||
// which are used in problematic areas where a normal pilla cannot fit.
|
||||
((ConfigOptionPercent, branchingsupport_small_pillar_diameter_percent))
|
||||
|
||||
// How much bridge (supporting another pinhead) can be placed on a pillar.
|
||||
((ConfigOptionInt, branchingsupport_max_bridges_on_pillar))
|
||||
|
||||
// How the pillars are bridged together
|
||||
((ConfigOptionEnum<SLAPillarConnectionMode>, branchingsupport_pillar_connection_mode))
|
||||
|
||||
// Generate only ground facing supports
|
||||
((ConfigOptionBool, branchingsupport_buildplate_only))
|
||||
|
||||
((ConfigOptionFloat, branchingsupport_max_weight_on_model))
|
||||
|
||||
((ConfigOptionFloat, branchingsupport_pillar_widening_factor))
|
||||
|
||||
// Radius in mm of the pillar base.
|
||||
((ConfigOptionFloat, branchingsupport_base_diameter))/*= 2.0*/
|
||||
|
||||
// The height of the pillar base cone in mm.
|
||||
((ConfigOptionFloat, branchingsupport_base_height))/*= 1.0*/
|
||||
|
||||
// The minimum distance of the pillar base from the model in mm.
|
||||
((ConfigOptionFloat, branchingsupport_base_safety_distance)) /*= 1.0*/
|
||||
|
||||
// The default angle for connecting support sticks and junctions.
|
||||
((ConfigOptionFloat, branchingsupport_critical_angle))/*= 45*/
|
||||
|
||||
// The max length of a bridge in mm
|
||||
((ConfigOptionFloat, branchingsupport_max_bridge_length))/*= 15.0*/
|
||||
|
||||
// The max distance of two pillars to get cross linked.
|
||||
((ConfigOptionFloat, branchingsupport_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, branchingsupport_object_elevation))/*= 5.0*/
|
||||
|
||||
|
||||
|
||||
/////// Following options influence automatic support points placement:
|
||||
((ConfigOptionInt, support_points_density_relative))
|
||||
((ConfigOptionFloat, support_points_minimal_distance))
|
||||
@ -1108,6 +1167,8 @@ Points get_bed_shape(const DynamicPrintConfig &cfg);
|
||||
Points get_bed_shape(const PrintConfig &cfg);
|
||||
Points get_bed_shape(const SLAPrinterConfig &cfg);
|
||||
|
||||
std::string get_sla_suptree_prefix(const DynamicPrintConfig &config);
|
||||
|
||||
// ModelConfig is a wrapper around DynamicPrintConfig with an addition of a timestamp.
|
||||
// Each change of ModelConfig is tracked by assigning a new timestamp from a global counter.
|
||||
// The counter is used for faster synchronization of the background slicing thread
|
||||
|
@ -13,20 +13,28 @@
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
inline constexpr const auto &beam_ex_policy = ex_tbb;
|
||||
|
||||
class BranchingTreeBuilder: public branchingtree::Builder {
|
||||
SupportTreeBuilder &m_builder;
|
||||
const SupportableMesh &m_sm;
|
||||
const branchingtree::PointCloud &m_cloud;
|
||||
|
||||
std::vector<branchingtree::Node> m_pillars; // to put an index over them
|
||||
|
||||
// cache succesfull ground connections
|
||||
mutable std::map<int, GroundConnection> m_gnd_connections;
|
||||
mutable execution::SpinningMutex<ExecutionTBB> m_gnd_connections_mtx;
|
||||
|
||||
// Scaling of the input value 'widening_factor:<0, 1>' to produce resonable
|
||||
// widening behaviour
|
||||
static constexpr double WIDENING_SCALE = 0.02;
|
||||
static constexpr double WIDENING_SCALE = 0.05;
|
||||
|
||||
double get_radius(const branchingtree::Node &j)
|
||||
double get_radius(const branchingtree::Node &j) const
|
||||
{
|
||||
double w = WIDENING_SCALE * m_sm.cfg.pillar_widening_factor * j.weight;
|
||||
|
||||
return std::min(m_sm.cfg.base_radius_mm, double(j.Rmin) + w);
|
||||
return double(j.Rmin) + w;
|
||||
}
|
||||
|
||||
std::vector<size_t> m_unroutable_pinheads;
|
||||
@ -78,6 +86,44 @@ class BranchingTreeBuilder: public branchingtree::Builder {
|
||||
});
|
||||
}
|
||||
|
||||
void discard_subtree_rescure(size_t root)
|
||||
{
|
||||
// Discard all the support points connecting to this branch.
|
||||
// As a last resort, try to route child nodes to ground and stop
|
||||
// traversing if any child branch succeeds.
|
||||
traverse(m_cloud, root, [this](const branchingtree::Node &node) {
|
||||
branchingtree::TraverseReturnT ret{true, true};
|
||||
|
||||
int suppid_parent = m_cloud.get_leaf_id(node.id);
|
||||
int suppid_left = branchingtree::Node::ID_NONE;
|
||||
int suppid_right = branchingtree::Node::ID_NONE;
|
||||
|
||||
double glvl = ground_level(m_sm);
|
||||
branchingtree::Node dst = node;
|
||||
dst.pos.z() = glvl;
|
||||
dst.weight += node.pos.z() - glvl;
|
||||
|
||||
if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), dst))
|
||||
ret.to_left = false;
|
||||
else
|
||||
suppid_left = m_cloud.get_leaf_id(node.left);
|
||||
|
||||
if (node.right >= 0 && add_ground_bridge(m_cloud.get(node.right), dst))
|
||||
ret.to_right = false;
|
||||
else
|
||||
suppid_right = m_cloud.get_leaf_id(node.right);
|
||||
|
||||
if (suppid_parent >= 0)
|
||||
m_unroutable_pinheads.emplace_back(suppid_parent);
|
||||
if (suppid_left >= 0)
|
||||
m_unroutable_pinheads.emplace_back(suppid_left);
|
||||
if (suppid_right >= 0)
|
||||
m_unroutable_pinheads.emplace_back(suppid_right);
|
||||
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
BranchingTreeBuilder(SupportTreeBuilder &builder,
|
||||
const SupportableMesh &sm,
|
||||
@ -98,13 +144,24 @@ public:
|
||||
bool add_mesh_bridge(const branchingtree::Node &from,
|
||||
const branchingtree::Node &to) override;
|
||||
|
||||
std::optional<Vec3f> suggest_avoidance(const branchingtree::Node &from,
|
||||
float max_bridge_len) const override;
|
||||
|
||||
void report_unroutable(const branchingtree::Node &j) override
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Cannot route junction at " << j.pos.x()
|
||||
<< " " << j.pos.y() << " " << j.pos.z();
|
||||
double glvl = ground_level(m_sm);
|
||||
branchingtree::Node dst = j;
|
||||
dst.pos.z() = glvl;
|
||||
dst.weight += j.pos.z() - glvl;
|
||||
if (add_ground_bridge(j, dst))
|
||||
return;
|
||||
|
||||
BOOST_LOG_TRIVIAL(warning) << "Cannot route junction at " << j.pos.x()
|
||||
<< " " << j.pos.y() << " " << j.pos.z();
|
||||
|
||||
// Discard all the support points connecting to this branch.
|
||||
discard_subtree(j.id);
|
||||
discard_subtree_rescure(j.id);
|
||||
// discard_subtree(j.id);
|
||||
}
|
||||
|
||||
const std::vector<size_t>& unroutable_pinheads() const
|
||||
@ -113,15 +170,28 @@ public:
|
||||
}
|
||||
|
||||
bool is_valid() const override { return !m_builder.ctl().stopcondition(); }
|
||||
|
||||
const std::vector<branchingtree::Node> & pillars() const { return m_pillars; }
|
||||
|
||||
const GroundConnection *ground_conn(size_t pillar) const
|
||||
{
|
||||
const GroundConnection *ret = nullptr;
|
||||
|
||||
auto it = m_gnd_connections.find(m_pillars[pillar].id);
|
||||
if (it != m_gnd_connections.end())
|
||||
ret = &it->second;
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from,
|
||||
const branchingtree::Node &to)
|
||||
const branchingtree::Node &to)
|
||||
{
|
||||
Vec3d fromd = from.pos.cast<double>(), tod = to.pos.cast<double>();
|
||||
double fromR = get_radius(from), toR = get_radius(to);
|
||||
Beam beam{Ball{fromd, fromR}, Ball{tod, toR}};
|
||||
auto hit = beam_mesh_hit(ex_tbb, m_sm.emesh, beam,
|
||||
auto hit = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam,
|
||||
m_sm.cfg.safety_distance_mm);
|
||||
|
||||
bool ret = hit.distance() > (tod - fromd).norm();
|
||||
@ -144,8 +214,8 @@ bool BranchingTreeBuilder::add_merger(const branchingtree::Node &node,
|
||||
Beam beam2{Ball{from2d, closestR}, Ball{tod, mergeR}};
|
||||
|
||||
auto sd = m_sm.cfg.safety_distance_mm ;
|
||||
auto hit1 = beam_mesh_hit(ex_tbb, m_sm.emesh, beam1, sd);
|
||||
auto hit2 = beam_mesh_hit(ex_tbb, m_sm.emesh, beam2, sd);
|
||||
auto hit1 = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam1, sd);
|
||||
auto hit2 = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam2, sd);
|
||||
|
||||
bool ret = hit1.distance() > (tod - from1d).norm() &&
|
||||
hit2.distance() > (tod - from2d).norm();
|
||||
@ -156,12 +226,31 @@ bool BranchingTreeBuilder::add_merger(const branchingtree::Node &node,
|
||||
bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from,
|
||||
const branchingtree::Node &to)
|
||||
{
|
||||
bool ret = search_ground_route(ex_tbb, m_builder, m_sm,
|
||||
sla::Junction{from.pos.cast<double>(),
|
||||
get_radius(from)},
|
||||
get_radius(to)).first;
|
||||
bool ret = false;
|
||||
|
||||
if (ret) {
|
||||
namespace bgi = boost::geometry::index;
|
||||
|
||||
auto it = m_gnd_connections.find(from.id);
|
||||
const GroundConnection *connptr = nullptr;
|
||||
|
||||
if (it == m_gnd_connections.end()) {
|
||||
sla::Junction j{from.pos.cast<double>(), get_radius(from)};
|
||||
Vec3d init_dir = (to.pos - from.pos).cast<double>().normalized();
|
||||
|
||||
auto conn = deepsearch_ground_connection(beam_ex_policy , m_sm, j,
|
||||
get_radius(to), init_dir);
|
||||
|
||||
// Remember that this node was tested if can go to ground, don't
|
||||
// test it with any other destination ground point because
|
||||
// it is unlikely that search_ground_route would find a better solution
|
||||
connptr = &(m_gnd_connections[from.id] = conn);
|
||||
} else {
|
||||
connptr = &(it->second);
|
||||
}
|
||||
|
||||
if (connptr && *connptr) {
|
||||
m_pillars.emplace_back(from);
|
||||
ret = true;
|
||||
build_subtree(from.id);
|
||||
}
|
||||
|
||||
@ -171,17 +260,20 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from,
|
||||
bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from,
|
||||
const branchingtree::Node &to)
|
||||
{
|
||||
if (from.weight > m_sm.cfg.max_weight_on_model_support)
|
||||
return false;
|
||||
|
||||
sla::Junction fromj = {from.pos.cast<double>(), get_radius(from)};
|
||||
|
||||
auto anchor = m_sm.cfg.ground_facing_only ?
|
||||
std::optional<Anchor>{} : // If no mesh connections are allowed
|
||||
calculate_anchor_placement(ex_tbb, m_sm, fromj,
|
||||
calculate_anchor_placement(beam_ex_policy , m_sm, fromj,
|
||||
to.pos.cast<double>());
|
||||
|
||||
if (anchor) {
|
||||
sla::Junction toj = {anchor->junction_point(), anchor->r_back_mm};
|
||||
|
||||
auto hit = beam_mesh_hit(ex_tbb, m_sm.emesh,
|
||||
auto hit = beam_mesh_hit(beam_ex_policy , m_sm.emesh,
|
||||
Beam{{fromj.pos, fromj.r}, {toj.pos, toj.r}}, 0.);
|
||||
|
||||
if (hit.distance() > distance(fromj.pos, toj.pos)) {
|
||||
@ -197,6 +289,70 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from,
|
||||
return bool(anchor);
|
||||
}
|
||||
|
||||
static std::optional<Vec3f> get_avoidance(const GroundConnection &conn,
|
||||
float maxdist)
|
||||
{
|
||||
std::optional<Vec3f> ret;
|
||||
|
||||
if (conn) {
|
||||
if (conn.path.size() > 1) {
|
||||
ret = conn.path[1].pos.cast<float>();
|
||||
} else {
|
||||
Vec3f pbeg = conn.path[0].pos.cast<float>();
|
||||
Vec3f pend = conn.pillar_base->pos.cast<float>();
|
||||
pbeg.z() = std::max(pbeg.z() - maxdist, pend.z());
|
||||
ret = pbeg;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::optional<Vec3f> BranchingTreeBuilder::suggest_avoidance(
|
||||
const branchingtree::Node &from, float max_bridge_len) const
|
||||
{
|
||||
std::optional<Vec3f> ret;
|
||||
|
||||
double glvl = ground_level(m_sm);
|
||||
branchingtree::Node dst = from;
|
||||
dst.pos.z() = glvl;
|
||||
dst.weight += from.pos.z() - glvl;
|
||||
sla::Junction j{from.pos.cast<double>(), get_radius(from)};
|
||||
|
||||
auto found_it = m_gnd_connections.end();
|
||||
{
|
||||
std::lock_guard lk{m_gnd_connections_mtx};
|
||||
found_it = m_gnd_connections.find(from.id);
|
||||
}
|
||||
|
||||
if (found_it != m_gnd_connections.end()) {
|
||||
ret = get_avoidance(found_it->second, max_bridge_len);
|
||||
} else {
|
||||
auto conn = deepsearch_ground_connection(
|
||||
beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN);
|
||||
|
||||
{
|
||||
std::lock_guard lk{m_gnd_connections_mtx};
|
||||
m_gnd_connections[from.id] = conn;
|
||||
}
|
||||
|
||||
ret = get_avoidance(conn, max_bridge_len);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline void build_pillars(SupportTreeBuilder &builder,
|
||||
BranchingTreeBuilder &vbuilder,
|
||||
const SupportableMesh &sm)
|
||||
{
|
||||
for (size_t pill_id = 0; pill_id < vbuilder.pillars().size(); ++pill_id) {
|
||||
auto * conn = vbuilder.ground_conn(pill_id);
|
||||
if (conn)
|
||||
build_ground_connection(builder, sm, *conn);
|
||||
}
|
||||
}
|
||||
|
||||
void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &sm)
|
||||
{
|
||||
auto coordfn = [&sm](size_t id, size_t dim) { return sm.pts[id].pos(dim); };
|
||||
@ -221,7 +377,7 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s
|
||||
for (auto &h : heads)
|
||||
if (h && h->is_valid()) {
|
||||
leafs.emplace_back(h->junction_point().cast<float>(), h->r_back_mm);
|
||||
h->id = leafs.size() - 1;
|
||||
h->id = long(leafs.size() - 1);
|
||||
builder.add_head(h->id, *h);
|
||||
}
|
||||
|
||||
@ -240,15 +396,29 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s
|
||||
props.sampling_radius());
|
||||
|
||||
auto bedpts = branchingtree::sample_bed(props.bed_shape(),
|
||||
props.ground_level(),
|
||||
float(props.ground_level()),
|
||||
props.sampling_radius());
|
||||
|
||||
for (auto &bp : bedpts)
|
||||
bp.Rmin = sm.cfg.head_back_radius_mm;
|
||||
|
||||
branchingtree::PointCloud nodes{std::move(meshpts), std::move(bedpts),
|
||||
std::move(leafs), props};
|
||||
|
||||
BranchingTreeBuilder vbuilder{builder, sm, nodes};
|
||||
|
||||
execution::for_each(ex_tbb,
|
||||
size_t(0),
|
||||
nodes.get_leafs().size(),
|
||||
[&nodes, &vbuilder](size_t leaf_idx) {
|
||||
vbuilder.suggest_avoidance(nodes.get_leafs()[leaf_idx],
|
||||
nodes.properties().max_branch_length());
|
||||
});
|
||||
|
||||
branchingtree::build_tree(nodes, vbuilder);
|
||||
|
||||
build_pillars(builder, vbuilder, sm);
|
||||
|
||||
for (size_t id : vbuilder.unroutable_pinheads())
|
||||
builder.head(id).invalidate();
|
||||
|
||||
|
@ -340,14 +340,17 @@ bool DefaultSupportTree::connect_to_nearpillar(const Head &head,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DefaultSupportTree::create_ground_pillar(const Vec3d &hjp,
|
||||
bool DefaultSupportTree::create_ground_pillar(const Junction &hjp,
|
||||
const Vec3d &sourcedir,
|
||||
double radius,
|
||||
long head_id)
|
||||
{
|
||||
auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy,
|
||||
m_builder, m_sm, hjp,
|
||||
sourcedir, radius, radius,
|
||||
m_builder,
|
||||
m_sm,
|
||||
hjp.pos,
|
||||
sourcedir,
|
||||
hjp.r,
|
||||
hjp.r,
|
||||
head_id);
|
||||
|
||||
if (pillar_id >= 0) // Save the pillar endpoint in the spatial index
|
||||
@ -362,20 +365,19 @@ void DefaultSupportTree::add_pinheads()
|
||||
// The minimum distance for two support points to remain valid.
|
||||
const double /*constexpr*/ D_SP = 0.1;
|
||||
|
||||
// Get the points that are too close to each other and keep only the
|
||||
// first one
|
||||
// Get the points that are too close to each other and keep only the
|
||||
// first one
|
||||
auto aliases = cluster(m_points, D_SP, 2);
|
||||
|
||||
PtIndices filtered_indices;
|
||||
filtered_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
|
||||
// calculate the normals to the triangles for filtered points
|
||||
auto nmls = normals(suptree_ex_policy, m_points, m_sm.emesh,
|
||||
m_sm.cfg.head_front_radius_mm, m_thr,
|
||||
filtered_indices);
|
||||
@ -404,22 +406,22 @@ void DefaultSupportTree::add_pinheads()
|
||||
|
||||
Vec3d n = nmls.row(Eigen::Index(i));
|
||||
|
||||
// for all normals we generate the spherical coordinates and
|
||||
// saturate the polar angle to 45 degrees from the bottom then
|
||||
// convert back to standard coordinates to get the new normal.
|
||||
// Then we just create a quaternion from the two normals
|
||||
// (Quaternion::FromTwoVectors) and apply the rotation to the
|
||||
// arrow head.
|
||||
// for all normals we generate the spherical coordinates and
|
||||
// saturate the polar angle to 45 degrees from the bottom then
|
||||
// convert back to standard coordinates to get the new normal.
|
||||
// Then we just create a quaternion from the two normals
|
||||
// (Quaternion::FromTwoVectors) and apply the rotation to the
|
||||
// arrow head.
|
||||
|
||||
auto [polar, azimuth] = dir_to_spheric(n);
|
||||
|
||||
// skip if the tilt is not sane
|
||||
// skip if the tilt is not sane
|
||||
if (polar < PI - m_sm.cfg.normal_cutoff_angle) return;
|
||||
|
||||
// We saturate the polar angle to 3pi/4
|
||||
// We saturate the polar angle to 3pi/4
|
||||
polar = std::max(polar, PI - m_sm.cfg.bridge_slope);
|
||||
|
||||
// save the head (pinpoint) position
|
||||
// save the head (pinpoint) position
|
||||
Vec3d hp = m_points.row(fidx);
|
||||
|
||||
double lmin = m_sm.cfg.head_width_mm, lmax = lmin;
|
||||
@ -428,18 +430,17 @@ void DefaultSupportTree::add_pinheads()
|
||||
lmin = 0., lmax = m_sm.cfg.head_penetration_mm;
|
||||
}
|
||||
|
||||
// The distance needed for a pinhead to not collide with model.
|
||||
// The distance needed for a pinhead to not collide with model.
|
||||
double w = lmin + 2 * back_r + 2 * m_sm.cfg.head_front_radius_mm -
|
||||
m_sm.cfg.head_penetration_mm;
|
||||
|
||||
double pin_r = double(m_sm.pts[fidx].head_front_radius);
|
||||
|
||||
// Reassemble the now corrected normal
|
||||
// Reassemble the now corrected normal
|
||||
auto nn = spheric_to_dir(polar, azimuth).normalized();
|
||||
|
||||
// check available distance
|
||||
AABBMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r,
|
||||
back_r, w);
|
||||
// check available distance
|
||||
AABBMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r, back_r, w);
|
||||
|
||||
if (t.distance() < w) {
|
||||
// Let's try to optimize this angle, there might be a
|
||||
@ -509,10 +510,10 @@ void DefaultSupportTree::classify()
|
||||
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)
|
||||
// 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 : m_iheads) {
|
||||
m_thr();
|
||||
|
||||
@ -530,10 +531,10 @@ void DefaultSupportTree::classify()
|
||||
m_head_to_ground_scans[i] = hit;
|
||||
}
|
||||
|
||||
// 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 pointfn = [this](unsigned i) {
|
||||
return m_builder.head(i).junction_point();
|
||||
@ -559,13 +560,13 @@ void DefaultSupportTree::routing_to_ground()
|
||||
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
|
||||
// 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.
|
||||
// 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;
|
||||
|
||||
@ -587,7 +588,7 @@ void DefaultSupportTree::routing_to_ground()
|
||||
|
||||
Head &h = m_builder.head(hid);
|
||||
|
||||
if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) {
|
||||
if (!create_ground_pillar(h.junction(), h.dir, h.id)) {
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< "Pillar cannot be created for support point id: " << hid;
|
||||
m_iheads_onmodel.emplace_back(h.id);
|
||||
@ -595,11 +596,11 @@ void DefaultSupportTree::routing_to_ground()
|
||||
}
|
||||
}
|
||||
|
||||
// now we will go through the clusters ones again and connect the
|
||||
// sidepoints with the cluster centroid (which is a ground pillar)
|
||||
// or a nearby pillar if the centroid is unreachable.
|
||||
// now we will go through the clusters ones again and connect the
|
||||
// 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 : m_pillar_clusters) {
|
||||
for (const std::vector<unsigned> &cl : m_pillar_clusters) {
|
||||
m_thr();
|
||||
|
||||
auto cidx = cl_centroids[ci++];
|
||||
@ -615,10 +616,9 @@ void DefaultSupportTree::routing_to_ground()
|
||||
|
||||
if (!connect_to_nearpillar(sidehead, centerpillarID) &&
|
||||
!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
|
||||
create_ground_pillar(pstart, sidehead.dir, sidehead.r_back_mm, sidehead.id);
|
||||
create_ground_pillar(sidehead.junction(), sidehead.dir, sidehead.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -664,10 +664,10 @@ bool DefaultSupportTree::connect_to_model_body(Head &head)
|
||||
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...
|
||||
// The width of the tail head that we would like to have...
|
||||
h = std::min(hit.distance() - head.r_back_mm, h);
|
||||
|
||||
// If this is a mini pillar dont bother with the tail width, can be 0.
|
||||
// If this is a mini pillar dont bother with the tail width, can be 0.
|
||||
if (head.r_back_mm < m_sm.cfg.head_back_radius_mm) h = std::max(h, 0.);
|
||||
else if (h <= 0.) return false;
|
||||
|
||||
@ -773,23 +773,23 @@ void DefaultSupportTree::interconnect_pillars()
|
||||
// Ideally every pillar should be connected with at least one of its
|
||||
// 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.
|
||||
// Pillars with height exceeding H1 will require at least one neighbor
|
||||
// to connect with. Height exceeding H2 require two neighbors.
|
||||
double H1 = m_sm.cfg.max_solo_pillar_height_mm;
|
||||
double H2 = m_sm.cfg.max_dual_pillar_height_mm;
|
||||
double d = m_sm.cfg.max_pillar_link_distance_mm;
|
||||
|
||||
//A connection between two pillars only counts if the height ratio is
|
||||
// bigger than 50%
|
||||
// A connection between two pillars only counts if the height ratio is
|
||||
// bigger than 50%
|
||||
double min_height_ratio = 0.5;
|
||||
|
||||
std::set<unsigned long> pairs;
|
||||
|
||||
// A function to connect one pillar with its neighbors. THe number of
|
||||
// neighbors is given in the configuration. This function if called
|
||||
// for every pillar in the pillar index. A pair of pillar will not
|
||||
// be connected multiple times this is ensured by the 'pairs' set which
|
||||
// remembers the processed pillar pairs
|
||||
// A function to connect one pillar with its neighbors. THe number of
|
||||
// neighbors is given in the configuration. This function if called
|
||||
// for every pillar in the pillar index. A pair of pillar will not
|
||||
// be connected multiple times this is ensured by the 'pairs' set which
|
||||
// remembers the processed pillar pairs
|
||||
auto cascadefn =
|
||||
[this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el)
|
||||
{
|
||||
@ -797,10 +797,10 @@ void DefaultSupportTree::interconnect_pillars()
|
||||
|
||||
const Pillar& pillar = m_builder.pillar(el.second); // actual pillar
|
||||
|
||||
// Get the max number of neighbors a pillar should connect to
|
||||
// Get the max number of neighbors a pillar should connect to
|
||||
unsigned neighbors = m_sm.cfg.pillar_cascade_neighbors;
|
||||
|
||||
// connections are already enough for the pillar
|
||||
// connections are already enough for the pillar
|
||||
if(pillar.links >= neighbors) return;
|
||||
|
||||
double max_d = d * pillar.r_start / m_sm.cfg.head_back_radius_mm;
|
||||
@ -809,7 +809,7 @@ void DefaultSupportTree::interconnect_pillars()
|
||||
return distance(e.first, qp) < max_d;
|
||||
});
|
||||
|
||||
// sort the result by distance (have to check if this is needed)
|
||||
// sort the result by distance (have to check if this is needed)
|
||||
std::sort(qres.begin(), qres.end(),
|
||||
[qp](const PointIndexEl& e1, const PointIndexEl& e2){
|
||||
return distance(e1.first, qp) < distance(e2.first, qp);
|
||||
@ -821,23 +821,23 @@ void DefaultSupportTree::interconnect_pillars()
|
||||
|
||||
auto a = el.second, b = re.second;
|
||||
|
||||
// Get unique hash for the given pair (order doesn't matter)
|
||||
// Get unique hash for the given pair (order doesn't matter)
|
||||
auto hashval = pairhash(a, b);
|
||||
|
||||
// Search for the pair amongst the remembered pairs
|
||||
// Search for the pair amongst the remembered pairs
|
||||
if(pairs.find(hashval) != pairs.end()) continue;
|
||||
|
||||
const Pillar& neighborpillar = m_builder.pillar(re.second);
|
||||
|
||||
// this neighbor is occupied, skip
|
||||
// this neighbor is occupied, skip
|
||||
if (neighborpillar.links >= neighbors) continue;
|
||||
if (neighborpillar.r_start < pillar.r_start) continue;
|
||||
|
||||
if(interconnect(pillar, neighborpillar)) {
|
||||
pairs.insert(hashval);
|
||||
|
||||
// If the interconnection length between the two pillars is
|
||||
// less than 50% of the longer pillar's height, don't count
|
||||
// If the interconnection length between the two pillars is
|
||||
// less than 50% of the longer pillar's height, don't count
|
||||
if(pillar.height < H1 ||
|
||||
neighborpillar.height / pillar.height > min_height_ratio)
|
||||
m_builder.increment_links(pillar);
|
||||
@ -848,30 +848,30 @@ void DefaultSupportTree::interconnect_pillars()
|
||||
|
||||
}
|
||||
|
||||
// connections are enough for one pillar
|
||||
// connections are enough for one pillar
|
||||
if(pillar.links >= neighbors) break;
|
||||
}
|
||||
};
|
||||
|
||||
// Run the cascade for the pillars in the index
|
||||
// Run the cascade for the pillars in the index
|
||||
m_pillar_index.foreach(cascadefn);
|
||||
|
||||
// We would be done here if we could allow some pillars to not be
|
||||
// connected with any neighbors. But this might leave the support tree
|
||||
// unprintable.
|
||||
//
|
||||
// The current solution is to insert additional pillars next to these
|
||||
// lonely pillars. One or even two additional pillar might get inserted
|
||||
// depending on the length of the lonely pillar.
|
||||
// We would be done here if we could allow some pillars to not be
|
||||
// connected with any neighbors. But this might leave the support tree
|
||||
// unprintable.
|
||||
//
|
||||
// The current solution is to insert additional pillars next to these
|
||||
// lonely pillars. One or even two additional pillar might get inserted
|
||||
// depending on the length of the lonely pillar.
|
||||
|
||||
size_t pillarcount = m_builder.pillarcount();
|
||||
|
||||
// Again, go through all pillars, this time in the whole support tree
|
||||
// not just the index.
|
||||
// Again, go through all pillars, this time in the whole support tree
|
||||
// not just the index.
|
||||
for(size_t pid = 0; pid < pillarcount; pid++) {
|
||||
auto pillar = [this, pid]() { return m_builder.pillar(pid); };
|
||||
|
||||
// Decide how many additional pillars will be needed:
|
||||
// Decide how many additional pillars will be needed:
|
||||
|
||||
unsigned needpillars = 0;
|
||||
if (pillar().bridges > m_sm.cfg.max_bridges_on_pillar)
|
||||
@ -887,17 +887,17 @@ void DefaultSupportTree::interconnect_pillars()
|
||||
needpillars = std::max(pillar().links, needpillars) - pillar().links;
|
||||
if (needpillars == 0) continue;
|
||||
|
||||
// Search for new pillar locations:
|
||||
// Search for new pillar locations:
|
||||
|
||||
bool found = false;
|
||||
double alpha = 0; // goes to 2Pi
|
||||
double r = 2 * m_sm.cfg.base_radius_mm;
|
||||
Vec3d pillarsp = pillar().startpoint();
|
||||
|
||||
// temp value for starting point detection
|
||||
// temp value for starting point detection
|
||||
Vec3d sp(pillarsp.x(), pillarsp.y(), pillarsp.z() - r);
|
||||
|
||||
// A vector of bool for placement feasbility
|
||||
// A vector of bool for placement feasbility
|
||||
std::vector<bool> canplace(needpillars, false);
|
||||
std::vector<Vec3d> spts(needpillars); // vector of starting points
|
||||
|
||||
@ -916,12 +916,12 @@ void DefaultSupportTree::interconnect_pillars()
|
||||
s.y() += std::sin(a) * r;
|
||||
spts[n] = s;
|
||||
|
||||
// Check the path vertically down
|
||||
// Check the path vertically down
|
||||
Vec3d check_from = s + Vec3d{0., 0., pillar().r_start};
|
||||
auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r_start);
|
||||
Vec3d gndsp{s.x(), s.y(), gnd};
|
||||
|
||||
// If the path is clear, check for pillar base collisions
|
||||
// If the path is clear, check for pillar base collisions
|
||||
canplace[n] = std::isinf(hr.distance()) &&
|
||||
std::sqrt(m_sm.emesh.squared_distance(gndsp)) >
|
||||
min_dist;
|
||||
@ -930,7 +930,7 @@ void DefaultSupportTree::interconnect_pillars()
|
||||
found = std::all_of(canplace.begin(), canplace.end(),
|
||||
[](bool v) { return v; });
|
||||
|
||||
// 20 angles will be tried...
|
||||
// 20 angles will be tried...
|
||||
alpha += 0.1 * PI;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#ifndef LEGACYSUPPORTTREE_HPP
|
||||
#define LEGACYSUPPORTTREE_HPP
|
||||
|
||||
#include "SupportTreeUtils.hpp"
|
||||
#include "SupportTreeUtilsLegacy.hpp"
|
||||
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/Execution/ExecutionTBB.hpp>
|
||||
@ -62,7 +62,6 @@ class DefaultSupportTree {
|
||||
|
||||
PtIndices m_iheads; // support points with pinhead
|
||||
PtIndices m_iheads_onmodel;
|
||||
PtIndices m_iheadless; // headless support points
|
||||
|
||||
std::map<unsigned, AABBMesh::hit_result> m_head_to_ground_scans;
|
||||
|
||||
@ -176,9 +175,8 @@ class DefaultSupportTree {
|
||||
// jp is the starting junction point which needs to be routed down.
|
||||
// sourcedir is the allowed direction of an optional bridge between the
|
||||
// jp junction and the final pillar.
|
||||
bool create_ground_pillar(const Vec3d &jp,
|
||||
bool create_ground_pillar(const Junction &jp,
|
||||
const Vec3d &sourcedir,
|
||||
double radius,
|
||||
long head_id = SupportTreeNode::ID_UNSET);
|
||||
|
||||
void add_pillar_base(long pid)
|
||||
|
@ -18,6 +18,8 @@
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <libslic3r/I18N.hpp>
|
||||
|
||||
#include <libnest2d/tools/benchmark.h>
|
||||
|
||||
//! macro used to mark string used at localization,
|
||||
//! return same string
|
||||
#define L(s) Slic3r::I18N::translate(s)
|
||||
@ -30,6 +32,9 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm,
|
||||
auto builder = make_unique<SupportTreeBuilder>(ctl);
|
||||
|
||||
if (sm.cfg.enabled) {
|
||||
Benchmark bench;
|
||||
bench.start();
|
||||
|
||||
switch (sm.cfg.tree_type) {
|
||||
case SupportTreeType::Default: {
|
||||
create_default_tree(*builder, sm);
|
||||
@ -39,9 +44,18 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm,
|
||||
create_branching_tree(*builder, sm);
|
||||
break;
|
||||
}
|
||||
case SupportTreeType::Organic: {
|
||||
// TODO
|
||||
}
|
||||
default:;
|
||||
}
|
||||
|
||||
bench.stop();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Support tree creation took: "
|
||||
<< bench.getElapsedSec()
|
||||
<< " seconds";
|
||||
|
||||
builder->merge_and_cleanup(); // clean metadata, leave only the meshes.
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ struct SupportTreeConfig
|
||||
// when bridges and pillars are merged. The resulting pillar should be a bit
|
||||
// thicker than the ones merging into it. How much thicker? I don't know
|
||||
// but it will be derived from this value.
|
||||
double pillar_widening_factor = .05;
|
||||
double pillar_widening_factor = .5;
|
||||
|
||||
// Radius in mm of the pillar base.
|
||||
double base_radius_mm = 2.0;
|
||||
@ -76,12 +76,20 @@ struct SupportTreeConfig
|
||||
double pillar_base_safety_distance_mm = 0.5;
|
||||
|
||||
unsigned max_bridges_on_pillar = 3;
|
||||
|
||||
|
||||
double max_weight_on_model_support = 10.f;
|
||||
|
||||
double head_fullwidth() const {
|
||||
return 2 * head_front_radius_mm + head_width_mm +
|
||||
2 * head_back_radius_mm - head_penetration_mm;
|
||||
}
|
||||
|
||||
double safety_distance() const { return safety_distance_mm; }
|
||||
double safety_distance(double r) const
|
||||
{
|
||||
return std::min(safety_distance_mm, r * safety_distance_mm / head_back_radius_mm);
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Compile time configuration values (candidates for runtime)
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
@ -89,13 +97,15 @@ struct SupportTreeConfig
|
||||
// The max Z angle for a normal at which it will get completely ignored.
|
||||
static const double constexpr normal_cutoff_angle = 150.0 * M_PI / 180.0;
|
||||
|
||||
// The shortest distance of any support structure from the model surface
|
||||
// The safety gap between a support structure and model body. For support
|
||||
// struts smaller than head_back_radius, the safety distance is scaled
|
||||
// down accordingly. see method safety_distance()
|
||||
static const double constexpr safety_distance_mm = 0.5;
|
||||
|
||||
static const double constexpr max_solo_pillar_height_mm = 15.0;
|
||||
static const double constexpr max_dual_pillar_height_mm = 35.0;
|
||||
static const double constexpr optimizer_rel_score_diff = 1e-6;
|
||||
static const unsigned constexpr optimizer_max_iterations = 1000;
|
||||
static const double constexpr optimizer_rel_score_diff = 1e-10;
|
||||
static const unsigned constexpr optimizer_max_iterations = 2000;
|
||||
static const unsigned constexpr pillar_cascade_neighbors = 3;
|
||||
|
||||
};
|
||||
@ -115,11 +125,11 @@ struct SupportableMesh
|
||||
: emesh{trmsh}, pts{sp}, cfg{c}
|
||||
{}
|
||||
|
||||
explicit SupportableMesh(const AABBMesh &em,
|
||||
const SupportPoints &sp,
|
||||
const SupportTreeConfig &c)
|
||||
: emesh{em}, pts{sp}, cfg{c}
|
||||
{}
|
||||
// explicit SupportableMesh(const AABBMesh &em,
|
||||
// const SupportPoints &sp,
|
||||
// const SupportTreeConfig &c)
|
||||
// : emesh{em}, pts{sp}, cfg{c}
|
||||
// {}
|
||||
};
|
||||
|
||||
inline double ground_level(const SupportableMesh &sm)
|
||||
|
@ -67,25 +67,32 @@ struct SupportTreeNode
|
||||
long id = ID_UNSET; // For identification withing a tree.
|
||||
};
|
||||
|
||||
// A junction connecting bridges and pillars
|
||||
struct Junction: public SupportTreeNode {
|
||||
double r = 1;
|
||||
Vec3d pos;
|
||||
|
||||
Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {}
|
||||
};
|
||||
|
||||
// A pinhead originating from a support point
|
||||
struct Head: public SupportTreeNode {
|
||||
Vec3d dir = DOWN;
|
||||
Vec3d pos = {0, 0, 0};
|
||||
|
||||
|
||||
double r_back_mm = 1;
|
||||
double r_pin_mm = 0.5;
|
||||
double width_mm = 2;
|
||||
double penetration_mm = 0.5;
|
||||
|
||||
|
||||
// If there is a pillar connecting to this head, then the id will be set.
|
||||
long pillar_id = ID_UNSET;
|
||||
|
||||
|
||||
long bridge_id = ID_UNSET;
|
||||
|
||||
|
||||
inline void invalidate() { id = ID_UNSET; }
|
||||
inline bool is_valid() const { return id >= 0; }
|
||||
|
||||
|
||||
Head(double r_big_mm,
|
||||
double r_small_mm,
|
||||
double length_mm,
|
||||
@ -103,21 +110,21 @@ struct Head: public SupportTreeNode {
|
||||
{
|
||||
return real_width() - penetration_mm;
|
||||
}
|
||||
|
||||
|
||||
inline Junction junction() const
|
||||
{
|
||||
Junction j{pos + (fullwidth() - r_back_mm) * dir, r_back_mm};
|
||||
j.id = -this->id; // Remember that this junction is from a head
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
inline Vec3d junction_point() const
|
||||
{
|
||||
return pos + (fullwidth() - r_back_mm) * dir;
|
||||
return junction().pos;
|
||||
}
|
||||
};
|
||||
|
||||
// A junction connecting bridges and pillars
|
||||
struct Junction: public SupportTreeNode {
|
||||
double r = 1;
|
||||
Vec3d pos;
|
||||
|
||||
Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {}
|
||||
};
|
||||
|
||||
// A straight pillar. Only has an endpoint and a height. No explicit starting
|
||||
// point is given, as it would allow the pillar to be angled.
|
||||
// Some connection info with other primitives can also be tracked.
|
||||
@ -189,6 +196,10 @@ struct DiffBridge: public Bridge {
|
||||
DiffBridge(const Vec3d &p_s, const Vec3d &p_e, double r_s, double r_e)
|
||||
: Bridge{p_s, p_e, r_s}, end_r{r_e}
|
||||
{}
|
||||
|
||||
DiffBridge(const Junction &j_s, const Junction &j_e)
|
||||
: Bridge{j_s.pos, j_e.pos, j_s.r}, end_r{j_e.r}
|
||||
{}
|
||||
};
|
||||
|
||||
// This class will hold the support tree parts (not meshes, but logical parts)
|
||||
|
@ -165,7 +165,8 @@ indexed_triangle_set halfcone(double baseheight,
|
||||
{
|
||||
assert(steps > 0);
|
||||
|
||||
if (baseheight <= 0 || steps <= 0) return {};
|
||||
if (baseheight <= 0 || steps <= 0 || (r_bottom <= 0. && r_top <= 0.))
|
||||
return {};
|
||||
|
||||
indexed_triangle_set base;
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
enum class SupportTreeType { Default, Branching };
|
||||
enum class SupportTreeType { Default, Branching, Organic };
|
||||
enum class PillarConnectionMode { zigzag, cross, dynamic };
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
@ -6,10 +6,13 @@
|
||||
|
||||
#include <libslic3r/Execution/Execution.hpp>
|
||||
#include <libslic3r/Optimize/NLoptOptimizer.hpp>
|
||||
#include <libslic3r/Optimize/BruteforceOptimizer.hpp>
|
||||
#include <libslic3r/MeshNormals.hpp>
|
||||
#include <libslic3r/Geometry.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
@ -23,30 +26,6 @@ using Slic3r::opt::AlgNLoptGenetic;
|
||||
using Slic3r::Geometry::dir_to_spheric;
|
||||
using Slic3r::Geometry::spheric_to_dir;
|
||||
|
||||
// Helper function for pillar interconnection where pairs of already connected
|
||||
// pillars should be checked for not to be processed again. This can be done
|
||||
// in constant time with a set of hash values uniquely representing a pair of
|
||||
// integers. The order of numbers within the pair should not matter, it has
|
||||
// the same unique hash. The hash value has to have twice as many bits as the
|
||||
// arguments need. If the same integral type is used for args and return val,
|
||||
// make sure the arguments use only the half of the type's bit depth.
|
||||
template<class I, class DoubleI = IntegerOnly<I>>
|
||||
IntegerOnly<DoubleI> pairhash(I a, I b)
|
||||
{
|
||||
using std::ceil; using std::log2; using std::max; using std::min;
|
||||
static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT);
|
||||
static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT);
|
||||
static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits;
|
||||
|
||||
I g = min(a, b), l = max(a, b);
|
||||
|
||||
// Assume the hash will fit into the output variable
|
||||
assert((g ? (ceil(log2(g))) : 0) <= shift);
|
||||
assert((l ? (ceil(log2(l))) : 0) <= shift);
|
||||
|
||||
return (DoubleI(g) << shift) + l;
|
||||
}
|
||||
|
||||
// Give points on a 3D ring with given center, radius and orientation
|
||||
// method based on:
|
||||
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
|
||||
@ -131,31 +110,40 @@ inline StopCriteria get_criteria(const SupportTreeConfig &cfg)
|
||||
// A simple sphere with a center and a radius
|
||||
struct Ball { Vec3d p; double R; };
|
||||
|
||||
struct Beam { // Defines a set of rays displaced along a cone's surface
|
||||
static constexpr size_t SAMPLES = 8;
|
||||
template<size_t Samples = 8>
|
||||
struct Beam_ { // Defines a set of rays displaced along a cone's surface
|
||||
static constexpr size_t SAMPLES = Samples;
|
||||
|
||||
Vec3d src;
|
||||
Vec3d dir;
|
||||
Vec3d src;
|
||||
Vec3d dir;
|
||||
double r1;
|
||||
double r2; // radius of the beam 1 unit further from src in dir direction
|
||||
|
||||
Beam(const Vec3d &s, const Vec3d &d, double R1, double R2):
|
||||
src{s}, dir{d}, r1{R1}, r2{R2} {};
|
||||
Beam_(const Vec3d &s, const Vec3d &d, double R1, double R2)
|
||||
: src{s}, dir{d}, r1{R1}, r2{R2} {};
|
||||
|
||||
Beam(const Ball &src_ball, const Ball &dst_ball):
|
||||
src{src_ball.p}, dir{dirv(src_ball.p, dst_ball.p)}, r1{src_ball.R}
|
||||
Beam_(const Ball &src_ball, const Ball &dst_ball)
|
||||
: src{src_ball.p}, dir{dirv(src_ball.p, dst_ball.p)}, r1{src_ball.R}
|
||||
{
|
||||
r2 = src_ball.R +
|
||||
(dst_ball.R - src_ball.R) / distance(src_ball.p, dst_ball.p);
|
||||
r2 = src_ball.R;
|
||||
auto d = distance(src_ball.p, dst_ball.p);
|
||||
|
||||
if (d > EPSILON)
|
||||
r2 += (dst_ball.R - src_ball.R) / d;
|
||||
}
|
||||
|
||||
Beam(const Vec3d &s, const Vec3d &d, double R)
|
||||
Beam_(const Vec3d &s, const Vec3d &d, double R)
|
||||
: src{s}, dir{d}, r1{R}, r2{R}
|
||||
{}
|
||||
};
|
||||
|
||||
template<class Ex>
|
||||
Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd)
|
||||
using Beam = Beam_<8>;
|
||||
|
||||
template<class Ex, size_t RayCount = 8>
|
||||
Hit beam_mesh_hit(Ex policy,
|
||||
const AABBMesh &mesh,
|
||||
const Beam_<RayCount> &beam,
|
||||
double sd)
|
||||
{
|
||||
Vec3d src = beam.src;
|
||||
Vec3d dst = src + beam.dir;
|
||||
@ -164,19 +152,19 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd)
|
||||
|
||||
Vec3d D = (dst - src);
|
||||
Vec3d dir = D.normalized();
|
||||
PointRing<Beam::SAMPLES> ring{dir};
|
||||
PointRing<RayCount> ring{dir};
|
||||
|
||||
using Hit = AABBMesh::hit_result;
|
||||
|
||||
// Hit results
|
||||
std::array<Hit, Beam::SAMPLES> hits;
|
||||
std::array<Hit, RayCount> hits;
|
||||
|
||||
execution::for_each(
|
||||
ex, size_t(0), hits.size(),
|
||||
policy, size_t(0), hits.size(),
|
||||
[&mesh, r_src, r_dst, src, dst, &ring, dir, sd, &hits](size_t i) {
|
||||
Hit &hit = hits[i];
|
||||
|
||||
// Point on the circle on the pin sphere
|
||||
// Point on the circle on the pin sphere
|
||||
Vec3d p_src = ring.get(i, src, r_src + sd);
|
||||
Vec3d p_dst = ring.get(i, dst, r_dst + sd);
|
||||
Vec3d raydir = (p_dst - p_src).normalized();
|
||||
@ -193,7 +181,7 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd)
|
||||
}
|
||||
} else
|
||||
hit = hr;
|
||||
}, std::min(execution::max_concurrency(ex), Beam::SAMPLES));
|
||||
}, std::min(execution::max_concurrency(policy), RayCount));
|
||||
|
||||
return min_hit(hits.begin(), hits.end());
|
||||
}
|
||||
@ -283,199 +271,12 @@ Hit pinhead_mesh_hit(Ex ex,
|
||||
|
||||
template<class Ex>
|
||||
Hit pinhead_mesh_hit(Ex ex,
|
||||
const AABBMesh &mesh,
|
||||
const Head &head,
|
||||
double safety_d)
|
||||
const AABBMesh &mesh,
|
||||
const Head &head,
|
||||
double safety_d)
|
||||
{
|
||||
return pinhead_mesh_hit(ex, mesh, head.pos, head.dir, head.r_pin_mm,
|
||||
head.r_back_mm, head.width_mm, safety_d);
|
||||
}
|
||||
|
||||
template<class Ex>
|
||||
std::optional<DiffBridge> search_widening_path(Ex policy,
|
||||
const SupportableMesh &sm,
|
||||
const Vec3d &jp,
|
||||
const Vec3d &dir,
|
||||
double radius,
|
||||
double new_radius)
|
||||
{
|
||||
double w = radius + 2 * sm.cfg.head_back_radius_mm;
|
||||
double stopval = w + jp.z() - ground_level(sm);
|
||||
Optimizer<AlgNLoptSubplex> solver(get_criteria(sm.cfg).stop_score(stopval));
|
||||
|
||||
auto [polar, azimuth] = dir_to_spheric(dir);
|
||||
|
||||
double fallback_ratio = radius / sm.cfg.head_back_radius_mm;
|
||||
|
||||
auto oresult = solver.to_max().optimize(
|
||||
[&policy, &sm, jp, radius, new_radius](const opt::Input<3> &input) {
|
||||
auto &[plr, azm, t] = input;
|
||||
|
||||
auto d = spheric_to_dir(plr, azm).normalized();
|
||||
|
||||
auto sd = new_radius * sm.cfg.safety_distance_mm /
|
||||
sm.cfg.head_back_radius_mm;
|
||||
|
||||
double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius,
|
||||
new_radius, t, sd)
|
||||
.distance();
|
||||
|
||||
Beam beam{jp + t * d, d, new_radius};
|
||||
double down = beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
|
||||
|
||||
if (ret > t && std::isinf(down))
|
||||
ret += jp.z() - ground_level(sm);
|
||||
|
||||
return ret;
|
||||
},
|
||||
initvals({polar, azimuth, w}), // start with what we have
|
||||
bounds({
|
||||
{PI - sm.cfg.bridge_slope, PI}, // Must not exceed the slope limit
|
||||
{-PI, PI}, // azimuth can be a full search
|
||||
{radius + sm.cfg.head_back_radius_mm,
|
||||
fallback_ratio * sm.cfg.max_bridge_length_mm}
|
||||
}));
|
||||
|
||||
if (oresult.score >= stopval) {
|
||||
polar = std::get<0>(oresult.optimum);
|
||||
azimuth = std::get<1>(oresult.optimum);
|
||||
double t = std::get<2>(oresult.optimum);
|
||||
Vec3d endp = jp + t * spheric_to_dir(polar, azimuth);
|
||||
|
||||
return DiffBridge(jp, endp, radius, sm.cfg.head_back_radius_mm);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// This is a proxy function for pillar creation which will mind the gap
|
||||
// between the pad and the model bottom in zero elevation mode.
|
||||
// 'pinhead_junctionpt' is the starting junction point which needs to be
|
||||
// routed down. sourcedir is the allowed direction of an optional bridge
|
||||
// between the jp junction and the final pillar.
|
||||
template<class Ex>
|
||||
std::pair<bool, long> create_ground_pillar(
|
||||
Ex policy,
|
||||
SupportTreeBuilder &builder,
|
||||
const SupportableMesh &sm,
|
||||
const Vec3d &pinhead_junctionpt,
|
||||
const Vec3d &sourcedir,
|
||||
double radius,
|
||||
double end_radius,
|
||||
long head_id = SupportTreeNode::ID_UNSET)
|
||||
{
|
||||
Vec3d jp = pinhead_junctionpt, endp = jp, dir = sourcedir;
|
||||
long pillar_id = SupportTreeNode::ID_UNSET;
|
||||
bool can_add_base = false, non_head = false;
|
||||
|
||||
double gndlvl = 0.; // The Z level where pedestals should be
|
||||
double jp_gnd = 0.; // The lowest Z where a junction center can be
|
||||
double gap_dist = 0.; // The gap distance between the model and the pad
|
||||
|
||||
double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm));
|
||||
|
||||
auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; };
|
||||
|
||||
auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd]
|
||||
(bool base_en = true)
|
||||
{
|
||||
can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm;
|
||||
double base_r = can_add_base ? sm.cfg.base_radius_mm : 0.;
|
||||
gndlvl = ground_level(sm);
|
||||
if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm;
|
||||
jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm);
|
||||
gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON;
|
||||
};
|
||||
|
||||
eval_limits();
|
||||
|
||||
// We are dealing with a mini pillar that's potentially too long
|
||||
if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius)
|
||||
{
|
||||
std::optional<DiffBridge> diffbr =
|
||||
search_widening_path(policy, sm, jp, dir, radius,
|
||||
sm.cfg.head_back_radius_mm);
|
||||
|
||||
if (diffbr && diffbr->endp.z() > jp_gnd) {
|
||||
auto &br = builder.add_diffbridge(*diffbr);
|
||||
if (head_id >= 0) builder.head(head_id).bridge_id = br.id;
|
||||
endp = diffbr->endp;
|
||||
radius = diffbr->end_r;
|
||||
builder.add_junction(endp, radius);
|
||||
non_head = true;
|
||||
dir = diffbr->get_dir();
|
||||
eval_limits();
|
||||
} else return {false, pillar_id};
|
||||
}
|
||||
|
||||
if (sm.cfg.object_elevation_mm < EPSILON)
|
||||
{
|
||||
// get a suitable direction for the corrector bridge. It is the
|
||||
// original sourcedir's azimuth but the polar angle is saturated to the
|
||||
// configured bridge slope.
|
||||
auto [polar, azimuth] = dir_to_spheric(dir);
|
||||
polar = PI - sm.cfg.bridge_slope;
|
||||
Vec3d d = spheric_to_dir(polar, azimuth).normalized();
|
||||
auto sd = radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
|
||||
double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance();
|
||||
double tmax = std::min(sm.cfg.max_bridge_length_mm, t);
|
||||
t = 0.;
|
||||
|
||||
double zd = endp.z() - jp_gnd;
|
||||
double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
|
||||
tmax = std::min(tmax, tmax2);
|
||||
|
||||
Vec3d nexp = endp;
|
||||
double dlast = 0.;
|
||||
while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
|
||||
!std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius, r2}, sd).distance())) &&
|
||||
t < tmax)
|
||||
{
|
||||
t += radius;
|
||||
nexp = endp + t * d;
|
||||
}
|
||||
|
||||
if (dlast < gap_dist && can_add_base) {
|
||||
nexp = endp;
|
||||
t = 0.;
|
||||
can_add_base = false;
|
||||
eval_limits(can_add_base);
|
||||
|
||||
zd = endp.z() - jp_gnd;
|
||||
tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
|
||||
tmax = std::min(tmax, tmax2);
|
||||
|
||||
while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
|
||||
!std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius}, sd).distance())) && t < tmax) {
|
||||
t += radius;
|
||||
nexp = endp + t * d;
|
||||
}
|
||||
}
|
||||
|
||||
// Could not find a path to avoid the pad gap
|
||||
if (dlast < gap_dist) return {false, pillar_id};
|
||||
|
||||
if (t > 0.) { // Need to make additional bridge
|
||||
const Bridge& br = builder.add_bridge(endp, nexp, radius);
|
||||
if (head_id >= 0) builder.head(head_id).bridge_id = br.id;
|
||||
|
||||
builder.add_junction(nexp, radius);
|
||||
endp = nexp;
|
||||
non_head = true;
|
||||
}
|
||||
}
|
||||
|
||||
Vec3d gp = to_floor(endp);
|
||||
double h = endp.z() - gp.z();
|
||||
|
||||
pillar_id = head_id >= 0 && !non_head ? builder.add_pillar(head_id, h) :
|
||||
builder.add_pillar(gp, h, radius, end_radius);
|
||||
|
||||
if (can_add_base)
|
||||
builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm,
|
||||
sm.cfg.base_radius_mm);
|
||||
|
||||
return {true, pillar_id};
|
||||
head.r_back_mm, head.width_mm, safety_d);
|
||||
}
|
||||
|
||||
inline double distance(const SupportPoint &a, const SupportPoint &b)
|
||||
@ -530,13 +331,13 @@ bool optimize_pinhead_placement(Ex policy,
|
||||
|
||||
double back_r = head.r_back_mm;
|
||||
|
||||
// skip if the tilt is not sane
|
||||
// skip if the tilt is not sane
|
||||
if (polar < PI - m.cfg.normal_cutoff_angle) return false;
|
||||
|
||||
// We saturate the polar angle to 3pi/4
|
||||
// We saturate the polar angle to 3pi/4
|
||||
polar = std::max(polar, PI - m.cfg.bridge_slope);
|
||||
|
||||
// save the head (pinpoint) position
|
||||
// save the head (pinpoint) position
|
||||
Vec3d hp = head.pos;
|
||||
|
||||
double lmin = m.cfg.head_width_mm, lmax = lmin;
|
||||
@ -545,28 +346,26 @@ bool optimize_pinhead_placement(Ex policy,
|
||||
lmin = 0., lmax = m.cfg.head_penetration_mm;
|
||||
}
|
||||
|
||||
// The distance needed for a pinhead to not collide with model.
|
||||
// The distance needed for a pinhead to not collide with model.
|
||||
double w = lmin + 2 * back_r + 2 * m.cfg.head_front_radius_mm -
|
||||
m.cfg.head_penetration_mm;
|
||||
|
||||
double pin_r = head.r_pin_mm;
|
||||
|
||||
// Reassemble the now corrected normal
|
||||
// Reassemble the now corrected normal
|
||||
auto nn = spheric_to_dir(polar, azimuth).normalized();
|
||||
|
||||
double sd = back_r * m.cfg.safety_distance_mm /
|
||||
m.cfg.head_back_radius_mm;
|
||||
double sd = m.cfg.safety_distance(back_r);
|
||||
|
||||
// check available distance
|
||||
Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w,
|
||||
sd);
|
||||
// check available distance
|
||||
Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, sd);
|
||||
|
||||
if (t.distance() < 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.
|
||||
|
||||
Optimizer<AlgNLoptGenetic> solver(get_criteria(m.cfg).stop_score(w).max_iterations(100));
|
||||
Optimizer<opt::AlgNLoptMLSL_Subplx> solver(get_criteria(m.cfg).stop_score(w).max_iterations(100));
|
||||
solver.seed(0); // we want deterministic behavior
|
||||
|
||||
auto oresult = solver.to_max().optimize(
|
||||
@ -627,7 +426,7 @@ std::optional<Head> calculate_pinhead_placement(Ex policy,
|
||||
};
|
||||
|
||||
if (optimize_pinhead_placement(policy, sm, head)) {
|
||||
head.id = suppt_idx;
|
||||
head.id = long(suppt_idx);
|
||||
|
||||
return head;
|
||||
}
|
||||
@ -635,81 +434,335 @@ std::optional<Head> calculate_pinhead_placement(Ex policy,
|
||||
return {};
|
||||
}
|
||||
|
||||
template<class Ex>
|
||||
std::pair<bool, long> connect_to_ground(Ex policy,
|
||||
SupportTreeBuilder &builder,
|
||||
const SupportableMesh &sm,
|
||||
const Junction &j,
|
||||
const Vec3d &dir,
|
||||
double end_r)
|
||||
struct GroundConnection {
|
||||
// Currently, a ground connection will contain at most 2 additional junctions
|
||||
// which will not require any allocations. If I come up with an algo that
|
||||
// can produce a route to ground with more junctions, this design will be
|
||||
// able to handle that.
|
||||
static constexpr size_t MaxExpectedJunctions = 3;
|
||||
|
||||
boost::container::small_vector<Junction, MaxExpectedJunctions> path;
|
||||
std::optional<Pedestal> pillar_base;
|
||||
|
||||
operator bool() const { return pillar_base.has_value() && !path.empty(); }
|
||||
};
|
||||
|
||||
inline long build_ground_connection(SupportTreeBuilder &builder,
|
||||
const SupportableMesh &sm,
|
||||
const GroundConnection &conn)
|
||||
{
|
||||
auto hjp = j.pos;
|
||||
double r = j.r;
|
||||
auto sd = r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
|
||||
double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm));
|
||||
long ret = SupportTreeNode::ID_UNSET;
|
||||
|
||||
double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance();
|
||||
double d = 0, tdown = 0;
|
||||
t = std::min(t, sm.cfg.max_bridge_length_mm * r / sm.cfg.head_back_radius_mm);
|
||||
if (!conn)
|
||||
return ret;
|
||||
|
||||
while (d < t &&
|
||||
!std::isinf(tdown = beam_mesh_hit(policy, sm.emesh,
|
||||
Beam{hjp + d * dir, DOWN, r, r2}, sd)
|
||||
.distance())) {
|
||||
d += r;
|
||||
auto it = conn.path.begin();
|
||||
auto itnx = std::next(it);
|
||||
|
||||
while (itnx != conn.path.end()) {
|
||||
builder.add_diffbridge(*it, *itnx);
|
||||
builder.add_junction(*itnx);
|
||||
++it; ++itnx;
|
||||
}
|
||||
|
||||
if(!std::isinf(tdown))
|
||||
return {false, SupportTreeNode::ID_UNSET};
|
||||
auto gp = conn.path.back().pos;
|
||||
gp.z() = ground_level(sm);
|
||||
double h = conn.path.back().pos.z() - gp.z();
|
||||
|
||||
Vec3d endp = hjp + d * dir;
|
||||
auto ret = create_ground_pillar(policy, builder, sm, endp, dir, r, end_r);
|
||||
if (conn.pillar_base->r_top < sm.cfg.head_back_radius_mm) {
|
||||
h += sm.pad_cfg.wall_thickness_mm;
|
||||
gp.z() -= sm.pad_cfg.wall_thickness_mm;
|
||||
}
|
||||
|
||||
if (ret.second >= 0) {
|
||||
builder.add_bridge(hjp, endp, r);
|
||||
builder.add_junction(endp, r);
|
||||
// TODO: does not work yet
|
||||
// if (conn.path.back().id < 0) {
|
||||
// // this is a head
|
||||
// long head_id = std::abs(conn.path.back().id);
|
||||
// ret = builder.add_pillar(head_id, h);
|
||||
// } else
|
||||
|
||||
ret = builder.add_pillar(gp, h, conn.path.back().r, conn.pillar_base->r_top);
|
||||
|
||||
if (conn.pillar_base->r_top >= sm.cfg.head_back_radius_mm)
|
||||
builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class Fn>
|
||||
constexpr bool IsWideningFn = std::is_invocable_r_v</*retval*/ double,
|
||||
Fn,
|
||||
Ball /*source*/,
|
||||
Vec3d /*dir*/,
|
||||
double /*length*/>;
|
||||
|
||||
// A widening function can determine how many ray samples should a beam contain
|
||||
// (see in beam_mesh_hit)
|
||||
template<class WFn> struct BeamSamples { static constexpr size_t Value = 8; };
|
||||
template<class WFn> constexpr size_t BeamSamplesV = BeamSamples<remove_cvref_t<WFn>>::Value;
|
||||
|
||||
// To use with check_ground_route, full will check the bridge and the pillar,
|
||||
// PillarOnly checks only the pillar for collisions.
|
||||
enum class GroundRouteCheck { Full, PillarOnly };
|
||||
|
||||
// Returns the collision point with mesh if there is a collision or a ground point,
|
||||
// given a source point with a direction of a potential avoidance bridge and
|
||||
// a bridge length.
|
||||
template<class Ex, class WideningFn,
|
||||
class = std::enable_if_t<IsWideningFn<WideningFn>> >
|
||||
Vec3d check_ground_route(
|
||||
Ex policy,
|
||||
const SupportableMesh &sm,
|
||||
const Junction &source, // source location
|
||||
const Vec3d &dir, // direction of the bridge from the source
|
||||
double bridge_len, // lenght of the avoidance bridge
|
||||
WideningFn &&wideningfn, // Widening strategy
|
||||
GroundRouteCheck type = GroundRouteCheck::Full
|
||||
)
|
||||
{
|
||||
static const constexpr auto Samples = BeamSamplesV<WideningFn>;
|
||||
|
||||
Vec3d ret;
|
||||
|
||||
const auto sd = sm.cfg.safety_distance(source.r);
|
||||
const auto gndlvl = ground_level(sm);
|
||||
|
||||
// Intersection of the suggested bridge with ground plane. If the bridge
|
||||
// spans below ground, stop it at ground level.
|
||||
double t = (gndlvl - source.pos.z()) / dir.z();
|
||||
bridge_len = std::min(t, bridge_len);
|
||||
|
||||
Vec3d bridge_end = source.pos + bridge_len * dir;
|
||||
|
||||
double down_l = bridge_end.z() - gndlvl;
|
||||
double bridge_r = wideningfn(Ball{source.pos, source.r}, dir, bridge_len);
|
||||
double brhit_dist = 0.;
|
||||
|
||||
if (bridge_len > EPSILON && type == GroundRouteCheck::Full) {
|
||||
// beam_mesh_hit with a zero lenght bridge is invalid
|
||||
|
||||
Beam_<Samples> bridgebeam{Ball{source.pos, source.r},
|
||||
Ball{bridge_end, bridge_r}};
|
||||
|
||||
auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd);
|
||||
brhit_dist = brhit.distance();
|
||||
} else {
|
||||
brhit_dist = bridge_len;
|
||||
}
|
||||
|
||||
if (brhit_dist < bridge_len) {
|
||||
ret = (source.pos + brhit_dist * dir);
|
||||
} else if (down_l > 0.) {
|
||||
// check if pillar can be placed below
|
||||
auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl};
|
||||
double end_radius = wideningfn(
|
||||
Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl);
|
||||
|
||||
Beam_<Samples> gndbeam {{bridge_end, bridge_r}, {gp, end_radius}};
|
||||
auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd);
|
||||
double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON);
|
||||
|
||||
if (source.r >= sm.cfg.head_back_radius_mm && gndhit.distance() > down_l && sm.cfg.object_elevation_mm < EPSILON) {
|
||||
// Dealing with zero elevation mode, to not route pillars
|
||||
// into the gap between the optional pad and the model
|
||||
double gap = std::sqrt(sm.emesh.squared_distance(gp));
|
||||
double base_r = std::max(sm.cfg.base_radius_mm, end_radius);
|
||||
double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r;
|
||||
|
||||
if (gap < min_gap) {
|
||||
gnd_hit_d = down_l - min_gap + gap;
|
||||
}
|
||||
}
|
||||
|
||||
ret = Vec3d{bridge_end.x(), bridge_end.y(), bridge_end.z() - gnd_hit_d};
|
||||
} else {
|
||||
ret = bridge_end;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class Ex>
|
||||
std::pair<bool, long> search_ground_route(Ex policy,
|
||||
SupportTreeBuilder &builder,
|
||||
const SupportableMesh &sm,
|
||||
const Junction &j,
|
||||
double end_radius,
|
||||
const Vec3d &init_dir = DOWN)
|
||||
// Searching a ground connection from an arbitrary source point.
|
||||
// Currently, the result will contain one avoidance bridge (at most) and a
|
||||
// pillar to the ground, if it's feasible
|
||||
template<class Ex, class WideningFn,
|
||||
class = std::enable_if_t<IsWideningFn<WideningFn>> >
|
||||
GroundConnection deepsearch_ground_connection(
|
||||
Ex policy,
|
||||
const SupportableMesh &sm,
|
||||
const Junction &source,
|
||||
WideningFn &&wideningfn,
|
||||
const Vec3d &init_dir = DOWN)
|
||||
{
|
||||
double downdst = j.pos.z() - ground_level(sm);
|
||||
constexpr unsigned MaxIterationsGlobal = 5000;
|
||||
constexpr unsigned MaxIterationsLocal = 100;
|
||||
constexpr double RelScoreDiff = 0.05;
|
||||
|
||||
auto res = connect_to_ground(policy, builder, sm, j, init_dir, end_radius);
|
||||
if (res.first)
|
||||
return res;
|
||||
const auto gndlvl = ground_level(sm);
|
||||
|
||||
// Optimize bridge direction:
|
||||
// Straight path failed so we will try to search for a suitable
|
||||
// direction out of the cavity.
|
||||
auto [polar, azimuth] = dir_to_spheric(init_dir);
|
||||
// The used solver (AlgNLoptMLSL_Subplx search method) is composed of a global (MLSL)
|
||||
// and a local (Subplex) search method. Criteria can be set in a way that
|
||||
// local searches are quick and less accurate. The global method will only
|
||||
// consider the max iteration number and the stop score (Z level <= ground)
|
||||
|
||||
Optimizer<AlgNLoptGenetic> solver(get_criteria(sm.cfg).stop_score(1e6));
|
||||
solver.seed(0); // we want deterministic behavior
|
||||
auto criteria = get_criteria(sm.cfg); // get defaults from cfg
|
||||
criteria.max_iterations(MaxIterationsGlobal);
|
||||
criteria.abs_score_diff(NaNd);
|
||||
criteria.rel_score_diff(NaNd);
|
||||
criteria.stop_score(gndlvl);
|
||||
|
||||
auto sd = j.r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
|
||||
auto oresult = solver.to_max().optimize(
|
||||
[&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) {
|
||||
auto &[plr, azm] = input;
|
||||
Vec3d n = spheric_to_dir(plr, azm).normalized();
|
||||
Beam beam{Ball{j.pos, j.r}, Ball{j.pos + downdst * n, end_radius}};
|
||||
return beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
|
||||
},
|
||||
initvals({polar, azimuth}), // let's start with what we have
|
||||
bounds({ {PI - sm.cfg.bridge_slope, PI}, {-PI, PI} })
|
||||
);
|
||||
auto criteria_loc = criteria;
|
||||
criteria_loc.max_iterations(MaxIterationsLocal);
|
||||
criteria_loc.abs_score_diff(EPSILON);
|
||||
criteria_loc.rel_score_diff(RelScoreDiff);
|
||||
|
||||
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
|
||||
Optimizer<opt::AlgNLoptMLSL_Subplx> solver(criteria);
|
||||
solver.set_loc_criteria(criteria_loc);
|
||||
solver.seed(0); // require repeatability
|
||||
|
||||
return connect_to_ground(policy, builder, sm, j, bridgedir, end_radius);
|
||||
// functor returns the z height of collision point, given a polar and
|
||||
// azimuth angles as bridge direction and bridge length. The route is
|
||||
// traced from source, through this bridge and an attached pillar. If there
|
||||
// is a collision with the mesh, the Z height is returned. Otherwise the
|
||||
// z level of ground is returned.
|
||||
auto z_fn = [&](const opt::Input<3> &input) {
|
||||
// solver suggests polar, azimuth and bridge length values:
|
||||
auto &[plr, azm, bridge_len] = input;
|
||||
|
||||
Vec3d n = spheric_to_dir(plr, azm);
|
||||
|
||||
Vec3d hitpt = check_ground_route(policy, sm, source, n, bridge_len, wideningfn);
|
||||
|
||||
return hitpt.z();
|
||||
};
|
||||
|
||||
// Calculate the initial direction of the search by
|
||||
// saturating the polar angle to max tilt defined in config
|
||||
auto [plr_init, azm_init] = dir_to_spheric(init_dir);
|
||||
plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope);
|
||||
|
||||
auto bound_constraints =
|
||||
bounds({
|
||||
{PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle
|
||||
{-PI, PI}, // bounds for azimuth
|
||||
{0., sm.cfg.max_bridge_length_mm} // bounds bridge length
|
||||
});
|
||||
|
||||
// The optimizer can navigate fairly well on the mesh surface, finding
|
||||
// lower and lower Z coordinates as collision points. MLSL is not a local
|
||||
// search method, so it should not be trapped in a local minima. Eventually,
|
||||
// this search should arrive at a ground location.
|
||||
auto oresult = solver.to_min().optimize(
|
||||
z_fn,
|
||||
initvals({plr_init, azm_init, 0.}),
|
||||
bound_constraints
|
||||
);
|
||||
|
||||
GroundConnection conn;
|
||||
|
||||
// Extract and apply the result
|
||||
auto [plr, azm, bridge_l] = oresult.optimum;
|
||||
Vec3d n = spheric_to_dir(plr, azm);
|
||||
assert(std::abs(n.norm() - 1.) < EPSILON);
|
||||
|
||||
double t = (gndlvl - source.pos.z()) / n.z();
|
||||
bridge_l = std::min(t, bridge_l);
|
||||
|
||||
// Now the optimizer gave a possible route to ground with a bridge direction
|
||||
// and length. This length can be shortened further by brute-force queries
|
||||
// of free route straigt down for a possible pillar.
|
||||
// NOTE: This requirement could be incorporated into the optimization as a
|
||||
// constraint, but it would not find quickly enough an accurate solution,
|
||||
// and it would be very hard to define a stop score which is very useful in
|
||||
// terminating the search as soon as the ground is found.
|
||||
double l = 0., l_max = bridge_l;
|
||||
double zlvl = std::numeric_limits<double>::infinity();
|
||||
while(zlvl > gndlvl && l <= l_max) {
|
||||
|
||||
zlvl = check_ground_route(policy, sm, source, n, l, wideningfn,
|
||||
GroundRouteCheck::PillarOnly).z();
|
||||
|
||||
if (zlvl <= gndlvl)
|
||||
bridge_l = l;
|
||||
|
||||
l += source.r;
|
||||
}
|
||||
|
||||
Vec3d bridge_end = source.pos + bridge_l * n;
|
||||
Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl};
|
||||
|
||||
double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_l);
|
||||
double down_l = bridge_end.z() - gndlvl;
|
||||
double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l);
|
||||
double base_r = std::max(sm.cfg.base_radius_mm, end_radius);
|
||||
|
||||
// Even if the search was not succesful, the result is populated by the
|
||||
// source and the last best result of the optimization.
|
||||
conn.path.emplace_back(source);
|
||||
if (bridge_l > EPSILON)
|
||||
conn.path.emplace_back(Junction{bridge_end, bridge_r});
|
||||
|
||||
// The resulting ground connection is only valid if the pillar base is set.
|
||||
// At this point it will only be set if the search was succesful.
|
||||
if (z_fn(opt::Input<3>({plr, azm, bridge_l})) <= gndlvl)
|
||||
conn.pillar_base =
|
||||
Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius};
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
// Ground route search with a predefined end radius
|
||||
template<class Ex>
|
||||
GroundConnection deepsearch_ground_connection(Ex policy,
|
||||
const SupportableMesh &sm,
|
||||
const Junction &source,
|
||||
double end_radius,
|
||||
const Vec3d &init_dir = DOWN)
|
||||
{
|
||||
double gndlvl = ground_level(sm);
|
||||
auto wfn = [end_radius, gndlvl](const Ball &src, const Vec3d &dir, double len) {
|
||||
if (len < EPSILON)
|
||||
return src.R;
|
||||
|
||||
Vec3d dst = src.p + len * dir;
|
||||
double widening = end_radius - src.R;
|
||||
double zlen = dst.z() - gndlvl;
|
||||
double full_len = len + zlen;
|
||||
double r = src.R + widening * len / full_len;
|
||||
|
||||
return r;
|
||||
};
|
||||
|
||||
static_assert(IsWideningFn<decltype(wfn)>, "Not a widening function");
|
||||
|
||||
return deepsearch_ground_connection(policy, sm, source, wfn, init_dir);
|
||||
}
|
||||
|
||||
struct DefaultWideningModel {
|
||||
static constexpr double WIDENING_SCALE = 0.02;
|
||||
const SupportableMesh &sm;
|
||||
|
||||
double operator()(const Ball &src, const Vec3d & /*dir*/, double len) {
|
||||
static_assert(IsWideningFn<DefaultWideningModel>,
|
||||
"DefaultWideningModel is not a widening function");
|
||||
|
||||
double w = WIDENING_SCALE * sm.cfg.pillar_widening_factor * len;
|
||||
return std::max(src.R, sm.cfg.head_back_radius_mm) + w;
|
||||
};
|
||||
};
|
||||
|
||||
template<> struct BeamSamples<DefaultWideningModel> {
|
||||
static constexpr size_t Value = 16;
|
||||
};
|
||||
|
||||
template<class Ex>
|
||||
GroundConnection deepsearch_ground_connection(Ex policy,
|
||||
const SupportableMesh &sm,
|
||||
const Junction &source,
|
||||
const Vec3d &init_dir = DOWN)
|
||||
{
|
||||
return deepsearch_ground_connection(policy, sm, source,
|
||||
DefaultWideningModel{sm}, init_dir);
|
||||
}
|
||||
|
||||
template<class Ex>
|
||||
@ -729,8 +782,7 @@ bool optimize_anchor_placement(Ex policy,
|
||||
double lmax = std::min(sm.cfg.head_width_mm,
|
||||
distance(from.pos, anchor.pos) - 2 * from.r);
|
||||
|
||||
double sd = anchor.r_back_mm * sm.cfg.safety_distance_mm /
|
||||
sm.cfg.head_back_radius_mm;
|
||||
double sd = sm.cfg.safety_distance(anchor.r_back_mm);
|
||||
|
||||
Optimizer<AlgNLoptGenetic> solver(get_criteria(sm.cfg)
|
||||
.stop_score(anchor.fullwidth())
|
||||
@ -796,6 +848,92 @@ std::optional<Anchor> calculate_anchor_placement(Ex policy,
|
||||
return anchor;
|
||||
}
|
||||
|
||||
inline bool is_outside_support_cone(const Vec3f &supp,
|
||||
const Vec3f &pt,
|
||||
float angle)
|
||||
{
|
||||
using namespace Slic3r;
|
||||
|
||||
Vec3d D = (pt - supp).cast<double>();
|
||||
double dot_sq = -D.z() * std::abs(-D.z());
|
||||
|
||||
return dot_sq <
|
||||
D.squaredNorm() * std::cos(angle) * std::abs(std::cos(angle));
|
||||
}
|
||||
|
||||
inline // TODO: should be in a cpp
|
||||
std::optional<Vec3f> find_merge_pt(const Vec3f &A,
|
||||
const Vec3f &B,
|
||||
float critical_angle)
|
||||
{
|
||||
// The idea is that A and B both have their support cones. But searching
|
||||
// for the intersection of these support cones is difficult and its enough
|
||||
// to reduce this problem to 2D and search for the intersection of two
|
||||
// rays that merge somewhere between A and B. The 2D plane is a vertical
|
||||
// slice of the 3D scene where the 2D Y axis is equal to the 3D Z axis and
|
||||
// the 2D X axis is determined by the XY direction of the AB vector.
|
||||
//
|
||||
// Z^
|
||||
// | A *
|
||||
// | . . B *
|
||||
// | . . . .
|
||||
// | . . . .
|
||||
// | . x .
|
||||
// -------------------> XY
|
||||
|
||||
// Determine the transformation matrix for the 2D projection:
|
||||
Vec3f diff = {B.x() - A.x(), B.y() - A.y(), 0.f};
|
||||
Vec3f dir = diff.normalized(); // TODO: avoid normalization
|
||||
|
||||
Eigen::Matrix<float, 2, 3> tr2D;
|
||||
tr2D.row(0) = Vec3f{dir.x(), dir.y(), dir.z()};
|
||||
tr2D.row(1) = Vec3f{0.f, 0.f, 1.f};
|
||||
|
||||
// Transform the 2 vectors A and B into 2D vector 'a' and 'b'. Here we can
|
||||
// omit 'a', pretend that its the origin and use BA as the vector b.
|
||||
Vec2f b = tr2D * (B - A);
|
||||
|
||||
// Get the square sine of the ray emanating from 'a' towards 'b'. This ray might
|
||||
// exceed the allowed angle but that is corrected subsequently.
|
||||
// The sign of the original sine is also needed, hence b.y is multiplied by
|
||||
// abs(b.y)
|
||||
float b_sqn = b.squaredNorm();
|
||||
float sin2sig_a = b_sqn > EPSILON ? (b.y() * std::abs(b.y())) / b_sqn : 0.f;
|
||||
|
||||
// sine2 from 'b' to 'a' is the opposite of sine2 from a to b
|
||||
float sin2sig_b = -sin2sig_a;
|
||||
|
||||
// Derive the allowed angles from the given critical angle.
|
||||
// critical_angle is measured from the horizontal X axis.
|
||||
// The rays need to go downwards which corresponds to negative angles
|
||||
|
||||
float sincrit = std::sin(critical_angle); // sine of the critical angle
|
||||
float sin2crit = -sincrit * sincrit; // signed sine squared
|
||||
sin2sig_a = std::min(sin2sig_a, sin2crit); // Do the angle saturation of both rays
|
||||
sin2sig_b = std::min(sin2sig_b, sin2crit); //
|
||||
float sin2_a = std::abs(sin2sig_a); // Get cosine squared values
|
||||
float sin2_b = std::abs(sin2sig_b);
|
||||
float cos2_a = 1.f - sin2_a;
|
||||
float cos2_b = 1.f - sin2_b;
|
||||
|
||||
// Derive the new direction vectors. This is by square rooting the sin2
|
||||
// and cos2 values and restoring the original signs
|
||||
Vec2f Da = {std::copysign(std::sqrt(cos2_a), b.x()), std::copysign(std::sqrt(sin2_a), sin2sig_a)};
|
||||
Vec2f Db = {-std::copysign(std::sqrt(cos2_b), b.x()), std::copysign(std::sqrt(sin2_b), sin2sig_b)};
|
||||
|
||||
// Determine where two rays ([0, 0], Da), (b, Db) intersect.
|
||||
// Based on
|
||||
// https://stackoverflow.com/questions/27459080/given-two-points-and-two-direction-vectors-find-the-point-where-they-intersect
|
||||
// One ray is emanating from (0, 0) so the formula is simplified
|
||||
double t1 = (Db.y() * b.x() - b.y() * Db.x()) /
|
||||
(Da.x() * Db.y() - Da.y() * Db.x());
|
||||
|
||||
Vec2f mp = t1 * Da;
|
||||
Vec3f Mp = A + tr2D.transpose() * mp;
|
||||
|
||||
return t1 >= 0.f ? Mp : Vec3f{};
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // SLASUPPORTTREEUTILS_H
|
||||
|
300
src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp
Normal file
300
src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp
Normal file
@ -0,0 +1,300 @@
|
||||
#ifndef SUPPORTTREEUTILSLEGACY_HPP
|
||||
#define SUPPORTTREEUTILSLEGACY_HPP
|
||||
|
||||
#include "SupportTreeUtils.hpp"
|
||||
|
||||
// Old functions are gathered here that are used in DefaultSupportTree
|
||||
// to maintain functionality that was well tested.
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
// Helper function for pillar interconnection where pairs of already connected
|
||||
// pillars should be checked for not to be processed again. This can be done
|
||||
// in constant time with a set of hash values uniquely representing a pair of
|
||||
// integers. The order of numbers within the pair should not matter, it has
|
||||
// the same unique hash. The hash value has to have twice as many bits as the
|
||||
// arguments need. If the same integral type is used for args and return val,
|
||||
// make sure the arguments use only the half of the type's bit depth.
|
||||
template<class I, class DoubleI = IntegerOnly<I>>
|
||||
IntegerOnly<DoubleI> pairhash(I a, I b)
|
||||
{
|
||||
using std::ceil; using std::log2; using std::max; using std::min;
|
||||
static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT);
|
||||
static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT);
|
||||
static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits;
|
||||
|
||||
I g = min(a, b), l = max(a, b);
|
||||
|
||||
// Assume the hash will fit into the output variable
|
||||
assert((g ? (ceil(log2(g))) : 0) <= shift);
|
||||
assert((l ? (ceil(log2(l))) : 0) <= shift);
|
||||
|
||||
return (DoubleI(g) << shift) + l;
|
||||
}
|
||||
|
||||
template<class Ex>
|
||||
std::optional<DiffBridge> search_widening_path(Ex policy,
|
||||
const SupportableMesh &sm,
|
||||
const Vec3d &jp,
|
||||
const Vec3d &dir,
|
||||
double radius,
|
||||
double new_radius)
|
||||
{
|
||||
double w = radius + 2 * sm.cfg.head_back_radius_mm;
|
||||
double stopval = w + jp.z() - ground_level(sm);
|
||||
Optimizer<AlgNLoptSubplex> solver(get_criteria(sm.cfg).stop_score(stopval));
|
||||
|
||||
auto [polar, azimuth] = dir_to_spheric(dir);
|
||||
|
||||
double fallback_ratio = radius / sm.cfg.head_back_radius_mm;
|
||||
|
||||
auto oresult = solver.to_max().optimize(
|
||||
[&policy, &sm, jp, radius, new_radius](const opt::Input<3> &input) {
|
||||
auto &[plr, azm, t] = input;
|
||||
|
||||
auto d = spheric_to_dir(plr, azm).normalized();
|
||||
|
||||
auto sd = sm.cfg.safety_distance(new_radius);
|
||||
|
||||
double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius,
|
||||
new_radius, t, sd)
|
||||
.distance();
|
||||
|
||||
Beam beam{jp + t * d, d, new_radius};
|
||||
double down = beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
|
||||
|
||||
if (ret > t && std::isinf(down))
|
||||
ret += jp.z() - ground_level(sm);
|
||||
|
||||
return ret;
|
||||
},
|
||||
initvals({polar, azimuth, w}), // start with what we have
|
||||
bounds({
|
||||
{PI - sm.cfg.bridge_slope, PI}, // Must not exceed the slope limit
|
||||
{-PI, PI}, // azimuth can be a full search
|
||||
{radius + sm.cfg.head_back_radius_mm,
|
||||
fallback_ratio * sm.cfg.max_bridge_length_mm}
|
||||
}));
|
||||
|
||||
if (oresult.score >= stopval) {
|
||||
polar = std::get<0>(oresult.optimum);
|
||||
azimuth = std::get<1>(oresult.optimum);
|
||||
double t = std::get<2>(oresult.optimum);
|
||||
Vec3d endp = jp + t * spheric_to_dir(polar, azimuth);
|
||||
|
||||
return DiffBridge(jp, endp, radius, sm.cfg.head_back_radius_mm);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// This is a proxy function for pillar creation which will mind the gap
|
||||
// between the pad and the model bottom in zero elevation mode.
|
||||
// 'pinhead_junctionpt' is the starting junction point which needs to be
|
||||
// routed down. sourcedir is the allowed direction of an optional bridge
|
||||
// between the jp junction and the final pillar.
|
||||
template<class Ex>
|
||||
std::pair<bool, long> create_ground_pillar(
|
||||
Ex policy,
|
||||
SupportTreeBuilder &builder,
|
||||
const SupportableMesh &sm,
|
||||
const Vec3d &pinhead_junctionpt,
|
||||
const Vec3d &sourcedir,
|
||||
double radius,
|
||||
double end_radius,
|
||||
long head_id = SupportTreeNode::ID_UNSET)
|
||||
{
|
||||
Vec3d jp = pinhead_junctionpt, endp = jp, dir = sourcedir;
|
||||
long pillar_id = SupportTreeNode::ID_UNSET;
|
||||
bool can_add_base = false, non_head = false;
|
||||
|
||||
double gndlvl = 0.; // The Z level where pedestals should be
|
||||
double jp_gnd = 0.; // The lowest Z where a junction center can be
|
||||
double gap_dist = 0.; // The gap distance between the model and the pad
|
||||
|
||||
double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm));
|
||||
|
||||
auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; };
|
||||
|
||||
auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd]
|
||||
(bool base_en = true)
|
||||
{
|
||||
can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm;
|
||||
double base_r = can_add_base ? sm.cfg.base_radius_mm : 0.;
|
||||
gndlvl = ground_level(sm);
|
||||
if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm;
|
||||
jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm);
|
||||
gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON;
|
||||
};
|
||||
|
||||
eval_limits();
|
||||
|
||||
// We are dealing with a mini pillar that's potentially too long
|
||||
if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius)
|
||||
{
|
||||
std::optional<DiffBridge> diffbr =
|
||||
search_widening_path(policy, sm, jp, dir, radius,
|
||||
sm.cfg.head_back_radius_mm);
|
||||
|
||||
if (diffbr && diffbr->endp.z() > jp_gnd) {
|
||||
auto &br = builder.add_diffbridge(*diffbr);
|
||||
if (head_id >= 0) builder.head(head_id).bridge_id = br.id;
|
||||
endp = diffbr->endp;
|
||||
radius = diffbr->end_r;
|
||||
builder.add_junction(endp, radius);
|
||||
non_head = true;
|
||||
dir = diffbr->get_dir();
|
||||
eval_limits();
|
||||
} else return {false, pillar_id};
|
||||
}
|
||||
|
||||
if (sm.cfg.object_elevation_mm < EPSILON)
|
||||
{
|
||||
// get a suitable direction for the corrector bridge. It is the
|
||||
// original sourcedir's azimuth but the polar angle is saturated to the
|
||||
// configured bridge slope.
|
||||
auto [polar, azimuth] = dir_to_spheric(dir);
|
||||
polar = PI - sm.cfg.bridge_slope;
|
||||
Vec3d d = spheric_to_dir(polar, azimuth).normalized();
|
||||
auto sd = radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
|
||||
double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance();
|
||||
double tmax = std::min(sm.cfg.max_bridge_length_mm, t);
|
||||
t = 0.;
|
||||
|
||||
double zd = endp.z() - jp_gnd;
|
||||
double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
|
||||
tmax = std::min(tmax, tmax2);
|
||||
|
||||
Vec3d nexp = endp;
|
||||
double dlast = 0.;
|
||||
while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
|
||||
!std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius, r2}, sd).distance())) &&
|
||||
t < tmax)
|
||||
{
|
||||
t += radius;
|
||||
nexp = endp + t * d;
|
||||
}
|
||||
|
||||
if (dlast < gap_dist && can_add_base) {
|
||||
nexp = endp;
|
||||
t = 0.;
|
||||
can_add_base = false;
|
||||
eval_limits(can_add_base);
|
||||
|
||||
zd = endp.z() - jp_gnd;
|
||||
tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
|
||||
tmax = std::min(tmax, tmax2);
|
||||
|
||||
while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
|
||||
!std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius}, sd).distance())) && t < tmax) {
|
||||
t += radius;
|
||||
nexp = endp + t * d;
|
||||
}
|
||||
}
|
||||
|
||||
// Could not find a path to avoid the pad gap
|
||||
if (dlast < gap_dist) return {false, pillar_id};
|
||||
|
||||
if (t > 0.) { // Need to make additional bridge
|
||||
const Bridge& br = builder.add_bridge(endp, nexp, radius);
|
||||
if (head_id >= 0) builder.head(head_id).bridge_id = br.id;
|
||||
|
||||
builder.add_junction(nexp, radius);
|
||||
endp = nexp;
|
||||
non_head = true;
|
||||
}
|
||||
}
|
||||
|
||||
Vec3d gp = to_floor(endp);
|
||||
double h = endp.z() - gp.z();
|
||||
|
||||
pillar_id = head_id >= 0 && !non_head ? builder.add_pillar(head_id, h) :
|
||||
builder.add_pillar(gp, h, radius, end_radius);
|
||||
|
||||
if (can_add_base)
|
||||
builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm,
|
||||
sm.cfg.base_radius_mm);
|
||||
|
||||
return {true, pillar_id};
|
||||
}
|
||||
|
||||
template<class Ex>
|
||||
std::pair<bool, long> connect_to_ground(Ex policy,
|
||||
SupportTreeBuilder &builder,
|
||||
const SupportableMesh &sm,
|
||||
const Junction &j,
|
||||
const Vec3d &dir,
|
||||
double end_r)
|
||||
{
|
||||
auto hjp = j.pos;
|
||||
double r = j.r;
|
||||
auto sd = r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
|
||||
double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm));
|
||||
|
||||
double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance();
|
||||
double d = 0, tdown = 0;
|
||||
t = std::min(t, sm.cfg.max_bridge_length_mm * r / sm.cfg.head_back_radius_mm);
|
||||
|
||||
while (d < t &&
|
||||
!std::isinf(tdown = beam_mesh_hit(policy, sm.emesh,
|
||||
Beam{hjp + d * dir, DOWN, r, r2}, sd)
|
||||
.distance())) {
|
||||
d += r;
|
||||
}
|
||||
|
||||
if(!std::isinf(tdown))
|
||||
return {false, SupportTreeNode::ID_UNSET};
|
||||
|
||||
Vec3d endp = hjp + d * dir;
|
||||
auto ret = create_ground_pillar(policy, builder, sm, endp, dir, r, end_r);
|
||||
|
||||
if (ret.second >= 0) {
|
||||
builder.add_bridge(hjp, endp, r);
|
||||
builder.add_junction(endp, r);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class Ex>
|
||||
std::pair<bool, long> search_ground_route(Ex policy,
|
||||
SupportTreeBuilder &builder,
|
||||
const SupportableMesh &sm,
|
||||
const Junction &j,
|
||||
double end_radius,
|
||||
const Vec3d &init_dir = DOWN)
|
||||
{
|
||||
double downdst = j.pos.z() - ground_level(sm);
|
||||
|
||||
auto res = connect_to_ground(policy, builder, sm, j, init_dir, end_radius);
|
||||
if (res.first)
|
||||
return res;
|
||||
|
||||
// Optimize bridge direction:
|
||||
// Straight path failed so we will try to search for a suitable
|
||||
// direction out of the cavity.
|
||||
auto [polar, azimuth] = dir_to_spheric(init_dir);
|
||||
|
||||
Optimizer<AlgNLoptGenetic> solver(get_criteria(sm.cfg).stop_score(1e6));
|
||||
solver.seed(0); // we want deterministic behavior
|
||||
|
||||
auto sd = j.r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
|
||||
auto oresult = solver.to_max().optimize(
|
||||
[&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) {
|
||||
auto &[plr, azm] = input;
|
||||
Vec3d n = spheric_to_dir(plr, azm).normalized();
|
||||
Beam beam{Ball{j.pos, j.r}, Ball{j.pos + downdst * n, end_radius}};
|
||||
return beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
|
||||
},
|
||||
initvals({polar, azimuth}), // let's start with what we have
|
||||
bounds({ {PI - sm.cfg.bridge_slope, PI}, {-PI, PI} })
|
||||
);
|
||||
|
||||
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
|
||||
|
||||
return connect_to_ground(policy, builder, sm, j, bridgedir, end_radius);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // SUPPORTTREEUTILSLEGACY_HPP
|
@ -44,28 +44,63 @@ sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c)
|
||||
|
||||
scfg.enabled = c.supports_enable.getBool();
|
||||
scfg.tree_type = c.support_tree_type.value;
|
||||
scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat();
|
||||
double pillar_r = 0.5 * c.support_pillar_diameter.getFloat();
|
||||
scfg.head_back_radius_mm = pillar_r;
|
||||
scfg.head_fallback_radius_mm =
|
||||
0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r;
|
||||
scfg.head_penetration_mm = c.support_head_penetration.getFloat();
|
||||
scfg.head_width_mm = c.support_head_width.getFloat();
|
||||
scfg.object_elevation_mm = is_zero_elevation(c) ?
|
||||
0. : 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();
|
||||
scfg.pillar_connection_mode = c.support_pillar_connection_mode.value;
|
||||
scfg.ground_facing_only = c.support_buildplate_only.getBool();
|
||||
scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat();
|
||||
scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat();
|
||||
scfg.base_height_mm = c.support_base_height.getFloat();
|
||||
scfg.pillar_base_safety_distance_mm =
|
||||
c.support_base_safety_distance.getFloat() < EPSILON ?
|
||||
scfg.safety_distance_mm : c.support_base_safety_distance.getFloat();
|
||||
|
||||
scfg.max_bridges_on_pillar = unsigned(c.support_max_bridges_on_pillar.getInt());
|
||||
|
||||
switch(scfg.tree_type) {
|
||||
case sla::SupportTreeType::Default: {
|
||||
scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat();
|
||||
double pillar_r = 0.5 * c.support_pillar_diameter.getFloat();
|
||||
scfg.head_back_radius_mm = pillar_r;
|
||||
scfg.head_fallback_radius_mm =
|
||||
0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r;
|
||||
scfg.head_penetration_mm = c.support_head_penetration.getFloat();
|
||||
scfg.head_width_mm = c.support_head_width.getFloat();
|
||||
scfg.object_elevation_mm = is_zero_elevation(c) ?
|
||||
0. : 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();
|
||||
scfg.pillar_connection_mode = c.support_pillar_connection_mode.value;
|
||||
scfg.ground_facing_only = c.support_buildplate_only.getBool();
|
||||
scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat();
|
||||
scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat();
|
||||
scfg.base_height_mm = c.support_base_height.getFloat();
|
||||
scfg.pillar_base_safety_distance_mm =
|
||||
c.support_base_safety_distance.getFloat() < EPSILON ?
|
||||
scfg.safety_distance_mm : c.support_base_safety_distance.getFloat();
|
||||
|
||||
scfg.max_bridges_on_pillar = unsigned(c.support_max_bridges_on_pillar.getInt());
|
||||
scfg.max_weight_on_model_support = c.support_max_weight_on_model.getFloat();
|
||||
break;
|
||||
}
|
||||
case sla::SupportTreeType::Branching:
|
||||
[[fallthrough]];
|
||||
case sla::SupportTreeType::Organic:{
|
||||
scfg.head_front_radius_mm = 0.5*c.branchingsupport_head_front_diameter.getFloat();
|
||||
double pillar_r = 0.5 * c.branchingsupport_pillar_diameter.getFloat();
|
||||
scfg.head_back_radius_mm = pillar_r;
|
||||
scfg.head_fallback_radius_mm =
|
||||
0.01 * c.branchingsupport_small_pillar_diameter_percent.getFloat() * pillar_r;
|
||||
scfg.head_penetration_mm = c.branchingsupport_head_penetration.getFloat();
|
||||
scfg.head_width_mm = c.branchingsupport_head_width.getFloat();
|
||||
scfg.object_elevation_mm = is_zero_elevation(c) ?
|
||||
0. : c.branchingsupport_object_elevation.getFloat();
|
||||
scfg.bridge_slope = c.branchingsupport_critical_angle.getFloat() * PI / 180.0 ;
|
||||
scfg.max_bridge_length_mm = c.branchingsupport_max_bridge_length.getFloat();
|
||||
scfg.max_pillar_link_distance_mm = c.branchingsupport_max_pillar_link_distance.getFloat();
|
||||
scfg.pillar_connection_mode = c.branchingsupport_pillar_connection_mode.value;
|
||||
scfg.ground_facing_only = c.branchingsupport_buildplate_only.getBool();
|
||||
scfg.pillar_widening_factor = c.branchingsupport_pillar_widening_factor.getFloat();
|
||||
scfg.base_radius_mm = 0.5*c.branchingsupport_base_diameter.getFloat();
|
||||
scfg.base_height_mm = c.branchingsupport_base_height.getFloat();
|
||||
scfg.pillar_base_safety_distance_mm =
|
||||
c.branchingsupport_base_safety_distance.getFloat() < EPSILON ?
|
||||
scfg.safety_distance_mm : c.branchingsupport_base_safety_distance.getFloat();
|
||||
|
||||
scfg.max_bridges_on_pillar = unsigned(c.branchingsupport_max_bridges_on_pillar.getInt());
|
||||
scfg.max_weight_on_model_support = c.branchingsupport_max_weight_on_model.getFloat();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return scfg;
|
||||
}
|
||||
@ -827,6 +862,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|
||||
|| opt_key == "supports_enable"
|
||||
|| opt_key == "support_tree_type"
|
||||
|| opt_key == "support_object_elevation"
|
||||
|| opt_key == "branchingsupport_object_elevation"
|
||||
|| opt_key == "pad_around_object"
|
||||
|| opt_key == "pad_around_object_everywhere"
|
||||
|| opt_key == "slice_closing_radius"
|
||||
@ -843,6 +879,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|
||||
|| opt_key == "support_pillar_diameter"
|
||||
|| opt_key == "support_pillar_widening_factor"
|
||||
|| opt_key == "support_small_pillar_diameter_percent"
|
||||
|| opt_key == "support_max_weight_on_model"
|
||||
|| opt_key == "support_max_bridges_on_pillar"
|
||||
|| opt_key == "support_pillar_connection_mode"
|
||||
|| opt_key == "support_buildplate_only"
|
||||
@ -852,6 +889,24 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|
||||
|| opt_key == "support_max_bridge_length"
|
||||
|| opt_key == "support_max_pillar_link_distance"
|
||||
|| opt_key == "support_base_safety_distance"
|
||||
|
||||
|| opt_key == "branchingsupport_head_front_diameter"
|
||||
|| opt_key == "branchingsupport_head_penetration"
|
||||
|| opt_key == "branchingsupport_head_width"
|
||||
|| opt_key == "branchingsupport_pillar_diameter"
|
||||
|| opt_key == "branchingsupport_pillar_widening_factor"
|
||||
|| opt_key == "branchingsupport_small_pillar_diameter_percent"
|
||||
|| opt_key == "branchingsupport_max_weight_on_model"
|
||||
|| opt_key == "branchingsupport_max_bridges_on_pillar"
|
||||
|| opt_key == "branchingsupport_pillar_connection_mode"
|
||||
|| opt_key == "branchingsupport_buildplate_only"
|
||||
|| opt_key == "branchingsupport_base_diameter"
|
||||
|| opt_key == "branchingsupport_base_height"
|
||||
|| opt_key == "branchingsupport_critical_angle"
|
||||
|| opt_key == "branchingsupport_max_bridge_length"
|
||||
|| opt_key == "branchingsupport_max_pillar_link_distance"
|
||||
|| opt_key == "branchingsupport_base_safety_distance"
|
||||
|
||||
|| opt_key == "pad_object_gap"
|
||||
) {
|
||||
steps.emplace_back(slaposSupportTree);
|
||||
|
@ -380,6 +380,8 @@ inline IntegerOnly<I, I> fast_round_up(double a)
|
||||
return a == 0.49999999999999994 ? I(0) : I(floor(a + 0.5));
|
||||
}
|
||||
|
||||
template<class T> using SamePair = std::pair<T, T>;
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // _libslic3r_h_
|
||||
|
@ -374,22 +374,42 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
|
||||
bool is_default_tree = treetype == sla::SupportTreeType::Default;
|
||||
bool is_branching_tree = treetype == sla::SupportTreeType::Branching;
|
||||
|
||||
toggle_field("support_head_front_diameter", supports_en);
|
||||
toggle_field("support_head_penetration", supports_en);
|
||||
toggle_field("support_head_width", supports_en);
|
||||
toggle_field("support_pillar_diameter", supports_en);
|
||||
toggle_field("support_small_pillar_diameter_percent", supports_en);
|
||||
toggle_field("support_tree_type", supports_en);
|
||||
|
||||
toggle_field("support_head_front_diameter", supports_en && is_default_tree);
|
||||
toggle_field("support_head_penetration", supports_en && is_default_tree);
|
||||
toggle_field("support_head_width", supports_en && is_default_tree);
|
||||
toggle_field("support_pillar_diameter", supports_en && is_default_tree);
|
||||
toggle_field("support_small_pillar_diameter_percent", supports_en && is_default_tree);
|
||||
toggle_field("support_max_bridges_on_pillar", supports_en && is_default_tree);
|
||||
toggle_field("support_pillar_connection_mode", supports_en && is_default_tree);
|
||||
toggle_field("support_tree_type", supports_en);
|
||||
toggle_field("support_buildplate_only", supports_en);
|
||||
toggle_field("support_base_diameter", supports_en);
|
||||
toggle_field("support_base_height", supports_en);
|
||||
toggle_field("support_base_safety_distance", supports_en);
|
||||
toggle_field("support_critical_angle", supports_en);
|
||||
toggle_field("support_max_bridge_length", supports_en);
|
||||
toggle_field("support_buildplate_only", supports_en && is_default_tree);
|
||||
toggle_field("support_base_diameter", supports_en && is_default_tree);
|
||||
toggle_field("support_base_height", supports_en && is_default_tree);
|
||||
toggle_field("support_base_safety_distance", supports_en && is_default_tree);
|
||||
toggle_field("support_critical_angle", supports_en && is_default_tree);
|
||||
toggle_field("support_max_bridge_length", supports_en && is_default_tree);
|
||||
toggle_field("support_max_pillar_link_distance", supports_en && is_default_tree);
|
||||
toggle_field("support_pillar_widening_factor", supports_en && is_branching_tree);
|
||||
toggle_field("support_pillar_widening_factor", false);
|
||||
toggle_field("support_max_weight_on_model", false);
|
||||
|
||||
toggle_field("branchingsupport_head_front_diameter", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_head_penetration", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_head_width", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_pillar_diameter", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_small_pillar_diameter_percent", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_max_bridges_on_pillar", false);
|
||||
toggle_field("branchingsupport_pillar_connection_mode", false);
|
||||
toggle_field("branchingsupport_buildplate_only", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_base_diameter", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_base_height", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_base_safety_distance", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_critical_angle", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_max_bridge_length", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_max_pillar_link_distance", false);
|
||||
toggle_field("branchingsupport_pillar_widening_factor", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_max_weight_on_model", supports_en && is_branching_tree);
|
||||
|
||||
toggle_field("support_points_density_relative", supports_en);
|
||||
toggle_field("support_points_minimal_distance", supports_en);
|
||||
|
||||
@ -406,7 +426,8 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
|
||||
|
||||
bool zero_elev = config->opt_bool("pad_around_object") && pad_en;
|
||||
|
||||
toggle_field("support_object_elevation", supports_en && !zero_elev);
|
||||
toggle_field("support_object_elevation", supports_en && is_default_tree && !zero_elev);
|
||||
toggle_field("branchingsupport_object_elevation", supports_en && is_branching_tree && !zero_elev);
|
||||
toggle_field("pad_object_gap", zero_elev);
|
||||
toggle_field("pad_around_object_everywhere", zero_elev);
|
||||
toggle_field("pad_object_connector_stride", zero_elev);
|
||||
|
@ -553,10 +553,13 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) :
|
||||
const bool supports_enable = selection == _("None") ? false : true;
|
||||
new_conf.set_key_value("supports_enable", new ConfigOptionBool(supports_enable));
|
||||
|
||||
std::string treetype = get_sla_suptree_prefix(new_conf);
|
||||
|
||||
if (selection == _("Everywhere"))
|
||||
new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(false));
|
||||
new_conf.set_key_value(treetype + "support_buildplate_only", new ConfigOptionBool(false));
|
||||
else if (selection == _("Support on build plate only"))
|
||||
new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(true));
|
||||
new_conf.set_key_value(treetype + "support_buildplate_only", new ConfigOptionBool(true));
|
||||
|
||||
}
|
||||
|
||||
tab->load_config(new_conf);
|
||||
|
@ -1038,8 +1038,11 @@ void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bo
|
||||
|
||||
static wxString support_combo_value_for_config(const DynamicPrintConfig &config, bool is_fff)
|
||||
{
|
||||
std::string slatree = is_fff ? "" : get_sla_suptree_prefix(config);
|
||||
|
||||
const std::string support = is_fff ? "support_material" : "supports_enable";
|
||||
const std::string buildplate_only = is_fff ? "support_material_buildplate_only" : "support_buildplate_only";
|
||||
const std::string buildplate_only = is_fff ? "support_material_buildplate_only" : slatree + "support_buildplate_only";
|
||||
|
||||
return
|
||||
! config.opt_bool(support) ?
|
||||
_("None") :
|
||||
@ -1082,7 +1085,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
|
||||
|
||||
if (is_fff ?
|
||||
(opt_key == "support_material" || opt_key == "support_material_auto" || opt_key == "support_material_buildplate_only") :
|
||||
(opt_key == "supports_enable" || opt_key == "support_buildplate_only"))
|
||||
(opt_key == "supports_enable" || opt_key == "support_tree_type" || opt_key == get_sla_suptree_prefix(*m_config) + "support_buildplate_only"))
|
||||
og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff));
|
||||
|
||||
if (! is_fff && (opt_key == "pad_enable" || opt_key == "pad_around_object"))
|
||||
@ -4821,6 +4824,60 @@ void TabSLAMaterial::update()
|
||||
wxGetApp().mainframe->on_config_changed(m_config);
|
||||
}
|
||||
|
||||
static void add_options_into_line(ConfigOptionsGroupShp &optgroup,
|
||||
const std::vector<SamePair<std::string>> &prefixes,
|
||||
const std::string &optkey)
|
||||
{
|
||||
auto opt = optgroup->get_option(prefixes.front().first + optkey);
|
||||
Line line{ opt.opt.label, "" };
|
||||
line.full_width = 1;
|
||||
for (auto &prefix : prefixes) {
|
||||
opt = optgroup->get_option(prefix.first + optkey);
|
||||
opt.opt.label = prefix.second;
|
||||
opt.opt.width = 12; // TODO
|
||||
line.append_option(opt);
|
||||
}
|
||||
optgroup->append_line(line);
|
||||
}
|
||||
|
||||
void TabSLAPrint::build_sla_support_params(const std::vector<SamePair<std::string>> &prefixes,
|
||||
const Slic3r::GUI::PageShp &page)
|
||||
{
|
||||
|
||||
auto optgroup = page->new_optgroup(L("Support head"));
|
||||
add_options_into_line(optgroup, prefixes, "support_head_front_diameter");
|
||||
add_options_into_line(optgroup, prefixes, "support_head_penetration");
|
||||
add_options_into_line(optgroup, prefixes, "support_head_width");
|
||||
|
||||
optgroup = page->new_optgroup(L("Support pillar"));
|
||||
add_options_into_line(optgroup, prefixes, "support_pillar_diameter");
|
||||
add_options_into_line(optgroup, prefixes, "support_small_pillar_diameter_percent");
|
||||
add_options_into_line(optgroup, prefixes, "support_max_bridges_on_pillar");
|
||||
|
||||
add_options_into_line(optgroup, prefixes, "support_pillar_connection_mode");
|
||||
add_options_into_line(optgroup, prefixes, "support_buildplate_only");
|
||||
add_options_into_line(optgroup, prefixes, "support_pillar_widening_factor");
|
||||
add_options_into_line(optgroup, prefixes, "support_max_weight_on_model");
|
||||
add_options_into_line(optgroup, prefixes, "support_base_diameter");
|
||||
add_options_into_line(optgroup, prefixes, "support_base_height");
|
||||
add_options_into_line(optgroup, prefixes, "support_base_safety_distance");
|
||||
|
||||
// Mirrored parameter from Pad page for toggling elevation on the same page
|
||||
add_options_into_line(optgroup, prefixes, "support_object_elevation");
|
||||
|
||||
Line line{ "", "" };
|
||||
line.full_width = 1;
|
||||
line.widget = [this](wxWindow* parent) {
|
||||
return description_line_widget(parent, &m_support_object_elevation_description_line);
|
||||
};
|
||||
optgroup->append_line(line);
|
||||
|
||||
optgroup = page->new_optgroup(L("Connection of the support sticks and junctions"));
|
||||
add_options_into_line(optgroup, prefixes, "support_critical_angle");
|
||||
add_options_into_line(optgroup, prefixes, "support_max_bridge_length");
|
||||
add_options_into_line(optgroup, prefixes, "support_max_pillar_link_distance");
|
||||
}
|
||||
|
||||
void TabSLAPrint::build()
|
||||
{
|
||||
m_presets = &m_preset_bundle->sla_prints;
|
||||
@ -4833,41 +4890,12 @@ void TabSLAPrint::build()
|
||||
optgroup->append_single_option_line("faded_layers");
|
||||
|
||||
page = add_options_page(L("Supports"), "support"/*"sla_supports"*/);
|
||||
|
||||
optgroup = page->new_optgroup(L("Supports"));
|
||||
optgroup->append_single_option_line("supports_enable");
|
||||
optgroup->append_single_option_line("support_tree_type");
|
||||
|
||||
optgroup = page->new_optgroup(L("Support head"));
|
||||
optgroup->append_single_option_line("support_head_front_diameter");
|
||||
optgroup->append_single_option_line("support_head_penetration");
|
||||
optgroup->append_single_option_line("support_head_width");
|
||||
|
||||
optgroup = page->new_optgroup(L("Support pillar"));
|
||||
optgroup->append_single_option_line("support_pillar_diameter");
|
||||
optgroup->append_single_option_line("support_small_pillar_diameter_percent");
|
||||
optgroup->append_single_option_line("support_max_bridges_on_pillar");
|
||||
|
||||
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");
|
||||
optgroup->append_single_option_line("support_base_diameter");
|
||||
optgroup->append_single_option_line("support_base_height");
|
||||
optgroup->append_single_option_line("support_base_safety_distance");
|
||||
|
||||
// Mirrored parameter from Pad page for toggling elevation on the same page
|
||||
optgroup->append_single_option_line("support_object_elevation");
|
||||
|
||||
Line line{ "", "" };
|
||||
line.full_width = 1;
|
||||
line.widget = [this](wxWindow* parent) {
|
||||
return description_line_widget(parent, &m_support_object_elevation_description_line);
|
||||
};
|
||||
optgroup->append_line(line);
|
||||
|
||||
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");
|
||||
build_sla_support_params({{"", "Default"}, {"branching", "Branching"}}, page);
|
||||
|
||||
optgroup = page->new_optgroup(L("Automatic generation"));
|
||||
optgroup->append_single_option_line("support_points_density_relative");
|
||||
|
@ -339,7 +339,7 @@ public:
|
||||
|
||||
void on_roll_back_value(const bool to_sys = false);
|
||||
|
||||
PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false);
|
||||
PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false);
|
||||
static wxString translate_category(const wxString& title, Preset::Type preset_type);
|
||||
|
||||
virtual void OnActivate();
|
||||
@ -526,6 +526,12 @@ public:
|
||||
|
||||
class TabSLAPrint : public Tab
|
||||
{
|
||||
// Methods are a vector of method prefix -> method label pairs
|
||||
// method prefix is the prefix whith which all the config values are prefixed
|
||||
// for a particular method. The label is the friendly name for the method
|
||||
void build_sla_support_params(const std::vector<SamePair<std::string>> &methods,
|
||||
const Slic3r::GUI::PageShp &page);
|
||||
|
||||
public:
|
||||
TabSLAPrint(wxBookCtrlBase* parent) :
|
||||
Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_SLA_PRINT) {}
|
||||
|
76
tests/data/U_overhang.obj
Normal file
76
tests/data/U_overhang.obj
Normal file
@ -0,0 +1,76 @@
|
||||
####
|
||||
#
|
||||
# OBJ File Generated by Meshlab
|
||||
#
|
||||
####
|
||||
# Object U_overhang.obj
|
||||
#
|
||||
# Vertices: 16
|
||||
# Faces: 28
|
||||
#
|
||||
####
|
||||
vn 1.570797 1.570796 1.570796
|
||||
v 10.000000 10.000000 11.000000
|
||||
vn 4.712389 1.570796 -1.570796
|
||||
v 10.000000 1.000000 10.000000
|
||||
vn 1.570796 1.570796 -1.570796
|
||||
v 10.000000 10.000000 10.000000
|
||||
vn 1.570796 -1.570796 1.570796
|
||||
v 10.000000 0.000000 11.000000
|
||||
vn 4.712389 1.570796 1.570796
|
||||
v 10.000000 1.000000 1.000000
|
||||
vn 1.570797 1.570796 -1.570796
|
||||
v 10.000000 10.000000 0.000000
|
||||
vn 1.570796 1.570796 1.570796
|
||||
v 10.000000 10.000000 1.000000
|
||||
vn 1.570796 -1.570796 -1.570796
|
||||
v 10.000000 0.000000 0.000000
|
||||
vn -1.570796 1.570796 1.570796
|
||||
v 0.000000 10.000000 1.000000
|
||||
vn -4.712389 1.570796 1.570796
|
||||
v 0.000000 1.000000 1.000000
|
||||
vn -1.570796 -1.570796 -1.570796
|
||||
v 0.000000 0.000000 0.000000
|
||||
vn -1.570797 1.570796 -1.570796
|
||||
v 0.000000 10.000000 0.000000
|
||||
vn -4.712389 1.570796 -1.570796
|
||||
v 0.000000 1.000000 10.000000
|
||||
vn -1.570797 1.570796 1.570796
|
||||
v 0.000000 10.000000 11.000000
|
||||
vn -1.570796 1.570796 -1.570796
|
||||
v 0.000000 10.000000 10.000000
|
||||
vn -1.570796 -1.570796 1.570796
|
||||
v 0.000000 0.000000 11.000000
|
||||
# 16 vertices, 0 vertices normals
|
||||
|
||||
f 1//1 2//2 3//3
|
||||
f 2//2 4//4 5//5
|
||||
f 4//4 2//2 1//1
|
||||
f 5//5 6//6 7//7
|
||||
f 5//5 8//8 6//6
|
||||
f 8//8 5//5 4//4
|
||||
f 9//9 5//5 7//7
|
||||
f 5//5 9//9 10//10
|
||||
f 11//11 6//6 8//8
|
||||
f 6//6 11//11 12//12
|
||||
f 12//12 10//10 9//9
|
||||
f 10//10 11//11 13//13
|
||||
f 11//11 10//10 12//12
|
||||
f 13//13 14//14 15//15
|
||||
f 13//13 16//16 14//14
|
||||
f 16//16 13//13 11//11
|
||||
f 6//6 9//9 7//7
|
||||
f 9//9 6//6 12//12
|
||||
f 11//11 4//4 16//16
|
||||
f 4//4 11//11 8//8
|
||||
f 13//13 3//3 2//2
|
||||
f 3//3 13//13 15//15
|
||||
f 5//5 13//13 2//2
|
||||
f 13//13 5//5 10//10
|
||||
f 14//14 4//4 1//1
|
||||
f 4//4 14//14 16//16
|
||||
f 3//3 14//14 1//1
|
||||
f 14//14 3//3 15//15
|
||||
# 28 faces, 0 coords texture
|
||||
|
||||
# End of File
|
@ -4,6 +4,7 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp
|
||||
sla_test_utils.hpp sla_test_utils.cpp
|
||||
sla_supptgen_tests.cpp
|
||||
sla_raycast_tests.cpp
|
||||
sla_supptreeutils_tests.cpp
|
||||
sla_archive_readwrite_tests.cpp)
|
||||
|
||||
# mold linker for successful linking needs also to link TBB library and link it before libslic3r.
|
||||
|
@ -1,4 +1,3 @@
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <random>
|
||||
#include <numeric>
|
||||
@ -32,13 +31,6 @@ const char *const SUPPORT_TEST_MODELS[] = {
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Pillar pairhash should be unique", "[SLASupportGeneration]") {
|
||||
test_pairhash<int, int>();
|
||||
test_pairhash<int, long>();
|
||||
test_pairhash<unsigned, unsigned>();
|
||||
test_pairhash<unsigned, unsigned long>();
|
||||
}
|
||||
|
||||
TEST_CASE("Support point generator should be deterministic if seeded",
|
||||
"[SLASupportGeneration], [SLAPointGen]") {
|
||||
TriangleMesh mesh = load_model("A_upsidedown.obj");
|
||||
@ -173,106 +165,6 @@ TEST_CASE("DefaultSupports::FloorSupportsDoNotPierceModel", "[SLASupportGenerati
|
||||
// for (auto &fname: SUPPORT_TEST_MODELS) test_supports(fname, supportcfg);
|
||||
//}
|
||||
|
||||
bool is_outside_support_cone(const Vec3f &supp, const Vec3f &pt, float angle)
|
||||
{
|
||||
Vec3d D = (pt - supp).cast<double>();
|
||||
double dot_sq = -D.z() * std::abs(-D.z());
|
||||
|
||||
return dot_sq <
|
||||
D.squaredNorm() * std::cos(angle) * std::abs(std::cos(angle));
|
||||
}
|
||||
|
||||
TEST_CASE("BranchingSupports::MergePointFinder", "[SLASupportGeneration][Branching]") {
|
||||
SECTION("Identical points have the same merge point") {
|
||||
Vec3f a{0.f, 0.f, 0.f}, b = a;
|
||||
auto slope = float(PI / 4.);
|
||||
|
||||
auto mergept = branchingtree::find_merge_pt(a, b, slope);
|
||||
|
||||
REQUIRE(bool(mergept));
|
||||
REQUIRE((*mergept - b).norm() < EPSILON);
|
||||
REQUIRE((*mergept - a).norm() < EPSILON);
|
||||
}
|
||||
|
||||
// ^ Z
|
||||
// | a *
|
||||
// |
|
||||
// | b * <= mergept
|
||||
SECTION("Points at different heights have the lower point as mergepoint") {
|
||||
Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 0.f, -1.f};
|
||||
auto slope = float(PI / 4.);
|
||||
|
||||
auto mergept = branchingtree::find_merge_pt(a, b, slope);
|
||||
|
||||
REQUIRE(bool(mergept));
|
||||
REQUIRE((*mergept - b).squaredNorm() < 2 * EPSILON);
|
||||
}
|
||||
|
||||
// -|---------> X
|
||||
// a b
|
||||
// * *
|
||||
// * <= mergept
|
||||
SECTION("Points at different X have mergept in the middle at lower Z") {
|
||||
Vec3f a{0.f, 0.f, 0.f}, b = {1.f, 0.f, 0.f};
|
||||
auto slope = float(PI / 4.);
|
||||
|
||||
auto mergept = branchingtree::find_merge_pt(a, b, slope);
|
||||
|
||||
REQUIRE(bool(mergept));
|
||||
|
||||
// Distance of mergept should be equal from both input points
|
||||
float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm());
|
||||
|
||||
REQUIRE(D < EPSILON);
|
||||
REQUIRE(!is_outside_support_cone(a, *mergept, slope));
|
||||
REQUIRE(!is_outside_support_cone(b, *mergept, slope));
|
||||
}
|
||||
|
||||
// -|---------> Y
|
||||
// a b
|
||||
// * *
|
||||
// * <= mergept
|
||||
SECTION("Points at different Y have mergept in the middle at lower Z") {
|
||||
Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 1.f, 0.f};
|
||||
auto slope = float(PI / 4.);
|
||||
|
||||
auto mergept = branchingtree::find_merge_pt(a, b, slope);
|
||||
|
||||
REQUIRE(bool(mergept));
|
||||
|
||||
// Distance of mergept should be equal from both input points
|
||||
float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm());
|
||||
|
||||
REQUIRE(D < EPSILON);
|
||||
REQUIRE(!is_outside_support_cone(a, *mergept, slope));
|
||||
REQUIRE(!is_outside_support_cone(b, *mergept, slope));
|
||||
}
|
||||
|
||||
SECTION("Points separated by less than critical angle have the lower point as mergept") {
|
||||
Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -2.f};
|
||||
auto slope = float(PI / 4.);
|
||||
|
||||
auto mergept = branchingtree::find_merge_pt(a, b, slope);
|
||||
|
||||
REQUIRE(bool(mergept));
|
||||
REQUIRE((*mergept - b).norm() < 2 * EPSILON);
|
||||
}
|
||||
|
||||
// -|----------------------------> Y
|
||||
// a b
|
||||
// * * <= mergept *
|
||||
//
|
||||
SECTION("Points at same height have mergepoint in the middle if critical angle is zero ") {
|
||||
Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -1.f};
|
||||
auto slope = EPSILON;
|
||||
|
||||
auto mergept = branchingtree::find_merge_pt(a, b, slope);
|
||||
|
||||
REQUIRE(bool(mergept));
|
||||
Vec3f middle = (b + a) / 2.;
|
||||
REQUIRE((*mergept - middle).norm() < 4 * EPSILON);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BranchingSupports::ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration][Branching]") {
|
||||
|
||||
|
377
tests/sla_print/sla_supptreeutils_tests.cpp
Normal file
377
tests/sla_print/sla_supptreeutils_tests.cpp
Normal file
@ -0,0 +1,377 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <test_utils.hpp>
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include "libslic3r/Execution/ExecutionSeq.hpp"
|
||||
#include "libslic3r/SLA/SupportTreeUtils.hpp"
|
||||
#include "libslic3r/SLA/SupportTreeUtilsLegacy.hpp"
|
||||
|
||||
// Test pair hash for 'nums' random number pairs.
|
||||
template <class I, class II> void test_pairhash()
|
||||
{
|
||||
const constexpr size_t nums = 1000;
|
||||
I A[nums] = {0}, B[nums] = {0};
|
||||
std::unordered_set<I> CH;
|
||||
std::unordered_map<II, std::pair<I, I>> ints;
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
|
||||
const I Ibits = int(sizeof(I) * CHAR_BIT);
|
||||
const II IIbits = int(sizeof(II) * CHAR_BIT);
|
||||
|
||||
int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits;
|
||||
if (std::is_signed<I>::value) bits -= 1;
|
||||
const I Imin = 0;
|
||||
const I Imax = I(std::pow(2., bits) - 1);
|
||||
|
||||
std::uniform_int_distribution<I> dis(Imin, Imax);
|
||||
|
||||
for (size_t i = 0; i < nums;) {
|
||||
I a = dis(gen);
|
||||
if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; }
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < nums;) {
|
||||
I b = dis(gen);
|
||||
if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; }
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < nums; ++i) {
|
||||
I a = A[i], b = B[i];
|
||||
|
||||
REQUIRE(a != b);
|
||||
|
||||
II hash_ab = Slic3r::sla::pairhash<I, II>(a, b);
|
||||
II hash_ba = Slic3r::sla::pairhash<I, II>(b, a);
|
||||
REQUIRE(hash_ab == hash_ba);
|
||||
|
||||
auto it = ints.find(hash_ab);
|
||||
|
||||
if (it != ints.end()) {
|
||||
REQUIRE((
|
||||
(it->second.first == a && it->second.second == b) ||
|
||||
(it->second.first == b && it->second.second == a)
|
||||
));
|
||||
} else
|
||||
ints[hash_ab] = std::make_pair(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Pillar pairhash should be unique", "[suptreeutils]") {
|
||||
test_pairhash<int, int>();
|
||||
test_pairhash<int, long>();
|
||||
test_pairhash<unsigned, unsigned>();
|
||||
test_pairhash<unsigned, unsigned long>();
|
||||
}
|
||||
|
||||
static void eval_ground_conn(const Slic3r::sla::GroundConnection &conn,
|
||||
const Slic3r::sla::SupportableMesh &sm,
|
||||
const Slic3r::sla::Junction &j,
|
||||
double end_r,
|
||||
const std::string &stl_fname = "output.stl")
|
||||
{
|
||||
using namespace Slic3r;
|
||||
|
||||
//#ifndef NDEBUG
|
||||
|
||||
sla::SupportTreeBuilder builder;
|
||||
|
||||
if (!conn)
|
||||
builder.add_junction(j);
|
||||
|
||||
sla::build_ground_connection(builder, sm, conn);
|
||||
|
||||
indexed_triangle_set mesh = *sm.emesh.get_triangle_mesh();
|
||||
its_merge(mesh, builder.merged_mesh());
|
||||
|
||||
its_write_stl_ascii(stl_fname.c_str(), "stl_fname", mesh);
|
||||
//#endif
|
||||
|
||||
REQUIRE(bool(conn));
|
||||
|
||||
// The route should include the source and one avoidance junction.
|
||||
REQUIRE(conn.path.size() == 2);
|
||||
|
||||
// Check if the radius increases with each node
|
||||
REQUIRE(conn.path.front().r < conn.path.back().r);
|
||||
REQUIRE(conn.path.back().r < conn.pillar_base->r_top);
|
||||
|
||||
// The end radius and the pillar base's upper radius should match
|
||||
REQUIRE(conn.pillar_base->r_top == Approx(end_r));
|
||||
}
|
||||
|
||||
TEST_CASE("Pillar search dumb case", "[suptreeutils]") {
|
||||
using namespace Slic3r;
|
||||
|
||||
constexpr double FromR = 0.5;
|
||||
auto j = sla::Junction{Vec3d::Zero(), FromR};
|
||||
|
||||
SECTION("with empty mesh") {
|
||||
sla::SupportableMesh sm{indexed_triangle_set{},
|
||||
sla::SupportPoints{},
|
||||
sla::SupportTreeConfig{}};
|
||||
|
||||
constexpr double EndR = 1.;
|
||||
sla::GroundConnection conn =
|
||||
sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN);
|
||||
|
||||
REQUIRE(conn);
|
||||
// REQUIRE(conn.path.size() == 1);
|
||||
REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm)));
|
||||
}
|
||||
|
||||
SECTION("with zero R source and destination") {
|
||||
sla::SupportableMesh sm{indexed_triangle_set{},
|
||||
sla::SupportPoints{},
|
||||
sla::SupportTreeConfig{}};
|
||||
|
||||
j.r = 0.;
|
||||
constexpr double EndR = 0.;
|
||||
sla::GroundConnection conn =
|
||||
sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN);
|
||||
|
||||
REQUIRE(conn);
|
||||
// REQUIRE(conn.path.size() == 1);
|
||||
REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm)));
|
||||
REQUIRE(conn.pillar_base->r_top == Approx(0.));
|
||||
}
|
||||
|
||||
SECTION("with zero init direction") {
|
||||
sla::SupportableMesh sm{indexed_triangle_set{},
|
||||
sla::SupportPoints{},
|
||||
sla::SupportTreeConfig{}};
|
||||
|
||||
constexpr double EndR = 1.;
|
||||
Vec3d init_dir = Vec3d::Zero();
|
||||
sla::GroundConnection conn =
|
||||
sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, init_dir);
|
||||
|
||||
REQUIRE(conn);
|
||||
// REQUIRE(conn.path.size() == 1);
|
||||
REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Avoid disk below junction", "[suptreeutils]")
|
||||
{
|
||||
// In this test there will be a disk mesh with some radius, centered at
|
||||
// (0, 0, 0) and above the disk, a junction from which the support pillar
|
||||
// should be routed. The algorithm needs to find an avoidance route.
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
constexpr double FromRadius = .5;
|
||||
constexpr double EndRadius = 1.;
|
||||
constexpr double CylRadius = 4.;
|
||||
constexpr double CylHeight = 1.;
|
||||
|
||||
sla::SupportTreeConfig cfg;
|
||||
|
||||
indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight);
|
||||
|
||||
// 2.5 * CyRadius height should be enough to be able to insert a bridge
|
||||
// with 45 degree tilt above the disk.
|
||||
sla::Junction j{Vec3d{0., 0., 2.5 * CylRadius}, FromRadius};
|
||||
|
||||
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
|
||||
|
||||
SECTION("with elevation") {
|
||||
|
||||
sla::GroundConnection conn =
|
||||
sla::deepsearch_ground_connection(ex_tbb, sm, j, EndRadius, sla::DOWN);
|
||||
|
||||
eval_ground_conn(conn, sm, j, EndRadius, "disk.stl");
|
||||
|
||||
// Check if the avoidance junction is indeed outside of the disk barrier's
|
||||
// edge.
|
||||
auto p = conn.path.back().pos;
|
||||
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
|
||||
REQUIRE(pR + FromRadius > CylRadius);
|
||||
}
|
||||
|
||||
SECTION("without elevation") {
|
||||
sm.cfg.object_elevation_mm = 0.;
|
||||
|
||||
sla::GroundConnection conn =
|
||||
sla::deepsearch_ground_connection(ex_tbb, sm, j, EndRadius, sla::DOWN);
|
||||
|
||||
eval_ground_conn(conn, sm, j, EndRadius, "disk_ze.stl");
|
||||
|
||||
// Check if the avoidance junction is indeed outside of the disk barrier's
|
||||
// edge.
|
||||
auto p = conn.path.back().pos;
|
||||
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
|
||||
REQUIRE(pR + FromRadius > CylRadius);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]")
|
||||
{
|
||||
// In this test there will be a disk mesh with some radius, centered at
|
||||
// (0, 0, 0) and above the disk, a junction from which the support pillar
|
||||
// should be routed. The algorithm needs to find an avoidance route.
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
constexpr double FromRadius = .5;
|
||||
constexpr double EndRadius = 1.;
|
||||
constexpr double CylRadius = 4.;
|
||||
constexpr double CylHeight = 1.;
|
||||
constexpr double JElevX = 2.5;
|
||||
|
||||
sla::SupportTreeConfig cfg;
|
||||
|
||||
indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight);
|
||||
indexed_triangle_set wall = its_make_cube(1., 2 * CylRadius, JElevX * CylRadius);
|
||||
its_translate(wall, Vec3f{float(FromRadius), -float(CylRadius), 0.f});
|
||||
its_merge(disk, wall);
|
||||
|
||||
// 2.5 * CyRadius height should be enough to be able to insert a bridge
|
||||
// with 45 degree tilt above the disk.
|
||||
sla::Junction j{Vec3d{0., 0., JElevX * CylRadius}, FromRadius};
|
||||
|
||||
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
|
||||
|
||||
SECTION("with elevation") {
|
||||
sla::GroundConnection conn =
|
||||
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
||||
|
||||
eval_ground_conn(conn, sm, j, EndRadius, "disk_with_barrier.stl");
|
||||
|
||||
// Check if the avoidance junction is indeed outside of the disk barrier's
|
||||
// edge.
|
||||
auto p = conn.path.back().pos;
|
||||
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
|
||||
REQUIRE(pR + FromRadius > CylRadius);
|
||||
}
|
||||
|
||||
SECTION("without elevation") {
|
||||
sm.cfg.object_elevation_mm = 0.;
|
||||
|
||||
sla::GroundConnection conn =
|
||||
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
||||
|
||||
eval_ground_conn(conn, sm, j, EndRadius, "disk_with_barrier_ze.stl");
|
||||
|
||||
// Check if the avoidance junction is indeed outside of the disk barrier's
|
||||
// edge.
|
||||
auto p = conn.path.back().pos;
|
||||
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
|
||||
REQUIRE(pR + FromRadius > CylRadius);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Find ground route just above ground", "[suptreeutils]") {
|
||||
using namespace Slic3r;
|
||||
|
||||
sla::SupportTreeConfig cfg;
|
||||
cfg.object_elevation_mm = 0.;
|
||||
|
||||
sla::Junction j{Vec3d{0., 0., 2. * cfg.head_back_radius_mm}, cfg.head_back_radius_mm};
|
||||
|
||||
sla::SupportableMesh sm{{}, sla::SupportPoints{}, cfg};
|
||||
|
||||
sla::GroundConnection conn =
|
||||
sla::deepsearch_ground_connection(ex_seq, sm, j, Geometry::spheric_to_dir(3 * PI/ 4, PI));
|
||||
|
||||
REQUIRE(conn);
|
||||
|
||||
REQUIRE(conn.pillar_base->pos.z() >= Approx(ground_level(sm)));
|
||||
}
|
||||
|
||||
TEST_CASE("BranchingSupports::MergePointFinder", "[suptreeutils]") {
|
||||
using namespace Slic3r;
|
||||
|
||||
SECTION("Identical points have the same merge point") {
|
||||
Vec3f a{0.f, 0.f, 0.f}, b = a;
|
||||
auto slope = float(PI / 4.);
|
||||
|
||||
auto mergept = sla::find_merge_pt(a, b, slope);
|
||||
|
||||
REQUIRE(bool(mergept));
|
||||
REQUIRE((*mergept - b).norm() < EPSILON);
|
||||
REQUIRE((*mergept - a).norm() < EPSILON);
|
||||
}
|
||||
|
||||
// ^ Z
|
||||
// | a *
|
||||
// |
|
||||
// | b * <= mergept
|
||||
SECTION("Points at different heights have the lower point as mergepoint") {
|
||||
Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 0.f, -1.f};
|
||||
auto slope = float(PI / 4.);
|
||||
|
||||
auto mergept = sla::find_merge_pt(a, b, slope);
|
||||
|
||||
REQUIRE(bool(mergept));
|
||||
REQUIRE((*mergept - b).squaredNorm() < 2 * EPSILON);
|
||||
}
|
||||
|
||||
// -|---------> X
|
||||
// a b
|
||||
// * *
|
||||
// * <= mergept
|
||||
SECTION("Points at different X have mergept in the middle at lower Z") {
|
||||
Vec3f a{0.f, 0.f, 0.f}, b = {1.f, 0.f, 0.f};
|
||||
auto slope = float(PI / 4.);
|
||||
|
||||
auto mergept = sla::find_merge_pt(a, b, slope);
|
||||
|
||||
REQUIRE(bool(mergept));
|
||||
|
||||
// Distance of mergept should be equal from both input points
|
||||
float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm());
|
||||
|
||||
REQUIRE(D < EPSILON);
|
||||
REQUIRE(!sla::is_outside_support_cone(a, *mergept, slope));
|
||||
REQUIRE(!sla::is_outside_support_cone(b, *mergept, slope));
|
||||
}
|
||||
|
||||
// -|---------> Y
|
||||
// a b
|
||||
// * *
|
||||
// * <= mergept
|
||||
SECTION("Points at different Y have mergept in the middle at lower Z") {
|
||||
Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 1.f, 0.f};
|
||||
auto slope = float(PI / 4.);
|
||||
|
||||
auto mergept = sla::find_merge_pt(a, b, slope);
|
||||
|
||||
REQUIRE(bool(mergept));
|
||||
|
||||
// Distance of mergept should be equal from both input points
|
||||
float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm());
|
||||
|
||||
REQUIRE(D < EPSILON);
|
||||
REQUIRE(!sla::is_outside_support_cone(a, *mergept, slope));
|
||||
REQUIRE(!sla::is_outside_support_cone(b, *mergept, slope));
|
||||
}
|
||||
|
||||
SECTION("Points separated by less than critical angle have the lower point as mergept") {
|
||||
Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -2.f};
|
||||
auto slope = float(PI / 4.);
|
||||
|
||||
auto mergept = sla::find_merge_pt(a, b, slope);
|
||||
|
||||
REQUIRE(bool(mergept));
|
||||
REQUIRE((*mergept - b).norm() < 2 * EPSILON);
|
||||
}
|
||||
|
||||
// -|----------------------------> Y
|
||||
// a b
|
||||
// * * <= mergept *
|
||||
//
|
||||
SECTION("Points at same height have mergepoint in the middle if critical angle is zero ") {
|
||||
Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -1.f};
|
||||
auto slope = EPSILON;
|
||||
|
||||
auto mergept = sla::find_merge_pt(a, b, slope);
|
||||
|
||||
REQUIRE(bool(mergept));
|
||||
Vec3f middle = (b + a) / 2.;
|
||||
REQUIRE((*mergept - middle).norm() < 4 * EPSILON);
|
||||
}
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ void test_supports(const std::string &obj_filename,
|
||||
|
||||
// Create the special index-triangle mesh with spatial indexing which
|
||||
// is the input of the support point and support mesh generators
|
||||
AABBMesh emesh{mesh};
|
||||
sla::SupportableMesh sm{mesh.its, {}, supportcfg};
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
if (hollowingcfg.enabled)
|
||||
@ -130,23 +130,23 @@ void test_supports(const std::string &obj_filename,
|
||||
// Create the support point generator
|
||||
sla::SupportPointGenerator::Config autogencfg;
|
||||
autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm);
|
||||
sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}};
|
||||
sla::SupportPointGenerator point_gen{sm.emesh, autogencfg, [] {}, [](int) {}};
|
||||
|
||||
point_gen.seed(0); // Make the test repeatable
|
||||
point_gen.execute(out.model_slices, out.slicegrid);
|
||||
|
||||
// Get the calculated support points.
|
||||
std::vector<sla::SupportPoint> support_points = point_gen.output();
|
||||
sm.pts = point_gen.output();
|
||||
|
||||
int validityflags = ASSUME_NO_REPAIR;
|
||||
|
||||
// If there is no elevation, support points shall be removed from the
|
||||
// bottom of the object.
|
||||
if (std::abs(supportcfg.object_elevation_mm) < EPSILON) {
|
||||
sla::remove_bottom_points(support_points, zmin + supportcfg.base_height_mm);
|
||||
sla::remove_bottom_points(sm.pts, zmin + supportcfg.base_height_mm);
|
||||
} else {
|
||||
// Should be support points at least on the bottom of the model
|
||||
REQUIRE_FALSE(support_points.empty());
|
||||
REQUIRE_FALSE(sm.pts.empty());
|
||||
|
||||
// Also the support mesh should not be empty.
|
||||
validityflags |= ASSUME_NO_EMPTY;
|
||||
@ -154,7 +154,6 @@ void test_supports(const std::string &obj_filename,
|
||||
|
||||
// Generate the actual support tree
|
||||
sla::SupportTreeBuilder treebuilder;
|
||||
sla::SupportableMesh sm{emesh, support_points, supportcfg};
|
||||
|
||||
switch (sm.cfg.tree_type) {
|
||||
case sla::SupportTreeType::Default: {
|
||||
@ -182,6 +181,18 @@ void test_supports(const std::string &obj_filename,
|
||||
if (std::abs(supportcfg.object_elevation_mm) < EPSILON)
|
||||
allowed_zmin = zmin - 2 * supportcfg.head_back_radius_mm;
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (!(obb.min.z() >= Approx(allowed_zmin)) || !(obb.max.z() <= Approx(zmax)))
|
||||
{
|
||||
indexed_triangle_set its;
|
||||
treebuilder.retrieve_full_mesh(its);
|
||||
TriangleMesh m{its};
|
||||
m.merge(mesh);
|
||||
m.WriteOBJFile((Catch::getResultCapture().getCurrentTestName() + "_" +
|
||||
obj_filename).c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
REQUIRE(obb.min.z() >= Approx(allowed_zmin));
|
||||
REQUIRE(obb.max.z() <= Approx(zmax));
|
||||
|
||||
|
@ -14,14 +14,12 @@
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/SLA/Pad.hpp"
|
||||
#include "libslic3r/SLA/SupportTreeBuilder.hpp"
|
||||
#include "libslic3r/SLA/SupportTreeUtils.hpp"
|
||||
#include "libslic3r/SLA/SupportPointGenerator.hpp"
|
||||
#include "libslic3r/SLA/AGGRaster.hpp"
|
||||
#include "libslic3r/SLA/ConcaveHull.hpp"
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
|
||||
#include "libslic3r/SVG.hpp"
|
||||
#include "libslic3r/Format/OBJ.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
@ -111,58 +109,6 @@ inline void test_support_model_collision(
|
||||
test_support_model_collision(obj_filename, input_supportcfg, hcfg, {});
|
||||
}
|
||||
|
||||
// Test pair hash for 'nums' random number pairs.
|
||||
template <class I, class II> void test_pairhash()
|
||||
{
|
||||
const constexpr size_t nums = 1000;
|
||||
I A[nums] = {0}, B[nums] = {0};
|
||||
std::unordered_set<I> CH;
|
||||
std::unordered_map<II, std::pair<I, I>> ints;
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
|
||||
const I Ibits = int(sizeof(I) * CHAR_BIT);
|
||||
const II IIbits = int(sizeof(II) * CHAR_BIT);
|
||||
|
||||
int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits;
|
||||
if (std::is_signed<I>::value) bits -= 1;
|
||||
const I Imin = 0;
|
||||
const I Imax = I(std::pow(2., bits) - 1);
|
||||
|
||||
std::uniform_int_distribution<I> dis(Imin, Imax);
|
||||
|
||||
for (size_t i = 0; i < nums;) {
|
||||
I a = dis(gen);
|
||||
if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; }
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < nums;) {
|
||||
I b = dis(gen);
|
||||
if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; }
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < nums; ++i) {
|
||||
I a = A[i], b = B[i];
|
||||
|
||||
REQUIRE(a != b);
|
||||
|
||||
II hash_ab = sla::pairhash<I, II>(a, b);
|
||||
II hash_ba = sla::pairhash<I, II>(b, a);
|
||||
REQUIRE(hash_ab == hash_ba);
|
||||
|
||||
auto it = ints.find(hash_ab);
|
||||
|
||||
if (it != ints.end()) {
|
||||
REQUIRE((
|
||||
(it->second.first == a && it->second.second == b) ||
|
||||
(it->second.first == b && it->second.second == a)
|
||||
));
|
||||
} else
|
||||
ints[hash_ab] = std::make_pair(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
// SLA Raster test utils:
|
||||
|
||||
using TPixel = uint8_t;
|
||||
|
Loading…
Reference in New Issue
Block a user