Merge branch 'tm_branching_tree_fixes_SPE-1224'

This commit is contained in:
tamasmeszaros 2023-01-18 10:35:13 +01:00
commit b87561a7ff
34 changed files with 2417 additions and 1017 deletions

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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});
}
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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*/) {}

View File

@ -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",

View File

@ -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>

View File

@ -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

View File

@ -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();

View File

@ -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;
}

View File

@ -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)

View File

@ -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.
}

View File

@ -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)

View File

@ -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)

View File

@ -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;

View File

@ -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

View File

@ -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

View 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

View File

@ -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);

View File

@ -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_

View File

@ -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);

View File

@ -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);

View File

@ -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");

View File

@ -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
View 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

View 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.

View File

@ -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]") {

View 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);
}
}

View File

@ -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));

View File

@ -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;