Upgrade support tree route search functions, add tests
This commit is contained in:
parent
4dc0741766
commit
1e9bd28714
@ -20,6 +20,9 @@ class Properties
|
|||||||
ExPolygons m_bed_shape;
|
ExPolygons m_bed_shape;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
constexpr bool group_pillars() const noexcept { return false; }
|
||||||
|
|
||||||
// Maximum slope for bridges of the tree
|
// Maximum slope for bridges of the tree
|
||||||
Properties &max_slope(double val) noexcept
|
Properties &max_slope(double val) noexcept
|
||||||
{
|
{
|
||||||
|
@ -356,6 +356,8 @@ set(SLIC3R_SOURCES
|
|||||||
BranchingTree/BranchingTree.hpp
|
BranchingTree/BranchingTree.hpp
|
||||||
BranchingTree/PointCloud.cpp
|
BranchingTree/PointCloud.cpp
|
||||||
BranchingTree/PointCloud.hpp
|
BranchingTree/PointCloud.hpp
|
||||||
|
OrganicTree/OrganicTree.hpp
|
||||||
|
OrganicTree/OrganicTreeImpl.hpp
|
||||||
|
|
||||||
Arachne/BeadingStrategy/BeadingStrategy.hpp
|
Arachne/BeadingStrategy/BeadingStrategy.hpp
|
||||||
Arachne/BeadingStrategy/BeadingStrategy.cpp
|
Arachne/BeadingStrategy/BeadingStrategy.cpp
|
||||||
|
@ -13,7 +13,9 @@ template<size_t N>
|
|||||||
long num_iter(const std::array<size_t, N> &idx, size_t gridsz)
|
long num_iter(const std::array<size_t, N> &idx, size_t gridsz)
|
||||||
{
|
{
|
||||||
long ret = 0;
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ public:
|
|||||||
return optimize(nl, std::forward<Func>(func), initvals);
|
return optimize(nl, std::forward<Func>(func), initvals);
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {}
|
explicit NLoptOpt(const StopCriteria &stopcr = {}) : m_stopcr(stopcr) {}
|
||||||
|
|
||||||
void set_criteria(const StopCriteria &cr) { m_stopcr = cr; }
|
void set_criteria(const StopCriteria &cr) { m_stopcr = cr; }
|
||||||
const StopCriteria &get_criteria() const noexcept { return m_stopcr; }
|
const StopCriteria &get_criteria() const noexcept { return m_stopcr; }
|
||||||
@ -226,7 +226,7 @@ using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
|
|||||||
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
|
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
|
||||||
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
|
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
|
||||||
using AlgNLoptDIRECT = detail::NLoptAlg<NLOPT_GN_DIRECT>;
|
using AlgNLoptDIRECT = detail::NLoptAlg<NLOPT_GN_DIRECT>;
|
||||||
using AlgNLoptMLSL = detail::NLoptAlg<NLOPT_GN_MLSL>;
|
using AlgNLoptMLSL = detail::NLoptAlgComb<NLOPT_GN_MLSL, NLOPT_LN_SBPLX>;
|
||||||
|
|
||||||
}} // namespace Slic3r::opt
|
}} // namespace Slic3r::opt
|
||||||
|
|
||||||
|
95
src/libslic3r/OrganicTree/OrganicTree.hpp
Normal file
95
src/libslic3r/OrganicTree/OrganicTree.hpp
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
#ifndef ORGANICTREE_HPP
|
||||||
|
#define ORGANICTREE_HPP
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace Slic3r { namespace organictree {
|
||||||
|
|
||||||
|
enum class NodeType { Bed, Mesh, Junction };
|
||||||
|
|
||||||
|
template <class T> struct DomainTraits_ {
|
||||||
|
using Node = typename T::Node;
|
||||||
|
|
||||||
|
static void push(const T &dom, const Node &n)
|
||||||
|
{
|
||||||
|
dom.push_junction(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Node pop(T &dom) { return dom.pop(); }
|
||||||
|
|
||||||
|
static bool empty(const T &dom) { return dom.empty(); }
|
||||||
|
|
||||||
|
static std::optional<std::pair<Node, NodeType>>
|
||||||
|
closest(const T &dom, const Node &n)
|
||||||
|
{
|
||||||
|
return dom.closest(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Node merge_node(const T &dom, const Node &a, const Node &b)
|
||||||
|
{
|
||||||
|
return dom.merge_node(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bridge(T &dom, const Node &from, const Node &to)
|
||||||
|
{
|
||||||
|
dom.bridge(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void anchor(T &dom, const Node &from, const Node &to)
|
||||||
|
{
|
||||||
|
dom.anchor(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pillar(T &dom, const Node &from, const Node &to)
|
||||||
|
{
|
||||||
|
dom.pillar(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void merge (T &dom, const Node &n1, const Node &n2, const Node &mrg)
|
||||||
|
{
|
||||||
|
dom.merge(n1, n2, mrg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void report_fail(T &dom, const Node &n) { dom.report_fail(n); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Domain>
|
||||||
|
void build_tree(Domain &&D)
|
||||||
|
{
|
||||||
|
using Dom = DomainTraits_<std::remove_cv_t<std::remove_reference_t<Domain>>>;
|
||||||
|
using Node = typename Dom::Node;
|
||||||
|
|
||||||
|
while (! Dom::empty(D)) {
|
||||||
|
Node n = Dom::pop(D);
|
||||||
|
|
||||||
|
std::optional<std::pair<Node, NodeType>> C = Dom::closest(D, n);
|
||||||
|
|
||||||
|
if (!C) {
|
||||||
|
Dom::report_fail(D, n);
|
||||||
|
} else switch (C->second) {
|
||||||
|
case NodeType::Bed:
|
||||||
|
Dom::pillar(D, n, C->first);
|
||||||
|
break;
|
||||||
|
case NodeType::Mesh:
|
||||||
|
Dom::anchor(D, n, C->first);
|
||||||
|
break;
|
||||||
|
case NodeType::Junction: {
|
||||||
|
Node M = Dom::merge_node(D, n, C->first);
|
||||||
|
|
||||||
|
if (M == C->first) {
|
||||||
|
Dom::bridge(D, n, C->first);
|
||||||
|
} else {
|
||||||
|
Dom::push(D, M);
|
||||||
|
Dom::merge(D, n, M, C->first);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace Slic3r::organictree
|
||||||
|
|
||||||
|
#endif // ORGANICTREE_HPP
|
11
src/libslic3r/OrganicTree/OrganicTreeImpl.hpp
Normal file
11
src/libslic3r/OrganicTree/OrganicTreeImpl.hpp
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#ifndef ORGANICTREEIMPL_HPP
|
||||||
|
#define ORGANICTREEIMPL_HPP
|
||||||
|
|
||||||
|
namespace Slic3r { namespace organictree {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}}
|
||||||
|
|
||||||
|
#endif // ORGANICTREEIMPL_HPP
|
@ -253,13 +253,11 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from,
|
|||||||
// if (!result) {
|
// if (!result) {
|
||||||
auto conn = optimize_ground_connection(
|
auto conn = optimize_ground_connection(
|
||||||
ex_tbb,
|
ex_tbb,
|
||||||
m_builder,
|
|
||||||
m_sm,
|
m_sm,
|
||||||
j,
|
j,
|
||||||
get_radius(to));
|
get_radius(to));
|
||||||
|
|
||||||
if (conn) {
|
if (conn) {
|
||||||
// build_ground_connection(m_builder, m_sm, conn);
|
|
||||||
// Junction connlast = conn.path.back();
|
// Junction connlast = conn.path.back();
|
||||||
// branchingtree::Node n{connlast.pos.cast<float>(), float(connlast.r)};
|
// branchingtree::Node n{connlast.pos.cast<float>(), float(connlast.r)};
|
||||||
// n.left = from.id;
|
// n.left = from.id;
|
||||||
@ -321,6 +319,17 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from,
|
|||||||
return bool(anchor);
|
return bool(anchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &sm)
|
||||||
{
|
{
|
||||||
auto coordfn = [&sm](size_t id, size_t dim) { return sm.pts[id].pos(dim); };
|
auto coordfn = [&sm](size_t id, size_t dim) { return sm.pts[id].pos(dim); };
|
||||||
@ -373,34 +382,22 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s
|
|||||||
BranchingTreeBuilder vbuilder{builder, sm, nodes};
|
BranchingTreeBuilder vbuilder{builder, sm, nodes};
|
||||||
branchingtree::build_tree(nodes, vbuilder);
|
branchingtree::build_tree(nodes, vbuilder);
|
||||||
|
|
||||||
std::vector<branchingtree::Node> bedleafs;
|
if constexpr (props.group_pillars()) {
|
||||||
for (auto n : vbuilder.pillars()) {
|
|
||||||
n.left = branchingtree::Node::ID_NONE;
|
|
||||||
n.right = branchingtree::Node::ID_NONE;
|
|
||||||
bedleafs.emplace_back(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
props.max_branch_length(50.f);
|
std::vector<branchingtree::Node> bedleafs;
|
||||||
auto gndsm = sm;
|
for (auto n : vbuilder.pillars()) {
|
||||||
branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props};
|
n.left = branchingtree::Node::ID_NONE;
|
||||||
BranchingTreeBuilder gndbuilder{builder, sm, gndnodes};
|
n.right = branchingtree::Node::ID_NONE;
|
||||||
branchingtree::build_tree(gndnodes, gndbuilder);
|
bedleafs.emplace_back(n);
|
||||||
|
}
|
||||||
|
|
||||||
// All leafs of gndbuilder are nodes that already proved to be routable
|
branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props};
|
||||||
// to the ground. gndbuilder should not encounter any unroutable nodes
|
BranchingTreeBuilder gndbuilder{builder, sm, gndnodes};
|
||||||
// assert(gndbuilder.unroutable_pinheads().empty());
|
branchingtree::build_tree(gndnodes, gndbuilder);
|
||||||
|
|
||||||
|
build_pillars(builder, gndbuilder, sm);
|
||||||
// for (size_t pill_id = 0; pill_id < vbuilder.pillars().size(); ++pill_id) {
|
} else {
|
||||||
// auto * conn = vbuilder.ground_conn(pill_id);
|
build_pillars(builder, vbuilder, sm);
|
||||||
// if (conn)
|
|
||||||
// build_ground_connection(builder, sm, *conn);
|
|
||||||
// }
|
|
||||||
|
|
||||||
for (size_t pill_id = 0; pill_id < gndbuilder.pillars().size(); ++pill_id) {
|
|
||||||
auto * conn = gndbuilder.ground_conn(pill_id);
|
|
||||||
if (conn)
|
|
||||||
build_ground_connection(builder, sm, *conn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t id : vbuilder.unroutable_pinheads())
|
for (size_t id : vbuilder.unroutable_pinheads())
|
||||||
|
@ -96,8 +96,8 @@ struct SupportTreeConfig
|
|||||||
|
|
||||||
static const double constexpr max_solo_pillar_height_mm = 15.0;
|
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 max_dual_pillar_height_mm = 35.0;
|
||||||
static const double constexpr optimizer_rel_score_diff = 1e-16;
|
static const double constexpr optimizer_rel_score_diff = 1e-10;
|
||||||
static const unsigned constexpr optimizer_max_iterations = 20000;
|
static const unsigned constexpr optimizer_max_iterations = 2000;
|
||||||
static const unsigned constexpr pillar_cascade_neighbors = 3;
|
static const unsigned constexpr pillar_cascade_neighbors = 3;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -165,7 +165,8 @@ indexed_triangle_set halfcone(double baseheight,
|
|||||||
{
|
{
|
||||||
assert(steps > 0);
|
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;
|
indexed_triangle_set base;
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <libslic3r/Execution/Execution.hpp>
|
#include <libslic3r/Execution/Execution.hpp>
|
||||||
#include <libslic3r/Optimize/NLoptOptimizer.hpp>
|
#include <libslic3r/Optimize/NLoptOptimizer.hpp>
|
||||||
|
#include <libslic3r/Optimize/BruteforceOptimizer.hpp>
|
||||||
#include <libslic3r/MeshNormals.hpp>
|
#include <libslic3r/MeshNormals.hpp>
|
||||||
#include <libslic3r/Geometry.hpp>
|
#include <libslic3r/Geometry.hpp>
|
||||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||||
@ -178,7 +179,7 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd)
|
|||||||
[&mesh, r_src, r_dst, src, dst, &ring, dir, sd, &hits](size_t i) {
|
[&mesh, r_src, r_dst, src, dst, &ring, dir, sd, &hits](size_t i) {
|
||||||
Hit &hit = hits[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_src = ring.get(i, src, r_src + sd);
|
||||||
Vec3d p_dst = ring.get(i, dst, r_dst + sd);
|
Vec3d p_dst = ring.get(i, dst, r_dst + sd);
|
||||||
Vec3d raydir = (p_dst - p_src).normalized();
|
Vec3d raydir = (p_dst - p_src).normalized();
|
||||||
@ -644,10 +645,9 @@ struct GroundConnection {
|
|||||||
static constexpr size_t MaxExpectedJunctions = 3;
|
static constexpr size_t MaxExpectedJunctions = 3;
|
||||||
|
|
||||||
boost::container::small_vector<Junction, MaxExpectedJunctions> path;
|
boost::container::small_vector<Junction, MaxExpectedJunctions> path;
|
||||||
double end_radius;
|
|
||||||
std::optional<Pedestal> pillar_base;
|
std::optional<Pedestal> pillar_base;
|
||||||
|
|
||||||
operator bool() const { return !path.empty(); }
|
operator bool() const { return pillar_base.has_value() && !path.empty(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class Ex>
|
template<class Ex>
|
||||||
@ -659,6 +659,8 @@ GroundConnection find_pillar_route(Ex policy,
|
|||||||
{
|
{
|
||||||
GroundConnection ret;
|
GroundConnection ret;
|
||||||
|
|
||||||
|
ret.path.emplace_back(source);
|
||||||
|
|
||||||
Vec3d jp = source.pos, endp = jp, dir = sourcedir;
|
Vec3d jp = source.pos, endp = jp, dir = sourcedir;
|
||||||
bool can_add_base = false/*, non_head = false*/;
|
bool can_add_base = false/*, non_head = false*/;
|
||||||
|
|
||||||
@ -666,6 +668,7 @@ GroundConnection find_pillar_route(Ex policy,
|
|||||||
double jp_gnd = 0.; // The lowest Z where a junction center can 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 gap_dist = 0.; // The gap distance between the model and the pad
|
||||||
double radius = source.r;
|
double radius = source.r;
|
||||||
|
double sd = sm.cfg.safety_distance_mm;
|
||||||
|
|
||||||
double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm));
|
double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm));
|
||||||
|
|
||||||
@ -692,7 +695,6 @@ GroundConnection find_pillar_route(Ex policy,
|
|||||||
sm.cfg.head_back_radius_mm);
|
sm.cfg.head_back_radius_mm);
|
||||||
|
|
||||||
if (diffbr && diffbr->endp.z() > jp_gnd) {
|
if (diffbr && diffbr->endp.z() > jp_gnd) {
|
||||||
ret.path.emplace_back(source);
|
|
||||||
endp = diffbr->endp;
|
endp = diffbr->endp;
|
||||||
radius = diffbr->end_r;
|
radius = diffbr->end_r;
|
||||||
ret.path.emplace_back(endp, radius);
|
ret.path.emplace_back(endp, radius);
|
||||||
@ -709,7 +711,6 @@ GroundConnection find_pillar_route(Ex policy,
|
|||||||
auto [polar, azimuth] = dir_to_spheric(dir);
|
auto [polar, azimuth] = dir_to_spheric(dir);
|
||||||
polar = PI - sm.cfg.bridge_slope;
|
polar = PI - sm.cfg.bridge_slope;
|
||||||
Vec3d d = spheric_to_dir(polar, azimuth).normalized();
|
Vec3d d = spheric_to_dir(polar, azimuth).normalized();
|
||||||
auto sd = sm.cfg.safety_distance_mm; //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 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);
|
double tmax = std::min(sm.cfg.max_bridge_length_mm, t);
|
||||||
t = 0.;
|
t = 0.;
|
||||||
@ -759,15 +760,19 @@ GroundConnection find_pillar_route(Ex policy,
|
|||||||
}
|
}
|
||||||
|
|
||||||
Vec3d gp = to_floor(endp);
|
Vec3d gp = to_floor(endp);
|
||||||
|
auto hit = beam_mesh_hit(policy, sm.emesh,
|
||||||
|
Beam{{endp, radius}, {gp, end_radius}}, sd);
|
||||||
|
|
||||||
ret.end_radius = end_radius;
|
if (std::isinf(hit.distance())) {
|
||||||
|
double base_radius = can_add_base ?
|
||||||
|
std::max(sm.cfg.base_radius_mm, end_radius) : end_radius;
|
||||||
|
|
||||||
if (can_add_base) {
|
Vec3d gp = to_floor(endp);
|
||||||
ret.pillar_base =
|
ret.pillar_base =
|
||||||
Pedestal{gp, sm.cfg.base_height_mm, sm.cfg.base_radius_mm, end_radius};
|
Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius};
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret; //{true, pillar_id};
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline long build_ground_connection(SupportTreeBuilder &builder,
|
inline long build_ground_connection(SupportTreeBuilder &builder,
|
||||||
@ -798,10 +803,9 @@ inline long build_ground_connection(SupportTreeBuilder &builder,
|
|||||||
// long head_id = std::abs(conn.path.back().id);
|
// long head_id = std::abs(conn.path.back().id);
|
||||||
// ret = builder.add_pillar(head_id, h);
|
// ret = builder.add_pillar(head_id, h);
|
||||||
// } else
|
// } else
|
||||||
ret = builder.add_pillar(gp, h, conn.path.back().r, conn.end_radius);
|
ret = builder.add_pillar(gp, h, conn.path.back().r, conn.pillar_base->r_top);
|
||||||
|
|
||||||
if (conn.pillar_base)
|
builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom);
|
||||||
builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -809,7 +813,6 @@ inline long build_ground_connection(SupportTreeBuilder &builder,
|
|||||||
template<class Ex>
|
template<class Ex>
|
||||||
GroundConnection find_ground_connection(
|
GroundConnection find_ground_connection(
|
||||||
Ex policy,
|
Ex policy,
|
||||||
SupportTreeBuilder &builder,
|
|
||||||
const SupportableMesh &sm,
|
const SupportableMesh &sm,
|
||||||
const Junction &j,
|
const Junction &j,
|
||||||
const Vec3d &dir,
|
const Vec3d &dir,
|
||||||
@ -817,39 +820,36 @@ GroundConnection find_ground_connection(
|
|||||||
{
|
{
|
||||||
auto hjp = j.pos;
|
auto hjp = j.pos;
|
||||||
double r = j.r;
|
double r = j.r;
|
||||||
auto sd = sm.cfg.safety_distance_mm; //r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
|
auto sd = sm.cfg.safety_distance_mm;
|
||||||
double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm));
|
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)
|
double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance();
|
||||||
.distance();
|
t = std::min(t, sm.cfg.max_bridge_length_mm);
|
||||||
double d = 0, tdown = 0;
|
double d = 0.;
|
||||||
t = std::min(t,
|
|
||||||
sm.cfg.max_bridge_length_mm * r / sm.cfg.head_back_radius_mm);
|
GroundConnection gnd_route;
|
||||||
|
|
||||||
|
while (!gnd_route && d < t) {
|
||||||
|
Vec3d endp = hjp + d * dir;
|
||||||
|
double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level()));
|
||||||
|
double pill_r = r + bridge_ratio * (end_r - r);
|
||||||
|
gnd_route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r);
|
||||||
|
|
||||||
while (
|
|
||||||
d < t &&
|
|
||||||
!std::isinf(tdown = beam_mesh_hit(policy, sm.emesh,
|
|
||||||
Beam{hjp + d * dir, DOWN, r, r2}, sd)
|
|
||||||
.distance())) {
|
|
||||||
d += r;
|
d += r;
|
||||||
}
|
}
|
||||||
|
|
||||||
GroundConnection ret;
|
GroundConnection ret;
|
||||||
ret.end_radius = end_r;
|
|
||||||
|
|
||||||
if (std::isinf(tdown)) {
|
if (d > 0.)
|
||||||
ret.path.emplace_back(j);
|
ret.path.emplace_back(j);
|
||||||
Vec3d endp = hjp + d * dir;
|
|
||||||
double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level()));
|
|
||||||
double pill_r = r + bridge_ratio * (end_r - r);
|
|
||||||
|
|
||||||
auto route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r);
|
for (auto &p : gnd_route.path)
|
||||||
for (auto &j : route.path)
|
ret.path.emplace_back(p);
|
||||||
ret.path.emplace_back(j);
|
|
||||||
|
|
||||||
ret.pillar_base = route.pillar_base;
|
// This will ultimately determine if the route is valid or not
|
||||||
ret.end_radius = end_r;
|
// but the path junctions will be provided anyways, so invalid paths
|
||||||
}
|
// can be debugged
|
||||||
|
ret.pillar_base = gnd_route.pillar_base;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -857,7 +857,6 @@ GroundConnection find_ground_connection(
|
|||||||
template<class Ex>
|
template<class Ex>
|
||||||
GroundConnection optimize_ground_connection(
|
GroundConnection optimize_ground_connection(
|
||||||
Ex policy,
|
Ex policy,
|
||||||
SupportTreeBuilder &builder,
|
|
||||||
const SupportableMesh &sm,
|
const SupportableMesh &sm,
|
||||||
const Junction &j,
|
const Junction &j,
|
||||||
double end_radius,
|
double end_radius,
|
||||||
@ -865,8 +864,8 @@ GroundConnection optimize_ground_connection(
|
|||||||
{
|
{
|
||||||
double downdst = j.pos.z() - ground_level(sm);
|
double downdst = j.pos.z() - ground_level(sm);
|
||||||
|
|
||||||
auto res = find_ground_connection(policy, builder, sm, j, init_dir, end_radius);
|
auto res = find_ground_connection(policy, sm, j, init_dir, end_radius);
|
||||||
if (!res)
|
if (res)
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
// Optimize bridge direction:
|
// Optimize bridge direction:
|
||||||
@ -874,7 +873,7 @@ GroundConnection optimize_ground_connection(
|
|||||||
// direction out of the cavity.
|
// direction out of the cavity.
|
||||||
auto [polar, azimuth] = dir_to_spheric(init_dir);
|
auto [polar, azimuth] = dir_to_spheric(init_dir);
|
||||||
|
|
||||||
Optimizer<AlgNLoptGenetic> solver(get_criteria(sm.cfg).stop_score(1e6));
|
Optimizer<opt::AlgNLoptMLSL> solver(get_criteria(sm.cfg).stop_score(1e6));
|
||||||
solver.seed(0); // we want deterministic behavior
|
solver.seed(0); // we want deterministic behavior
|
||||||
|
|
||||||
auto sd = /*j.r **/ sm.cfg.safety_distance_mm /*/ sm.cfg.head_back_radius_mm*/;
|
auto sd = /*j.r **/ sm.cfg.safety_distance_mm /*/ sm.cfg.head_back_radius_mm*/;
|
||||||
@ -891,7 +890,7 @@ GroundConnection optimize_ground_connection(
|
|||||||
|
|
||||||
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
|
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
|
||||||
|
|
||||||
return find_ground_connection(policy, builder, sm, j, bridgedir, end_radius);
|
return find_ground_connection(policy, sm, j, bridgedir, end_radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Ex>
|
template<class Ex>
|
||||||
@ -904,7 +903,7 @@ std::pair<bool, long> connect_to_ground(Ex policy,
|
|||||||
{
|
{
|
||||||
std::pair<bool, long> ret = {false, SupportTreeNode::ID_UNSET};
|
std::pair<bool, long> ret = {false, SupportTreeNode::ID_UNSET};
|
||||||
|
|
||||||
auto conn = find_ground_connection(policy, builder, sm, j, dir, end_r);
|
auto conn = find_ground_connection(policy, sm, j, dir, end_r);
|
||||||
ret.first = bool(conn);
|
ret.first = bool(conn);
|
||||||
ret.second = build_ground_connection(builder, sm, conn);
|
ret.second = build_ground_connection(builder, sm, conn);
|
||||||
|
|
||||||
@ -921,8 +920,7 @@ std::pair<bool, long> search_ground_route(Ex policy,
|
|||||||
{
|
{
|
||||||
std::pair<bool, long> ret = {false, SupportTreeNode::ID_UNSET};
|
std::pair<bool, long> ret = {false, SupportTreeNode::ID_UNSET};
|
||||||
|
|
||||||
auto conn = optimize_ground_connection(policy, builder, sm, j,
|
auto conn = optimize_ground_connection(policy, sm, j, end_r, init_dir);
|
||||||
end_r, init_dir);
|
|
||||||
|
|
||||||
ret.first = bool(conn);
|
ret.first = bool(conn);
|
||||||
ret.second = build_ground_connection(builder, sm, conn);
|
ret.second = build_ground_connection(builder, sm, conn);
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
#include <wx/settings.h>
|
#include <wx/settings.h>
|
||||||
#include <wx/stattext.h>
|
#include <wx/stattext.h>
|
||||||
|
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
#include "slic3r/GUI/GUI_App.hpp"
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
#include "slic3r/GUI/GUI.hpp"
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
#include "slic3r/GUI/GUI_ObjectSettings.hpp"
|
#include "slic3r/GUI/GUI_ObjectSettings.hpp"
|
||||||
@ -1019,6 +1021,8 @@ void GLGizmoSlaSupports::select_point(int i)
|
|||||||
m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f;
|
m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (!m_editing_cache[i].selected)
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "Support point selected [" << i << "]: " << m_editing_cache[i].support_point.pos.transpose() << " \tnormal: " << m_editing_cache[i].normal.transpose();
|
||||||
m_editing_cache[i].selected = true;
|
m_editing_cache[i].selected = true;
|
||||||
m_selection_empty = false;
|
m_selection_empty = false;
|
||||||
m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f;
|
m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f;
|
||||||
|
76
tests/data/U_overhang.obj
Normal file
76
tests/data/U_overhang.obj
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
####
|
||||||
|
#
|
||||||
|
# OBJ File Generated by Meshlab
|
||||||
|
#
|
||||||
|
####
|
||||||
|
# Object U_overhang.obj
|
||||||
|
#
|
||||||
|
# Vertices: 16
|
||||||
|
# Faces: 28
|
||||||
|
#
|
||||||
|
####
|
||||||
|
vn 1.570797 1.570796 1.570796
|
||||||
|
v 10.000000 10.000000 11.000000
|
||||||
|
vn 4.712389 1.570796 -1.570796
|
||||||
|
v 10.000000 1.000000 10.000000
|
||||||
|
vn 1.570796 1.570796 -1.570796
|
||||||
|
v 10.000000 10.000000 10.000000
|
||||||
|
vn 1.570796 -1.570796 1.570796
|
||||||
|
v 10.000000 0.000000 11.000000
|
||||||
|
vn 4.712389 1.570796 1.570796
|
||||||
|
v 10.000000 1.000000 1.000000
|
||||||
|
vn 1.570797 1.570796 -1.570796
|
||||||
|
v 10.000000 10.000000 0.000000
|
||||||
|
vn 1.570796 1.570796 1.570796
|
||||||
|
v 10.000000 10.000000 1.000000
|
||||||
|
vn 1.570796 -1.570796 -1.570796
|
||||||
|
v 10.000000 0.000000 0.000000
|
||||||
|
vn -1.570796 1.570796 1.570796
|
||||||
|
v 0.000000 10.000000 1.000000
|
||||||
|
vn -4.712389 1.570796 1.570796
|
||||||
|
v 0.000000 1.000000 1.000000
|
||||||
|
vn -1.570796 -1.570796 -1.570796
|
||||||
|
v 0.000000 0.000000 0.000000
|
||||||
|
vn -1.570797 1.570796 -1.570796
|
||||||
|
v 0.000000 10.000000 0.000000
|
||||||
|
vn -4.712389 1.570796 -1.570796
|
||||||
|
v 0.000000 1.000000 10.000000
|
||||||
|
vn -1.570797 1.570796 1.570796
|
||||||
|
v 0.000000 10.000000 11.000000
|
||||||
|
vn -1.570796 1.570796 -1.570796
|
||||||
|
v 0.000000 10.000000 10.000000
|
||||||
|
vn -1.570796 -1.570796 1.570796
|
||||||
|
v 0.000000 0.000000 11.000000
|
||||||
|
# 16 vertices, 0 vertices normals
|
||||||
|
|
||||||
|
f 1//1 2//2 3//3
|
||||||
|
f 2//2 4//4 5//5
|
||||||
|
f 4//4 2//2 1//1
|
||||||
|
f 5//5 6//6 7//7
|
||||||
|
f 5//5 8//8 6//6
|
||||||
|
f 8//8 5//5 4//4
|
||||||
|
f 9//9 5//5 7//7
|
||||||
|
f 5//5 9//9 10//10
|
||||||
|
f 11//11 6//6 8//8
|
||||||
|
f 6//6 11//11 12//12
|
||||||
|
f 12//12 10//10 9//9
|
||||||
|
f 10//10 11//11 13//13
|
||||||
|
f 11//11 10//10 12//12
|
||||||
|
f 13//13 14//14 15//15
|
||||||
|
f 13//13 16//16 14//14
|
||||||
|
f 16//16 13//13 11//11
|
||||||
|
f 6//6 9//9 7//7
|
||||||
|
f 9//9 6//6 12//12
|
||||||
|
f 11//11 4//4 16//16
|
||||||
|
f 4//4 11//11 8//8
|
||||||
|
f 13//13 3//3 2//2
|
||||||
|
f 3//3 13//13 15//15
|
||||||
|
f 5//5 13//13 2//2
|
||||||
|
f 13//13 5//5 10//10
|
||||||
|
f 14//14 4//4 1//1
|
||||||
|
f 4//4 14//14 16//16
|
||||||
|
f 3//3 14//14 1//1
|
||||||
|
f 14//14 3//3 15//15
|
||||||
|
# 28 faces, 0 coords texture
|
||||||
|
|
||||||
|
# End of File
|
@ -4,6 +4,7 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp
|
|||||||
sla_test_utils.hpp sla_test_utils.cpp
|
sla_test_utils.hpp sla_test_utils.cpp
|
||||||
sla_supptgen_tests.cpp
|
sla_supptgen_tests.cpp
|
||||||
sla_raycast_tests.cpp
|
sla_raycast_tests.cpp
|
||||||
|
sla_supptreeutils_tests.cpp
|
||||||
sla_archive_readwrite_tests.cpp)
|
sla_archive_readwrite_tests.cpp)
|
||||||
|
|
||||||
# mold linker for successful linking needs also to link TBB library and link it before libslic3r.
|
# mold linker for successful linking needs also to link TBB library and link it before libslic3r.
|
||||||
|
119
tests/sla_print/sla_supptreeutils_tests.cpp
Normal file
119
tests/sla_print/sla_supptreeutils_tests.cpp
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
#include <catch2/catch.hpp>
|
||||||
|
#include <test_utils.hpp>
|
||||||
|
|
||||||
|
#include "libslic3r/Execution/ExecutionSeq.hpp"
|
||||||
|
#include "libslic3r/SLA/SupportTreeUtils.hpp"
|
||||||
|
|
||||||
|
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};
|
||||||
|
|
||||||
|
sla::GroundConnection conn =
|
||||||
|
sla::optimize_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
|
||||||
|
sla::SupportTreeBuilder builder;
|
||||||
|
|
||||||
|
if (!conn)
|
||||||
|
builder.add_junction(j);
|
||||||
|
|
||||||
|
sla::build_ground_connection(builder, sm, conn);
|
||||||
|
|
||||||
|
its_merge(disk, builder.merged_mesh());
|
||||||
|
|
||||||
|
its_write_stl_ascii("output_disk.stl", "disk", disk);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
REQUIRE(bool(conn));
|
||||||
|
|
||||||
|
// The route should include the source and one avoidance junction.
|
||||||
|
REQUIRE(conn.path.size() == 2);
|
||||||
|
|
||||||
|
// The end radius end the pillar base's upper radius should match
|
||||||
|
REQUIRE(conn.pillar_base->r_top == Approx(EndRadius));
|
||||||
|
|
||||||
|
// 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};
|
||||||
|
|
||||||
|
sla::GroundConnection conn =
|
||||||
|
sla::optimize_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
|
||||||
|
sla::SupportTreeBuilder builder;
|
||||||
|
|
||||||
|
if (!conn)
|
||||||
|
builder.add_junction(j);
|
||||||
|
|
||||||
|
sla::build_ground_connection(builder, sm, conn);
|
||||||
|
|
||||||
|
its_merge(disk, builder.merged_mesh());
|
||||||
|
|
||||||
|
its_write_stl_ascii("output_disk_wall.stl", "disk_wall", disk);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
REQUIRE(bool(conn));
|
||||||
|
|
||||||
|
// The route should include the source and one avoidance junction.
|
||||||
|
REQUIRE(conn.path.size() == 2);
|
||||||
|
|
||||||
|
// The end radius end the pillar base's upper radius should match
|
||||||
|
REQUIRE(conn.pillar_base->r_top == Approx(EndRadius));
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user