From 1e9bd28714d936277cb925fddcaa3d835bb2ab85 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 11 Nov 2022 15:12:53 +0100 Subject: [PATCH] Upgrade support tree route search functions, add tests --- src/libslic3r/BranchingTree/BranchingTree.hpp | 3 + src/libslic3r/CMakeLists.txt | 2 + .../Optimize/BruteforceOptimizer.hpp | 4 +- src/libslic3r/Optimize/NLoptOptimizer.hpp | 4 +- src/libslic3r/OrganicTree/OrganicTree.hpp | 95 ++++++++++++++ src/libslic3r/OrganicTree/OrganicTreeImpl.hpp | 11 ++ src/libslic3r/SLA/BranchingTreeSLA.cpp | 51 ++++---- src/libslic3r/SLA/SupportTree.hpp | 4 +- src/libslic3r/SLA/SupportTreeMesher.cpp | 3 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 84 ++++++------- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 4 + tests/data/U_overhang.obj | 76 +++++++++++ tests/sla_print/CMakeLists.txt | 1 + tests/sla_print/sla_supptreeutils_tests.cpp | 119 ++++++++++++++++++ 14 files changed, 385 insertions(+), 76 deletions(-) create mode 100644 src/libslic3r/OrganicTree/OrganicTree.hpp create mode 100644 src/libslic3r/OrganicTree/OrganicTreeImpl.hpp create mode 100644 tests/data/U_overhang.obj create mode 100644 tests/sla_print/sla_supptreeutils_tests.cpp diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index fd69fe4cd..3f7fd7b80 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -20,6 +20,9 @@ class Properties ExPolygons m_bed_shape; public: + + constexpr bool group_pillars() const noexcept { return false; } + // Maximum slope for bridges of the tree Properties &max_slope(double val) noexcept { diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6217a11df..3740d4e01 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -356,6 +356,8 @@ set(SLIC3R_SOURCES BranchingTree/BranchingTree.hpp BranchingTree/PointCloud.cpp BranchingTree/PointCloud.hpp + OrganicTree/OrganicTree.hpp + OrganicTree/OrganicTreeImpl.hpp Arachne/BeadingStrategy/BeadingStrategy.hpp Arachne/BeadingStrategy/BeadingStrategy.cpp diff --git a/src/libslic3r/Optimize/BruteforceOptimizer.hpp b/src/libslic3r/Optimize/BruteforceOptimizer.hpp index 2daef538e..2f6b42224 100644 --- a/src/libslic3r/Optimize/BruteforceOptimizer.hpp +++ b/src/libslic3r/Optimize/BruteforceOptimizer.hpp @@ -13,7 +13,9 @@ template long num_iter(const std::array &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; } diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index cc7edb97a..3859217da 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -158,7 +158,7 @@ public: return optimize(nl, std::forward(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; } const StopCriteria &get_criteria() const noexcept { return m_stopcr; } @@ -226,7 +226,7 @@ using AlgNLoptGenetic = detail::NLoptAlgComb; using AlgNLoptSubplex = detail::NLoptAlg; using AlgNLoptSimplex = detail::NLoptAlg; using AlgNLoptDIRECT = detail::NLoptAlg; -using AlgNLoptMLSL = detail::NLoptAlg; +using AlgNLoptMLSL = detail::NLoptAlgComb; }} // namespace Slic3r::opt diff --git a/src/libslic3r/OrganicTree/OrganicTree.hpp b/src/libslic3r/OrganicTree/OrganicTree.hpp new file mode 100644 index 000000000..cf34c9eb3 --- /dev/null +++ b/src/libslic3r/OrganicTree/OrganicTree.hpp @@ -0,0 +1,95 @@ +#ifndef ORGANICTREE_HPP +#define ORGANICTREE_HPP + +#include +#include +#include + +namespace Slic3r { namespace organictree { + +enum class NodeType { Bed, Mesh, Junction }; + +template 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> + 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 +void build_tree(Domain &&D) +{ + using Dom = DomainTraits_>>; + using Node = typename Dom::Node; + + while (! Dom::empty(D)) { + Node n = Dom::pop(D); + + std::optional> 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 diff --git a/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp b/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp new file mode 100644 index 000000000..6e18550da --- /dev/null +++ b/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp @@ -0,0 +1,11 @@ +#ifndef ORGANICTREEIMPL_HPP +#define ORGANICTREEIMPL_HPP + +namespace Slic3r { namespace organictree { + + + + +}} + +#endif // ORGANICTREEIMPL_HPP diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index a4605bbfd..fc5dc2982 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -253,13 +253,11 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, // if (!result) { auto conn = optimize_ground_connection( ex_tbb, - m_builder, m_sm, j, get_radius(to)); if (conn) { -// build_ground_connection(m_builder, m_sm, conn); // Junction connlast = conn.path.back(); // branchingtree::Node n{connlast.pos.cast(), float(connlast.r)}; // n.left = from.id; @@ -321,6 +319,17 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from, 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) { 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}; branchingtree::build_tree(nodes, vbuilder); - std::vector bedleafs; - for (auto n : vbuilder.pillars()) { - n.left = branchingtree::Node::ID_NONE; - n.right = branchingtree::Node::ID_NONE; - bedleafs.emplace_back(n); - } + if constexpr (props.group_pillars()) { - props.max_branch_length(50.f); - auto gndsm = sm; - branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; - BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; - branchingtree::build_tree(gndnodes, gndbuilder); + std::vector bedleafs; + for (auto n : vbuilder.pillars()) { + n.left = branchingtree::Node::ID_NONE; + n.right = branchingtree::Node::ID_NONE; + bedleafs.emplace_back(n); + } - // All leafs of gndbuilder are nodes that already proved to be routable - // to the ground. gndbuilder should not encounter any unroutable nodes -// assert(gndbuilder.unroutable_pinheads().empty()); + branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; + BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; + branchingtree::build_tree(gndnodes, gndbuilder); - -// 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); -// } - - 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); + build_pillars(builder, gndbuilder, sm); + } else { + build_pillars(builder, vbuilder, sm); } for (size_t id : vbuilder.unroutable_pinheads()) diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index b70f77319..051d56926 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -96,8 +96,8 @@ struct SupportTreeConfig 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-16; - static const unsigned constexpr optimizer_max_iterations = 20000; + 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; }; diff --git a/src/libslic3r/SLA/SupportTreeMesher.cpp b/src/libslic3r/SLA/SupportTreeMesher.cpp index 3f0b6c841..6d91de7e6 100644 --- a/src/libslic3r/SLA/SupportTreeMesher.cpp +++ b/src/libslic3r/SLA/SupportTreeMesher.cpp @@ -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; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 1cf9dfdc5..070ffbf0d 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -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) { 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(); @@ -644,10 +645,9 @@ struct GroundConnection { static constexpr size_t MaxExpectedJunctions = 3; boost::container::small_vector path; - double end_radius; std::optional pillar_base; - operator bool() const { return !path.empty(); } + operator bool() const { return pillar_base.has_value() && !path.empty(); } }; template @@ -659,6 +659,8 @@ GroundConnection find_pillar_route(Ex policy, { GroundConnection ret; + ret.path.emplace_back(source); + Vec3d jp = source.pos, endp = jp, dir = sourcedir; 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 gap_dist = 0.; // The gap distance between the model and the pad double radius = source.r; + double sd = sm.cfg.safety_distance_mm; 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); if (diffbr && diffbr->endp.z() > jp_gnd) { - ret.path.emplace_back(source); endp = diffbr->endp; radius = diffbr->end_r; ret.path.emplace_back(endp, radius); @@ -709,7 +711,6 @@ GroundConnection find_pillar_route(Ex policy, auto [polar, azimuth] = dir_to_spheric(dir); polar = PI - sm.cfg.bridge_slope; 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 tmax = std::min(sm.cfg.max_bridge_length_mm, t); t = 0.; @@ -759,15 +760,19 @@ GroundConnection find_pillar_route(Ex policy, } 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 = - 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, @@ -798,10 +803,9 @@ inline long build_ground_connection(SupportTreeBuilder &builder, // 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.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; } @@ -809,7 +813,6 @@ inline long build_ground_connection(SupportTreeBuilder &builder, template GroundConnection find_ground_connection( Ex policy, - SupportTreeBuilder &builder, const SupportableMesh &sm, const Junction &j, const Vec3d &dir, @@ -817,39 +820,36 @@ GroundConnection find_ground_connection( { auto hjp = j.pos; 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 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); + double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance(); + t = std::min(t, sm.cfg.max_bridge_length_mm); + double d = 0.; + + 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; } GroundConnection ret; - ret.end_radius = end_r; - if (std::isinf(tdown)) { + if (d > 0.) 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 &j : route.path) - ret.path.emplace_back(j); + for (auto &p : gnd_route.path) + ret.path.emplace_back(p); - ret.pillar_base = route.pillar_base; - ret.end_radius = end_r; - } + // This will ultimately determine if the route is valid or not + // but the path junctions will be provided anyways, so invalid paths + // can be debugged + ret.pillar_base = gnd_route.pillar_base; return ret; } @@ -857,7 +857,6 @@ GroundConnection find_ground_connection( template GroundConnection optimize_ground_connection( Ex policy, - SupportTreeBuilder &builder, const SupportableMesh &sm, const Junction &j, double end_radius, @@ -865,8 +864,8 @@ GroundConnection optimize_ground_connection( { double downdst = j.pos.z() - ground_level(sm); - auto res = find_ground_connection(policy, builder, sm, j, init_dir, end_radius); - if (!res) + auto res = find_ground_connection(policy, sm, j, init_dir, end_radius); + if (res) return res; // Optimize bridge direction: @@ -874,7 +873,7 @@ GroundConnection optimize_ground_connection( // direction out of the cavity. auto [polar, azimuth] = dir_to_spheric(init_dir); - Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); + Optimizer 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*/; @@ -891,7 +890,7 @@ GroundConnection optimize_ground_connection( 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 @@ -904,7 +903,7 @@ std::pair connect_to_ground(Ex policy, { std::pair 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.second = build_ground_connection(builder, sm, conn); @@ -921,8 +920,7 @@ std::pair search_ground_route(Ex policy, { std::pair ret = {false, SupportTreeNode::ID_UNSET}; - auto conn = optimize_ground_connection(policy, builder, sm, j, - end_r, init_dir); + auto conn = optimize_ground_connection(policy, sm, j, end_r, init_dir); ret.first = bool(conn); ret.second = build_ground_connection(builder, sm, conn); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index c22cdf606..e0f3acb70 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI.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; } 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_selection_empty = false; m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f; diff --git a/tests/data/U_overhang.obj b/tests/data/U_overhang.obj new file mode 100644 index 000000000..70453e58f --- /dev/null +++ b/tests/data/U_overhang.obj @@ -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 diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt index 24e9552c5..2a800cc50 100644 --- a/tests/sla_print/CMakeLists.txt +++ b/tests/sla_print/CMakeLists.txt @@ -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. diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp new file mode 100644 index 000000000..b433e80ee --- /dev/null +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -0,0 +1,119 @@ +#include +#include + +#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); +}