From bef64ecec083a2c2e59bd18e373652c597bd6c35 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 24 Oct 2022 15:20:45 +0200 Subject: [PATCH 01/59] Don't call search_ground_route for every ground point candidate search_ground_route already tries to find a suitable ground point globally from the source. Different destination arguments will not help much but will cause a lot of redundant cpu burning SPE-1303 --- src/libslic3r/BranchingTree/BranchingTree.cpp | 2 +- src/libslic3r/BranchingTree/BranchingTree.hpp | 1 - src/libslic3r/SLA/BranchingTreeSLA.cpp | 14 +++++++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 6463a30de..3e5e0a686 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -5,7 +5,7 @@ #include #include -#include "libslic3r/SLA/SupportTreeUtils.hpp" +#include "libslic3r/TriangleMesh.hpp" namespace Slic3r { namespace branchingtree { diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index c88410b3a..b54d47ca2 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -5,7 +5,6 @@ #include #include "libslic3r/ExPolygon.hpp" -#include "libslic3r/BoundingBox.hpp" namespace Slic3r { namespace branchingtree { diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 72314c9d8..59ec05113 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -18,6 +18,8 @@ class BranchingTreeBuilder: public branchingtree::Builder { const SupportableMesh &m_sm; const branchingtree::PointCloud &m_cloud; + std::set m_ground_mem; + // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour static constexpr double WIDENING_SCALE = 0.02; @@ -156,11 +158,21 @@ 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, + bool ret = false; + + auto it = m_ground_mem.find(from.id); + if (it == m_ground_mem.end()) { + ret = search_ground_route(ex_tbb, m_builder, m_sm, sla::Junction{from.pos.cast(), get_radius(from)}, get_radius(to)).first; + // 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 + m_ground_mem.insert(from.id); + } + if (ret) { build_subtree(from.id); } From 31fb0ae04902acd0083e09baca0d3b632fa60b6e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 24 Oct 2022 16:58:06 +0200 Subject: [PATCH 02/59] Do not add junctions for elongated bed connections It prevents search_ground_route from finding good solutions This should at least improve on SPE-1311 --- src/libslic3r/BranchingTree/BranchingTree.cpp | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 3e5e0a686..72ecf8c86 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -76,18 +76,7 @@ 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; - 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))) { + 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); From f5c16236421d7ae29674fc7cafea9bb5299ec24c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 24 Oct 2022 17:33:58 +0200 Subject: [PATCH 03/59] Try routing unsuccessful branches to ground recursively This helps to avoid huge branches being discarded where there is an obvious route to ground for a sub-branch. Should improve SPE-1311 --- src/libslic3r/BranchingTree/PointCloud.hpp | 6 +++--- src/libslic3r/SLA/BranchingTreeSLA.cpp | 23 ++++++++++++++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/BranchingTree/PointCloud.hpp b/src/libslic3r/BranchingTree/PointCloud.hpp index f99b17990..143ab72ce 100644 --- a/src/libslic3r/BranchingTree/PointCloud.hpp +++ b/src/libslic3r/BranchingTree/PointCloud.hpp @@ -259,9 +259,9 @@ template 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); + bool r = fn(nroot); + if (r && nroot.left >= 0) traverse(pc, nroot.left, fn); + if (r && nroot.right >= 0) traverse(pc, nroot.right, fn); } } diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 59ec05113..2660b8dea 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -61,22 +61,41 @@ class BranchingTreeBuilder: public branchingtree::Builder { toR); m_builder.add_junction(tod, toR); } + + return true; }); } void discard_subtree(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) { + bool ret = true; + int suppid_parent = m_cloud.get_leaf_id(node.id); - int suppid_left = m_cloud.get_leaf_id(node.left); - int suppid_right = m_cloud.get_leaf_id(node.right); + int suppid_left = branchingtree::Node::ID_NONE; + int suppid_right = branchingtree::Node::ID_NONE; + + if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), node)) + ret = false; + else + suppid_left = m_cloud.get_leaf_id(node.left); + + if (node.right >= 0 && add_ground_bridge(m_cloud.get(node.right), node)) + ret = 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; }); } From 1b54235d67ae8c1d49b14a821ff10a1c7e009c2a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Oct 2022 13:34:56 +0200 Subject: [PATCH 04/59] connect_to_ground() now handles widening correctly --- src/libslic3r/SLA/SupportTreeUtils.hpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 38515f879..983380d12 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -558,8 +558,7 @@ bool optimize_pinhead_placement(Ex policy, m.cfg.head_back_radius_mm; // check available distance - Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, - sd); + 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 @@ -663,11 +662,13 @@ std::pair connect_to_ground(Ex policy, return {false, SupportTreeNode::ID_UNSET}; Vec3d endp = hjp + d * dir; - auto ret = create_ground_pillar(policy, builder, sm, endp, dir, r, end_r); + double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); + double pill_r = r + bridge_ratio * (end_r - r); + auto ret = create_ground_pillar(policy, builder, sm, endp, dir, pill_r, end_r); if (ret.second >= 0) { - builder.add_bridge(hjp, endp, r); - builder.add_junction(endp, r); + builder.add_diffbridge(hjp, endp, r, pill_r); + builder.add_junction(endp, pill_r); } return ret; @@ -687,9 +688,9 @@ std::pair search_ground_route(Ex policy, 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. + // 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 solver(get_criteria(sm.cfg).stop_score(1e6)); From ba6b202aa41f30ccc924921b3ff06db7bb2f487a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Oct 2022 13:37:05 +0200 Subject: [PATCH 05/59] Fix weight calculation for fallback ground routing in branching tree --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 2660b8dea..633b18324 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -78,12 +78,16 @@ class BranchingTreeBuilder: public branchingtree::Builder { int suppid_left = branchingtree::Node::ID_NONE; int suppid_right = branchingtree::Node::ID_NONE; - if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), node)) + branchingtree::Node dst = node; + dst.weight += node.pos.z(); + dst.Rmin = std::max(node.Rmin, dst.Rmin); + + if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), dst)) ret = false; else suppid_left = m_cloud.get_leaf_id(node.left); - if (node.right >= 0 && add_ground_bridge(m_cloud.get(node.right), node)) + if (node.right >= 0 && add_ground_bridge(m_cloud.get(node.right), dst)) ret = false; else suppid_right = m_cloud.get_leaf_id(node.right); @@ -143,7 +147,7 @@ bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, 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, - m_sm.cfg.safety_distance_mm); + 0.9 * m_sm.cfg.safety_distance_mm); bool ret = hit.distance() > (tod - fromd).norm(); From ccab4b3dc9c32479ab0298ee1df4e243a7b4304f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Oct 2022 13:37:19 +0200 Subject: [PATCH 06/59] Improve optimization accuracy --- src/libslic3r/SLA/SupportTree.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 0ac29ff7a..66262fb34 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -94,8 +94,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-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; }; From 6448f36c2bbf1304b4ed9896fc5082cba08428da Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Oct 2022 13:37:32 +0200 Subject: [PATCH 07/59] Remove redundant headers --- src/libslic3r/BranchingTree/PointCloud.cpp | 1 - src/libslic3r/BranchingTree/PointCloud.hpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libslic3r/BranchingTree/PointCloud.cpp b/src/libslic3r/BranchingTree/PointCloud.cpp index f1d7ae521..5f07d91c7 100644 --- a/src/libslic3r/BranchingTree/PointCloud.cpp +++ b/src/libslic3r/BranchingTree/PointCloud.cpp @@ -1,6 +1,5 @@ #include "PointCloud.hpp" -#include "libslic3r/Geometry.hpp" #include "libslic3r/Tesselate.hpp" #include diff --git a/src/libslic3r/BranchingTree/PointCloud.hpp b/src/libslic3r/BranchingTree/PointCloud.hpp index 143ab72ce..08a1eb8e0 100644 --- a/src/libslic3r/BranchingTree/PointCloud.hpp +++ b/src/libslic3r/BranchingTree/PointCloud.hpp @@ -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" From 15a1d9a50a0185e4dbaacf89228d6c8da0012425 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Oct 2022 16:10:06 +0200 Subject: [PATCH 08/59] Keep track of avoidance paths and merge them if possible --- src/libslic3r/BranchingTree/BranchingTree.hpp | 5 + src/libslic3r/BranchingTree/PointCloud.hpp | 41 ++++-- src/libslic3r/SLA/BranchingTreeSLA.cpp | 86 ++++++++++-- src/libslic3r/SLA/SupportTreeBuilder.hpp | 4 + src/libslic3r/SLA/SupportTreeUtils.hpp | 129 +++++++++++++----- 5 files changed, 208 insertions(+), 57 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index b54d47ca2..fd69fe4cd 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -76,6 +76,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 diff --git a/src/libslic3r/BranchingTree/PointCloud.hpp b/src/libslic3r/BranchingTree/PointCloud.hpp index 08a1eb8e0..cbd01a465 100644 --- a/src/libslic3r/BranchingTree/PointCloud.hpp +++ b/src/libslic3r/BranchingTree/PointCloud.hpp @@ -78,14 +78,6 @@ private: rtree /* ? */> m_ktree; - bool is_outside_support_cone(const Vec3f &supp, const Vec3f &pt) const - { - Vec3d D = (pt - supp).cast(); - double dot_sq = -D.z() * std::abs(-D.z()); - - return dot_sq < D.squaredNorm() * cos2bridge_slope; - } - template 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 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,13 +255,32 @@ public: } }; +template constexpr bool IsTraverseFn = std::is_invocable_v; + +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 void traverse(PC &&pc, size_t root, Fn &&fn) { if (auto nodeptr = pc.find(root); nodeptr != nullptr) { auto &nroot = *nodeptr; - bool r = fn(nroot); - if (r && nroot.left >= 0) traverse(pc, nroot.left, fn); - if (r && nroot.right >= 0) traverse(pc, nroot.right, fn); + TraverseReturnT ret{true, true}; + + if constexpr (std::is_same_v, 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); } } diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 633b18324..b8c34399f 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -20,6 +20,14 @@ class BranchingTreeBuilder: public branchingtree::Builder { std::set m_ground_mem; + // Establish an index of + using PointIndexEl = std::pair; + boost::geometry::index:: + rtree /* ? */> + m_pillar_index; + + std::vector m_pillars; + // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour static constexpr double WIDENING_SCALE = 0.02; @@ -61,8 +69,6 @@ class BranchingTreeBuilder: public branchingtree::Builder { toR); m_builder.add_junction(tod, toR); } - - return true; }); } @@ -72,7 +78,7 @@ class BranchingTreeBuilder: public branchingtree::Builder { // 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) { - bool ret = true; + branchingtree::TraverseReturnT ret{true, true}; int suppid_parent = m_cloud.get_leaf_id(node.id); int suppid_left = branchingtree::Node::ID_NONE; @@ -83,12 +89,12 @@ class BranchingTreeBuilder: public branchingtree::Builder { dst.Rmin = std::max(node.Rmin, dst.Rmin); if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), dst)) - ret = false; + 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 = false; + ret.to_right = false; else suppid_right = m_cloud.get_leaf_id(node.right); @@ -141,7 +147,7 @@ public: }; bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, - const branchingtree::Node &to) + const branchingtree::Node &to) { Vec3d fromd = from.pos.cast(), tod = to.pos.cast(); double fromR = get_radius(from), toR = get_radius(to); @@ -183,12 +189,72 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, { bool ret = false; + namespace bgi = boost::geometry::index; + + struct Output { + std::optional &res; + + Output& operator *() { return *this; } + void operator=(const PointIndexEl &el) { res = el; } + Output& operator++() { return *this; } + }; + auto it = m_ground_mem.find(from.id); if (it == m_ground_mem.end()) { - ret = search_ground_route(ex_tbb, m_builder, m_sm, - sla::Junction{from.pos.cast(), - get_radius(from)}, - get_radius(to)).first; + std::optional result; + auto filter = bgi::satisfies( + [this, &from](const PointIndexEl &e) { + auto len = (from.pos - e.first).norm(); + return !branchingtree::is_occupied(m_pillars[e.second]) + && len < m_sm.cfg.max_bridge_length_mm + && !m_cloud.is_outside_support_cone(from.pos, e.first) + && beam_mesh_hit(ex_tbb, + m_sm.emesh, + Beam{Ball{from.pos.cast(), + get_radius(from)}, + Ball{e.first.cast(), + get_radius( + m_pillars[e.second])}}, + 0.9 * m_sm.cfg.safety_distance_mm) + .distance() + > len; + }); + m_pillar_index.query(filter && bgi::nearest(from.pos, 1), Output{result}); + + sla::Junction j{from.pos.cast(), get_radius(from)}; + if (!result) { + auto [found_conn, cjunc] = optimize_ground_connection( + ex_tbb, + m_builder, + m_sm, + j, + get_radius(to)); + + if (found_conn) { + Vec3d endp = cjunc? cjunc->pos : j.pos; + double R = cjunc? cjunc->r : j.r; + Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; + auto plr = create_ground_pillar(ex_tbb, m_builder, m_sm, endp, dir, R, get_radius(to)); + + if (plr.second >= 0) { + m_builder.add_junction(endp, R); + if (cjunc) { + m_builder.add_diffbridge(j.pos, endp, j.r, R); + branchingtree::Node n{cjunc->pos.cast(), float(R)}; + n.left = from.id; + m_pillars.emplace_back(n); + m_pillar_index.insert({n.pos, m_pillars.size() - 1}); + } + + ret = true; + } + } + } else { + const auto &resnode = m_pillars[result->second]; + m_builder.add_diffbridge(j.pos, resnode.pos.cast(), j.r, get_radius(resnode)); + m_pillars[result->second].right = from.id; + ret = true; + } // Remember that this node was tested if can go to ground, don't // test it with any other destination ground point because diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index 29d34ab8e..87e250f65 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -189,6 +189,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) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 983380d12..eed5e7938 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -283,17 +283,17 @@ Hit pinhead_mesh_hit(Ex ex, template 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); + head.r_back_mm, head.width_mm, safety_d); } template -std::optional search_widening_path(Ex policy, - const SupportableMesh &sm, +std::optional search_widening_path(Ex policy, + const SupportableMesh &sm, const Vec3d &jp, const Vec3d &dir, double radius, @@ -635,56 +635,61 @@ std::optional calculate_pinhead_placement(Ex policy, } template -std::pair connect_to_ground(Ex policy, - SupportTreeBuilder &builder, - const SupportableMesh &sm, - const Junction &j, - const Vec3d &dir, - double end_r) +std::pair> find_ground_connection( + 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); + 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())) { + 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}; + std::pair> ret; - 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 ret = create_ground_pillar(policy, builder, sm, endp, dir, pill_r, end_r); + if (std::isinf(tdown)) { + ret.first = true; + if (d > 0) { + 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); - if (ret.second >= 0) { - builder.add_diffbridge(hjp, endp, r, pill_r); - builder.add_junction(endp, pill_r); + ret.second = Junction{endp, pill_r}; + } } return ret; } template -std::pair search_ground_route(Ex policy, - SupportTreeBuilder &builder, - const SupportableMesh &sm, - const Junction &j, - double end_radius, - const Vec3d &init_dir = DOWN) +std::pair> optimize_ground_connection( + 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); + auto res = find_ground_connection(policy, builder, sm, j, init_dir, end_radius); if (res.first) return res; @@ -710,7 +715,59 @@ std::pair search_ground_route(Ex policy, Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized(); - return connect_to_ground(policy, builder, sm, j, bridgedir, end_radius); + return find_ground_connection(policy, builder, sm, j, bridgedir, end_radius); +} + +template +std::pair connect_to_ground(Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &j, + const Vec3d &dir, + double end_r) +{ + std::pair ret = {false, SupportTreeNode::ID_UNSET}; + + auto [found_c, cjunc] = find_ground_connection(policy, builder, sm, j, dir, end_r); + + if (found_c) { + Vec3d endp = cjunc? cjunc->pos : j.pos; + double R = cjunc? cjunc->r : j.r; + ret = create_ground_pillar(policy, builder, sm, endp, dir, R, end_r); + + if (ret.second >= 0) { + builder.add_diffbridge(j.pos, endp, j.r, R); + builder.add_junction(endp, R); + } + } + + return ret; +} + +template +std::pair search_ground_route(Ex policy, + SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &j, + double end_r, + const Vec3d &init_dir = DOWN) +{ + std::pair ret = {false, SupportTreeNode::ID_UNSET}; + + auto [found_c, cjunc] = optimize_ground_connection(policy, builder, sm, j, end_r, init_dir); + if (found_c) { + Vec3d endp = cjunc? cjunc->pos : j.pos; + double R = cjunc? cjunc->r : j.r; + Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; + ret = create_ground_pillar(policy, builder, sm, endp, dir, R, end_r); + + if (ret.second >= 0) { + builder.add_diffbridge(j.pos, endp, j.r, R); + builder.add_junction(endp, R); + } + } + + return ret; } template From 84784259ba6deeb742209034c7bcd2c7629704b9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Oct 2022 17:17:59 +0200 Subject: [PATCH 09/59] Add max_weight_on_model parameter Limiting the weight of subtrees going to the model body --- src/libslic3r/Preset.cpp | 1 + src/libslic3r/PrintConfig.cpp | 11 +++++++++++ src/libslic3r/PrintConfig.hpp | 2 ++ src/libslic3r/SLA/BranchingTreeSLA.cpp | 3 +++ src/libslic3r/SLA/SupportTree.hpp | 4 +++- src/libslic3r/SLAPrint.cpp | 2 ++ src/slic3r/GUI/ConfigManipulation.cpp | 1 + src/slic3r/GUI/Tab.cpp | 1 + 8 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 206844b7b..ce3c27bf6 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -501,6 +501,7 @@ static std::vector s_Preset_sla_print_options { "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", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index bc6d81b2d..482e36e43 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3676,6 +3676,17 @@ void PrintConfigDef::init_sla_params() def->mode = comExpert; def->set_default_value(new ConfigOptionInt(3)); + def = this->add("support_max_weight_on_model", coFloat); + def->label = L("Max weight on model"); + def->category = L("Supports"); + def->tooltip = 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.)); + 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." diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index a36125a07..47c43148a 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -861,6 +861,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 diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index b8c34399f..62431a347 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -272,6 +272,9 @@ 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(), get_radius(from)}; auto anchor = m_sm.cfg.ground_facing_only ? diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 66262fb34..051d56926 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -76,7 +76,9 @@ 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; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index d9b8e33df..21895f943 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -66,6 +66,7 @@ sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c) 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(); return scfg; } @@ -843,6 +844,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vectorappend_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_max_weight_on_model"); 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"); From a20cf5521df9b118351988edb2967c992f30bcd4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 2 Nov 2022 18:11:01 +0100 Subject: [PATCH 10/59] ground route merges wip on separating ground route search and actual creation wip Some fixes but still problems with pedestals --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 104 ++++++---- src/libslic3r/SLA/DefaultSupportTree.cpp | 11 +- src/libslic3r/SLA/DefaultSupportTree.hpp | 3 +- src/libslic3r/SLA/SupportTreeBuilder.hpp | 29 +-- src/libslic3r/SLA/SupportTreeUtils.hpp | 234 +++++++++++++++++++---- 5 files changed, 291 insertions(+), 90 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 62431a347..96675288d 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -32,7 +32,7 @@ class BranchingTreeBuilder: public branchingtree::Builder { // widening behaviour static constexpr double WIDENING_SCALE = 0.02; - 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; @@ -109,6 +109,44 @@ class BranchingTreeBuilder: public branchingtree::Builder { }); } + std::optional + search_for_existing_pillar(const branchingtree::Node &from) const + { + namespace bgi = boost::geometry::index; + + struct Output + { + std::optional &res; + + Output &operator*() { return *this; } + void operator=(const PointIndexEl &el) { res = el; } + Output &operator++() { return *this; } + }; + + std::optional result; + + auto filter = bgi::satisfies([this, &from](const PointIndexEl &e) { + assert(e.second < m_pillars.size()); + + auto len = (from.pos - e.first).norm(); + const branchingtree::Node &to = m_pillars[e.second]; + double sd = m_sm.cfg.safety_distance_mm; + + Beam beam{Ball{from.pos.cast(), get_radius(from)}, + Ball{e.first.cast(), get_radius(to)}}; + + return !branchingtree::is_occupied(to) && + len < m_sm.cfg.max_bridge_length_mm && + !m_cloud.is_outside_support_cone(from.pos, e.first) && + beam_mesh_hit(ex_tbb, m_sm.emesh, beam, sd).distance() > len; + }); + + m_pillar_index.query(filter && bgi::nearest(from.pos, 1), + Output{result}); + + return result; + } + public: BranchingTreeBuilder(SupportTreeBuilder &builder, const SupportableMesh &sm, @@ -153,7 +191,7 @@ bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, 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, - 0.9 * m_sm.cfg.safety_distance_mm); + m_sm.cfg.safety_distance_mm); bool ret = hit.distance() > (tod - fromd).norm(); @@ -201,53 +239,43 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, auto it = m_ground_mem.find(from.id); if (it == m_ground_mem.end()) { - std::optional result; - auto filter = bgi::satisfies( - [this, &from](const PointIndexEl &e) { - auto len = (from.pos - e.first).norm(); - return !branchingtree::is_occupied(m_pillars[e.second]) - && len < m_sm.cfg.max_bridge_length_mm - && !m_cloud.is_outside_support_cone(from.pos, e.first) - && beam_mesh_hit(ex_tbb, - m_sm.emesh, - Beam{Ball{from.pos.cast(), - get_radius(from)}, - Ball{e.first.cast(), - get_radius( - m_pillars[e.second])}}, - 0.9 * m_sm.cfg.safety_distance_mm) - .distance() - > len; - }); - m_pillar_index.query(filter && bgi::nearest(from.pos, 1), Output{result}); + std::optional result = search_for_existing_pillar(from); sla::Junction j{from.pos.cast(), get_radius(from)}; if (!result) { - auto [found_conn, cjunc] = optimize_ground_connection( + auto conn = optimize_ground_connection( ex_tbb, m_builder, m_sm, j, get_radius(to)); - if (found_conn) { - Vec3d endp = cjunc? cjunc->pos : j.pos; - double R = cjunc? cjunc->r : j.r; - Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; - auto plr = create_ground_pillar(ex_tbb, m_builder, m_sm, endp, dir, R, 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; + m_pillars.emplace_back(n); + m_pillar_index.insert({n.pos, m_pillars.size() - 1}); + ret = true; - if (plr.second >= 0) { - m_builder.add_junction(endp, R); - if (cjunc) { - m_builder.add_diffbridge(j.pos, endp, j.r, R); - branchingtree::Node n{cjunc->pos.cast(), float(R)}; - n.left = from.id; - m_pillars.emplace_back(n); - m_pillar_index.insert({n.pos, m_pillars.size() - 1}); - } +// Vec3d endp = cjunc? cjunc->pos : j.pos; +// double R = cjunc? cjunc->r : j.r; +// Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; +// auto plr = create_ground_pillar(ex_tbb, m_builder, m_sm, endp, dir, R, get_radius(to)); - ret = true; - } +// if (plr.second >= 0) { +// m_builder.add_junction(endp, R); +// if (cjunc) { +// m_builder.add_diffbridge(j.pos, endp, j.r, R); +// branchingtree::Node n{cjunc->pos.cast(), float(R)}; +// n.left = from.id; +// m_pillars.emplace_back(n); +// m_pillar_index.insert({n.pos, m_pillars.size() - 1}); +// } + +// ret = true; +// } } } else { const auto &resnode = m_pillars[result->second]; diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 9e21fca45..63280b9df 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -340,15 +340,13 @@ 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, - head_id); + sourcedir, hjp.r, head_id); if (pillar_id >= 0) // Save the pillar endpoint in the spatial index m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt, @@ -587,7 +585,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); @@ -615,10 +613,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); } } } diff --git a/src/libslic3r/SLA/DefaultSupportTree.hpp b/src/libslic3r/SLA/DefaultSupportTree.hpp index ff0269978..cea4b65e1 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.hpp +++ b/src/libslic3r/SLA/DefaultSupportTree.hpp @@ -176,9 +176,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) diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index 87e250f65..e4567e405 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -67,6 +67,14 @@ 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; @@ -77,7 +85,6 @@ struct Head: public SupportTreeNode { 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; @@ -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. diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index eed5e7938..e7bf9f018 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -10,6 +10,8 @@ #include #include +#include +#include #include namespace Slic3r { namespace sla { @@ -358,19 +360,19 @@ std::pair create_ground_pillar( Ex policy, SupportTreeBuilder &builder, const SupportableMesh &sm, - const Vec3d &pinhead_junctionpt, + const Junction &pinhead_junctionpt, const Vec3d &sourcedir, - double radius, double end_radius, long head_id = SupportTreeNode::ID_UNSET) { - Vec3d jp = pinhead_junctionpt, endp = jp, dir = sourcedir; + Vec3d jp = pinhead_junctionpt.pos, 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 radius = pinhead_junctionpt.r; double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm)); @@ -634,8 +636,185 @@ std::optional calculate_pinhead_placement(Ex policy, return {}; } +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 path; + double end_radius; + std::optional pillar_base; + + operator bool() const { return !path.empty(); } +}; + template -std::pair> find_ground_connection( +GroundConnection find_pillar_route(Ex policy, +// SupportTreeBuilder &builder, + const SupportableMesh &sm, + const Junction &source, + const Vec3d &sourcedir, + double end_radius) +{ + GroundConnection ret; + + Vec3d jp = source.pos, 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 radius = source.r; + + 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 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; + ret.path.emplace_back(source); + endp = diffbr->endp; + radius = diffbr->end_r; +// builder.add_junction(endp, radius); + ret.path.emplace_back(endp, radius); +// non_head = true; + dir = diffbr->get_dir(); + eval_limits(); + } else return ret;//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) { + ret.path.clear(); + return ret; + //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); + ret.path.emplace_back(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); + ret.end_radius = end_radius; + + if (can_add_base) { + ret.pillar_base = Pedestal{gp, h, sm.cfg.base_height_mm, sm.cfg.base_radius_mm}; +// builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm, +// sm.cfg.base_radius_mm); + } + + return ret; //{true, pillar_id}; +} + +inline long build_ground_connection(SupportTreeBuilder &builder, + const SupportableMesh &sm, + const GroundConnection &conn) +{ + long ret = SupportTreeNode::ID_UNSET; + + if (!conn) + return ret; + + auto it = conn.path.begin(); + auto itnx = std::next(it); + + while (itnx != conn.path.end()) { + builder.add_diffbridge(*it, *itnx); + } + + auto gp = conn.path.back().pos; + gp.z() = ground_level(sm); + double h = conn.path.back().pos.z() - gp.z(); + ret = builder.add_pillar(gp, h, conn.path.back().r, conn.end_radius); + if (conn.pillar_base) + builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); + + return ret; +} + +template +GroundConnection find_ground_connection( Ex policy, SupportTreeBuilder &builder, const SupportableMesh &sm, @@ -662,16 +841,23 @@ std::pair> find_ground_connection( d += r; } - std::pair> ret; + GroundConnection ret; + ret.end_radius = end_r; if (std::isinf(tdown)) { - ret.first = true; + ret.path.emplace_back(j); if (d > 0) { 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); - ret.second = Junction{endp, pill_r}; +// ret.path.emplace_back(endp, pill_r); + auto route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); + for (auto &j : route.path) + ret.path.emplace_back(j); + + ret.pillar_base = route.pillar_base; + ret.end_radius = end_r; } } @@ -679,7 +865,7 @@ std::pair> find_ground_connection( } template -std::pair> optimize_ground_connection( +GroundConnection optimize_ground_connection( Ex policy, SupportTreeBuilder &builder, const SupportableMesh &sm, @@ -690,7 +876,7 @@ std::pair> 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.first) + if (!res) return res; // Optimize bridge direction: @@ -728,18 +914,9 @@ std::pair connect_to_ground(Ex policy, { std::pair ret = {false, SupportTreeNode::ID_UNSET}; - auto [found_c, cjunc] = find_ground_connection(policy, builder, sm, j, dir, end_r); - - if (found_c) { - Vec3d endp = cjunc? cjunc->pos : j.pos; - double R = cjunc? cjunc->r : j.r; - ret = create_ground_pillar(policy, builder, sm, endp, dir, R, end_r); - - if (ret.second >= 0) { - builder.add_diffbridge(j.pos, endp, j.r, R); - builder.add_junction(endp, R); - } - } + auto conn = find_ground_connection(policy, builder, sm, j, dir, end_r); + ret.first = bool(conn); + ret.second = build_ground_connection(builder, sm, conn); return ret; } @@ -754,18 +931,11 @@ std::pair search_ground_route(Ex policy, { std::pair ret = {false, SupportTreeNode::ID_UNSET}; - auto [found_c, cjunc] = optimize_ground_connection(policy, builder, sm, j, end_r, init_dir); - if (found_c) { - Vec3d endp = cjunc? cjunc->pos : j.pos; - double R = cjunc? cjunc->r : j.r; - Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; - ret = create_ground_pillar(policy, builder, sm, endp, dir, R, end_r); + auto conn = optimize_ground_connection(policy, builder, sm, j, + end_r, init_dir); - if (ret.second >= 0) { - builder.add_diffbridge(j.pos, endp, j.r, R); - builder.add_junction(endp, R); - } - } + ret.first = bool(conn); + ret.second = build_ground_connection(builder, sm, conn); return ret; } From f028bfe680c905b52f8050fd9310269ff64f3eee Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 3 Nov 2022 18:16:15 +0100 Subject: [PATCH 11/59] Pillar creation restored but only in branchingtree --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 36 ++++++++-------- src/libslic3r/SLA/DefaultSupportTree.cpp | 14 +++++-- src/libslic3r/SLA/SupportTreeUtils.hpp | 52 +++++++++--------------- 3 files changed, 46 insertions(+), 56 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 96675288d..fcd546ae0 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -26,7 +26,10 @@ class BranchingTreeBuilder: public branchingtree::Builder { rtree /* ? */> m_pillar_index; - std::vector m_pillars; + std::vector m_pillars; // to put an index over them + + // cache succesfull ground connections + std::map m_gnd_connections; // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour @@ -182,6 +185,13 @@ public: } bool is_valid() const override { return !m_builder.ctl().stopcondition(); } + + + void group_pillars() + { + + } + }; bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, @@ -251,31 +261,15 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, get_radius(to)); if (conn) { - build_ground_connection(m_builder, m_sm, 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; m_pillars.emplace_back(n); m_pillar_index.insert({n.pos, m_pillars.size() - 1}); + m_gnd_connections[m_pillars.size() - 1] = conn; + ret = true; - -// Vec3d endp = cjunc? cjunc->pos : j.pos; -// double R = cjunc? cjunc->r : j.r; -// Vec3d dir = cjunc? Vec3d((j.pos - cjunc->pos).normalized()) : DOWN; -// auto plr = create_ground_pillar(ex_tbb, m_builder, m_sm, endp, dir, R, get_radius(to)); - -// if (plr.second >= 0) { -// m_builder.add_junction(endp, R); -// if (cjunc) { -// m_builder.add_diffbridge(j.pos, endp, j.r, R); -// branchingtree::Node n{cjunc->pos.cast(), float(R)}; -// n.left = from.id; -// m_pillars.emplace_back(n); -// m_pillar_index.insert({n.pos, m_pillars.size() - 1}); -// } - -// ret = true; -// } } } else { const auto &resnode = m_pillars[result->second]; @@ -381,6 +375,8 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s BranchingTreeBuilder vbuilder{builder, sm, nodes}; branchingtree::build_tree(nodes, vbuilder); + vbuilder.group_pillars(); + for (size_t id : vbuilder.unroutable_pinheads()) builder.head(id).invalidate(); diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 63280b9df..06477c40c 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -344,15 +344,21 @@ bool DefaultSupportTree::create_ground_pillar(const Junction &hjp, const Vec3d &sourcedir, long head_id) { - auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, - m_builder, m_sm, hjp, - sourcedir, hjp.r, head_id); + long pillar_id = SupportTreeNode::ID_UNSET; + + auto conn = sla::find_pillar_route(suptree_ex_policy, m_sm, hjp, sourcedir, hjp.r); + if (conn) + pillar_id = build_ground_connection(m_builder, m_sm, conn); + +// auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, +// m_builder, m_sm, hjp, +// sourcedir, hjp.r, head_id); if (pillar_id >= 0) // Save the pillar endpoint in the spatial index m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt, unsigned(pillar_id)); - return ret; + return bool(conn); } void DefaultSupportTree::add_pinheads() diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index e7bf9f018..ecc9fdbf7 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -652,7 +652,6 @@ struct GroundConnection { template GroundConnection find_pillar_route(Ex policy, -// SupportTreeBuilder &builder, const SupportableMesh &sm, const Junction &source, const Vec3d &sourcedir, @@ -661,7 +660,6 @@ GroundConnection find_pillar_route(Ex policy, GroundConnection ret; Vec3d jp = source.pos, 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 @@ -694,18 +692,13 @@ GroundConnection find_pillar_route(Ex policy, 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; ret.path.emplace_back(source); endp = diffbr->endp; radius = diffbr->end_r; -// builder.add_junction(endp, radius); ret.path.emplace_back(endp, radius); -// non_head = true; dir = diffbr->get_dir(); eval_limits(); - } else return ret;//return {false, pillar_id}; + } else return ret; } if (sm.cfg.object_elevation_mm < EPSILON) @@ -760,28 +753,18 @@ GroundConnection find_pillar_route(Ex policy, } 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); ret.path.emplace_back(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); ret.end_radius = end_radius; if (can_add_base) { - ret.pillar_base = Pedestal{gp, h, sm.cfg.base_height_mm, sm.cfg.base_radius_mm}; -// builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm, -// sm.cfg.base_radius_mm); + ret.pillar_base = + Pedestal{gp, sm.cfg.base_height_mm, sm.cfg.base_radius_mm, end_radius}; } return ret; //{true, pillar_id}; @@ -806,7 +789,15 @@ inline long build_ground_connection(SupportTreeBuilder &builder, auto gp = conn.path.back().pos; gp.z() = ground_level(sm); double h = conn.path.back().pos.z() - gp.z(); - ret = builder.add_pillar(gp, h, conn.path.back().r, conn.end_radius); + +// 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.end_radius); + if (conn.pillar_base) builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); @@ -846,19 +837,16 @@ GroundConnection find_ground_connection( if (std::isinf(tdown)) { ret.path.emplace_back(j); - if (d > 0) { - 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); + 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); -// ret.path.emplace_back(endp, pill_r); - auto route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); - for (auto &j : route.path) - ret.path.emplace_back(j); + auto route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); + for (auto &j : route.path) + ret.path.emplace_back(j); - ret.pillar_base = route.pillar_base; - ret.end_radius = end_r; - } + ret.pillar_base = route.pillar_base; + ret.end_radius = end_r; } return ret; From 834f428ba01a6f8721ed7803430a866b3ec77e84 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 4 Nov 2022 16:22:23 +0100 Subject: [PATCH 12/59] WIP on grouping ground pillars for branching tree supports --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 28 +++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index fcd546ae0..ce440b227 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -186,12 +186,18 @@ public: bool is_valid() const override { return !m_builder.ctl().stopcondition(); } + const std::vector & pillars() const { return m_pillars; } - void group_pillars() + const GroundConnection *ground_conn(size_t pillar) const { + const GroundConnection *ret = nullptr; + auto it = m_gnd_connections.find(pillar); + if (it != m_gnd_connections.end()) + ret = &it->second; + + return ret; } - }; bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, @@ -375,7 +381,23 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s BranchingTreeBuilder vbuilder{builder, sm, nodes}; branchingtree::build_tree(nodes, vbuilder); - vbuilder.group_pillars(); + std::vector bedleafs; + 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(20.f); + 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 < 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()) builder.head(id).invalidate(); From e62873ff1341544083d5727ae0235f2ddb8085d2 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 8 Nov 2022 08:29:42 +0100 Subject: [PATCH 13/59] Prevent uninitialized value in nlopt optimizer --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index f5d314046..cc7edb97a 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -68,7 +68,7 @@ template class NLoptOpt {}; template class NLoptOpt> { protected: StopCriteria m_stopcr; - OptDir m_dir; + OptDir m_dir = OptDir::MIN; template using TOptData = std::tuple*, NLoptOpt*, nlopt_opt>; From b4b5e8eb8e6f0ef939b4e7b600ec123f74bdbce9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 8 Nov 2022 08:30:02 +0100 Subject: [PATCH 14/59] WIP on pillar grouping for sla branching supports --- src/libslic3r/BranchingTree/PointCloud.hpp | 5 ++ src/libslic3r/SLA/BranchingTreeSLA.cpp | 54 ++++++++++++---------- src/libslic3r/SLA/SupportTree.hpp | 4 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 2 +- 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/BranchingTree/PointCloud.hpp b/src/libslic3r/BranchingTree/PointCloud.hpp index cbd01a465..03b935f76 100644 --- a/src/libslic3r/BranchingTree/PointCloud.hpp +++ b/src/libslic3r/BranchingTree/PointCloud.hpp @@ -286,6 +286,11 @@ template void traverse(PC &&pc, size_t root, Fn &&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 diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index ce440b227..a4605bbfd 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -122,7 +122,7 @@ class BranchingTreeBuilder: public branchingtree::Builder { std::optional &res; Output &operator*() { return *this; } - void operator=(const PointIndexEl &el) { res = el; } + Output &operator=(const PointIndexEl &el) { res = el; return *this; } Output &operator++() { return *this; } }; @@ -245,20 +245,12 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, namespace bgi = boost::geometry::index; - struct Output { - std::optional &res; - - Output& operator *() { return *this; } - void operator=(const PointIndexEl &el) { res = el; } - Output& operator++() { return *this; } - }; - auto it = m_ground_mem.find(from.id); if (it == m_ground_mem.end()) { - std::optional result = search_for_existing_pillar(from); +// std::optional result = search_for_existing_pillar(from); sla::Junction j{from.pos.cast(), get_radius(from)}; - if (!result) { +// if (!result) { auto conn = optimize_ground_connection( ex_tbb, m_builder, @@ -268,21 +260,21 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, 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; - m_pillars.emplace_back(n); - m_pillar_index.insert({n.pos, m_pillars.size() - 1}); +// Junction connlast = conn.path.back(); +// branchingtree::Node n{connlast.pos.cast(), float(connlast.r)}; +// n.left = from.id; + m_pillars.emplace_back(from); +// m_pillar_index.insert({n.pos, m_pillars.size() - 1}); m_gnd_connections[m_pillars.size() - 1] = conn; ret = true; } - } else { - const auto &resnode = m_pillars[result->second]; - m_builder.add_diffbridge(j.pos, resnode.pos.cast(), j.r, get_radius(resnode)); - m_pillars[result->second].right = from.id; - ret = true; - } +// } else { +// const auto &resnode = m_pillars[result->second]; +// m_builder.add_diffbridge(j.pos, resnode.pos.cast(), j.r, get_radius(resnode)); +// m_pillars[result->second].right = from.id; +// ret = true; +// } // Remember that this node was tested if can go to ground, don't // test it with any other destination ground point because @@ -353,7 +345,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(), h->r_back_mm); - h->id = leafs.size() - 1; + h->id = long(leafs.size() - 1); builder.add_head(h->id, *h); } @@ -372,7 +364,7 @@ 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()); branchingtree::PointCloud nodes{std::move(meshpts), std::move(bedpts), @@ -388,11 +380,23 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s bedleafs.emplace_back(n); } - props.max_branch_length(20.f); + 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); + // 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()); + + +// 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) diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 051d56926..b70f77319 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-10; - static const unsigned constexpr optimizer_max_iterations = 2000; + static const double constexpr optimizer_rel_score_diff = 1e-16; + static const unsigned constexpr optimizer_max_iterations = 20000; static const unsigned constexpr pillar_cascade_neighbors = 3; }; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index ecc9fdbf7..834ade358 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -875,7 +875,7 @@ GroundConnection optimize_ground_connection( 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; + 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; From 823d28ec4b415c447367fdf3011f5d4f08972bbd Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 8 Nov 2022 10:03:55 +0100 Subject: [PATCH 15/59] Fix infinite loop in build_ground_connection --- src/libslic3r/SLA/SupportTreeUtils.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 834ade358..ef132c969 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -784,6 +784,8 @@ inline long build_ground_connection(SupportTreeBuilder &builder, while (itnx != conn.path.end()) { builder.add_diffbridge(*it, *itnx); + builder.add_junction(*itnx); + ++it; ++itnx; } auto gp = conn.path.back().pos; From 4dc0741766d1adf526b87fb8e8d87d928cc81c67 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 8 Nov 2022 10:07:33 +0100 Subject: [PATCH 16/59] set strict safety distance Don't change with pillar radius --- src/libslic3r/SLA/SupportTreeUtils.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index ef132c969..1cf9dfdc5 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -709,7 +709,7 @@ 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 = radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + 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.; @@ -817,7 +817,7 @@ GroundConnection find_ground_connection( { auto hjp = j.pos; double r = j.r; - auto sd = r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm; + auto sd = sm.cfg.safety_distance_mm; //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) From 1e9bd28714d936277cb925fddcaa3d835bb2ab85 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 11 Nov 2022 15:12:53 +0100 Subject: [PATCH 17/59] 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); +} From d3a2f11e2929c7726fb8cf0745da6febfd528060 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 11 Nov 2022 16:53:56 +0100 Subject: [PATCH 18/59] Use old pillar creation functions for default support tree --- src/libslic3r/SLA/DefaultSupportTree.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 06477c40c..63280b9df 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -344,21 +344,15 @@ bool DefaultSupportTree::create_ground_pillar(const Junction &hjp, const Vec3d &sourcedir, long head_id) { - long pillar_id = SupportTreeNode::ID_UNSET; - - auto conn = sla::find_pillar_route(suptree_ex_policy, m_sm, hjp, sourcedir, hjp.r); - if (conn) - pillar_id = build_ground_connection(m_builder, m_sm, conn); - -// auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, -// m_builder, m_sm, hjp, -// sourcedir, hjp.r, head_id); + auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, + m_builder, m_sm, hjp, + sourcedir, hjp.r, head_id); if (pillar_id >= 0) // Save the pillar endpoint in the spatial index m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt, unsigned(pillar_id)); - return bool(conn); + return ret; } void DefaultSupportTree::add_pinheads() From 5f63b4496d7c3da51005fbb13e647c5d0e5e4c7f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 11 Nov 2022 16:54:53 +0100 Subject: [PATCH 19/59] WIP on pillar grouping --- src/libslic3r/BranchingTree/BranchingTree.hpp | 2 +- src/libslic3r/BranchingTree/PointCloud.cpp | 5 ++++- src/libslic3r/SLA/BranchingTreeSLA.cpp | 10 +++++----- src/libslic3r/SLA/SupportTreeUtils.hpp | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index 3f7fd7b80..d30499c11 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -21,7 +21,7 @@ class Properties public: - constexpr bool group_pillars() const noexcept { return false; } + constexpr bool group_pillars() const noexcept { return true; } // Maximum slope for bridges of the tree Properties &max_slope(double val) noexcept diff --git a/src/libslic3r/BranchingTree/PointCloud.cpp b/src/libslic3r/BranchingTree/PointCloud.cpp index 5f07d91c7..319f334ff 100644 --- a/src/libslic3r/BranchingTree/PointCloud.cpp +++ b/src/libslic3r/BranchingTree/PointCloud.cpp @@ -197,7 +197,10 @@ PointCloud::PointCloud(std::vector 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}); } } diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index fc5dc2982..2edc0fe7b 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -382,20 +382,20 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s BranchingTreeBuilder vbuilder{builder, sm, nodes}; branchingtree::build_tree(nodes, vbuilder); + std::cout << "Original pillar count: " << vbuilder.pillars().size() << std::endl; + if constexpr (props.group_pillars()) { std::vector bedleafs; - for (auto n : vbuilder.pillars()) { - n.left = branchingtree::Node::ID_NONE; - n.right = branchingtree::Node::ID_NONE; - bedleafs.emplace_back(n); - } + std::copy(vbuilder.pillars().begin(), vbuilder.pillars().end(), std::back_inserter(bedleafs)); branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; branchingtree::build_tree(gndnodes, gndbuilder); + std::cout << "Grouped pillar count: " << gndbuilder.pillars().size() << std::endl; build_pillars(builder, gndbuilder, sm); + } else { build_pillars(builder, vbuilder, sm); } diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 070ffbf0d..243152189 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -629,7 +629,7 @@ std::optional calculate_pinhead_placement(Ex policy, }; if (optimize_pinhead_placement(policy, sm, head)) { - head.id = suppt_idx; + head.id = long(suppt_idx); return head; } From 963e8e6585ae79c4a927ef8f41d5dca5affa6cad Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 14 Nov 2022 12:37:34 +0100 Subject: [PATCH 20/59] Revert util functions of DefaultSupportTree to original To not break DefautlSupportTree --- src/libslic3r/CMakeLists.txt | 1 + src/libslic3r/SLA/DefaultSupportTree.cpp | 9 +- src/libslic3r/SLA/DefaultSupportTree.hpp | 2 +- src/libslic3r/SLA/SupportTree.hpp | 2 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 190 +--------------- src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp | 220 +++++++++++++++++++ tests/sla_print/sla_supptreeutils_tests.cpp | 10 +- 7 files changed, 251 insertions(+), 183 deletions(-) create mode 100644 src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 3740d4e01..232285cee 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -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 diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 63280b9df..53475542a 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -345,8 +345,13 @@ bool DefaultSupportTree::create_ground_pillar(const Junction &hjp, long head_id) { auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy, - m_builder, m_sm, hjp, - sourcedir, hjp.r, head_id); + 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 m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt, diff --git a/src/libslic3r/SLA/DefaultSupportTree.hpp b/src/libslic3r/SLA/DefaultSupportTree.hpp index cea4b65e1..ee58e9ded 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.hpp +++ b/src/libslic3r/SLA/DefaultSupportTree.hpp @@ -1,7 +1,7 @@ #ifndef LEGACYSUPPORTTREE_HPP #define LEGACYSUPPORTTREE_HPP -#include "SupportTreeUtils.hpp" +#include "SupportTreeUtilsLegacy.hpp" #include #include diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 051d56926..b7aaf8aee 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -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; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 243152189..3ee72436a 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -351,136 +351,6 @@ std::optional search_widening_path(Ex policy, 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 -std::pair create_ground_pillar( - Ex policy, - SupportTreeBuilder &builder, - const SupportableMesh &sm, - const Junction &pinhead_junctionpt, - const Vec3d &sourcedir, - double end_radius, - long head_id = SupportTreeNode::ID_UNSET) -{ - Vec3d jp = pinhead_junctionpt.pos, 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 radius = pinhead_junctionpt.r; - - 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 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}; -} - inline double distance(const SupportPoint &a, const SupportPoint &b) { return (a.pos - b.pos).norm(); @@ -533,13 +403,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; @@ -548,19 +418,18 @@ 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_mm; - // check available distance + // check available distance Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, sd); if (t.distance() < w) { @@ -568,7 +437,7 @@ bool optimize_pinhead_placement(Ex policy, // viable normal that doesn't collide with the model // geometry and its very close to the default. - Optimizer solver(get_criteria(m.cfg).stop_score(w).max_iterations(100)); + Optimizer solver(get_criteria(m.cfg).stop_score(w).max_iterations(100)); solver.seed(0); // we want deterministic behavior auto oresult = solver.to_max().optimize( @@ -602,10 +471,10 @@ bool optimize_pinhead_placement(Ex policy, head.r_back_mm = back_r; ret = true; - } else if (back_r > m.cfg.head_fallback_radius_mm) { + } /*else if (back_r > m.cfg.head_fallback_radius_mm) { head.r_back_mm = m.cfg.head_fallback_radius_mm; ret = optimize_pinhead_placement(policy, m, head); - } + }*/ return ret; } @@ -848,7 +717,7 @@ GroundConnection find_ground_connection( // 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 + // can be inspected ret.pillar_base = gnd_route.pillar_base; return ret; @@ -876,7 +745,7 @@ GroundConnection optimize_ground_connection( 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*/; + auto sd = sm.cfg.safety_distance_mm; auto oresult = solver.to_max().optimize( [&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) { auto &[plr, azm] = input; @@ -893,41 +762,6 @@ GroundConnection optimize_ground_connection( return find_ground_connection(policy, sm, j, bridgedir, end_radius); } -template -std::pair connect_to_ground(Ex policy, - SupportTreeBuilder &builder, - const SupportableMesh &sm, - const Junction &j, - const Vec3d &dir, - double end_r) -{ - std::pair ret = {false, SupportTreeNode::ID_UNSET}; - - auto conn = find_ground_connection(policy, sm, j, dir, end_r); - ret.first = bool(conn); - ret.second = build_ground_connection(builder, sm, conn); - - return ret; -} - -template -std::pair search_ground_route(Ex policy, - SupportTreeBuilder &builder, - const SupportableMesh &sm, - const Junction &j, - double end_r, - const Vec3d &init_dir = DOWN) -{ - std::pair ret = {false, SupportTreeNode::ID_UNSET}; - - auto conn = optimize_ground_connection(policy, sm, j, end_r, init_dir); - - ret.first = bool(conn); - ret.second = build_ground_connection(builder, sm, conn); - - return ret; -} - template bool optimize_anchor_placement(Ex policy, const SupportableMesh &sm, diff --git a/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp b/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp new file mode 100644 index 000000000..43a8ddb59 --- /dev/null +++ b/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp @@ -0,0 +1,220 @@ +#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 { + +// 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 +std::pair 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 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 +std::pair 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 +std::pair 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 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 diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index b433e80ee..aca193a1b 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -49,7 +49,11 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") // 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 + // 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(EndRadius)); // Check if the avoidance junction is indeed outside of the disk barrier's @@ -108,6 +112,10 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" // 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 end the pillar base's upper radius should match REQUIRE(conn.pillar_base->r_top == Approx(EndRadius)); From a20659fc2d3f8b497cd4b0659fe378184452801d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 15 Nov 2022 15:08:28 +0100 Subject: [PATCH 21/59] New ground route search implemented Working gap avoidance for zero elevation --- src/libslic3r/AABBMesh.hpp | 6 +- src/libslic3r/SLA/BranchingTreeSLA.cpp | 34 +- src/libslic3r/SLA/SupportTree.hpp | 20 +- src/libslic3r/SLA/SupportTreeBuilder.hpp | 8 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 376 ++++++++++++++------ tests/sla_print/sla_supptreeutils_tests.cpp | 64 +++- tests/sla_print/sla_test_utils.cpp | 11 +- 7 files changed, 360 insertions(+), 159 deletions(-) diff --git a/src/libslic3r/AABBMesh.hpp b/src/libslic3r/AABBMesh.hpp index 6a08c4303..312d8926d 100644 --- a/src/libslic3r/AABBMesh.hpp +++ b/src/libslic3r/AABBMesh.hpp @@ -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 diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 2edc0fe7b..16236f7cd 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -39,7 +39,7 @@ class BranchingTreeBuilder: public branchingtree::Builder { { 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 m_unroutable_pinheads; @@ -247,32 +247,18 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, auto it = m_ground_mem.find(from.id); if (it == m_ground_mem.end()) { -// std::optional result = search_for_existing_pillar(from); - sla::Junction j{from.pos.cast(), get_radius(from)}; -// if (!result) { - auto conn = optimize_ground_connection( - ex_tbb, - m_sm, - j, - get_radius(to)); + Vec3d init_dir = (to.pos - from.pos).cast().normalized(); - if (conn) { -// Junction connlast = conn.path.back(); -// branchingtree::Node n{connlast.pos.cast(), float(connlast.r)}; -// n.left = from.id; - m_pillars.emplace_back(from); -// m_pillar_index.insert({n.pos, m_pillars.size() - 1}); - m_gnd_connections[m_pillars.size() - 1] = conn; + auto conn = deepsearch_ground_connection(ex_tbb, m_sm, j, + get_radius(to), init_dir); - ret = true; - } -// } else { -// const auto &resnode = m_pillars[result->second]; -// m_builder.add_diffbridge(j.pos, resnode.pos.cast(), j.r, get_radius(resnode)); -// m_pillars[result->second].right = from.id; -// ret = true; -// } + if (conn) { + m_pillars.emplace_back(from); + m_gnd_connections[m_pillars.size() - 1] = conn; + + ret = true; + } // Remember that this node was tested if can go to ground, don't // test it with any other destination ground point because diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index b7aaf8aee..e0d7db97d 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -84,6 +84,12 @@ struct SupportTreeConfig 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) // ///////////////////////////////////////////////////////////////////////// @@ -91,7 +97,9 @@ 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; @@ -117,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) diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index e4567e405..93fbead99 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -79,7 +79,7 @@ struct Junction: public SupportTreeNode { 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; @@ -87,12 +87,12 @@ struct Head: public SupportTreeNode { // 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, diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 3ee72436a..08e410431 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -316,8 +316,7 @@ std::optional search_widening_path(Ex policy, auto d = spheric_to_dir(plr, azm).normalized(); - auto sd = new_radius * sm.cfg.safety_distance_mm / - sm.cfg.head_back_radius_mm; + auto sd = sm.cfg.safety_distance(new_radius); double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius, new_radius, t, sd) @@ -427,7 +426,7 @@ bool optimize_pinhead_placement(Ex policy, // Reassemble the now corrected normal auto nn = spheric_to_dir(polar, azimuth).normalized(); - double sd = m.cfg.safety_distance_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); @@ -471,10 +470,10 @@ bool optimize_pinhead_placement(Ex policy, head.r_back_mm = back_r; ret = true; - } /*else if (back_r > m.cfg.head_fallback_radius_mm) { + } else if (back_r > m.cfg.head_fallback_radius_mm) { head.r_back_mm = m.cfg.head_fallback_radius_mm; ret = optimize_pinhead_placement(policy, m, head); - }*/ + } return ret; } @@ -527,116 +526,19 @@ GroundConnection find_pillar_route(Ex policy, double end_radius) { GroundConnection ret; - ret.path.emplace_back(source); - Vec3d jp = source.pos, endp = jp, dir = sourcedir; - bool can_add_base = false/*, non_head = false*/; + double sd = sm.cfg.safety_distance(source.r); + auto gp = Vec3d{source.pos.x(), source.pos.y(), ground_level(sm)}; - 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 radius = source.r; - double sd = sm.cfg.safety_distance_mm; - - 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 diffbr = - search_widening_path(policy, sm, jp, dir, radius, - sm.cfg.head_back_radius_mm); - - if (diffbr && diffbr->endp.z() > jp_gnd) { - endp = diffbr->endp; - radius = diffbr->end_r; - ret.path.emplace_back(endp, radius); - dir = diffbr->get_dir(); - eval_limits(); - } else return ret; - } - - 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(); - 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) { - ret.path.clear(); - return ret; - //return {false, pillar_id}; - } - - if (t > 0.) { // Need to make additional bridge - ret.path.emplace_back(nexp, radius); - endp = nexp; - } - } - - Vec3d gp = to_floor(endp); - auto hit = beam_mesh_hit(policy, sm.emesh, - Beam{{endp, radius}, {gp, end_radius}}, sd); + auto hit = beam_mesh_hit(policy, + sm.emesh, + Beam{{source.pos, source.r}, {gp, end_radius}}, + sd); if (std::isinf(hit.distance())) { - double base_radius = can_add_base ? - std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; + double base_radius = std::max(sm.cfg.base_radius_mm, end_radius); - Vec3d gp = to_floor(endp); ret.pillar_base = Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius}; } @@ -644,6 +546,139 @@ GroundConnection find_pillar_route(Ex policy, return ret; } +//template +//GroundConnection find_pillar_route(Ex policy, +// const SupportableMesh &sm, +// const Junction &source, +// const Vec3d &sourcedir, +// double end_radius) +//{ +// GroundConnection ret; + +// ret.path.emplace_back(source); + +// Vec3d jp = source.pos, endp = jp, dir = sourcedir; +// 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 radius = source.r; +// double sd = sm.cfg.safety_distance(radius); + +// 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, end_radius] +// (bool base_en = true) +// { +// can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm; +// double base_r = can_add_base ? std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; +// 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 diffbr = +// search_widening_path(policy, sm, jp, dir, radius, +// sm.cfg.head_back_radius_mm); + +// if (diffbr && diffbr->endp.z() > jp_gnd) { +// endp = diffbr->endp; +// radius = diffbr->end_r; +// ret.path.emplace_back(endp, radius); +// dir = diffbr->get_dir(); +// eval_limits(); +// } else return ret; +// } + +// 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(); +// 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.; +// double rnext = radius; +// while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || +// !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, rnext, end_radius}, sd).distance())) && +// t < tmax) +// { +// t += radius; +// nexp = endp + t * d; +// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm))); +// rnext = rnext + bridge_ratio * (end_radius - rnext); +// } + +// // If could not find avoidance bridge for the pad gap, try again +// // without the pillar base +// if (dlast < gap_dist && can_add_base) { +// nexp = endp; +// t = 0.; +// rnext = radius; +// 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, rnext, end_radius}, sd).distance())) && t < tmax) { +// t += radius; +// nexp = endp + t * d; + +// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm))); +// rnext = rnext + bridge_ratio * (end_radius - rnext); +// } +// } + +// // Could not find a path to avoid the pad gap +// if (dlast < gap_dist) { +// ret.path.clear(); +// return ret; +// } + +// if (t > 0.) { // Need to make additional bridge +// ret.path.emplace_back(nexp, rnext); +// endp = nexp; +// } +// } + +// Vec3d gp = to_floor(endp); +// auto hit = beam_mesh_hit(policy, sm.emesh, +// Beam{{endp, radius}, {gp, end_radius}}, sd); + +// if (std::isinf(hit.distance())) { +// double base_radius = can_add_base ? +// std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; + +// Vec3d gp = to_floor(endp); +// ret.pillar_base = +// Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius}; +// } + +// return ret; +//} + inline long build_ground_connection(SupportTreeBuilder &builder, const SupportableMesh &sm, const GroundConnection &conn) @@ -689,7 +724,7 @@ GroundConnection find_ground_connection( { auto hjp = j.pos; double r = j.r; - auto sd = sm.cfg.safety_distance_mm; + auto sd = sm.cfg.safety_distance(r); 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(); @@ -700,7 +735,7 @@ GroundConnection find_ground_connection( while (!gnd_route && d < t) { Vec3d endp = hjp + d * dir; - double bridge_ratio = d / (d + (endp.z() - sm.emesh.ground_level())); + double bridge_ratio = d / (d + (endp.z() - ground_level(sm))); double pill_r = r + bridge_ratio * (end_r - r); gnd_route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); @@ -745,7 +780,7 @@ GroundConnection optimize_ground_connection( Optimizer solver(get_criteria(sm.cfg).stop_score(1e6)); solver.seed(0); // we want deterministic behavior - auto sd = sm.cfg.safety_distance_mm; + auto sd = sm.cfg.safety_distance(j.r); auto oresult = solver.to_max().optimize( [&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) { auto &[plr, azm] = input; @@ -762,6 +797,120 @@ GroundConnection optimize_ground_connection( return find_ground_connection(policy, sm, j, bridgedir, end_radius); } +template +GroundConnection deepsearch_ground_connection( + Ex policy, + const SupportableMesh &sm, + const Junction &j, + double end_radius, + const Vec3d &init_dir = DOWN) +{ + // Score is the total lenght of the route. Feasible routes will have + // infinite length (rays not colliding with model), thus the stop score + // should be a reasonably big number. + constexpr double StopScore = 1e6; + + const auto sd = sm.cfg.safety_distance(j.r); + const auto gndlvl = ground_level(sm); + const double widening = end_radius - j.r; + const double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + const double zelev_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + + auto criteria = get_criteria(sm.cfg).stop_score(StopScore); + + Optimizer solver(criteria); + solver.seed(0); // enforce deterministic behavior + + auto optfn = [&](const opt::Input<3> &input) { + double ret = NaNd; + + // solver suggests polar, azimuth and bridge length values: + auto &[plr, azm, bridge_len] = input; + + Vec3d n = spheric_to_dir(plr, azm); + Vec3d bridge_end = j.pos + bridge_len * n; + + double full_len = bridge_len + bridge_end.z() - gndlvl; + double bridge_r = j.r + widening * bridge_len / full_len; + double brhit_dist = 0.; + + if (bridge_len > EPSILON) { + // beam_mesh_hit with a zero lenght bridge is invalid + + Beam bridgebeam{Ball{j.pos, j.r}, Ball{bridge_end, bridge_r}}; + auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); + brhit_dist = brhit.distance(); + } + + if (brhit_dist < bridge_len) { + ret = brhit_dist; + } else { + // check if pillar can be placed below + auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; + + Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; + auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); + + if (std::isinf(gndhit.distance())) { + // Ground route is free with this bridge + + if (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)); + if (gap < zelev_gap) + ret = full_len - zelev_gap + gap; + else // success + ret = StopScore; + } else { + // No zero elevation, return success + ret = StopScore; + } + } else { + // Ground route is not free + ret = bridge_len + gndhit.distance(); + } + } + + return ret; + }; + + auto [plr_init, azm_init] = dir_to_spheric(init_dir); + + // Saturate the polar angle to max tilt defined in config + plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); + + auto oresult = solver.to_max().optimize( + optfn, + initvals({plr_init, azm_init, 0.}), // start with a zero bridge + 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 + ); + + GroundConnection conn; + + if (oresult.score >= StopScore) { + // search was successful, extract and apply the result + auto &[plr, azm, bridge_len] = oresult.optimum; + + Vec3d n = spheric_to_dir(plr, azm); + Vec3d bridge_end = j.pos + bridge_len * n; + + double full_len = bridge_len + bridge_end.z() - gndlvl; + double bridge_r = j.r + widening * bridge_len / full_len; + Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + + conn.path.emplace_back(j); + conn.path.emplace_back(Junction{bridge_end, bridge_r}); + + conn.pillar_base = + Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; + } + + return conn; +} + template bool optimize_anchor_placement(Ex policy, const SupportableMesh &sm, @@ -779,8 +928,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 solver(get_criteria(sm.cfg) .stop_score(anchor.fullwidth()) diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index aca193a1b..95ac76633 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -28,7 +28,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; sla::GroundConnection conn = - sla::optimize_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); #ifndef NDEBUG @@ -63,6 +63,66 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") REQUIRE(pR + FromRadius > CylRadius); } +TEST_CASE("Avoid disk below junction - Zero elevation", "[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; + cfg.object_elevation_mm = 0.; + + 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::deepsearch_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_ze.stl", "disk", disk); +#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(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 @@ -91,7 +151,7 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; sla::GroundConnection conn = - sla::optimize_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); #ifndef NDEBUG diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 69feea31f..e097a3bb7 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -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 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: { From 0bbd50eaa01c83a2f5dbb8a5b9f9cc53a31769c0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 15 Nov 2022 17:00:16 +0100 Subject: [PATCH 22/59] Bugfixes and new tests for pillar search --- src/libslic3r/BranchingTree/BranchingTree.hpp | 2 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 331 +----------------- src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp | 80 +++++ tests/sla_print/sla_print_tests.cpp | 8 - tests/sla_print/sla_supptreeutils_tests.cpp | 307 ++++++++++------ tests/sla_print/sla_test_utils.hpp | 54 --- 6 files changed, 277 insertions(+), 505 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index d30499c11..3f7fd7b80 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -21,7 +21,7 @@ class Properties public: - constexpr bool group_pillars() const noexcept { return true; } + 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/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 08e410431..1a771e028 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -26,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> -IntegerOnly 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 @@ -294,62 +270,6 @@ Hit pinhead_mesh_hit(Ex ex, head.r_back_mm, head.width_mm, safety_d); } -template -std::optional 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 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 {}; -} - inline double distance(const SupportPoint &a, const SupportPoint &b) { return (a.pos - b.pos).norm(); @@ -518,167 +438,6 @@ struct GroundConnection { operator bool() const { return pillar_base.has_value() && !path.empty(); } }; -template -GroundConnection find_pillar_route(Ex policy, - const SupportableMesh &sm, - const Junction &source, - const Vec3d &sourcedir, - double end_radius) -{ - GroundConnection ret; - ret.path.emplace_back(source); - - double sd = sm.cfg.safety_distance(source.r); - auto gp = Vec3d{source.pos.x(), source.pos.y(), ground_level(sm)}; - - auto hit = beam_mesh_hit(policy, - sm.emesh, - Beam{{source.pos, source.r}, {gp, end_radius}}, - sd); - - if (std::isinf(hit.distance())) { - double base_radius = std::max(sm.cfg.base_radius_mm, end_radius); - - ret.pillar_base = - Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius}; - } - - return ret; -} - -//template -//GroundConnection find_pillar_route(Ex policy, -// const SupportableMesh &sm, -// const Junction &source, -// const Vec3d &sourcedir, -// double end_radius) -//{ -// GroundConnection ret; - -// ret.path.emplace_back(source); - -// Vec3d jp = source.pos, endp = jp, dir = sourcedir; -// 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 radius = source.r; -// double sd = sm.cfg.safety_distance(radius); - -// 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, end_radius] -// (bool base_en = true) -// { -// can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm; -// double base_r = can_add_base ? std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; -// 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 diffbr = -// search_widening_path(policy, sm, jp, dir, radius, -// sm.cfg.head_back_radius_mm); - -// if (diffbr && diffbr->endp.z() > jp_gnd) { -// endp = diffbr->endp; -// radius = diffbr->end_r; -// ret.path.emplace_back(endp, radius); -// dir = diffbr->get_dir(); -// eval_limits(); -// } else return ret; -// } - -// 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(); -// 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.; -// double rnext = radius; -// while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist || -// !std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, rnext, end_radius}, sd).distance())) && -// t < tmax) -// { -// t += radius; -// nexp = endp + t * d; -// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm))); -// rnext = rnext + bridge_ratio * (end_radius - rnext); -// } - -// // If could not find avoidance bridge for the pad gap, try again -// // without the pillar base -// if (dlast < gap_dist && can_add_base) { -// nexp = endp; -// t = 0.; -// rnext = radius; -// 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, rnext, end_radius}, sd).distance())) && t < tmax) { -// t += radius; -// nexp = endp + t * d; - -// double bridge_ratio = dlast / (dlast + (nexp.z() - ground_level(sm))); -// rnext = rnext + bridge_ratio * (end_radius - rnext); -// } -// } - -// // Could not find a path to avoid the pad gap -// if (dlast < gap_dist) { -// ret.path.clear(); -// return ret; -// } - -// if (t > 0.) { // Need to make additional bridge -// ret.path.emplace_back(nexp, rnext); -// endp = nexp; -// } -// } - -// Vec3d gp = to_floor(endp); -// auto hit = beam_mesh_hit(policy, sm.emesh, -// Beam{{endp, radius}, {gp, end_radius}}, sd); - -// if (std::isinf(hit.distance())) { -// double base_radius = can_add_base ? -// std::max(sm.cfg.base_radius_mm, end_radius) : end_radius; - -// Vec3d gp = to_floor(endp); -// ret.pillar_base = -// Pedestal{gp, sm.cfg.base_height_mm, base_radius, end_radius}; -// } - -// return ret; -//} - inline long build_ground_connection(SupportTreeBuilder &builder, const SupportableMesh &sm, const GroundConnection &conn) @@ -714,89 +473,6 @@ inline long build_ground_connection(SupportTreeBuilder &builder, return ret; } -template -GroundConnection find_ground_connection( - Ex policy, - const SupportableMesh &sm, - const Junction &j, - const Vec3d &dir, - double end_r) -{ - auto hjp = j.pos; - double r = j.r; - auto sd = sm.cfg.safety_distance(r); - 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(); - 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() - ground_level(sm))); - double pill_r = r + bridge_ratio * (end_r - r); - gnd_route = find_pillar_route(policy, sm, {endp, pill_r}, dir, end_r); - - d += r; - } - - GroundConnection ret; - - if (d > 0.) - ret.path.emplace_back(j); - - for (auto &p : gnd_route.path) - ret.path.emplace_back(p); - - // This will ultimately determine if the route is valid or not - // but the path junctions will be provided anyways, so invalid paths - // can be inspected - ret.pillar_base = gnd_route.pillar_base; - - return ret; -} - -template -GroundConnection optimize_ground_connection( - Ex policy, - const SupportableMesh &sm, - const Junction &j, - double end_radius, - const Vec3d &init_dir = DOWN) -{ - double downdst = j.pos.z() - ground_level(sm); - - auto res = find_ground_connection(policy, sm, j, init_dir, end_radius); - if (res) - 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 solver(get_criteria(sm.cfg).stop_score(1e6)); - solver.seed(0); // we want deterministic behavior - - auto sd = sm.cfg.safety_distance(j.r); - 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 find_ground_connection(policy, sm, j, bridgedir, end_radius); -} - template GroundConnection deepsearch_ground_connection( Ex policy, @@ -861,10 +537,10 @@ GroundConnection deepsearch_ground_connection( if (gap < zelev_gap) ret = full_len - zelev_gap + gap; else // success - ret = StopScore; + ret = StopScore + EPSILON; } else { // No zero elevation, return success - ret = StopScore; + ret = StopScore + EPSILON; } } else { // Ground route is not free @@ -902,7 +578,8 @@ GroundConnection deepsearch_ground_connection( Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; conn.path.emplace_back(j); - conn.path.emplace_back(Junction{bridge_end, bridge_r}); + if (bridge_len > EPSILON) + conn.path.emplace_back(Junction{bridge_end, bridge_r}); conn.pillar_base = Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; diff --git a/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp b/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp index 43a8ddb59..b504d82fb 100644 --- a/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp +++ b/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp @@ -8,6 +8,86 @@ 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> +IntegerOnly 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 +std::optional 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 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 diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 8ea91d57a..d581f8340 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -32,13 +31,6 @@ const char *const SUPPORT_TEST_MODELS[] = { } // namespace -TEST_CASE("Pillar pairhash should be unique", "[SLASupportGeneration]") { - test_pairhash(); - test_pairhash(); - test_pairhash(); - test_pairhash(); -} - TEST_CASE("Support point generator should be deterministic if seeded", "[SLASupportGeneration], [SLAPointGen]") { TriangleMesh mesh = load_model("A_upsidedown.obj"); diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index 95ac76633..69117358d 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -1,8 +1,158 @@ #include #include +#include + #include "libslic3r/Execution/ExecutionSeq.hpp" #include "libslic3r/SLA/SupportTreeUtils.hpp" +#include "libslic3r/SLA/SupportTreeUtilsLegacy.hpp" + +// Test pair hash for 'nums' random number pairs. +template void test_pairhash() +{ + const constexpr size_t nums = 1000; + I A[nums] = {0}, B[nums] = {0}; + std::unordered_set CH; + std::unordered_map> 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::value) bits -= 1; + const I Imin = 0; + const I Imax = I(std::pow(2., bits) - 1); + + std::uniform_int_distribution 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(a, b); + II hash_ba = Slic3r::sla::pairhash(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(); + test_pairhash(); + test_pairhash(); + test_pairhash(); +} + +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]") { @@ -27,100 +177,34 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; - sla::GroundConnection conn = - sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + SECTION("without elevation") { -#ifndef NDEBUG + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); - sla::SupportTreeBuilder builder; + eval_ground_conn(conn, sm, j, EndRadius, "disk.stl"); - if (!conn) - builder.add_junction(j); + // 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); + } - sla::build_ground_connection(builder, sm, conn); + SECTION("with elevation") { + sm.cfg.object_elevation_mm = 0.; - its_merge(disk, builder.merged_mesh()); + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); - its_write_stl_ascii("output_disk.stl", "disk", disk); -#endif + eval_ground_conn(conn, sm, j, EndRadius, "disk_ze.stl"); - 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(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 - Zero elevation", "[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; - cfg.object_elevation_mm = 0.; - - 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::deepsearch_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_ze.stl", "disk", disk); -#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(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); + // 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]") @@ -150,38 +234,31 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; - sla::GroundConnection conn = - sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + SECTION("without elevation") { + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); -#ifndef NDEBUG + eval_ground_conn(conn, sm, j, EndRadius, "disk_with_barrier.stl"); - sla::SupportTreeBuilder builder; + // 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); + } - if (!conn) - builder.add_junction(j); + SECTION("without elevation") { + sm.cfg.object_elevation_mm = 0.; - sla::build_ground_connection(builder, sm, conn); + sla::GroundConnection conn = + sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); - its_merge(disk, builder.merged_mesh()); + eval_ground_conn(conn, sm, j, EndRadius, "disk_with_barrier_ze.stl"); - 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); - - // 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 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); + // 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); + } } diff --git a/tests/sla_print/sla_test_utils.hpp b/tests/sla_print/sla_test_utils.hpp index 187a72d54..103b2f66a 100644 --- a/tests/sla_print/sla_test_utils.hpp +++ b/tests/sla_print/sla_test_utils.hpp @@ -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 void test_pairhash() -{ - const constexpr size_t nums = 1000; - I A[nums] = {0}, B[nums] = {0}; - std::unordered_set CH; - std::unordered_map> 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::value) bits -= 1; - const I Imin = 0; - const I Imax = I(std::pow(2., bits) - 1); - - std::uniform_int_distribution 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(a, b); - II hash_ba = sla::pairhash(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; From 003647e898cfe5ba49b712a6fd17e7fd858337ac Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 10:48:18 +0100 Subject: [PATCH 23/59] Prepare UI for organic supports option --- src/libslic3r/PrintConfig.cpp | 4 +++- src/libslic3r/SLA/SupportTree.cpp | 3 +++ src/libslic3r/SLA/SupportTreeStrategies.hpp | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 482e36e43..ebdfd2ff6 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -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); @@ -3614,6 +3615,7 @@ void PrintConfigDef::init_sla_params() def->enum_labels = ConfigOptionEnum::get_enum_names(); def->enum_labels[0] = L("Default"); def->enum_labels[1] = L("Branching"); + // TODO: def->enum_labels[2] = L("Organic"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(sla::SupportTreeType::Default)); diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index b221c4330..37c2e85e9 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -39,6 +39,9 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm, create_branching_tree(*builder, sm); break; } + case SupportTreeType::Organic: { + // TODO + } default:; } diff --git a/src/libslic3r/SLA/SupportTreeStrategies.hpp b/src/libslic3r/SLA/SupportTreeStrategies.hpp index 487f43575..2cef1a8a9 100644 --- a/src/libslic3r/SLA/SupportTreeStrategies.hpp +++ b/src/libslic3r/SLA/SupportTreeStrategies.hpp @@ -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 From 2565d45543432b1c39a00aaa94d9d4eb201a0202 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 21 Nov 2022 12:35:11 +0100 Subject: [PATCH 24/59] Extend Optimizer interface to accept constraint functions --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 94 +++++++++++++++++++---- src/libslic3r/Optimize/Optimizer.hpp | 24 +++++- 2 files changed, 102 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index 3859217da..87aec4b36 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -13,7 +13,7 @@ #include -#include +#include "Optimizer.hpp" namespace Slic3r { namespace opt { @@ -64,12 +64,36 @@ struct NLopt { // Helper RAII class for nlopt_opt template class NLoptOpt {}; +// Map a generic function to each argument following the mapping function +template +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)),...); + + return fn; +} + +template +Fn for_each_in_tuple(Fn fn, const std::tuple &tup) +{ + auto arg = std::tuple_cat(std::make_tuple(fn), tup); + auto mpfn = [](auto fn, auto...pack) { + return for_each_argument(fn, pack...); + }; + std::apply(mpfn, arg); + + return fn; +} + // Optimizers based on NLopt. template class NLoptOpt> { protected: StopCriteria m_stopcr; OptDir m_dir = OptDir::MIN; + static constexpr double ConstraintEps = 1e-6; + template using TOptData = std::tuple*, NLoptOpt*, nlopt_opt>; @@ -78,7 +102,7 @@ protected: double *gradient, void *data) { - assert(n >= N); + assert(n == N); auto tdata = static_cast*>(data); @@ -101,6 +125,21 @@ protected: return scoreval; } + template + static double constrain_func(unsigned n, const double *params, + double *gradient, + void *data) + { + assert(n == N); + + auto tdata = static_cast*>(data); + + auto &fnptr = std::get<0>(*tdata); + auto funval = to_arr(params); + + return (*fnptr)(funval); + } + template void set_up(NLopt &nl, const Bounds& bounds) { @@ -125,13 +164,30 @@ protected: nlopt_set_maxeval(nl.ptr, m_stopcr.max_iterations()); } - template - Result optimize(NLopt &nl, Fn &&fn, const Input &initvals) + template + Result optimize(NLopt &nl, Fn &&fn, const Input &initvals, + const std::tuple &equalities, + const std::tuple &inequalities) { Result r; TOptData data = std::make_tuple(&fn, this, nl.ptr); + auto do_for_each_eq = [this, &nl](auto &&arg) { + auto data = std::make_tuple(&arg, this, nl.ptr); + using F = std::remove_cv_t; + nlopt_add_equality_constraint (nl.ptr, constrain_func, &data, ConstraintEps); + }; + + auto do_for_each_ineq = [this, &nl](auto &&arg) { + auto data = std::make_tuple(&arg, this, nl.ptr); + using F = std::remove_cv_t; + nlopt_add_inequality_constraint (nl.ptr, constrain_func, &data, ConstraintEps); + }; + + for_each_in_tuple(do_for_each_eq, equalities); + for_each_in_tuple(do_for_each_ineq, inequalities); + switch(m_dir) { case OptDir::MIN: nlopt_set_min_objective(nl.ptr, optfunc, &data); break; @@ -147,15 +203,18 @@ protected: public: - template + template Result optimize(Func&& func, const Input &initvals, - const Bounds& bounds) + const Bounds& bounds, + const std::tuple &equalities, + const std::tuple &inequalities) { NLopt nl{alg, N}; set_up(nl, bounds); - return optimize(nl, std::forward(func), initvals); + return optimize(nl, std::forward(func), initvals, + equalities, inequalities); } explicit NLoptOpt(const StopCriteria &stopcr = {}) : m_stopcr(stopcr) {} @@ -173,10 +232,12 @@ class NLoptOpt>: public NLoptOpt> using Base = NLoptOpt>; public: - template + template Result optimize(Fn&& f, const Input &initvals, - const Bounds& bounds) + const Bounds& bounds, + const std::tuple &equalities, + const std::tuple &inequalities) { NLopt nl_glob{glob, N}, nl_loc{loc, N}; @@ -184,7 +245,8 @@ public: Base::set_up(nl_loc, bounds); nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); - return Base::optimize(nl_glob, std::forward(f), initvals); + return Base::optimize(nl_glob, std::forward(f), initvals, + equalities, inequalities); } explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {} @@ -201,12 +263,16 @@ 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 + template Result optimize(Func&& func, const Input &initvals, - const Bounds& bounds) + const Bounds& bounds, + const std::tuple &eq_constraints = {}, + const std::tuple &ineq_constraint = {}) { - return m_opt.optimize(std::forward(func), initvals, bounds); + return m_opt.optimize(std::forward(func), initvals, bounds, + eq_constraints, + ineq_constraint); } explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {} @@ -225,7 +291,9 @@ public: using AlgNLoptGenetic = detail::NLoptAlgComb; using AlgNLoptSubplex = detail::NLoptAlg; using AlgNLoptSimplex = detail::NLoptAlg; +using AlgNLoptCobyla = detail::NLoptAlg; using AlgNLoptDIRECT = detail::NLoptAlg; +using AlgNLoptISRES = detail::NLoptAlg; using AlgNLoptMLSL = detail::NLoptAlgComb; }} // namespace Slic3r::opt diff --git a/src/libslic3r/Optimize/Optimizer.hpp b/src/libslic3r/Optimize/Optimizer.hpp index bf95d9ee0..6212a5f59 100644 --- a/src/libslic3r/Optimize/Optimizer.hpp +++ b/src/libslic3r/Optimize/Optimizer.hpp @@ -12,6 +12,15 @@ namespace Slic3r { namespace opt { +template +using FloatingOnly = std::enable_if_t::value, O>; + +template> +constexpr T NaN = std::numeric_limits::quiet_NaN(); + +constexpr float NaNf = NaN; +constexpr double NaNd = NaN; + // A type to hold the complete result of the optimization. template 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) + // 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 + template Result optimize(Func&& /*func*/, const Input &/*initvals*/, - const Bounds& /*bounds*/) { return {}; } + const Bounds& /*bounds*/, + const std::tuple &eq_constraints = {}, + const std::tuple &ineq_constraint = {} + ) { return {}; } // optional for randomized methods: void seed(long /*s*/) {} From 2cd6a2025419db5ff57d37d6da9782ea76f633cf Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 10:59:52 +0100 Subject: [PATCH 25/59] Move merge point search out of pointcloud to support tree utils --- src/libslic3r/BranchingTree/PointCloud.cpp | 72 +------------- tests/sla_print/sla_print_tests.cpp | 100 -------------------- tests/sla_print/sla_supptreeutils_tests.cpp | 95 +++++++++++++++++++ 3 files changed, 98 insertions(+), 169 deletions(-) diff --git a/src/libslic3r/BranchingTree/PointCloud.cpp b/src/libslic3r/BranchingTree/PointCloud.cpp index 319f334ff..4075a20c2 100644 --- a/src/libslic3r/BranchingTree/PointCloud.cpp +++ b/src/libslic3r/BranchingTree/PointCloud.cpp @@ -1,81 +1,15 @@ #include "PointCloud.hpp" #include "libslic3r/Tesselate.hpp" +#include "libslic3r/SLA/SupportTreeUtils.hpp" #include namespace Slic3r { namespace branchingtree { -std::optional find_merge_pt(const Vec3f &A, - const Vec3f &B, - float critical_angle) +std::optional 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 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, diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index d581f8340..a733e77cc 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -165,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 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]") { diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index 69117358d..d529eefb5 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -262,3 +262,98 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" REQUIRE(pR + FromRadius > CylRadius); } } + +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); + } +} + From 9cdd6738aee3ce2b5f9d70ba683b339d169d73f1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 11:01:49 +0100 Subject: [PATCH 26/59] Widening improvements in SupportTreeUtils Fix failing tests after introducing wideningfn into ground route search --- src/libslic3r/SLA/SupportTreeUtils.hpp | 257 +++++++++++++++++++++++-- 1 file changed, 241 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 1a771e028..6da28f1fd 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -473,12 +473,20 @@ inline long build_ground_connection(SupportTreeBuilder &builder, return ret; } -template +template +constexpr bool IsWideningFn = std::is_invocable_r_v; + +template> > GroundConnection deepsearch_ground_connection( Ex policy, const SupportableMesh &sm, const Junction &j, - double end_radius, + WideningFn &&wideningfn, const Vec3d &init_dir = DOWN) { // Score is the total lenght of the route. Feasible routes will have @@ -488,9 +496,6 @@ GroundConnection deepsearch_ground_connection( const auto sd = sm.cfg.safety_distance(j.r); const auto gndlvl = ground_level(sm); - const double widening = end_radius - j.r; - const double base_r = std::max(sm.cfg.base_radius_mm, end_radius); - const double zelev_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; auto criteria = get_criteria(sm.cfg).stop_score(StopScore); @@ -507,7 +512,7 @@ GroundConnection deepsearch_ground_connection( Vec3d bridge_end = j.pos + bridge_len * n; double full_len = bridge_len + bridge_end.z() - gndlvl; - double bridge_r = j.r + widening * bridge_len / full_len; + double bridge_r = wideningfn(Ball{j.pos, j.r}, n, bridge_len); double brhit_dist = 0.; if (bridge_len > EPSILON) { @@ -522,7 +527,8 @@ GroundConnection deepsearch_ground_connection( ret = brhit_dist; } else { // check if pillar can be placed below - auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; + 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 gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); @@ -533,9 +539,11 @@ GroundConnection deepsearch_ground_connection( if (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)); - if (gap < zelev_gap) - ret = full_len - zelev_gap + gap; + double gap = std::sqrt(sm.emesh.squared_distance(gp)); + double base_r = std::max(sm.cfg.base_radius_mm, end_radius); + double max_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + if (gap < max_gap) + ret = full_len - max_gap + gap; else // success ret = StopScore + EPSILON; } else { @@ -560,8 +568,8 @@ GroundConnection deepsearch_ground_connection( optfn, initvals({plr_init, azm_init, 0.}), // start with a zero bridge 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 + {-PI, PI}, // bounds for azimuth + {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length ); GroundConnection conn; @@ -572,22 +580,153 @@ GroundConnection deepsearch_ground_connection( Vec3d n = spheric_to_dir(plr, azm); Vec3d bridge_end = j.pos + bridge_len * n; + Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; - double full_len = bridge_len + bridge_end.z() - gndlvl; - double bridge_r = j.r + widening * bridge_len / full_len; - Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + double bridge_r = wideningfn(Ball{j.pos, j.r}, n, bridge_len); + 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); conn.path.emplace_back(j); if (bridge_len > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); conn.pillar_base = - Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; + Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; } return conn; } +template +GroundConnection deepsearch_ground_connection(Ex policy, + const SupportableMesh &sm, + const Junction &j, + double end_radius, + const Vec3d &init_dir = DOWN) +{ + double gndlvl = ground_level(sm); + auto wfn = [end_radius, gndlvl](Ball src, Vec3d dir, double len) { + 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, "Not a widening function"); + + return deepsearch_ground_connection(policy, sm, j, wfn, init_dir); + +// // Score is the total lenght of the route. Feasible routes will have +// // infinite length (rays not colliding with model), thus the stop score +// // should be a reasonably big number. +// constexpr double StopScore = 1e6; + +// const auto sd = sm.cfg.safety_distance(j.r); +// const auto gndlvl = ground_level(sm); +// const double widening = end_radius - j.r; +// const double base_r = std::max(sm.cfg.base_radius_mm, end_radius); +// const double zelev_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + +// auto criteria = get_criteria(sm.cfg).stop_score(StopScore); + +// Optimizer solver(criteria); +// solver.seed(0); // enforce deterministic behavior + +// auto optfn = [&](const opt::Input<3> &input) { +// double ret = NaNd; + +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_len] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = j.pos + bridge_len * n; + +// double full_len = bridge_len + bridge_end.z() - gndlvl; +// double bridge_r = j.r + widening * bridge_len / full_len; +// double brhit_dist = 0.; + +// if (bridge_len > EPSILON) { +// // beam_mesh_hit with a zero lenght bridge is invalid + +// Beam bridgebeam{Ball{j.pos, j.r}, Ball{bridge_end, bridge_r}}; +// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); +// brhit_dist = brhit.distance(); +// } + +// if (brhit_dist < bridge_len) { +// ret = brhit_dist; +// } else { +// // check if pillar can be placed below +// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; + +// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; +// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); + +// if (std::isinf(gndhit.distance())) { +// // Ground route is free with this bridge + +// if (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)); +// if (gap < zelev_gap) +// ret = full_len - zelev_gap + gap; +// else // success +// ret = StopScore + EPSILON; +// } else { +// // No zero elevation, return success +// ret = StopScore + EPSILON; +// } +// } else { +// // Ground route is not free +// ret = bridge_len + gndhit.distance(); +// } +// } + +// return ret; +// }; + +// auto [plr_init, azm_init] = dir_to_spheric(init_dir); + +// // Saturate the polar angle to max tilt defined in config +// plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); + +// auto oresult = solver.to_max().optimize( +// optfn, +// initvals({plr_init, azm_init, 0.}), // start with a zero bridge +// 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 +// ); + +// GroundConnection conn; + +// if (oresult.score >= StopScore) { +// // search was successful, extract and apply the result +// auto &[plr, azm, bridge_len] = oresult.optimum; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = j.pos + bridge_len * n; + +// double full_len = bridge_len + bridge_end.z() - gndlvl; +// double bridge_r = j.r + widening * bridge_len / full_len; +// Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + +// conn.path.emplace_back(j); +// if (bridge_len > EPSILON) +// conn.path.emplace_back(Junction{bridge_end, bridge_r}); + +// conn.pillar_base = +// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; +// } + +// return conn; +} + template bool optimize_anchor_placement(Ex policy, const SupportableMesh &sm, @@ -671,6 +810,92 @@ std::optional 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 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 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 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 From 249d2550d359f133d1ffca256a2355ec19973593 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 11:12:52 +0100 Subject: [PATCH 27/59] Remove pillar grouping as it does not work nicely --- src/libslic3r/BranchingTree/BranchingTree.hpp | 2 - src/libslic3r/SLA/BranchingTreeSLA.cpp | 62 +------------------ 2 files changed, 1 insertion(+), 63 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index 3f7fd7b80..2d372452b 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -21,8 +21,6 @@ class Properties 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/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 16236f7cd..7d6f0b95d 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -20,12 +20,6 @@ class BranchingTreeBuilder: public branchingtree::Builder { std::set m_ground_mem; - // Establish an index of - using PointIndexEl = std::pair; - boost::geometry::index:: - rtree /* ? */> - m_pillar_index; - std::vector m_pillars; // to put an index over them // cache succesfull ground connections @@ -112,44 +106,6 @@ class BranchingTreeBuilder: public branchingtree::Builder { }); } - std::optional - search_for_existing_pillar(const branchingtree::Node &from) const - { - namespace bgi = boost::geometry::index; - - struct Output - { - std::optional &res; - - Output &operator*() { return *this; } - Output &operator=(const PointIndexEl &el) { res = el; return *this; } - Output &operator++() { return *this; } - }; - - std::optional result; - - auto filter = bgi::satisfies([this, &from](const PointIndexEl &e) { - assert(e.second < m_pillars.size()); - - auto len = (from.pos - e.first).norm(); - const branchingtree::Node &to = m_pillars[e.second]; - double sd = m_sm.cfg.safety_distance_mm; - - Beam beam{Ball{from.pos.cast(), get_radius(from)}, - Ball{e.first.cast(), get_radius(to)}}; - - return !branchingtree::is_occupied(to) && - len < m_sm.cfg.max_bridge_length_mm && - !m_cloud.is_outside_support_cone(from.pos, e.first) && - beam_mesh_hit(ex_tbb, m_sm.emesh, beam, sd).distance() > len; - }); - - m_pillar_index.query(filter && bgi::nearest(from.pos, 1), - Output{result}); - - return result; - } - public: BranchingTreeBuilder(SupportTreeBuilder &builder, const SupportableMesh &sm, @@ -368,23 +324,7 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s BranchingTreeBuilder vbuilder{builder, sm, nodes}; branchingtree::build_tree(nodes, vbuilder); - std::cout << "Original pillar count: " << vbuilder.pillars().size() << std::endl; - - if constexpr (props.group_pillars()) { - - std::vector bedleafs; - std::copy(vbuilder.pillars().begin(), vbuilder.pillars().end(), std::back_inserter(bedleafs)); - - branchingtree::PointCloud gndnodes{{}, nodes.get_bedpoints(), bedleafs, props}; - BranchingTreeBuilder gndbuilder{builder, sm, gndnodes}; - branchingtree::build_tree(gndnodes, gndbuilder); - - std::cout << "Grouped pillar count: " << gndbuilder.pillars().size() << std::endl; - build_pillars(builder, gndbuilder, sm); - - } else { - build_pillars(builder, vbuilder, sm); - } + build_pillars(builder, vbuilder, sm); for (size_t id : vbuilder.unroutable_pinheads()) builder.head(id).invalidate(); From 87336349b19ecf0083cb671ec6fc271a1d34f247 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 11:14:24 +0100 Subject: [PATCH 28/59] Widening improvements 2 --- src/libslic3r/SLA/SupportTreeUtils.hpp | 143 ++++++------------------- 1 file changed, 30 insertions(+), 113 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 6da28f1fd..41a1968b4 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -485,7 +485,7 @@ template EPSILON) { // beam_mesh_hit with a zero lenght bridge is invalid - Beam bridgebeam{Ball{j.pos, j.r}, Ball{bridge_end, bridge_r}}; + Beam 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(); } @@ -579,15 +579,15 @@ GroundConnection deepsearch_ground_connection( auto &[plr, azm, bridge_len] = oresult.optimum; Vec3d n = spheric_to_dir(plr, azm); - Vec3d bridge_end = j.pos + bridge_len * n; + Vec3d bridge_end = source.pos + bridge_len * n; Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; - double bridge_r = wideningfn(Ball{j.pos, j.r}, n, bridge_len); + double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); 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); - conn.path.emplace_back(j); + conn.path.emplace_back(source); if (bridge_len > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); @@ -601,12 +601,12 @@ GroundConnection deepsearch_ground_connection( template GroundConnection deepsearch_ground_connection(Ex policy, const SupportableMesh &sm, - const Junction &j, + const Junction &source, double end_radius, const Vec3d &init_dir = DOWN) { double gndlvl = ground_level(sm); - auto wfn = [end_radius, gndlvl](Ball src, Vec3d dir, double len) { + auto wfn = [end_radius, gndlvl](const Ball &src, const Vec3d &dir, double len) { Vec3d dst = src.p + len * dir; double widening = end_radius - src.R; double zlen = dst.z() - gndlvl; @@ -618,113 +618,30 @@ GroundConnection deepsearch_ground_connection(Ex policy, static_assert(IsWideningFn, "Not a widening function"); - return deepsearch_ground_connection(policy, sm, j, wfn, init_dir); + return deepsearch_ground_connection(policy, sm, source, wfn, init_dir); +} -// // Score is the total lenght of the route. Feasible routes will have -// // infinite length (rays not colliding with model), thus the stop score -// // should be a reasonably big number. -// constexpr double StopScore = 1e6; +struct DefaultWideningModel { + static constexpr double WIDENING_SCALE = 0.02; + const SupportableMesh &sm; -// const auto sd = sm.cfg.safety_distance(j.r); -// const auto gndlvl = ground_level(sm); -// const double widening = end_radius - j.r; -// const double base_r = std::max(sm.cfg.base_radius_mm, end_radius); -// const double zelev_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; + double operator()(const Ball &src, const Vec3d & /*dir*/, double len) { + static_assert(IsWideningFn, + "DefaultWideningModel is not a widening function"); -// auto criteria = get_criteria(sm.cfg).stop_score(StopScore); + double w = WIDENING_SCALE * sm.cfg.pillar_widening_factor * len; + return src.R + w; + }; +}; -// Optimizer solver(criteria); -// solver.seed(0); // enforce deterministic behavior - -// auto optfn = [&](const opt::Input<3> &input) { -// double ret = NaNd; - -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_len] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = j.pos + bridge_len * n; - -// double full_len = bridge_len + bridge_end.z() - gndlvl; -// double bridge_r = j.r + widening * bridge_len / full_len; -// double brhit_dist = 0.; - -// if (bridge_len > EPSILON) { -// // beam_mesh_hit with a zero lenght bridge is invalid - -// Beam bridgebeam{Ball{j.pos, j.r}, Ball{bridge_end, bridge_r}}; -// auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd); -// brhit_dist = brhit.distance(); -// } - -// if (brhit_dist < bridge_len) { -// ret = brhit_dist; -// } else { -// // check if pillar can be placed below -// auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; - -// Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; -// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); - -// if (std::isinf(gndhit.distance())) { -// // Ground route is free with this bridge - -// if (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)); -// if (gap < zelev_gap) -// ret = full_len - zelev_gap + gap; -// else // success -// ret = StopScore + EPSILON; -// } else { -// // No zero elevation, return success -// ret = StopScore + EPSILON; -// } -// } else { -// // Ground route is not free -// ret = bridge_len + gndhit.distance(); -// } -// } - -// return ret; -// }; - -// auto [plr_init, azm_init] = dir_to_spheric(init_dir); - -// // Saturate the polar angle to max tilt defined in config -// plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); - -// auto oresult = solver.to_max().optimize( -// optfn, -// initvals({plr_init, azm_init, 0.}), // start with a zero bridge -// 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 -// ); - -// GroundConnection conn; - -// if (oresult.score >= StopScore) { -// // search was successful, extract and apply the result -// auto &[plr, azm, bridge_len] = oresult.optimum; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = j.pos + bridge_len * n; - -// double full_len = bridge_len + bridge_end.z() - gndlvl; -// double bridge_r = j.r + widening * bridge_len / full_len; -// Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; - -// conn.path.emplace_back(j); -// if (bridge_len > EPSILON) -// conn.path.emplace_back(Junction{bridge_end, bridge_r}); - -// conn.pillar_base = -// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; -// } - -// return conn; +template +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 From 3d6bb38dd4b1d50ffe54d9957dba6da9bed2e549 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 14:33:17 +0100 Subject: [PATCH 29/59] Fix failing tests --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 4 ++-- src/libslic3r/SLA/SupportTreeUtils.hpp | 5 +++-- tests/sla_print/sla_supptreeutils_tests.cpp | 18 ++++++++++++++++++ tests/sla_print/sla_test_utils.cpp | 12 ++++++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 7d6f0b95d..1ad0fd93f 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -128,8 +128,8 @@ public: 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(); + 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); diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 41a1968b4..2d0fd859e 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -591,8 +591,9 @@ GroundConnection deepsearch_ground_connection( if (bridge_len > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); - conn.pillar_base = - Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; + if (bridge_end.z() >= gndlvl) + conn.pillar_base = + Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; } return conn; diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index d529eefb5..8d1029152 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -263,6 +263,24 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" } } +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; diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index e097a3bb7..b601cef11 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -181,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)); From cdac79016380ded66a02e8d65dfc34a90cbdc752 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 28 Nov 2022 18:23:34 +0100 Subject: [PATCH 30/59] Try to fix pillar route search --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 31 +++++++---- src/libslic3r/SLA/SupportTree.hpp | 2 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 60 ++++++++++++--------- tests/sla_print/sla_supptreeutils_tests.cpp | 6 +-- 4 files changed, 59 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index 87aec4b36..46a0d0648 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -141,7 +141,9 @@ protected: } template - void set_up(NLopt &nl, const Bounds& bounds) + static void set_up(NLopt &nl, + const Bounds &bounds, + const StopCriteria &stopcr) { std::array lb, ub; @@ -153,15 +155,15 @@ 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 @@ -211,7 +213,7 @@ public: const std::tuple &inequalities) { NLopt nl{alg, N}; - set_up(nl, bounds); + set_up(nl, bounds, m_stopcr); return optimize(nl, std::forward(func), initvals, equalities, inequalities); @@ -230,6 +232,7 @@ template class NLoptOpt>: public NLoptOpt> { using Base = NLoptOpt>; + StopCriteria m_loc_stopcr; public: template @@ -241,15 +244,20 @@ public: { NLopt nl_glob{glob, N}, nl_loc{loc, N}; - Base::set_up(nl_glob, bounds); - Base::set_up(nl_loc, bounds); + Base::set_up(nl_glob, bounds, Base::get_criteria()); + Base::set_up(nl_loc, bounds, m_loc_stopcr); nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); return Base::optimize(nl_glob, std::forward(f), initvals, equalities, inequalities); } - explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {} + explicit NLoptOpt(StopCriteria stopcr = {}) + : Base{stopcr}, m_loc_stopcr{stopcr} + {} + + void set_loc_criteria(const StopCriteria &cr) { m_loc_stopcr = cr; } + const StopCriteria &get_loc_criteria() const noexcept { return m_loc_stopcr; } }; } // namespace detail; @@ -285,6 +293,9 @@ 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 diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index e0d7db97d..ed5725780 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -105,7 +105,7 @@ 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-10; - static const unsigned constexpr optimizer_max_iterations = 2000; + static const unsigned constexpr optimizer_max_iterations = 30000; static const unsigned constexpr pillar_cascade_neighbors = 3; }; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 2d0fd859e..0dffd0852 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -492,17 +492,24 @@ GroundConnection deepsearch_ground_connection( // Score is the total lenght of the route. Feasible routes will have // infinite length (rays not colliding with model), thus the stop score // should be a reasonably big number. - constexpr double StopScore = 1e6; + constexpr double Penality = 1e5; + constexpr double PenOffs = 1e2; const auto sd = sm.cfg.safety_distance(source.r); const auto gndlvl = ground_level(sm); - auto criteria = get_criteria(sm.cfg).stop_score(StopScore); + auto criteria = get_criteria(sm.cfg); + criteria.abs_score_diff(1.); + criteria.rel_score_diff(0.1); + criteria.max_iterations(5000); Optimizer solver(criteria); + solver.set_loc_criteria(criteria.max_iterations(100).abs_score_diff(1.)); solver.seed(0); // enforce deterministic behavior + size_t icnt = 0; auto optfn = [&](const opt::Input<3> &input) { + ++icnt; double ret = NaNd; // solver suggests polar, azimuth and bridge length values: @@ -511,7 +518,7 @@ GroundConnection deepsearch_ground_connection( Vec3d n = spheric_to_dir(plr, azm); Vec3d bridge_end = source.pos + bridge_len * n; - double full_len = bridge_len + bridge_end.z() - gndlvl; + double down_l = bridge_end.z() - gndlvl; double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); double brhit_dist = 0.; @@ -524,7 +531,7 @@ GroundConnection deepsearch_ground_connection( } if (brhit_dist < bridge_len) { - ret = brhit_dist; + ret = brhit_dist + Penality; } else { // check if pillar can be placed below auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; @@ -532,28 +539,27 @@ GroundConnection deepsearch_ground_connection( Beam 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); + double penality = 0.; - if (std::isinf(gndhit.distance())) { - // Ground route is free with this bridge - - if (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 max_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; - if (gap < max_gap) - ret = full_len - max_gap + gap; - else // success - ret = StopScore + EPSILON; - } else { - // No zero elevation, return success - ret = StopScore + EPSILON; + if (!std::isinf(gndhit.distance())) + penality = Penality; + else if (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) { + penality = Penality + PenOffs * (min_gap - gap); } - } else { - // Ground route is not free - ret = bridge_len + gndhit.distance(); +// gnd_hit_d += std::max(0., min_gap - gap); //penality = Penality + 100000. * (min_gap - gap); +// if (gap < min_gap) { +// penality = Penality; +// } } + + ret = bridge_len + gnd_hit_d + penality; } return ret; @@ -564,7 +570,7 @@ GroundConnection deepsearch_ground_connection( // Saturate the polar angle to max tilt defined in config plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); - auto oresult = solver.to_max().optimize( + auto oresult = solver.to_min().optimize( optfn, initvals({plr_init, azm_init, 0.}), // start with a zero bridge bounds({ {PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle @@ -572,10 +578,12 @@ GroundConnection deepsearch_ground_connection( {0., sm.cfg.max_bridge_length_mm} }) // bounds bridge length ); + std::cout << "iters: " << icnt << std::endl; + GroundConnection conn; - if (oresult.score >= StopScore) { - // search was successful, extract and apply the result + if (oresult.score < Penality) { + // Extract and apply the result auto &[plr, azm, bridge_len] = oresult.optimum; Vec3d n = spheric_to_dir(plr, azm); diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index 8d1029152..58918db41 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -177,7 +177,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; - SECTION("without elevation") { + SECTION("with elevation") { sla::GroundConnection conn = sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); @@ -191,7 +191,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") REQUIRE(pR + FromRadius > CylRadius); } - SECTION("with elevation") { + SECTION("without elevation") { sm.cfg.object_elevation_mm = 0.; sla::GroundConnection conn = @@ -234,7 +234,7 @@ TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]" sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg}; - SECTION("without elevation") { + SECTION("with elevation") { sla::GroundConnection conn = sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); From 056e7400278cd81bf93aee67c6a751a0e9ddbbfc Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 29 Nov 2022 18:25:39 +0100 Subject: [PATCH 31/59] Better handling of nonlinear constraints in nlopt wrapper --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 147 ++++++++++++++++------ 1 file changed, 106 insertions(+), 41 deletions(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index 46a0d0648..900728804 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -74,18 +74,28 @@ Fn for_each_argument(Fn &&fn, Args&&...args) return fn; } -template -Fn for_each_in_tuple(Fn fn, const std::tuple &tup) +// Call fn on each element of the input tuple tup. +template +Fn for_each_in_tuple(Fn fn, Tup &&tup) { - auto arg = std::tuple_cat(std::make_tuple(fn), tup); - auto mpfn = [](auto fn, auto...pack) { - return for_each_argument(fn, pack...); + auto mpfn = [&fn](auto&...pack) { + for_each_argument(fn, pack...); }; - std::apply(mpfn, arg); + + 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 where T_i is the type of +// i-th element of tup. +template class W, class...Args> +auto wrap_tup(const std::tuple &tup) +{ + return std::tuple...>(tup); +} + // Optimizers based on NLopt. template class NLoptOpt> { protected: @@ -94,32 +104,38 @@ protected: static constexpr double ConstraintEps = 1e-6; - template using TOptData = - std::tuple*, NLoptOpt*, nlopt_opt>; + template 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 static double optfunc(unsigned n, const double *params, - double *gradient, - void *data) + double *gradient, void *data) { assert(n == N); - auto tdata = static_cast*>(data); + auto tdata = static_cast*>(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(params); double scoreval = 0.; - using RetT = decltype((*fnptr)(funval)); + using RetT = decltype(tdata->fn(funval)); if constexpr (std::is_convertible_v>) { - ScoreGradient score = (*fnptr)(funval); + ScoreGradient 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; @@ -127,17 +143,14 @@ protected: template static double constrain_func(unsigned n, const double *params, - double *gradient, - void *data) + double *gradient, void *data) { assert(n == N); - auto tdata = static_cast*>(data); - - auto &fnptr = std::get<0>(*tdata); + auto tdata = static_cast*>(data); auto funval = to_arr(params); - return (*fnptr)(funval); + return tdata->fn(funval); } template @@ -173,22 +186,27 @@ protected: { Result r; - TOptData data = std::make_tuple(&fn, this, nl.ptr); + OptData data {fn, this, nl.ptr}; - auto do_for_each_eq = [this, &nl](auto &&arg) { - auto data = std::make_tuple(&arg, this, nl.ptr); - using F = std::remove_cv_t; - nlopt_add_equality_constraint (nl.ptr, constrain_func, &data, ConstraintEps); + 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, &arg, ConstraintEps); }; - auto do_for_each_ineq = [this, &nl](auto &&arg) { - auto data = std::make_tuple(&arg, this, nl.ptr); - using F = std::remove_cv_t; - nlopt_add_inequality_constraint (nl.ptr, constrain_func, &data, 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, &arg, ConstraintEps); }; - for_each_in_tuple(do_for_each_eq, equalities); - for_each_in_tuple(do_for_each_ineq, inequalities); + auto eq_data = wrap_tup(equalities); + for_each_in_tuple(do_for_each_eq, eq_data); + + auto ineq_data = wrap_tup(inequalities); + for_each_in_tuple(do_for_each_ineq, ineq_data); switch(m_dir) { case OptDir::MIN: @@ -260,8 +278,19 @@ public: const StopCriteria &get_loc_criteria() const noexcept { return m_loc_stopcr; } }; +template struct AlgFeatures_ { + static constexpr bool SupportsInequalities = false; + static constexpr bool SupportsEqualities = false; +}; + } // namespace detail; +template constexpr bool SupportsEqualities = + detail::AlgFeatures_>::SupportsEqualities; + +template constexpr bool SupportsInequalities = + detail::AlgFeatures_>::SupportsInequalities; + // Optimizers based on NLopt. template class Optimizer> { detail::NLoptOpt m_opt; @@ -278,6 +307,14 @@ public: const std::tuple &eq_constraints = {}, const std::tuple &ineq_constraint = {}) { + static_assert(std::tuple_size_v> == 0 + || SupportsEqualities, + "Equality constraints are not supported."); + + static_assert(std::tuple_size_v> == 0 + || SupportsInequalities, + "Inequality constraints are not supported."); + return m_opt.optimize(std::forward(func), initvals, bounds, eq_constraints, ineq_constraint); @@ -299,13 +336,41 @@ public: }; // Predefinded NLopt algorithms -using AlgNLoptGenetic = detail::NLoptAlgComb; -using AlgNLoptSubplex = detail::NLoptAlg; -using AlgNLoptSimplex = detail::NLoptAlg; -using AlgNLoptCobyla = detail::NLoptAlg; -using AlgNLoptDIRECT = detail::NLoptAlg; -using AlgNLoptISRES = detail::NLoptAlg; -using AlgNLoptMLSL = detail::NLoptAlgComb; +using AlgNLoptGenetic = detail::NLoptAlgComb; +using AlgNLoptSubplex = detail::NLoptAlg; +using AlgNLoptSimplex = detail::NLoptAlg; +using AlgNLoptCobyla = detail::NLoptAlg; +using AlgNLoptDIRECT = detail::NLoptAlg; +using AlgNLoptORIG_DIRECT = detail::NLoptAlg; +using AlgNLoptISRES = detail::NLoptAlg; +using AlgNLoptAGS = detail::NLoptAlg; + +using AlgNLoptMLSL = detail::NLoptAlgComb; +using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb; + +namespace detail { + +template<> struct AlgFeatures_ { + static constexpr bool SupportsInequalities = true; + static constexpr bool SupportsEqualities = true; +}; + +template<> struct AlgFeatures_ { + static constexpr bool SupportsInequalities = true; + static constexpr bool SupportsEqualities = false; +}; + +template<> struct AlgFeatures_ { + static constexpr bool SupportsInequalities = true; + static constexpr bool SupportsEqualities = false; +}; + +template<> struct AlgFeatures_ { + static constexpr bool SupportsInequalities = true; + static constexpr bool SupportsEqualities = true; +}; + +} // namespace detail }} // namespace Slic3r::opt From 0f34dfbeac20dbb951341979175da38858ec4e91 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 29 Nov 2022 18:26:20 +0100 Subject: [PATCH 32/59] Trying 2 phase optimization for pillar route search --- src/libslic3r/SLA/SupportTree.hpp | 2 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 130 ++++++++++++-------- tests/sla_print/sla_supptreeutils_tests.cpp | 4 +- 3 files changed, 80 insertions(+), 56 deletions(-) diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index ed5725780..e0d7db97d 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -105,7 +105,7 @@ 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-10; - static const unsigned constexpr optimizer_max_iterations = 30000; + static const unsigned constexpr optimizer_max_iterations = 2000; static const unsigned constexpr pillar_cascade_neighbors = 3; }; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 0dffd0852..94da64844 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -489,26 +489,30 @@ GroundConnection deepsearch_ground_connection( WideningFn &&wideningfn, const Vec3d &init_dir = DOWN) { - // Score is the total lenght of the route. Feasible routes will have - // infinite length (rays not colliding with model), thus the stop score - // should be a reasonably big number. - constexpr double Penality = 1e5; - constexpr double PenOffs = 1e2; + const auto sd = sm.cfg.safety_distance(source.r); + const auto gndlvl = ground_level(sm); - const auto sd = sm.cfg.safety_distance(source.r); - const auto gndlvl = ground_level(sm); + auto criteria_heavy = get_criteria(sm.cfg); + criteria_heavy.max_iterations(30000); + criteria_heavy.abs_score_diff(NaNd); + criteria_heavy.rel_score_diff(NaNd); - auto criteria = get_criteria(sm.cfg); - criteria.abs_score_diff(1.); - criteria.rel_score_diff(0.1); - criteria.max_iterations(5000); + // Cobyla (local method) supports inequality constraints which will be + // needed here. + Optimizer solver_heavy(criteria_heavy); + solver_heavy.seed(0); - Optimizer solver(criteria); - solver.set_loc_criteria(criteria.max_iterations(100).abs_score_diff(1.)); - solver.seed(0); // enforce deterministic behavior + auto criteria_easy = get_criteria(sm.cfg); + criteria_easy.max_iterations(1000); + criteria_easy.abs_score_diff(NaNd); + criteria_easy.rel_score_diff(NaNd); + + Optimizer solver_easy(criteria_easy); + solver_easy.set_loc_criteria(StopCriteria{}.max_iterations(100).abs_score_diff(EPSILON).rel_score_diff(0.01)); + solver_easy.seed(0); size_t icnt = 0; - auto optfn = [&](const opt::Input<3> &input) { + auto l_fn = [&](const opt::Input<3> &input) { ++icnt; double ret = NaNd; @@ -531,7 +535,7 @@ GroundConnection deepsearch_ground_connection( } if (brhit_dist < bridge_len) { - ret = brhit_dist + Penality; + ret = brhit_dist; } else { // check if pillar can be placed below auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; @@ -539,70 +543,90 @@ GroundConnection deepsearch_ground_connection( Beam 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); - double penality = 0.; + double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - if (!std::isinf(gndhit.distance())) - penality = Penality; - else if (sm.cfg.object_elevation_mm < EPSILON) { + if (std::isinf(gndhit.distance()) && 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) { - penality = Penality + PenOffs * (min_gap - gap); - } -// gnd_hit_d += std::max(0., min_gap - gap); //penality = Penality + 100000. * (min_gap - gap); -// if (gap < min_gap) { -// penality = Penality; -// } + gnd_hit_d = gnd_hit_d - min_gap + gap; } - ret = bridge_len + gnd_hit_d + penality; + ret = bridge_len + gnd_hit_d; } return ret; }; + auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { + // solver suggests polar, azimuth and bridge length values: + auto &[plr, azm, bridge_l] = input; + + Vec3d n = spheric_to_dir(plr, azm); + Vec3d bridge_end = source.pos + bridge_l * n; + + double down_l = bridge_end.z() - gndlvl; + double full_l = bridge_l + down_l; + + return full_l; + }; + + auto ineq_fn = [&](const opt::Input<3> &input) { + double h = h_fn(input); + double l = l_fn(input); + double r = h - l; + + return r; + }; + auto [plr_init, azm_init] = dir_to_spheric(init_dir); // Saturate the polar angle to max tilt defined in config plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope); - - auto oresult = solver.to_min().optimize( - optfn, - initvals({plr_init, azm_init, 0.}), // start with a zero bridge + 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 + {-PI, PI}, // bounds for azimuth + {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length + + auto oresult_init = solver_easy.to_max().optimize( + l_fn, + initvals({plr_init, azm_init, 0.}), // start with a zero bridge + bound_constraints ); - std::cout << "iters: " << icnt << std::endl; + auto oresult = solver_heavy.to_min().optimize( + h_fn, + oresult_init.optimum, + bound_constraints, + {}, + std::make_tuple(ineq_fn) + ); + + std::cout << "Iterations: " << icnt << std::endl; GroundConnection conn; - if (oresult.score < Penality) { - // Extract and apply the result - auto &[plr, azm, bridge_len] = oresult.optimum; + // Extract and apply the result + auto &[plr, azm, bridge_l] = oresult.optimum; - Vec3d n = spheric_to_dir(plr, azm); - Vec3d bridge_end = source.pos + bridge_len * n; - Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; + Vec3d n = spheric_to_dir(plr, azm); + 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_len); - 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); + 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); - conn.path.emplace_back(source); - if (bridge_len > EPSILON) - conn.path.emplace_back(Junction{bridge_end, bridge_r}); + conn.path.emplace_back(source); + if (bridge_l > EPSILON) + conn.path.emplace_back(Junction{bridge_end, bridge_r}); - if (bridge_end.z() >= gndlvl) - conn.pillar_base = - Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; - } + if (ineq_fn(oresult.optimum) <= 0 && bridge_end.z() >= gndlvl) + conn.pillar_base = + Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; return conn; } diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index 58918db41..17bd17a03 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -180,7 +180,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") SECTION("with elevation") { sla::GroundConnection conn = - sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + sla::deepsearch_ground_connection(ex_tbb, sm, j, EndRadius, sla::DOWN); eval_ground_conn(conn, sm, j, EndRadius, "disk.stl"); @@ -195,7 +195,7 @@ TEST_CASE("Avoid disk below junction", "[suptreeutils]") sm.cfg.object_elevation_mm = 0.; sla::GroundConnection conn = - sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN); + sla::deepsearch_ground_connection(ex_tbb, sm, j, EndRadius, sla::DOWN); eval_ground_conn(conn, sm, j, EndRadius, "disk_ze.stl"); From dfa6d03bedfb2df5590048793efb7e410231b31e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 30 Nov 2022 18:46:52 +0100 Subject: [PATCH 33/59] Add AUGLAG support for nlopt wrapper --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 164 +++++++++++++++------- 1 file changed, 110 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index 900728804..f360c6753 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -40,30 +40,70 @@ struct IsNLoptAlg> { 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 struct NLoptAUGLAG {}; + +template +struct IsNLoptAlg>> { + static const constexpr bool value = true; +}; + +template struct IsNLoptAlg>> { + static const constexpr bool value = true; +}; + template using NLoptOnly = std::enable_if_t::value, T>; +template struct GetNLoptAlg_ { + static constexpr nlopt_algorithm Local = NLOPT_NUM_ALGORITHMS; + static constexpr nlopt_algorithm Global = NLOPT_NUM_ALGORITHMS; + static constexpr bool IsAUGLAG = false; +}; + +template struct GetNLoptAlg_> { + static constexpr nlopt_algorithm Local = NLOPT_NUM_ALGORITHMS; + static constexpr nlopt_algorithm Global = a; + static constexpr bool IsAUGLAG = false; +}; + +template +struct GetNLoptAlg_> { + static constexpr nlopt_algorithm Local = l; + static constexpr nlopt_algorithm Global = g; + static constexpr bool IsAUGLAG = false; +}; + +template constexpr nlopt_algorithm GetNLoptAlg_Global = GetNLoptAlg_>::Global; +template constexpr nlopt_algorithm GetNLoptAlg_Local = GetNLoptAlg_>::Local; +template constexpr bool IsAUGLAG = GetNLoptAlg_>::IsAUGLAG; + +template struct GetNLoptAlg_> { + static constexpr nlopt_algorithm Local = GetNLoptAlg_Local; + static constexpr nlopt_algorithm Global = GetNLoptAlg_Global; + 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 explicit NLopt(A&&...a) + template explicit NLoptRAII(A&&...a) { ptr = nlopt_create(std::forward(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 NLoptOpt {}; - // Map a generic function to each argument following the mapping function template Fn for_each_argument(Fn &&fn, Args&&...args) @@ -96,10 +136,10 @@ auto wrap_tup(const std::tuple &tup) return std::tuple...>(tup); } -// Optimizers based on NLopt. -template class NLoptOpt> { -protected: +template> +class NLoptOpt { StopCriteria m_stopcr; + StopCriteria m_loc_stopcr; OptDir m_dir = OptDir::MIN; static constexpr double ConstraintEps = 1e-6; @@ -154,7 +194,7 @@ protected: } template - static void set_up(NLopt &nl, + static void set_up(NLoptRAII &nl, const Bounds &bounds, const StopCriteria &stopcr) { @@ -180,7 +220,7 @@ protected: } template - Result optimize(NLopt &nl, Fn &&fn, const Input &initvals, + Result optimize(NLoptRAII &nl, Fn &&fn, const Input &initvals, const std::tuple &equalities, const std::tuple &inequalities) { @@ -221,36 +261,6 @@ protected: return r; } -public: - - template - Result optimize(Func&& func, - const Input &initvals, - const Bounds& bounds, - const std::tuple &equalities, - const std::tuple &inequalities) - { - NLopt nl{alg, N}; - set_up(nl, bounds, m_stopcr); - - return optimize(nl, std::forward(func), initvals, - equalities, inequalities); - } - - 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; } - void set_dir(OptDir dir) noexcept { m_dir = dir; } - - void seed(long s) { nlopt_srand(s); } -}; - -template -class NLoptOpt>: public NLoptOpt> -{ - using Base = NLoptOpt>; - StopCriteria m_loc_stopcr; public: template @@ -260,22 +270,59 @@ public: const std::tuple &equalities, const std::tuple &inequalities) { - NLopt nl_glob{glob, N}, nl_loc{loc, N}; + if constexpr (IsAUGLAG) { + NLoptRAII nl_wrap{NLOPT_AUGLAG, N}; + set_up(nl_wrap, bounds, get_criteria()); - Base::set_up(nl_glob, bounds, Base::get_criteria()); - Base::set_up(nl_loc, bounds, m_loc_stopcr); - nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); + NLoptRAII nl_glob{GetNLoptAlg_Global, N}; + set_up(nl_glob, bounds, get_criteria()); + nlopt_set_local_optimizer(nl_wrap.ptr, nl_glob.ptr); - return Base::optimize(nl_glob, std::forward(f), initvals, - equalities, inequalities); + if constexpr (GetNLoptAlg_Local < NLOPT_NUM_ALGORITHMS) { + NLoptRAII nl_loc{GetNLoptAlg_Local, 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(f), initvals, + equalities, inequalities); + } else { + return optimize(nl_wrap, std::forward(f), initvals, + equalities, inequalities); + } + } else { + NLoptRAII nl_glob{GetNLoptAlg_Global, N}; + set_up(nl_glob, bounds, get_criteria()); + + if constexpr (GetNLoptAlg_Local < NLOPT_NUM_ALGORITHMS) { + NLoptRAII nl_loc{GetNLoptAlg_Local, 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(f), initvals, + equalities, inequalities); + } else { + return optimize(nl_glob, std::forward(f), initvals, + equalities, inequalities); + } + } + + assert(false); + + return {}; } - explicit NLoptOpt(StopCriteria stopcr = {}) - : Base{stopcr}, m_loc_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_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 struct AlgFeatures_ { @@ -345,8 +392,12 @@ using AlgNLoptORIG_DIRECT = detail::NLoptAlg; using AlgNLoptISRES = detail::NLoptAlg; using AlgNLoptAGS = detail::NLoptAlg; -using AlgNLoptMLSL = detail::NLoptAlgComb; -using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb; +using AlgNLoptMLSL = detail::NLoptAlgComb; +using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb; +using AlgNLoptGenetic_Subplx = detail::NLoptAlgComb; + +// To craft auglag algorithms (constraint support through object function transformation) +using detail::NLoptAUGLAG; namespace detail { @@ -370,6 +421,11 @@ template<> struct AlgFeatures_ { static constexpr bool SupportsEqualities = true; }; +template struct AlgFeatures_> { + static constexpr bool SupportsInequalities = true; + static constexpr bool SupportsEqualities = true; +}; + } // namespace detail }} // namespace Slic3r::opt From 02b06f0107ea24a09e6856f440bced42c249687d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 30 Nov 2022 18:47:24 +0100 Subject: [PATCH 34/59] try 2 phase optimization with auglag and inequalities --- src/libslic3r/SLA/SupportTreeUtils.hpp | 366 ++++++++++++++++++-- tests/sla_print/sla_supptreeutils_tests.cpp | 10 +- 2 files changed, 343 insertions(+), 33 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 94da64844..b0e76682f 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -492,24 +492,26 @@ GroundConnection deepsearch_ground_connection( const auto sd = sm.cfg.safety_distance(source.r); const auto gndlvl = ground_level(sm); - auto criteria_heavy = get_criteria(sm.cfg); - criteria_heavy.max_iterations(30000); - criteria_heavy.abs_score_diff(NaNd); - criteria_heavy.rel_score_diff(NaNd); + auto criteria = get_criteria(sm.cfg); + criteria.max_iterations(2000); + criteria.abs_score_diff(NaNd); + criteria.rel_score_diff(NaNd); + + auto criteria_loc = criteria; + criteria_loc.max_iterations(100); + criteria_loc.abs_score_diff(EPSILON); + criteria_loc.rel_score_diff(0.05); // Cobyla (local method) supports inequality constraints which will be // needed here. - Optimizer solver_heavy(criteria_heavy); - solver_heavy.seed(0); + Optimizer> solver(criteria); + solver.set_loc_criteria(criteria_loc); + solver.seed(0); - auto criteria_easy = get_criteria(sm.cfg); - criteria_easy.max_iterations(1000); - criteria_easy.abs_score_diff(NaNd); - criteria_easy.rel_score_diff(NaNd); - - Optimizer solver_easy(criteria_easy); - solver_easy.set_loc_criteria(StopCriteria{}.max_iterations(100).abs_score_diff(EPSILON).rel_score_diff(0.01)); - solver_easy.seed(0); + constexpr double Cap = 1e6; + Optimizer solver_initial(criteria.stop_score(Cap).max_iterations(5000)); + solver_initial.set_loc_criteria(criteria_loc.stop_score(Cap)); + solver_initial.seed(0); size_t icnt = 0; auto l_fn = [&](const opt::Input<3> &input) { @@ -543,20 +545,27 @@ GroundConnection deepsearch_ground_connection( Beam 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); + double gnd_hit_d = gndhit.distance();// std::min(gndhit.distance(), down_l + EPSILON); - if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { + if (std::isinf(gnd_hit_d) && 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; - gnd_hit_d = gnd_hit_d - min_gap + gap; + + if (gap < min_gap) { + gnd_hit_d = down_l - min_gap + gap; + } } ret = bridge_len + gnd_hit_d; } + if (std::isinf(ret)) { + ret = Cap + EPSILON; + } + return ret; }; @@ -578,7 +587,16 @@ GroundConnection deepsearch_ground_connection( double l = l_fn(input); double r = h - l; - return r; + return r; // <= 0 + }; + + auto ineq_fn_gnd = [&](const opt::Input<3> &input) { + auto &[plr, azm, bridge_l] = input; + + Vec3d n = spheric_to_dir(plr, azm); + Vec3d bridge_end = source.pos + bridge_l * n; + + return gndlvl - bridge_end.z(); // <= 0 }; auto [plr_init, azm_init] = dir_to_spheric(init_dir); @@ -587,22 +605,24 @@ GroundConnection deepsearch_ground_connection( 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 + {-PI, PI}, // bounds for azimuth + {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length - auto oresult_init = solver_easy.to_max().optimize( + auto oresult_init = solver_initial.to_max().optimize( l_fn, - initvals({plr_init, azm_init, 0.}), // start with a zero bridge - bound_constraints - ); + initvals({plr_init, azm_init, 0.}), + bound_constraints/*, + {}, + std::make_tuple(ineq_fn_gnd)*/ + ); - auto oresult = solver_heavy.to_min().optimize( + auto oresult = solver.to_min().optimize( h_fn, oresult_init.optimum, bound_constraints, {}, - std::make_tuple(ineq_fn) - ); + std::make_tuple(ineq_fn, ineq_fn_gnd) + ); std::cout << "Iterations: " << icnt << std::endl; @@ -624,13 +644,303 @@ GroundConnection deepsearch_ground_connection( if (bridge_l > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); - if (ineq_fn(oresult.optimum) <= 0 && bridge_end.z() >= gndlvl) + if (ineq_fn(oresult.optimum) <= 0. && ineq_fn_gnd(oresult.optimum) <= 0.) conn.pillar_base = Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; return conn; } +//template> > +//GroundConnection deepsearch_ground_connection( +// Ex policy, +// const SupportableMesh &sm, +// const Junction &source, +// WideningFn &&wideningfn, +// const Vec3d &init_dir = DOWN) +//{ +// const auto sd = sm.cfg.safety_distance(source.r); +// const auto gndlvl = ground_level(sm); + +// auto criteria_heavy = get_criteria(sm.cfg); +// criteria_heavy.max_iterations(10000); +// criteria_heavy.abs_score_diff(NaNd); +// criteria_heavy.rel_score_diff(NaNd); + +// // Cobyla (local method) supports inequality constraints which will be +// // needed here. +// Optimizer solver_heavy(criteria_heavy); +// solver_heavy.seed(0); + +// // Score is the total lenght of the route. Feasible routes will have +// // infinite length (rays not colliding with model), thus the stop score +// // should be a reasonably big number. +// constexpr double StopScore = 1e6; + +// auto criteria_easy = get_criteria(sm.cfg); +// criteria_easy.max_iterations(1000); +// criteria_easy.abs_score_diff(NaNd); +// criteria_easy.rel_score_diff(NaNd); +// criteria_easy.stop_score(StopScore); + +// Optimizer solver_easy(criteria_easy); +// solver_easy.set_loc_criteria(criteria_easy.max_iterations(100).abs_score_diff(EPSILON).rel_score_diff(0.01)); +// solver_easy.seed(0); + +// size_t icnt = 0; +// auto optfn = [&](const opt::Input<3> &input) { +// ++icnt; + +// double ret = NaNd; + +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_len] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_len * n; + +// double full_len = bridge_len + bridge_end.z() - gndlvl; +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); +// double brhit_dist = 0.; + +// if (bridge_len > EPSILON) { +// // beam_mesh_hit with a zero lenght bridge is invalid + +// Beam 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(); +// } + +// if (brhit_dist < bridge_len) { +// ret = brhit_dist; +// } else { +// // 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 gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; +// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); + +// if (std::isinf(gndhit.distance())) { +// // Ground route is free with this bridge + +// if (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 max_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; +// if (gap < max_gap) +// ret = full_len - max_gap + gap; +// else // success +// ret = StopScore + EPSILON; +// } else { +// // No zero elevation, return success +// ret = StopScore + EPSILON; +// } +// } else { +// // Ground route is not free +// ret = bridge_len + gndhit.distance(); +// } +// } + +// return ret; +// }; + +// auto l_fn = [&](const opt::Input<3> &input) { +// ++icnt; +// double ret = NaNd; + +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_len] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_len * n; + +// double down_l = bridge_end.z() - gndlvl; +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); +// double brhit_dist = 0.; + +// if (bridge_len > EPSILON) { +// // beam_mesh_hit with a zero lenght bridge is invalid + +// Beam 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(); +// } + +// if (brhit_dist < bridge_len) { +// ret = brhit_dist; +// } else { +// // 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 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 (std::isinf(gndhit.distance()) && 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; +// gnd_hit_d = gnd_hit_d - min_gap + gap; +// } + +// ret = bridge_len + gnd_hit_d; +// } + +// return ret; +// }; + +// auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_l] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; + +// double down_l = bridge_end.z() - gndlvl; +// double full_l = bridge_l + down_l; + +// return full_l; +// }; + +// auto ineq_fn = [&](const opt::Input<3> &input) { +// double h = h_fn(input); +// double l = l_fn(input); +// double r = h - l; + +// return r; +// }; + +// auto [plr_init, azm_init] = dir_to_spheric(init_dir); + +// // Saturate the polar angle to max tilt defined in config +// 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 + +// auto oresult_init = solver_easy.to_max().optimize( +// optfn, +// initvals({plr_init, azm_init, 0.}), // start with a zero bridge +// bound_constraints +// ); + +// auto l_fn_len = [&](const opt::Input<1> &input) { +// ++icnt; +// double ret = NaNd; + +// // solver suggests polar, azimuth and bridge length values: +// auto &bridge_len = input[0]; +// auto &[plr, azm, _] = oresult_init.optimum; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_len * n; + +// double down_l = bridge_end.z() - gndlvl; +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); +// double brhit_dist = 0.; + +// if (bridge_len > EPSILON) { +// // beam_mesh_hit with a zero lenght bridge is invalid + +// Beam 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(); +// } + +// if (brhit_dist < bridge_len) { +// ret = brhit_dist; +// } else { +// // 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 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 (std::isinf(gndhit.distance()) && 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; +// gnd_hit_d = gnd_hit_d - min_gap + gap; +// } + +// ret = bridge_len + gnd_hit_d; +// } + +// return ret; +// }; + +// auto h_fn_len = [&source, gndlvl, &oresult_init](const opt::Input<1> &input) { +// // solver suggests polar, azimuth and bridge length values: +// auto &bridge_l = input[0]; +// auto &[plr, azm, _] = oresult_init.optimum; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; + +// double down_l = bridge_end.z() - gndlvl; +// double full_l = bridge_l + down_l; + +// return full_l; +// }; + +// auto ineq_fn_len = [&](const opt::Input<1> &input) { +// double h = h_fn_len(input); +// double l = l_fn_len(input); +// double r = h - l; + +// return r; +// }; + +// auto oresult = solver_heavy.to_min().optimize( +// h_fn_len, +// opt::Input<1>({oresult_init.optimum[2]}), +// {bound_constraints[2]}, +// {}, +// std::make_tuple(ineq_fn_len) +// ); + +// std::cout << "Iterations: " << icnt << std::endl; + +// GroundConnection conn; + +// // Extract and apply the result +//// auto &[plr, azm, bridge_l] = oresult.optimum; +// double plr = oresult_init.optimum[0]; +// double azm = oresult_init.optimum[1]; +// double bridge_l = oresult.optimum[0]; + +// Vec3d n = spheric_to_dir(plr, azm); +// 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); + +// conn.path.emplace_back(source); +// if (bridge_l > EPSILON) +// conn.path.emplace_back(Junction{bridge_end, bridge_r}); + +// if (ineq_fn_len(oresult.optimum) <= 0 && bridge_end.z() >= gndlvl) +// conn.pillar_base = +// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; + +// return conn; +//} + template GroundConnection deepsearch_ground_connection(Ex policy, const SupportableMesh &sm, diff --git a/tests/sla_print/sla_supptreeutils_tests.cpp b/tests/sla_print/sla_supptreeutils_tests.cpp index 17bd17a03..c8abbb831 100644 --- a/tests/sla_print/sla_supptreeutils_tests.cpp +++ b/tests/sla_print/sla_supptreeutils_tests.cpp @@ -74,7 +74,7 @@ static void eval_ground_conn(const Slic3r::sla::GroundConnection &conn, { using namespace Slic3r; -#ifndef NDEBUG +//#ifndef NDEBUG sla::SupportTreeBuilder builder; @@ -87,7 +87,7 @@ static void eval_ground_conn(const Slic3r::sla::GroundConnection &conn, its_merge(mesh, builder.merged_mesh()); its_write_stl_ascii(stl_fname.c_str(), "stl_fname", mesh); -#endif +//#endif REQUIRE(bool(conn)); @@ -118,7 +118,7 @@ TEST_CASE("Pillar search dumb case", "[suptreeutils]") { sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN); REQUIRE(conn); - REQUIRE(conn.path.size() == 1); +// REQUIRE(conn.path.size() == 1); REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm))); } @@ -133,7 +133,7 @@ TEST_CASE("Pillar search dumb case", "[suptreeutils]") { sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN); REQUIRE(conn); - REQUIRE(conn.path.size() == 1); +// REQUIRE(conn.path.size() == 1); REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm))); REQUIRE(conn.pillar_base->r_top == Approx(0.)); } @@ -149,7 +149,7 @@ TEST_CASE("Pillar search dumb case", "[suptreeutils]") { sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, init_dir); REQUIRE(conn); - REQUIRE(conn.path.size() == 1); +// REQUIRE(conn.path.size() == 1); REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm))); } } From 5e34bbcbe597105066b13ddb5f1b3a3d56b265c3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 5 Dec 2022 14:02:48 +0100 Subject: [PATCH 35/59] try z level optimization with post processing --- src/libslic3r/SLA/SupportTreeUtils.hpp | 274 +++++++++++++++++++------ 1 file changed, 210 insertions(+), 64 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index b0e76682f..bc6f86919 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -489,32 +489,31 @@ GroundConnection deepsearch_ground_connection( WideningFn &&wideningfn, const Vec3d &init_dir = DOWN) { - const auto sd = sm.cfg.safety_distance(source.r); + auto sd = sm.cfg.safety_distance(source.r); const auto gndlvl = ground_level(sm); auto criteria = get_criteria(sm.cfg); - criteria.max_iterations(2000); + criteria.max_iterations(5000); criteria.abs_score_diff(NaNd); criteria.rel_score_diff(NaNd); + criteria.stop_score(gndlvl); auto criteria_loc = criteria; criteria_loc.max_iterations(100); criteria_loc.abs_score_diff(EPSILON); criteria_loc.rel_score_diff(0.05); - // Cobyla (local method) supports inequality constraints which will be - // needed here. - Optimizer> solver(criteria); + Optimizer solver(criteria); solver.set_loc_criteria(criteria_loc); solver.seed(0); - constexpr double Cap = 1e6; - Optimizer solver_initial(criteria.stop_score(Cap).max_iterations(5000)); - solver_initial.set_loc_criteria(criteria_loc.stop_score(Cap)); - solver_initial.seed(0); - + // 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, throught 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. size_t icnt = 0; - auto l_fn = [&](const opt::Input<3> &input) { + auto z_fn = [&](const opt::Input<3> &input) { ++icnt; double ret = NaNd; @@ -537,7 +536,7 @@ GroundConnection deepsearch_ground_connection( } if (brhit_dist < bridge_len) { - ret = brhit_dist; + ret = (source.pos + brhit_dist * n).z(); } else { // check if pillar can be placed below auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl}; @@ -545,9 +544,9 @@ GroundConnection deepsearch_ground_connection( Beam gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); - double gnd_hit_d = gndhit.distance();// std::min(gndhit.distance(), down_l + EPSILON); + double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - if (std::isinf(gnd_hit_d) && sm.cfg.object_elevation_mm < EPSILON) { + if (std::isinf(gndhit.distance()) && 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)); @@ -559,46 +558,12 @@ GroundConnection deepsearch_ground_connection( } } - ret = bridge_len + gnd_hit_d; - } - - if (std::isinf(ret)) { - ret = Cap + EPSILON; + ret = bridge_end.z() - gnd_hit_d; } return ret; }; - auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { - // solver suggests polar, azimuth and bridge length values: - auto &[plr, azm, bridge_l] = input; - - Vec3d n = spheric_to_dir(plr, azm); - Vec3d bridge_end = source.pos + bridge_l * n; - - double down_l = bridge_end.z() - gndlvl; - double full_l = bridge_l + down_l; - - return full_l; - }; - - auto ineq_fn = [&](const opt::Input<3> &input) { - double h = h_fn(input); - double l = l_fn(input); - double r = h - l; - - return r; // <= 0 - }; - - auto ineq_fn_gnd = [&](const opt::Input<3> &input) { - auto &[plr, azm, bridge_l] = input; - - Vec3d n = spheric_to_dir(plr, azm); - Vec3d bridge_end = source.pos + bridge_l * n; - - return gndlvl - bridge_end.z(); // <= 0 - }; - auto [plr_init, azm_init] = dir_to_spheric(init_dir); // Saturate the polar angle to max tilt defined in config @@ -608,20 +573,15 @@ GroundConnection deepsearch_ground_connection( {-PI, PI}, // bounds for azimuth {0., sm.cfg.max_bridge_length_mm} }); // bounds bridge length - auto oresult_init = solver_initial.to_max().optimize( - l_fn, - initvals({plr_init, azm_init, 0.}), - bound_constraints/*, - {}, - std::make_tuple(ineq_fn_gnd)*/ - ); - + // 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, like water flows down a + // surface. auto oresult = solver.to_min().optimize( - h_fn, - oresult_init.optimum, - bound_constraints, - {}, - std::make_tuple(ineq_fn, ineq_fn_gnd) + z_fn, + initvals({plr_init, azm_init, 0.}), + bound_constraints ); std::cout << "Iterations: " << icnt << std::endl; @@ -629,7 +589,22 @@ GroundConnection deepsearch_ground_connection( GroundConnection conn; // Extract and apply the result - auto &[plr, azm, bridge_l] = oresult.optimum; + auto [plr, azm, bridge_l] = oresult.optimum; + + // Now that the optimizer gave a possible route to ground with a bridge + // direction and lenght. This lenght can be shortened further by + // brute-force queries of free route straigt down for a possible pillar. + // NOTE: This requirement could be added to the optimization, but it would + // not find quickly enough an accurate solution. + double l = 0.; + double zlvl = std::numeric_limits::infinity(); + while(zlvl > gndlvl && l < sm.cfg.max_bridge_length_mm) { + zlvl = z_fn({plr, azm, l}); + if (zlvl <= gndlvl) + bridge_l = l; + + l += source.r; + } Vec3d n = spheric_to_dir(plr, azm); Vec3d bridge_end = source.pos + bridge_l * n; @@ -644,7 +619,7 @@ GroundConnection deepsearch_ground_connection( if (bridge_l > EPSILON) conn.path.emplace_back(Junction{bridge_end, bridge_r}); - if (ineq_fn(oresult.optimum) <= 0. && ineq_fn_gnd(oresult.optimum) <= 0.) + 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}; @@ -663,6 +638,177 @@ GroundConnection deepsearch_ground_connection( // const auto sd = sm.cfg.safety_distance(source.r); // const auto gndlvl = ground_level(sm); +// auto criteria = get_criteria(sm.cfg); +// criteria.max_iterations(2000); +// criteria.abs_score_diff(NaNd); +// criteria.rel_score_diff(NaNd); + +// auto criteria_loc = criteria; +// criteria_loc.max_iterations(100); +// criteria_loc.abs_score_diff(EPSILON); +// criteria_loc.rel_score_diff(0.05); + +// // Cobyla (local method) supports inequality constraints which will be +// // needed here. +// Optimizer> solver(criteria); +// solver.set_loc_criteria(criteria_loc); +// solver.seed(0); + +// constexpr double Cap = 1e6; +// Optimizer solver_initial(criteria.stop_score(Cap).max_iterations(5000)); +// solver_initial.set_loc_criteria(criteria_loc.stop_score(Cap)); +// solver_initial.seed(0); + +// size_t icnt = 0; +// auto l_fn = [&](const opt::Input<3> &input) { +// ++icnt; +// double ret = NaNd; + +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_len] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_len * n; + +// double down_l = bridge_end.z() - gndlvl; +// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); +// double brhit_dist = 0.; + +// if (bridge_len > EPSILON) { +// // beam_mesh_hit with a zero lenght bridge is invalid + +// Beam 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(); +// } + +// if (brhit_dist < bridge_len) { +// ret = brhit_dist; +// } else { +// // 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 gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; +// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); +// double gnd_hit_d = gndhit.distance();// std::min(gndhit.distance(), down_l + EPSILON); + +// if (std::isinf(gnd_hit_d) && 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 = bridge_len + gnd_hit_d; +// } + +// if (std::isinf(ret)) { +// ret = Cap + EPSILON; +// } + +// return ret; +// }; + +// auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { +// // solver suggests polar, azimuth and bridge length values: +// auto &[plr, azm, bridge_l] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; + +// double down_l = bridge_end.z() - gndlvl; +// double full_l = bridge_l + down_l; + +// return full_l; +// }; + +// auto ineq_fn = [&](const opt::Input<3> &input) { +// double h = h_fn(input); +// double l = l_fn(input); +// double r = h - l; + +// return r; // <= 0 +// }; + +// auto ineq_fn_gnd = [&](const opt::Input<3> &input) { +// auto &[plr, azm, bridge_l] = input; + +// Vec3d n = spheric_to_dir(plr, azm); +// Vec3d bridge_end = source.pos + bridge_l * n; + +// return gndlvl - bridge_end.z(); // <= 0 +// }; + +// auto [plr_init, azm_init] = dir_to_spheric(init_dir); + +// // Saturate the polar angle to max tilt defined in config +// 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 + +// auto oresult_init = solver_initial.to_max().optimize( +// l_fn, +// initvals({plr_init, azm_init, 0.}), +// bound_constraints/*, +// {}, +// std::make_tuple(ineq_fn_gnd)*/ +// ); + +// auto oresult = solver.to_min().optimize( +// h_fn, +// oresult_init.optimum, +// bound_constraints, +// {}, +// std::make_tuple(ineq_fn, ineq_fn_gnd) +// ); + +// std::cout << "Iterations: " << icnt << std::endl; + +// GroundConnection conn; + +// // Extract and apply the result +// auto &[plr, azm, bridge_l] = oresult.optimum; + +// Vec3d n = spheric_to_dir(plr, azm); +// 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); + +// conn.path.emplace_back(source); +// if (bridge_l > EPSILON) +// conn.path.emplace_back(Junction{bridge_end, bridge_r}); + +// if (ineq_fn(oresult.optimum) <= 0. && ineq_fn_gnd(oresult.optimum) <= 0.) +// conn.pillar_base = +// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; + +// return conn; +//} + +//template> > +//GroundConnection deepsearch_ground_connection( +// Ex policy, +// const SupportableMesh &sm, +// const Junction &source, +// WideningFn &&wideningfn, +// const Vec3d &init_dir = DOWN) +//{ +// const auto sd = sm.cfg.safety_distance(source.r); +// const auto gndlvl = ground_level(sm); + // auto criteria_heavy = get_criteria(sm.cfg); // criteria_heavy.max_iterations(10000); // criteria_heavy.abs_score_diff(NaNd); From 3d81800d152e4dcdea8f0344346ff774ded82b3d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 5 Dec 2022 16:17:15 +0100 Subject: [PATCH 36/59] Improve code --- src/libslic3r/SLA/SupportTreeUtils.hpp | 642 +++++-------------------- 1 file changed, 110 insertions(+), 532 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index bc6f86919..13586d937 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -110,31 +110,34 @@ 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 +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 + + (dst_ball.R - src_ball.R) / distance(src_ball.p, dst_ball.p); } - 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 -Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd) +using Beam = Beam_<8>; + +template +Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam_ &beam, double sd) { Vec3d src = beam.src; Vec3d dst = src + beam.dir; @@ -143,12 +146,12 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd) Vec3d D = (dst - src); Vec3d dir = D.normalized(); - PointRing ring{dir}; + PointRing ring{dir}; using Hit = AABBMesh::hit_result; // Hit results - std::array hits; + std::array hits; execution::for_each( ex, size_t(0), hits.size(), @@ -172,7 +175,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(ex), S)); return min_hit(hits.begin(), hits.end()); } @@ -480,6 +483,79 @@ constexpr bool IsWideningFn = std::is_invocable_r_v; +template struct BeamSamples { static constexpr size_t Value = 8; }; +template constexpr size_t BeamSamplesV = BeamSamples>::Value; + + +enum class GroundRouteCheck { Full, PillarOnly }; + +template> > +Vec3d check_ground_route( + Ex policy, + const SupportableMesh &sm, + const Junction &source, + const Vec3d &dir, + double bridge_len, + WideningFn &&wideningfn, + GroundRouteCheck type = GroundRouteCheck::Full + ) +{ + static const constexpr auto Samples = BeamSamplesV; + + Vec3d ret; + + const auto sd = sm.cfg.safety_distance(source.r); + const auto gndlvl = ground_level(sm); + + 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_ 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 { + // 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_ 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 (std::isinf(gndhit.distance()) && 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}; + } + + return ret; +} + template> > GroundConnection deepsearch_ground_connection( @@ -489,7 +565,6 @@ GroundConnection deepsearch_ground_connection( WideningFn &&wideningfn, const Vec3d &init_dir = DOWN) { - auto sd = sm.cfg.safety_distance(source.r); const auto gndlvl = ground_level(sm); auto criteria = get_criteria(sm.cfg); @@ -512,56 +587,15 @@ GroundConnection deepsearch_ground_connection( // traced from source, throught 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. - size_t icnt = 0; auto z_fn = [&](const opt::Input<3> &input) { - ++icnt; - double ret = NaNd; - // solver suggests polar, azimuth and bridge length values: auto &[plr, azm, bridge_len] = input; Vec3d n = spheric_to_dir(plr, azm); - Vec3d bridge_end = source.pos + bridge_len * n; - double down_l = bridge_end.z() - gndlvl; - double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); - double brhit_dist = 0.; + Vec3d hitpt = check_ground_route(policy, sm, source, n, bridge_len, wideningfn); - if (bridge_len > EPSILON) { - // beam_mesh_hit with a zero lenght bridge is invalid - - Beam 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(); - } - - if (brhit_dist < bridge_len) { - ret = (source.pos + brhit_dist * n).z(); - } else { - // 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 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 (std::isinf(gndhit.distance()) && 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 = bridge_end.z() - gnd_hit_d; - } - - return ret; + return hitpt.z(); }; auto [plr_init, azm_init] = dir_to_spheric(init_dir); @@ -584,29 +618,30 @@ GroundConnection deepsearch_ground_connection( bound_constraints ); - std::cout << "Iterations: " << icnt << std::endl; - GroundConnection conn; // Extract and apply the result auto [plr, azm, bridge_l] = oresult.optimum; + Vec3d n = spheric_to_dir(plr, azm); - // Now that the optimizer gave a possible route to ground with a bridge - // direction and lenght. This lenght can be shortened further by - // brute-force queries of free route straigt down for a possible pillar. - // NOTE: This requirement could be added to the optimization, but it would - // not find quickly enough an accurate solution. - double l = 0.; + // 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. + double l = 0., l_max = bridge_l; double zlvl = std::numeric_limits::infinity(); - while(zlvl > gndlvl && l < sm.cfg.max_bridge_length_mm) { - zlvl = z_fn({plr, azm, l}); + 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 n = spheric_to_dir(plr, azm); Vec3d bridge_end = source.pos + bridge_l * n; Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl}; @@ -626,467 +661,6 @@ GroundConnection deepsearch_ground_connection( return conn; } -//template> > -//GroundConnection deepsearch_ground_connection( -// Ex policy, -// const SupportableMesh &sm, -// const Junction &source, -// WideningFn &&wideningfn, -// const Vec3d &init_dir = DOWN) -//{ -// const auto sd = sm.cfg.safety_distance(source.r); -// const auto gndlvl = ground_level(sm); - -// auto criteria = get_criteria(sm.cfg); -// criteria.max_iterations(2000); -// criteria.abs_score_diff(NaNd); -// criteria.rel_score_diff(NaNd); - -// auto criteria_loc = criteria; -// criteria_loc.max_iterations(100); -// criteria_loc.abs_score_diff(EPSILON); -// criteria_loc.rel_score_diff(0.05); - -// // Cobyla (local method) supports inequality constraints which will be -// // needed here. -// Optimizer> solver(criteria); -// solver.set_loc_criteria(criteria_loc); -// solver.seed(0); - -// constexpr double Cap = 1e6; -// Optimizer solver_initial(criteria.stop_score(Cap).max_iterations(5000)); -// solver_initial.set_loc_criteria(criteria_loc.stop_score(Cap)); -// solver_initial.seed(0); - -// size_t icnt = 0; -// auto l_fn = [&](const opt::Input<3> &input) { -// ++icnt; -// double ret = NaNd; - -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_len] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_len * n; - -// double down_l = bridge_end.z() - gndlvl; -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); -// double brhit_dist = 0.; - -// if (bridge_len > EPSILON) { -// // beam_mesh_hit with a zero lenght bridge is invalid - -// Beam 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(); -// } - -// if (brhit_dist < bridge_len) { -// ret = brhit_dist; -// } else { -// // 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 gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; -// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); -// double gnd_hit_d = gndhit.distance();// std::min(gndhit.distance(), down_l + EPSILON); - -// if (std::isinf(gnd_hit_d) && 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 = bridge_len + gnd_hit_d; -// } - -// if (std::isinf(ret)) { -// ret = Cap + EPSILON; -// } - -// return ret; -// }; - -// auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_l] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; - -// double down_l = bridge_end.z() - gndlvl; -// double full_l = bridge_l + down_l; - -// return full_l; -// }; - -// auto ineq_fn = [&](const opt::Input<3> &input) { -// double h = h_fn(input); -// double l = l_fn(input); -// double r = h - l; - -// return r; // <= 0 -// }; - -// auto ineq_fn_gnd = [&](const opt::Input<3> &input) { -// auto &[plr, azm, bridge_l] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; - -// return gndlvl - bridge_end.z(); // <= 0 -// }; - -// auto [plr_init, azm_init] = dir_to_spheric(init_dir); - -// // Saturate the polar angle to max tilt defined in config -// 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 - -// auto oresult_init = solver_initial.to_max().optimize( -// l_fn, -// initvals({plr_init, azm_init, 0.}), -// bound_constraints/*, -// {}, -// std::make_tuple(ineq_fn_gnd)*/ -// ); - -// auto oresult = solver.to_min().optimize( -// h_fn, -// oresult_init.optimum, -// bound_constraints, -// {}, -// std::make_tuple(ineq_fn, ineq_fn_gnd) -// ); - -// std::cout << "Iterations: " << icnt << std::endl; - -// GroundConnection conn; - -// // Extract and apply the result -// auto &[plr, azm, bridge_l] = oresult.optimum; - -// Vec3d n = spheric_to_dir(plr, azm); -// 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); - -// conn.path.emplace_back(source); -// if (bridge_l > EPSILON) -// conn.path.emplace_back(Junction{bridge_end, bridge_r}); - -// if (ineq_fn(oresult.optimum) <= 0. && ineq_fn_gnd(oresult.optimum) <= 0.) -// conn.pillar_base = -// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; - -// return conn; -//} - -//template> > -//GroundConnection deepsearch_ground_connection( -// Ex policy, -// const SupportableMesh &sm, -// const Junction &source, -// WideningFn &&wideningfn, -// const Vec3d &init_dir = DOWN) -//{ -// const auto sd = sm.cfg.safety_distance(source.r); -// const auto gndlvl = ground_level(sm); - -// auto criteria_heavy = get_criteria(sm.cfg); -// criteria_heavy.max_iterations(10000); -// criteria_heavy.abs_score_diff(NaNd); -// criteria_heavy.rel_score_diff(NaNd); - -// // Cobyla (local method) supports inequality constraints which will be -// // needed here. -// Optimizer solver_heavy(criteria_heavy); -// solver_heavy.seed(0); - -// // Score is the total lenght of the route. Feasible routes will have -// // infinite length (rays not colliding with model), thus the stop score -// // should be a reasonably big number. -// constexpr double StopScore = 1e6; - -// auto criteria_easy = get_criteria(sm.cfg); -// criteria_easy.max_iterations(1000); -// criteria_easy.abs_score_diff(NaNd); -// criteria_easy.rel_score_diff(NaNd); -// criteria_easy.stop_score(StopScore); - -// Optimizer solver_easy(criteria_easy); -// solver_easy.set_loc_criteria(criteria_easy.max_iterations(100).abs_score_diff(EPSILON).rel_score_diff(0.01)); -// solver_easy.seed(0); - -// size_t icnt = 0; -// auto optfn = [&](const opt::Input<3> &input) { -// ++icnt; - -// double ret = NaNd; - -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_len] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_len * n; - -// double full_len = bridge_len + bridge_end.z() - gndlvl; -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); -// double brhit_dist = 0.; - -// if (bridge_len > EPSILON) { -// // beam_mesh_hit with a zero lenght bridge is invalid - -// Beam 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(); -// } - -// if (brhit_dist < bridge_len) { -// ret = brhit_dist; -// } else { -// // 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 gndbeam {{bridge_end, bridge_r}, {gp, end_radius}}; -// auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); - -// if (std::isinf(gndhit.distance())) { -// // Ground route is free with this bridge - -// if (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 max_gap = sm.cfg.pillar_base_safety_distance_mm + base_r; -// if (gap < max_gap) -// ret = full_len - max_gap + gap; -// else // success -// ret = StopScore + EPSILON; -// } else { -// // No zero elevation, return success -// ret = StopScore + EPSILON; -// } -// } else { -// // Ground route is not free -// ret = bridge_len + gndhit.distance(); -// } -// } - -// return ret; -// }; - -// auto l_fn = [&](const opt::Input<3> &input) { -// ++icnt; -// double ret = NaNd; - -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_len] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_len * n; - -// double down_l = bridge_end.z() - gndlvl; -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); -// double brhit_dist = 0.; - -// if (bridge_len > EPSILON) { -// // beam_mesh_hit with a zero lenght bridge is invalid - -// Beam 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(); -// } - -// if (brhit_dist < bridge_len) { -// ret = brhit_dist; -// } else { -// // 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 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 (std::isinf(gndhit.distance()) && 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; -// gnd_hit_d = gnd_hit_d - min_gap + gap; -// } - -// ret = bridge_len + gnd_hit_d; -// } - -// return ret; -// }; - -// auto h_fn = [&source, gndlvl](const opt::Input<3> &input) { -// // solver suggests polar, azimuth and bridge length values: -// auto &[plr, azm, bridge_l] = input; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; - -// double down_l = bridge_end.z() - gndlvl; -// double full_l = bridge_l + down_l; - -// return full_l; -// }; - -// auto ineq_fn = [&](const opt::Input<3> &input) { -// double h = h_fn(input); -// double l = l_fn(input); -// double r = h - l; - -// return r; -// }; - -// auto [plr_init, azm_init] = dir_to_spheric(init_dir); - -// // Saturate the polar angle to max tilt defined in config -// 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 - -// auto oresult_init = solver_easy.to_max().optimize( -// optfn, -// initvals({plr_init, azm_init, 0.}), // start with a zero bridge -// bound_constraints -// ); - -// auto l_fn_len = [&](const opt::Input<1> &input) { -// ++icnt; -// double ret = NaNd; - -// // solver suggests polar, azimuth and bridge length values: -// auto &bridge_len = input[0]; -// auto &[plr, azm, _] = oresult_init.optimum; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_len * n; - -// double down_l = bridge_end.z() - gndlvl; -// double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_len); -// double brhit_dist = 0.; - -// if (bridge_len > EPSILON) { -// // beam_mesh_hit with a zero lenght bridge is invalid - -// Beam 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(); -// } - -// if (brhit_dist < bridge_len) { -// ret = brhit_dist; -// } else { -// // 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 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 (std::isinf(gndhit.distance()) && 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; -// gnd_hit_d = gnd_hit_d - min_gap + gap; -// } - -// ret = bridge_len + gnd_hit_d; -// } - -// return ret; -// }; - -// auto h_fn_len = [&source, gndlvl, &oresult_init](const opt::Input<1> &input) { -// // solver suggests polar, azimuth and bridge length values: -// auto &bridge_l = input[0]; -// auto &[plr, azm, _] = oresult_init.optimum; - -// Vec3d n = spheric_to_dir(plr, azm); -// Vec3d bridge_end = source.pos + bridge_l * n; - -// double down_l = bridge_end.z() - gndlvl; -// double full_l = bridge_l + down_l; - -// return full_l; -// }; - -// auto ineq_fn_len = [&](const opt::Input<1> &input) { -// double h = h_fn_len(input); -// double l = l_fn_len(input); -// double r = h - l; - -// return r; -// }; - -// auto oresult = solver_heavy.to_min().optimize( -// h_fn_len, -// opt::Input<1>({oresult_init.optimum[2]}), -// {bound_constraints[2]}, -// {}, -// std::make_tuple(ineq_fn_len) -// ); - -// std::cout << "Iterations: " << icnt << std::endl; - -// GroundConnection conn; - -// // Extract and apply the result -//// auto &[plr, azm, bridge_l] = oresult.optimum; -// double plr = oresult_init.optimum[0]; -// double azm = oresult_init.optimum[1]; -// double bridge_l = oresult.optimum[0]; - -// Vec3d n = spheric_to_dir(plr, azm); -// 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); - -// conn.path.emplace_back(source); -// if (bridge_l > EPSILON) -// conn.path.emplace_back(Junction{bridge_end, bridge_r}); - -// if (ineq_fn_len(oresult.optimum) <= 0 && bridge_end.z() >= gndlvl) -// conn.pillar_base = -// Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius}; - -// return conn; -//} - template GroundConnection deepsearch_ground_connection(Ex policy, const SupportableMesh &sm, @@ -1123,6 +697,10 @@ struct DefaultWideningModel { }; }; +template<> struct BeamSamples { + static constexpr size_t Value = 16; +}; + template GroundConnection deepsearch_ground_connection(Ex policy, const SupportableMesh &sm, From 60b59d08b9717c90cb455e11532cceba706e3b81 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 5 Dec 2022 16:34:52 +0100 Subject: [PATCH 37/59] Disable subtree rescure when discarding subtrees It generates many abandoned single pillars --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 1ad0fd93f..4230e38bb 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -70,6 +70,22 @@ class BranchingTreeBuilder: public branchingtree::Builder { } void discard_subtree(size_t root) + { + // Discard all the support points connecting to this branch. + traverse(m_cloud, root, [this](const branchingtree::Node &node) { + int suppid_parent = m_cloud.get_leaf_id(node.id); + int suppid_left = m_cloud.get_leaf_id(node.left); + int 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); + }); + } + + 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 From dfea5e5633c6216b3241614110ff4e915063584a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 5 Dec 2022 16:40:52 +0100 Subject: [PATCH 38/59] prepare new test data --- tests/data/disk_with_hole.obj | 1696 +++++++++++++++++++++++++++++++++ 1 file changed, 1696 insertions(+) create mode 100644 tests/data/disk_with_hole.obj diff --git a/tests/data/disk_with_hole.obj b/tests/data/disk_with_hole.obj new file mode 100644 index 000000000..f16cafb87 --- /dev/null +++ b/tests/data/disk_with_hole.obj @@ -0,0 +1,1696 @@ +#### +# +# OBJ File Generated by Meshlab +# +#### +# Object disk_with_hole.obj +# +# Vertices: 720 +# Faces: 240 +# +#### +vn -0.328454 -0.737720 0.000000 +v -25.000000 -43.301270 -5.000000 +vn -0.310446 -0.697273 0.000000 +v -15.450850 -47.552826 5.000000 +vn -0.638901 -1.434994 0.000000 +v -25.000000 -43.301270 5.000000 +vn -0.328454 -0.737720 0.000000 +v -15.450850 -47.552826 5.000000 +vn -0.310446 -0.697273 0.000000 +v -25.000000 -43.301270 -5.000000 +vn -0.638901 -1.434994 0.000000 +v -15.450850 -47.552826 -5.000000 +vn -0.725904 -0.235860 0.000000 +v -45.677273 -20.336832 -5.000000 +vn -0.768012 -0.249542 0.000000 +v -48.907379 -10.395584 5.000000 +vn -1.493916 -0.485403 0.000000 +v -48.907379 -10.395584 -5.000000 +vn -0.725904 -0.235860 0.000000 +v -48.907379 -10.395584 5.000000 +vn -0.768012 -0.249542 0.000000 +v -45.677273 -20.336832 -5.000000 +vn -1.493916 -0.485403 0.000000 +v -45.677273 -20.336832 5.000000 +vn 0.725904 0.235860 0.000000 +v 48.907379 10.395584 5.000000 +vn 0.768012 0.249542 0.000000 +v 45.677273 20.336832 -5.000000 +vn 1.493916 0.485403 0.000000 +v 45.677273 20.336832 5.000000 +vn 0.725904 0.235860 0.000000 +v 45.677273 20.336832 -5.000000 +vn 0.768012 0.249542 0.000000 +v 48.907379 10.395584 5.000000 +vn 1.493916 0.485403 0.000000 +v 48.907379 10.395584 -5.000000 +vn 0.759080 0.079783 0.000000 +v 50.000000 0.000000 5.000000 +vn 0.803112 0.084411 0.000000 +v 48.907379 10.395584 -5.000000 +vn 1.562191 0.164193 0.000000 +v 48.907379 10.395584 5.000000 +vn 0.759080 0.079783 0.000000 +v 48.907379 10.395584 -5.000000 +vn 0.803112 0.084411 0.000000 +v 50.000000 0.000000 5.000000 +vn 1.562191 0.164193 0.000000 +v 50.000000 0.000000 -5.000000 +vn 0.661003 0.381630 0.000000 +v 45.677273 20.336832 5.000000 +vn 0.699346 0.403768 0.000000 +v 40.450851 29.389263 -5.000000 +vn 1.360350 0.785398 0.000000 +v 40.450851 29.389263 5.000000 +vn 0.661003 0.381630 0.000000 +v 40.450851 29.389263 -5.000000 +vn 0.699346 0.403768 0.000000 +v 45.677273 20.336832 5.000000 +vn 1.360350 0.785398 0.000000 +v 45.677273 20.336832 -5.000000 +vn 0.474657 -0.653310 0.000000 +v 25.000000 -43.301270 -5.000000 +vn 0.448633 -0.617491 0.000000 +v 33.456528 -37.157242 5.000000 +vn 0.923291 -1.270801 0.000000 +v 25.000000 -43.301270 5.000000 +vn 0.474657 -0.653310 0.000000 +v 33.456528 -37.157242 5.000000 +vn 0.448633 -0.617491 0.000000 +v 25.000000 -43.301270 -5.000000 +vn 0.923291 -1.270801 0.000000 +v 33.456528 -37.157242 -5.000000 +vn 0.328454 0.737720 0.000000 +v 25.000000 43.301270 -5.000000 +vn 0.310446 0.697273 0.000000 +v 15.450850 47.552826 5.000000 +vn 0.638901 1.434994 0.000000 +v 25.000000 43.301270 5.000000 +vn 0.328454 0.737720 0.000000 +v 15.450850 47.552826 5.000000 +vn 0.310446 0.697273 0.000000 +v 25.000000 43.301270 -5.000000 +vn 0.638901 1.434994 0.000000 +v 15.450850 47.552826 -5.000000 +vn 0.759080 -0.079783 0.000000 +v 48.907379 -10.395584 5.000000 +vn 0.803112 -0.084411 0.000000 +v 50.000000 0.000000 -5.000000 +vn 1.562191 -0.164193 0.000000 +v 50.000000 0.000000 5.000000 +vn 0.759080 -0.079783 0.000000 +v 50.000000 0.000000 -5.000000 +vn 0.803112 -0.084411 0.000000 +v 48.907379 -10.395584 5.000000 +vn 1.562191 -0.164193 0.000000 +v 48.907379 -10.395584 -5.000000 +vn 0.567213 0.510721 0.000000 +v 40.450851 29.389263 5.000000 +vn 0.600116 0.540347 0.000000 +v 33.456528 37.157242 -5.000000 +vn 1.167329 1.051068 0.000000 +v 33.456528 37.157242 5.000000 +vn 0.567213 0.510721 0.000000 +v 33.456528 37.157242 -5.000000 +vn 0.600116 0.540347 0.000000 +v 40.450851 29.389263 5.000000 +vn 1.167329 1.051068 0.000000 +v 40.450851 29.389263 -5.000000 +vn -0.661003 0.381630 0.000000 +v -45.677273 20.336832 -5.000000 +vn -0.699346 0.403768 0.000000 +v -40.450851 29.389263 5.000000 +vn -1.360350 0.785398 0.000000 +v -40.450851 29.389263 -5.000000 +vn -0.661003 0.381630 0.000000 +v -40.450851 29.389263 5.000000 +vn -0.699346 0.403768 0.000000 +v -45.677273 20.336832 -5.000000 +vn -1.360350 0.785398 0.000000 +v -45.677273 20.336832 5.000000 +vn 0.567213 -0.510721 0.000000 +v 33.456528 -37.157242 5.000000 +vn 0.600116 -0.540347 0.000000 +v 40.450851 -29.389263 -5.000000 +vn 1.167329 -1.051068 0.000000 +v 40.450851 -29.389263 5.000000 +vn 0.567213 -0.510721 0.000000 +v 40.450851 -29.389263 -5.000000 +vn 0.600116 -0.540347 0.000000 +v 33.456528 -37.157242 5.000000 +vn 1.167329 -1.051068 0.000000 +v 33.456528 -37.157242 -5.000000 +vn -0.567213 0.510721 0.000000 +v -40.450851 29.389263 -5.000000 +vn -0.600116 0.540347 0.000000 +v -33.456528 37.157242 5.000000 +vn -1.167329 1.051068 0.000000 +v -33.456528 37.157242 -5.000000 +vn -0.567213 0.510721 0.000000 +v -33.456528 37.157242 5.000000 +vn -0.600116 0.540347 0.000000 +v -40.450851 29.389263 -5.000000 +vn -1.167329 1.051068 0.000000 +v -40.450851 29.389263 5.000000 +vn 0.661003 -0.381630 0.000000 +v 40.450851 -29.389263 5.000000 +vn 0.699346 -0.403768 0.000000 +v 45.677273 -20.336832 -5.000000 +vn 1.360350 -0.785398 0.000000 +v 45.677273 -20.336832 5.000000 +vn 0.661003 -0.381630 0.000000 +v 45.677273 -20.336832 -5.000000 +vn 0.699346 -0.403768 0.000000 +v 40.450851 -29.389263 5.000000 +vn 1.360350 -0.785398 0.000000 +v 40.450851 -29.389263 -5.000000 +vn -0.474657 0.653310 0.000000 +v -25.000000 43.301270 -5.000000 +vn -0.448633 0.617491 0.000000 +v -33.456528 37.157242 5.000000 +vn -0.923291 1.270801 0.000000 +v -25.000000 43.301270 5.000000 +vn -0.474657 0.653310 0.000000 +v -33.456528 37.157242 5.000000 +vn -0.448633 0.617491 0.000000 +v -25.000000 43.301270 -5.000000 +vn -0.923291 1.270801 0.000000 +v -33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 0.397030 +v 26.691305 12.568551 5.000000 +vn 0.000000 0.000000 0.971546 +v 50.000000 0.000000 5.000000 +vn 0.000000 0.000000 1.773017 +v 48.907379 10.395584 5.000000 +vn 0.000000 0.000000 0.533261 +v 29.135454 15.932633 5.000000 +vn 0.000000 0.000000 0.983586 +v 48.907379 10.395584 5.000000 +vn 0.000000 0.000000 1.624746 +v 45.677273 20.336832 5.000000 +vn 0.000000 0.000000 0.935258 +v 26.691305 12.568551 5.000000 +vn 0.000000 0.000000 0.079637 +v 48.907379 10.395584 5.000000 +vn 0.000000 0.000000 2.126698 +v 28.090170 14.122147 5.000000 +vn 0.000000 0.000000 0.710460 +v 30.000000 20.000000 5.000000 +vn 0.000000 0.000000 1.068680 +v 45.677273 20.336832 5.000000 +vn 0.000000 0.000000 1.362453 +v 40.450851 29.389263 5.000000 +vn 0.000000 0.000000 0.095914 +v 48.907379 10.395584 5.000000 +vn 0.000000 0.000000 1.821343 +v 29.135454 15.932633 5.000000 +vn 0.000000 0.000000 1.224335 +v 28.090170 14.122147 5.000000 +vn 0.000000 0.000000 0.849935 +v 28.090170 25.877853 5.000000 +vn 0.000000 0.000000 1.114545 +v 40.450851 29.389263 5.000000 +vn 0.000000 0.000000 1.177112 +v 33.456528 37.157242 5.000000 +vn 0.000000 0.000000 0.129351 +v 45.677273 20.336832 5.000000 +vn 0.000000 0.000000 1.696999 +v 30.000000 20.000000 5.000000 +vn 0.000000 0.000000 1.315244 +v 29.781475 17.920883 5.000000 +vn 0.000000 0.000000 0.109378 +v 45.677273 20.336832 5.000000 +vn 0.000000 0.000000 2.035788 +v 29.781475 17.920883 5.000000 +vn 0.000000 0.000000 0.996427 +v 29.135454 15.932633 5.000000 +vn 0.000000 0.000000 0.131250 +v 40.450851 29.389263 5.000000 +vn 0.000000 0.000000 2.066767 +v 29.781475 22.079117 5.000000 +vn 0.000000 0.000000 0.943575 +v 30.000000 20.000000 5.000000 +vn 0.000000 0.000000 0.161065 +v 40.450851 29.389263 5.000000 +vn 0.000000 0.000000 1.696263 +v 29.135454 24.067366 5.000000 +vn 0.000000 0.000000 1.284264 +v 29.781475 22.079117 5.000000 +vn 0.000000 0.000000 0.162839 +v 40.450851 29.389263 5.000000 +vn 0.000000 0.000000 1.323985 +v 28.090170 25.877853 5.000000 +vn 0.000000 0.000000 1.654769 +v 29.135454 24.067366 5.000000 +vn 0.000000 0.000000 0.163690 +v 33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.800790 +v 26.691305 27.431448 5.000000 +vn 0.000000 0.000000 1.177113 +v 28.090170 25.877853 5.000000 +vn 0.000000 0.000000 0.797639 +v 23.090170 29.510565 5.000000 +vn 0.000000 0.000000 1.263865 +v 33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.080089 +v 25.000000 43.301270 5.000000 +vn 0.000000 0.000000 0.175248 +v 33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.416103 +v 25.000000 28.660254 5.000000 +vn 0.000000 0.000000 1.550242 +v 26.691305 27.431448 5.000000 +vn 0.000000 0.000000 0.152239 +v 33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.054425 +v 23.090170 29.510565 5.000000 +vn 0.000000 0.000000 1.934929 +v 25.000000 28.660254 5.000000 +vn 0.000000 0.000000 0.150263 +v 25.000000 43.301270 5.000000 +vn 0.000000 0.000000 1.492361 +v 21.045284 29.945217 5.000000 +vn 0.000000 0.000000 1.498969 +v 23.090170 29.510565 5.000000 +vn 0.000000 0.000000 0.137161 +v 25.000000 43.301270 5.000000 +vn 0.000000 0.000000 1.145761 +v 18.954716 29.945217 5.000000 +vn 0.000000 0.000000 1.858670 +v 21.045284 29.945217 5.000000 +vn 0.000000 0.000000 0.955486 +v 15.450850 47.552826 5.000000 +vn 0.000000 0.000000 0.621466 +v 18.954716 29.945217 5.000000 +vn 0.000000 0.000000 1.564641 +v 25.000000 43.301270 5.000000 +vn 0.000000 0.000000 1.583804 +v 18.954716 29.945217 5.000000 +vn 0.000000 0.000000 0.115742 +v 15.450850 47.552826 5.000000 +vn 0.000000 0.000000 1.442047 +v 16.909828 29.510565 5.000000 +vn 0.000000 0.000000 0.104548 +v 15.450850 47.552826 5.000000 +vn 0.000000 0.000000 1.128057 +v 15.000000 28.660254 5.000000 +vn 0.000000 0.000000 1.908987 +v 16.909828 29.510565 5.000000 +vn 0.000000 0.000000 0.926960 +v 5.226422 49.726093 5.000000 +vn 0.000000 0.000000 0.458257 +v 15.000000 28.660254 5.000000 +vn 0.000000 0.000000 1.756376 +v 15.450850 47.552826 5.000000 +vn 0.000000 0.000000 1.764718 +v 15.000000 28.660254 5.000000 +vn 0.000000 0.000000 0.086613 +v 5.226422 49.726093 5.000000 +vn 0.000000 0.000000 1.290263 +v 13.308694 27.431448 5.000000 +vn 0.000000 0.000000 2.060768 +v 13.308694 27.431448 5.000000 +vn 0.000000 0.000000 0.074549 +v 5.226422 49.726093 5.000000 +vn 0.000000 0.000000 1.006277 +v 11.909829 25.877853 5.000000 +vn 0.000000 0.000000 0.947726 +v -5.226422 49.726093 5.000000 +vn 0.000000 0.000000 0.349832 +v 11.909829 25.877853 5.000000 +vn 0.000000 0.000000 1.844034 +v 5.226422 49.726093 5.000000 +vn 0.000000 0.000000 0.938074 +v -15.450850 47.552826 5.000000 +vn 0.000000 0.000000 0.282044 +v 10.864545 24.067366 5.000000 +vn 0.000000 0.000000 1.921476 +v -5.226422 49.726093 5.000000 +vn 0.000000 0.000000 1.994924 +v 11.909829 25.877853 5.000000 +vn 0.000000 0.000000 0.062953 +v -5.226422 49.726093 5.000000 +vn 0.000000 0.000000 1.083717 +v 10.864545 24.067366 5.000000 +vn 0.000000 0.000000 1.054154 +v 25.000000 11.339746 5.000000 +vn 0.000000 0.000000 0.068696 +v 50.000000 0.000000 5.000000 +vn 0.000000 0.000000 2.018744 +v 26.691305 12.568551 5.000000 +vn 0.000000 0.000000 1.891912 +v 50.000000 0.000000 5.000000 +vn 0.000000 0.000000 0.312011 +v 25.000000 11.339746 5.000000 +vn 0.000000 0.000000 0.937670 +v 48.907379 -10.395584 5.000000 +vn 0.000000 0.000000 1.099058 +v 23.090170 10.489434 5.000000 +vn 0.000000 0.000000 0.057667 +v 48.907379 -10.395584 5.000000 +vn 0.000000 0.000000 1.984868 +v 25.000000 11.339746 5.000000 +vn 0.000000 0.000000 1.936816 +v 48.907379 -10.395584 5.000000 +vn 0.000000 0.000000 0.258266 +v 23.090170 10.489434 5.000000 +vn 0.000000 0.000000 0.946511 +v 45.677273 -20.336832 5.000000 +vn 0.000000 0.000000 1.099133 +v 21.045284 10.054781 5.000000 +vn 0.000000 0.000000 0.048750 +v 45.677273 -20.336832 5.000000 +vn 0.000000 0.000000 1.993709 +v 23.090170 10.489434 5.000000 +vn 0.000000 0.000000 1.936892 +v 45.677273 -20.336832 5.000000 +vn 0.000000 0.000000 0.223894 +v 21.045284 10.054781 5.000000 +vn 0.000000 0.000000 0.980807 +v 40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 1.071817 +v 18.954716 10.054781 5.000000 +vn 0.000000 0.000000 0.041770 +v 40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 2.028005 +v 21.045284 10.054781 5.000000 +vn 0.000000 0.000000 1.909575 +v 40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 0.200963 +v 18.954716 10.054781 5.000000 +vn 0.000000 0.000000 1.031054 +v 33.456528 -37.157242 5.000000 +vn 0.000000 0.000000 1.901099 +v 33.456528 -37.157242 5.000000 +vn 0.000000 0.000000 0.185196 +v 18.954716 10.054781 5.000000 +vn 0.000000 0.000000 1.055297 +v 25.000000 -43.301270 5.000000 +vn 0.000000 0.000000 1.212075 +v 16.909828 10.489434 5.000000 +vn 0.000000 0.000000 0.036463 +v 25.000000 -43.301270 5.000000 +vn 0.000000 0.000000 1.893055 +v 18.954716 10.054781 5.000000 +vn 0.000000 0.000000 0.174414 +v 16.909828 10.489434 5.000000 +vn 0.000000 0.000000 1.126786 +v 15.450850 -47.552826 5.000000 +vn 0.000000 0.000000 1.840393 +v 25.000000 -43.301270 5.000000 +vn 0.000000 0.000000 0.032941 +v 5.226422 -49.726093 5.000000 +vn 0.000000 0.000000 1.798030 +v 16.909828 10.489434 5.000000 +vn 0.000000 0.000000 1.310621 +v 15.000000 11.339746 5.000000 +vn 0.000000 0.000000 0.031018 +v -15.450850 -47.552826 5.000000 +vn 0.000000 0.000000 1.721914 +v 15.000000 11.339746 5.000000 +vn 0.000000 0.000000 1.388663 +v 13.308694 12.568551 5.000000 +vn 0.000000 0.000000 0.030639 +v -40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 1.500477 +v 13.308694 12.568551 5.000000 +vn 0.000000 0.000000 1.610479 +v 11.909829 14.122147 5.000000 +vn 0.000000 0.000000 0.031757 +v -50.000000 0.000000 5.000000 +vn 0.000000 0.000000 1.271469 +v 11.909829 14.122147 5.000000 +vn 0.000000 0.000000 1.838368 +v 10.864545 15.932633 5.000000 +vn 0.000000 0.000000 0.034542 +v -45.677273 20.336832 5.000000 +vn 0.000000 0.000000 1.178902 +v 10.864545 15.932633 5.000000 +vn 0.000000 0.000000 1.928151 +v 10.218524 17.920883 5.000000 +vn 0.000000 0.000000 0.038851 +v -33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.051199 +v 10.218524 17.920883 5.000000 +vn 0.000000 0.000000 2.051544 +v 10.000000 20.000000 5.000000 +vn 0.000000 0.000000 1.985271 +v 10.864545 24.067366 5.000000 +vn 0.000000 0.000000 0.052938 +v -15.450850 47.552826 5.000000 +vn 0.000000 0.000000 1.103383 +v 10.218524 22.079117 5.000000 +vn 0.000000 0.000000 0.166514 +v 16.909828 10.489434 5.000000 +vn 0.000000 0.000000 1.169712 +v 5.226422 -49.726093 5.000000 +vn 0.000000 0.000000 1.805367 +v 15.450850 -47.552826 5.000000 +vn 0.000000 0.000000 0.961197 +v -25.000000 43.301270 5.000000 +vn 0.000000 0.000000 0.239254 +v 10.218524 22.079117 5.000000 +vn 0.000000 0.000000 1.941141 +v -15.450850 47.552826 5.000000 +vn 0.000000 0.000000 0.161147 +v 15.000000 11.339746 5.000000 +vn 0.000000 0.000000 1.250946 +v -5.226422 -49.726093 5.000000 +vn 0.000000 0.000000 1.729500 +v 5.226422 -49.726093 5.000000 +vn 0.000000 0.000000 2.008394 +v 10.218524 22.079117 5.000000 +vn 0.000000 0.000000 0.045050 +v -25.000000 43.301270 5.000000 +vn 0.000000 0.000000 1.088148 +v 10.000000 20.000000 5.000000 +vn 0.000000 0.000000 0.157350 +v 15.000000 11.339746 5.000000 +vn 0.000000 0.000000 1.303035 +v -15.450850 -47.552826 5.000000 +vn 0.000000 0.000000 1.681207 +v -5.226422 -49.726093 5.000000 +vn 0.000000 0.000000 1.004346 +v -33.456528 37.157242 5.000000 +vn 0.000000 0.000000 0.211341 +v 10.000000 20.000000 5.000000 +vn 0.000000 0.000000 1.925906 +v -25.000000 43.301270 5.000000 +vn 0.000000 0.000000 0.154864 +v 13.308694 12.568551 5.000000 +vn 0.000000 0.000000 1.388627 +v -25.000000 -43.301270 5.000000 +vn 0.000000 0.000000 1.598102 +v -15.450850 -47.552826 5.000000 +vn 0.000000 0.000000 0.192291 +v 10.218524 17.920883 5.000000 +vn 0.000000 0.000000 1.888957 +v -33.456528 37.157242 5.000000 +vn 0.000000 0.000000 1.060345 +v -40.450851 29.389263 5.000000 +vn 0.000000 0.000000 0.153677 +v 13.308694 12.568551 5.000000 +vn 0.000000 0.000000 1.444390 +v -33.456528 -37.157242 5.000000 +vn 0.000000 0.000000 1.543527 +v -25.000000 -43.301270 5.000000 +vn 0.000000 0.000000 0.179392 +v 10.218524 17.920883 5.000000 +vn 0.000000 0.000000 1.871808 +v -40.450851 29.389263 5.000000 +vn 0.000000 0.000000 1.090393 +v -45.677273 20.336832 5.000000 +vn 0.000000 0.000000 0.153352 +v 13.308694 12.568551 5.000000 +vn 0.000000 0.000000 1.500477 +v -40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 1.487764 +v -33.456528 -37.157242 5.000000 +vn 0.000000 0.000000 0.170109 +v 10.864545 15.932633 5.000000 +vn 0.000000 0.000000 1.807220 +v -45.677273 20.336832 5.000000 +vn 0.000000 0.000000 1.164264 +v -48.907379 10.395584 5.000000 +vn 0.000000 0.000000 0.154128 +v 11.909829 14.122147 5.000000 +vn 0.000000 0.000000 1.586425 +v -45.677273 -20.336832 5.000000 +vn 0.000000 0.000000 1.401039 +v -40.450851 -29.389263 5.000000 +vn 0.000000 0.000000 0.163654 +v 10.864545 15.932633 5.000000 +vn 0.000000 0.000000 1.767889 +v -48.907379 10.395584 5.000000 +vn 0.000000 0.000000 1.210049 +v -50.000000 0.000000 5.000000 +vn 0.000000 0.000000 0.156018 +v 11.909829 14.122147 5.000000 +vn 0.000000 0.000000 1.639846 +v -48.907379 -10.395584 5.000000 +vn 0.000000 0.000000 1.345728 +v -45.677273 -20.336832 5.000000 +vn 0.000000 0.000000 0.158937 +v 11.909829 14.122147 5.000000 +vn 0.000000 0.000000 1.690347 +v -50.000000 0.000000 5.000000 +vn 0.000000 0.000000 1.292307 +v -48.907379 -10.395584 5.000000 +vn 0.725904 -0.235860 0.000000 +v 45.677273 -20.336832 5.000000 +vn 0.768012 -0.249542 0.000000 +v 48.907379 -10.395584 -5.000000 +vn 1.493916 -0.485403 0.000000 +v 48.907379 -10.395584 5.000000 +vn 0.725904 -0.235860 0.000000 +v 48.907379 -10.395584 -5.000000 +vn 0.768012 -0.249542 0.000000 +v 45.677273 -20.336832 5.000000 +vn 1.493916 -0.485403 0.000000 +v 45.677273 -20.336832 -5.000000 +vn 0.000000 0.807535 0.000000 +v 5.226422 49.726093 -5.000000 +vn 0.000000 0.763261 0.000000 +v -5.226422 49.726093 5.000000 +vn 0.000000 1.570796 0.000000 +v 5.226422 49.726093 5.000000 +vn 0.000000 0.807535 0.000000 +v -5.226422 49.726093 5.000000 +vn 0.000000 0.763261 0.000000 +v 5.226422 49.726093 -5.000000 +vn 0.000000 1.570796 0.000000 +v -5.226422 49.726093 -5.000000 +vn -0.474657 -0.653310 0.000000 +v -33.456528 -37.157242 -5.000000 +vn -0.448633 -0.617491 0.000000 +v -25.000000 -43.301270 5.000000 +vn -0.923291 -1.270801 0.000000 +v -33.456528 -37.157242 5.000000 +vn -0.474657 -0.653310 0.000000 +v -25.000000 -43.301270 5.000000 +vn -0.448633 -0.617491 0.000000 +v -33.456528 -37.157242 -5.000000 +vn -0.923291 -1.270801 0.000000 +v -25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -0.312011 +v 25.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -1.891912 +v 50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -0.937670 +v 48.907379 -10.395584 -5.000000 +vn 0.000000 0.000000 -0.258266 +v 23.090170 10.489434 -5.000000 +vn 0.000000 0.000000 -1.936816 +v 48.907379 -10.395584 -5.000000 +vn 0.000000 0.000000 -0.946511 +v 45.677273 -20.336832 -5.000000 +vn 0.000000 0.000000 -0.068696 +v 50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -1.054154 +v 25.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -2.018744 +v 26.691305 12.568551 -5.000000 +vn 0.000000 0.000000 -0.223894 +v 21.045284 10.054781 -5.000000 +vn 0.000000 0.000000 -1.936892 +v 45.677273 -20.336832 -5.000000 +vn 0.000000 0.000000 -0.980807 +v 40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -0.079637 +v 48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -0.935258 +v 26.691305 12.568551 -5.000000 +vn 0.000000 0.000000 -2.126698 +v 28.090170 14.122147 -5.000000 +vn 0.000000 0.000000 -0.200963 +v 18.954716 10.054781 -5.000000 +vn 0.000000 0.000000 -1.909575 +v 40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -1.031054 +v 33.456528 -37.157242 -5.000000 +vn 0.000000 0.000000 -2.035788 +v 29.781475 17.920883 -5.000000 +vn 0.000000 0.000000 -0.109378 +v 45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -0.996427 +v 29.135454 15.932633 -5.000000 +vn 0.000000 0.000000 -1.068680 +v 45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -0.710460 +v 30.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -1.362453 +v 40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -2.066767 +v 29.781475 22.079117 -5.000000 +vn 0.000000 0.000000 -0.131250 +v 40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -0.943575 +v 30.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -1.696999 +v 30.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -0.129351 +v 45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -1.315244 +v 29.781475 17.920883 -5.000000 +vn 0.000000 0.000000 -0.983586 +v 48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -0.533261 +v 29.135454 15.932633 -5.000000 +vn 0.000000 0.000000 -1.624746 +v 45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -1.821343 +v 29.135454 15.932633 -5.000000 +vn 0.000000 0.000000 -0.095914 +v 48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -1.224335 +v 28.090170 14.122147 -5.000000 +vn 0.000000 0.000000 -0.185196 +v 18.954716 10.054781 -5.000000 +vn 0.000000 0.000000 -1.901099 +v 33.456528 -37.157242 -5.000000 +vn 0.000000 0.000000 -1.055297 +v 25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -0.971546 +v 50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -0.397030 +v 26.691305 12.568551 -5.000000 +vn 0.000000 0.000000 -1.773017 +v 48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -0.057667 +v 48.907379 -10.395584 -5.000000 +vn 0.000000 0.000000 -1.099058 +v 23.090170 10.489434 -5.000000 +vn 0.000000 0.000000 -1.984868 +v 25.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -0.048750 +v 45.677273 -20.336832 -5.000000 +vn 0.000000 0.000000 -1.099133 +v 21.045284 10.054781 -5.000000 +vn 0.000000 0.000000 -1.993709 +v 23.090170 10.489434 -5.000000 +vn 0.000000 0.000000 -0.041770 +v 40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -1.071817 +v 18.954716 10.054781 -5.000000 +vn 0.000000 0.000000 -2.028005 +v 21.045284 10.054781 -5.000000 +vn 0.000000 0.000000 -0.036463 +v 25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -1.212075 +v 16.909828 10.489434 -5.000000 +vn 0.000000 0.000000 -1.893055 +v 18.954716 10.054781 -5.000000 +vn 0.000000 0.000000 -1.126786 +v 15.450850 -47.552826 -5.000000 +vn 0.000000 0.000000 -0.174414 +v 16.909828 10.489434 -5.000000 +vn 0.000000 0.000000 -1.840393 +v 25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -1.169712 +v 5.226422 -49.726093 -5.000000 +vn 0.000000 0.000000 -0.166514 +v 16.909828 10.489434 -5.000000 +vn 0.000000 0.000000 -1.805367 +v 15.450850 -47.552826 -5.000000 +vn 0.000000 0.000000 -1.798030 +v 16.909828 10.489434 -5.000000 +vn 0.000000 0.000000 -0.032941 +v 5.226422 -49.726093 -5.000000 +vn 0.000000 0.000000 -1.310621 +v 15.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -1.250946 +v -5.226422 -49.726093 -5.000000 +vn 0.000000 0.000000 -0.161147 +v 15.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -1.729500 +v 5.226422 -49.726093 -5.000000 +vn 0.000000 0.000000 -1.303035 +v -15.450850 -47.552826 -5.000000 +vn 0.000000 0.000000 -0.157350 +v 15.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -1.681207 +v -5.226422 -49.726093 -5.000000 +vn 0.000000 0.000000 -1.721914 +v 15.000000 11.339746 -5.000000 +vn 0.000000 0.000000 -0.031018 +v -15.450850 -47.552826 -5.000000 +vn 0.000000 0.000000 -1.388663 +v 13.308694 12.568551 -5.000000 +vn 0.000000 0.000000 -1.388627 +v -25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -0.154864 +v 13.308694 12.568551 -5.000000 +vn 0.000000 0.000000 -1.598102 +v -15.450850 -47.552826 -5.000000 +vn 0.000000 0.000000 -1.444390 +v -33.456528 -37.157242 -5.000000 +vn 0.000000 0.000000 -0.153677 +v 13.308694 12.568551 -5.000000 +vn 0.000000 0.000000 -1.543527 +v -25.000000 -43.301270 -5.000000 +vn 0.000000 0.000000 -1.696263 +v 29.135454 24.067366 -5.000000 +vn 0.000000 0.000000 -0.161065 +v 40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -1.284264 +v 29.781475 22.079117 -5.000000 +vn 0.000000 0.000000 -1.323985 +v 28.090170 25.877853 -5.000000 +vn 0.000000 0.000000 -0.162839 +v 40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -1.654769 +v 29.135454 24.067366 -5.000000 +vn 0.000000 0.000000 -1.114545 +v 40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -0.849935 +v 28.090170 25.877853 -5.000000 +vn 0.000000 0.000000 -1.177112 +v 33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -1.800790 +v 26.691305 27.431448 -5.000000 +vn 0.000000 0.000000 -0.163690 +v 33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -1.177113 +v 28.090170 25.877853 -5.000000 +vn 0.000000 0.000000 -1.416103 +v 25.000000 28.660254 -5.000000 +vn 0.000000 0.000000 -0.175248 +v 33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -1.550242 +v 26.691305 27.431448 -5.000000 +vn 0.000000 0.000000 -1.054425 +v 23.090170 29.510565 -5.000000 +vn 0.000000 0.000000 -0.152239 +v 33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -1.934929 +v 25.000000 28.660254 -5.000000 +vn 0.000000 0.000000 -1.263865 +v 33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -0.797639 +v 23.090170 29.510565 -5.000000 +vn 0.000000 0.000000 -1.080089 +v 25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.492361 +v 21.045284 29.945217 -5.000000 +vn 0.000000 0.000000 -0.150263 +v 25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.498969 +v 23.090170 29.510565 -5.000000 +vn 0.000000 0.000000 -1.145761 +v 18.954716 29.945217 -5.000000 +vn 0.000000 0.000000 -0.137161 +v 25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.858670 +v 21.045284 29.945217 -5.000000 +vn 0.000000 0.000000 -0.115742 +v 15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.583804 +v 18.954716 29.945217 -5.000000 +vn 0.000000 0.000000 -1.442047 +v 16.909828 29.510565 -5.000000 +vn 0.000000 0.000000 -0.621466 +v 18.954716 29.945217 -5.000000 +vn 0.000000 0.000000 -0.955486 +v 15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.564641 +v 25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.128057 +v 15.000000 28.660254 -5.000000 +vn 0.000000 0.000000 -0.104548 +v 15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.908987 +v 16.909828 29.510565 -5.000000 +vn 0.000000 0.000000 -0.086613 +v 5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.764718 +v 15.000000 28.660254 -5.000000 +vn 0.000000 0.000000 -1.290263 +v 13.308694 27.431448 -5.000000 +vn 0.000000 0.000000 -0.074549 +v 5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -2.060768 +v 13.308694 27.431448 -5.000000 +vn 0.000000 0.000000 -1.006277 +v 11.909829 25.877853 -5.000000 +vn 0.000000 0.000000 -0.062953 +v -5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.994924 +v 11.909829 25.877853 -5.000000 +vn 0.000000 0.000000 -1.083717 +v 10.864545 24.067366 -5.000000 +vn 0.000000 0.000000 -0.052938 +v -15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.985271 +v 10.864545 24.067366 -5.000000 +vn 0.000000 0.000000 -1.103383 +v 10.218524 22.079117 -5.000000 +vn 0.000000 0.000000 -0.458257 +v 15.000000 28.660254 -5.000000 +vn 0.000000 0.000000 -0.926960 +v 5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.756376 +v 15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -0.045050 +v -25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -2.008394 +v 10.218524 22.079117 -5.000000 +vn 0.000000 0.000000 -1.088148 +v 10.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -1.500477 +v -40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -0.153352 +v 13.308694 12.568551 -5.000000 +vn 0.000000 0.000000 -1.487764 +v -33.456528 -37.157242 -5.000000 +vn 0.000000 0.000000 -1.500477 +v 13.308694 12.568551 -5.000000 +vn 0.000000 0.000000 -0.030639 +v -40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -1.610479 +v 11.909829 14.122147 -5.000000 +vn 0.000000 0.000000 -0.349832 +v 11.909829 25.877853 -5.000000 +vn 0.000000 0.000000 -0.947726 +v -5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.844034 +v 5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.586425 +v -45.677273 -20.336832 -5.000000 +vn 0.000000 0.000000 -0.154128 +v 11.909829 14.122147 -5.000000 +vn 0.000000 0.000000 -1.401039 +v -40.450851 -29.389263 -5.000000 +vn 0.000000 0.000000 -0.282044 +v 10.864545 24.067366 -5.000000 +vn 0.000000 0.000000 -0.938074 +v -15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.921476 +v -5.226422 49.726093 -5.000000 +vn 0.000000 0.000000 -1.639846 +v -48.907379 -10.395584 -5.000000 +vn 0.000000 0.000000 -0.156018 +v 11.909829 14.122147 -5.000000 +vn 0.000000 0.000000 -1.345728 +v -45.677273 -20.336832 -5.000000 +vn 0.000000 0.000000 -0.239254 +v 10.218524 22.079117 -5.000000 +vn 0.000000 0.000000 -0.961197 +v -25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.941141 +v -15.450850 47.552826 -5.000000 +vn 0.000000 0.000000 -1.690347 +v -50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -0.158937 +v 11.909829 14.122147 -5.000000 +vn 0.000000 0.000000 -1.292307 +v -48.907379 -10.395584 -5.000000 +vn 0.000000 0.000000 -0.211341 +v 10.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -1.004346 +v -33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -1.925906 +v -25.000000 43.301270 -5.000000 +vn 0.000000 0.000000 -1.271469 +v 11.909829 14.122147 -5.000000 +vn 0.000000 0.000000 -0.031757 +v -50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -1.838368 +v 10.864545 15.932633 -5.000000 +vn 0.000000 0.000000 -1.051199 +v 10.218524 17.920883 -5.000000 +vn 0.000000 0.000000 -0.038851 +v -33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -2.051544 +v 10.000000 20.000000 -5.000000 +vn 0.000000 0.000000 -1.767889 +v -48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -0.163654 +v 10.864545 15.932633 -5.000000 +vn 0.000000 0.000000 -1.210049 +v -50.000000 0.000000 -5.000000 +vn 0.000000 0.000000 -1.888957 +v -33.456528 37.157242 -5.000000 +vn 0.000000 0.000000 -0.192291 +v 10.218524 17.920883 -5.000000 +vn 0.000000 0.000000 -1.060345 +v -40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -1.807220 +v -45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -0.170109 +v 10.864545 15.932633 -5.000000 +vn 0.000000 0.000000 -1.164264 +v -48.907379 10.395584 -5.000000 +vn 0.000000 0.000000 -1.871808 +v -40.450851 29.389263 -5.000000 +vn 0.000000 0.000000 -0.179392 +v 10.218524 17.920883 -5.000000 +vn 0.000000 0.000000 -1.090393 +v -45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -1.178902 +v 10.864545 15.932633 -5.000000 +vn 0.000000 0.000000 -0.034542 +v -45.677273 20.336832 -5.000000 +vn 0.000000 0.000000 -1.928151 +v 10.218524 17.920883 -5.000000 +vn -0.328454 0.737720 0.000000 +v -15.450850 47.552826 -5.000000 +vn -0.310446 0.697273 0.000000 +v -25.000000 43.301270 5.000000 +vn -0.638901 1.434994 0.000000 +v -15.450850 47.552826 5.000000 +vn -0.328454 0.737720 0.000000 +v -25.000000 43.301270 5.000000 +vn -0.310446 0.697273 0.000000 +v -15.450850 47.552826 -5.000000 +vn -0.638901 1.434994 0.000000 +v -25.000000 43.301270 -5.000000 +vn -0.759080 -0.079783 0.000000 +v -48.907379 -10.395584 -5.000000 +vn -0.803112 -0.084411 0.000000 +v -50.000000 0.000000 5.000000 +vn -1.562191 -0.164193 0.000000 +v -50.000000 0.000000 -5.000000 +vn -0.759080 -0.079783 0.000000 +v -50.000000 0.000000 5.000000 +vn -0.803112 -0.084411 0.000000 +v -48.907379 -10.395584 -5.000000 +vn -1.562191 -0.164193 0.000000 +v -48.907379 -10.395584 5.000000 +vn -0.567213 -0.510721 0.000000 +v -33.456528 -37.157242 -5.000000 +vn -0.600116 -0.540347 0.000000 +v -40.450851 -29.389263 5.000000 +vn -1.167329 -1.051068 0.000000 +v -40.450851 -29.389263 -5.000000 +vn -0.567213 -0.510721 0.000000 +v -40.450851 -29.389263 5.000000 +vn -0.600116 -0.540347 0.000000 +v -33.456528 -37.157242 -5.000000 +vn -1.167329 -1.051068 0.000000 +v -33.456528 -37.157242 5.000000 +vn -0.661003 -0.381630 0.000000 +v -40.450851 -29.389263 -5.000000 +vn -0.699346 -0.403768 0.000000 +v -45.677273 -20.336832 5.000000 +vn -1.360350 -0.785398 0.000000 +v -45.677273 -20.336832 -5.000000 +vn -0.661003 -0.381630 0.000000 +v -45.677273 -20.336832 5.000000 +vn -0.699346 -0.403768 0.000000 +v -40.450851 -29.389263 -5.000000 +vn -1.360350 -0.785398 0.000000 +v -40.450851 -29.389263 5.000000 +vn 0.167896 -0.789889 0.000000 +v 5.226422 -49.726093 -5.000000 +vn 0.158691 -0.746582 0.000000 +v 15.450850 -47.552826 5.000000 +vn 0.326587 -1.536471 0.000000 +v 5.226422 -49.726093 5.000000 +vn 0.167896 -0.789889 0.000000 +v 15.450850 -47.552826 5.000000 +vn 0.158691 -0.746582 0.000000 +v 5.226422 -49.726093 -5.000000 +vn 0.326587 -1.536471 0.000000 +v 15.450850 -47.552826 -5.000000 +vn -0.759080 0.079783 0.000000 +v -50.000000 0.000000 -5.000000 +vn -0.803112 0.084411 0.000000 +v -48.907379 10.395584 5.000000 +vn -1.562191 0.164193 0.000000 +v -48.907379 10.395584 -5.000000 +vn -0.759080 0.079783 0.000000 +v -48.907379 10.395584 5.000000 +vn -0.803112 0.084411 0.000000 +v -50.000000 0.000000 -5.000000 +vn -1.562191 0.164193 0.000000 +v -50.000000 0.000000 5.000000 +vn 0.167896 0.789889 0.000000 +v 15.450850 47.552826 -5.000000 +vn 0.158691 0.746582 0.000000 +v 5.226422 49.726093 5.000000 +vn 0.326587 1.536471 0.000000 +v 15.450850 47.552826 5.000000 +vn 0.167896 0.789889 0.000000 +v 5.226422 49.726093 5.000000 +vn 0.158691 0.746582 0.000000 +v 15.450850 47.552826 -5.000000 +vn 0.326587 1.536471 0.000000 +v 5.226422 49.726093 -5.000000 +vn 0.474657 0.653310 0.000000 +v 33.456528 37.157242 -5.000000 +vn 0.448633 0.617491 0.000000 +v 25.000000 43.301270 5.000000 +vn 0.923291 1.270801 0.000000 +v 33.456528 37.157242 5.000000 +vn 0.474657 0.653310 0.000000 +v 25.000000 43.301270 5.000000 +vn 0.448633 0.617491 0.000000 +v 33.456528 37.157242 -5.000000 +vn 0.923291 1.270801 0.000000 +v 25.000000 43.301270 -5.000000 +vn -0.167896 0.789889 0.000000 +v -5.226422 49.726093 -5.000000 +vn -0.158691 0.746582 0.000000 +v -15.450850 47.552826 5.000000 +vn -0.326587 1.536471 0.000000 +v -5.226422 49.726093 5.000000 +vn -0.167896 0.789889 0.000000 +v -15.450850 47.552826 5.000000 +vn -0.158691 0.746582 0.000000 +v -5.226422 49.726093 -5.000000 +vn -0.326587 1.536471 0.000000 +v -15.450850 47.552826 -5.000000 +vn 0.328454 -0.737720 0.000000 +v 15.450850 -47.552826 -5.000000 +vn 0.310446 -0.697273 0.000000 +v 25.000000 -43.301270 5.000000 +vn 0.638901 -1.434994 0.000000 +v 15.450850 -47.552826 5.000000 +vn 0.328454 -0.737720 0.000000 +v 25.000000 -43.301270 5.000000 +vn 0.310446 -0.697273 0.000000 +v 15.450850 -47.552826 -5.000000 +vn 0.638901 -1.434994 0.000000 +v 25.000000 -43.301270 -5.000000 +vn 0.000000 -0.807535 0.000000 +v -5.226422 -49.726093 -5.000000 +vn 0.000000 -0.763261 0.000000 +v 5.226422 -49.726093 5.000000 +vn 0.000000 -1.570796 0.000000 +v -5.226422 -49.726093 5.000000 +vn 0.000000 -0.807535 0.000000 +v 5.226422 -49.726093 5.000000 +vn 0.000000 -0.763261 0.000000 +v -5.226422 -49.726093 -5.000000 +vn 0.000000 -1.570796 0.000000 +v 5.226422 -49.726093 -5.000000 +vn -0.167896 -0.789889 0.000000 +v -15.450850 -47.552826 -5.000000 +vn -0.158691 -0.746582 0.000000 +v -5.226422 -49.726093 5.000000 +vn -0.326587 -1.536471 0.000000 +v -15.450850 -47.552826 5.000000 +vn -0.167896 -0.789889 0.000000 +v -5.226422 -49.726093 5.000000 +vn -0.158691 -0.746582 0.000000 +v -15.450850 -47.552826 -5.000000 +vn -0.326587 -1.536471 0.000000 +v -5.226422 -49.726093 -5.000000 +vn -0.725904 0.235860 0.000000 +v -48.907379 10.395584 -5.000000 +vn -0.768012 0.249542 0.000000 +v -45.677273 20.336832 5.000000 +vn -1.493916 0.485403 0.000000 +v -45.677273 20.336832 -5.000000 +vn -0.725904 0.235860 0.000000 +v -45.677273 20.336832 5.000000 +vn -0.768012 0.249542 0.000000 +v -48.907379 10.395584 -5.000000 +vn -1.493916 0.485403 0.000000 +v -48.907379 10.395584 5.000000 +vn -1.297914 -0.421718 0.000000 +v 29.781475 22.079117 -5.000000 +vn -0.196002 -0.063685 0.000000 +v 29.135454 24.067366 5.000000 +vn -1.493916 -0.485403 0.000000 +v 29.135454 24.067366 -5.000000 +vn -1.297914 -0.421718 0.000000 +v 29.135454 24.067366 5.000000 +vn -0.196002 -0.063685 0.000000 +v 29.781475 22.079117 -5.000000 +vn -1.493916 -0.485403 0.000000 +v 29.781475 22.079117 5.000000 +vn -1.357231 -0.142651 0.000000 +v 30.000000 20.000000 -5.000000 +vn -0.204960 -0.021542 0.000000 +v 29.781475 22.079117 5.000000 +vn -1.562191 -0.164194 0.000000 +v 29.781475 22.079117 -5.000000 +vn -1.357231 -0.142651 0.000000 +v 29.781475 22.079117 5.000000 +vn -0.204960 -0.021542 0.000000 +v 30.000000 20.000000 -5.000000 +vn -1.562191 -0.164194 0.000000 +v 30.000000 20.000000 5.000000 +vn -1.181872 -0.682353 0.000000 +v 29.135454 24.067366 -5.000000 +vn -0.178478 -0.103044 0.000000 +v 28.090170 25.877853 5.000000 +vn -1.360350 -0.785397 0.000000 +v 28.090170 25.877853 -5.000000 +vn -1.181872 -0.682353 0.000000 +v 28.090170 25.877853 5.000000 +vn -0.178478 -0.103044 0.000000 +v 29.135454 24.067366 -5.000000 +vn -1.360350 -0.785397 0.000000 +v 29.135454 24.067366 5.000000 +vn 0.000000 0.206089 0.000000 +v 21.045284 10.054781 -5.000000 +vn 0.000000 1.364708 0.000000 +v 18.954716 10.054781 5.000000 +vn 0.000000 1.570796 0.000000 +v 21.045284 10.054781 5.000000 +vn 0.000000 0.206089 0.000000 +v 18.954716 10.054781 5.000000 +vn 0.000000 1.364708 0.000000 +v 21.045284 10.054781 -5.000000 +vn 0.000000 1.570796 0.000000 +v 18.954716 10.054781 -5.000000 +vn -0.083824 0.188272 0.000000 +v 25.000000 11.339746 -5.000000 +vn -0.555077 1.246722 0.000000 +v 23.090170 10.489434 5.000000 +vn -0.638901 1.434994 0.000000 +v 25.000000 11.339746 5.000000 +vn -0.083824 0.188272 0.000000 +v 23.090170 10.489434 5.000000 +vn -0.555077 1.246722 0.000000 +v 25.000000 11.339746 -5.000000 +vn -0.638901 1.434994 0.000000 +v 23.090170 10.489434 -5.000000 +vn 1.297914 0.421717 0.000000 +v 10.864545 15.932633 5.000000 +vn 0.196002 0.063685 0.000000 +v 10.218524 17.920883 -5.000000 +vn 1.493916 0.485402 0.000000 +v 10.218524 17.920883 5.000000 +vn 1.297914 0.421717 0.000000 +v 10.218524 17.920883 -5.000000 +vn 0.196002 0.063685 0.000000 +v 10.864545 15.932633 5.000000 +vn 1.493916 0.485402 0.000000 +v 10.864545 15.932633 -5.000000 +vn -1.357231 0.142651 0.000000 +v 29.781475 17.920883 -5.000000 +vn -0.204960 0.021542 0.000000 +v 30.000000 20.000000 5.000000 +vn -1.562191 0.164194 0.000000 +v 30.000000 20.000000 -5.000000 +vn -1.357231 0.142651 0.000000 +v 30.000000 20.000000 5.000000 +vn -0.204960 0.021542 0.000000 +v 29.781475 17.920883 -5.000000 +vn -1.562191 0.164194 0.000000 +v 29.781475 17.920883 5.000000 +vn 1.014175 -0.913168 0.000000 +v 11.909829 25.877853 5.000000 +vn 0.153154 -0.137900 0.000000 +v 13.308694 27.431448 -5.000000 +vn 1.167328 -1.051069 0.000000 +v 13.308694 27.431448 5.000000 +vn 1.014175 -0.913168 0.000000 +v 13.308694 27.431448 -5.000000 +vn 0.153154 -0.137900 0.000000 +v 11.909829 25.877853 5.000000 +vn 1.167328 -1.051069 0.000000 +v 11.909829 25.877853 -5.000000 +vn 0.042848 0.201585 0.000000 +v 18.954716 10.054781 -5.000000 +vn 0.283738 1.334885 0.000000 +v 16.909828 10.489434 5.000000 +vn 0.326586 1.536471 0.000000 +v 18.954716 10.054781 5.000000 +vn 0.042848 0.201585 0.000000 +v 16.909828 10.489434 5.000000 +vn 0.283738 1.334885 0.000000 +v 18.954716 10.054781 -5.000000 +vn 0.326586 1.536471 0.000000 +v 16.909828 10.489434 -5.000000 +vn -1.297914 0.421717 0.000000 +v 29.135454 15.932633 -5.000000 +vn -0.196002 0.063685 0.000000 +v 29.781475 17.920883 5.000000 +vn -1.493916 0.485402 0.000000 +v 29.781475 17.920883 -5.000000 +vn -1.297914 0.421717 0.000000 +v 29.781475 17.920883 5.000000 +vn -0.196002 0.063685 0.000000 +v 29.135454 15.932633 -5.000000 +vn -1.493916 0.485402 0.000000 +v 29.135454 15.932633 5.000000 +vn 0.083824 0.188271 0.000000 +v 16.909828 10.489434 -5.000000 +vn 0.555077 1.246722 0.000000 +v 15.000000 11.339746 5.000000 +vn 0.638901 1.434994 0.000000 +v 16.909828 10.489434 5.000000 +vn 0.083824 0.188271 0.000000 +v 15.000000 11.339746 5.000000 +vn 0.555077 1.246722 0.000000 +v 16.909828 10.489434 -5.000000 +vn 0.638901 1.434994 0.000000 +v 15.000000 11.339746 -5.000000 +vn -0.042848 -0.201585 0.000000 +v 21.045284 29.945217 -5.000000 +vn -0.283738 -1.334886 0.000000 +v 23.090170 29.510565 5.000000 +vn -0.326586 -1.536471 0.000000 +v 21.045284 29.945217 5.000000 +vn -0.042848 -0.201585 0.000000 +v 23.090170 29.510565 5.000000 +vn -0.283738 -1.334886 0.000000 +v 21.045284 29.945217 -5.000000 +vn -0.326586 -1.536471 0.000000 +v 23.090170 29.510565 -5.000000 +vn 0.000000 -0.206089 0.000000 +v 18.954716 29.945217 -5.000000 +vn 0.000000 -1.364708 0.000000 +v 21.045284 29.945217 5.000000 +vn 0.000000 -1.570796 0.000000 +v 18.954716 29.945217 5.000000 +vn 0.000000 -0.206089 0.000000 +v 21.045284 29.945217 5.000000 +vn 0.000000 -1.364708 0.000000 +v 18.954716 29.945217 -5.000000 +vn 0.000000 -1.570796 0.000000 +v 21.045284 29.945217 -5.000000 +vn -1.181872 0.682353 0.000000 +v 28.090170 14.122147 -5.000000 +vn -0.178478 0.103044 0.000000 +v 29.135454 15.932633 5.000000 +vn -1.360350 0.785398 0.000000 +v 29.135454 15.932633 -5.000000 +vn -1.181872 0.682353 0.000000 +v 29.135454 15.932633 5.000000 +vn -0.178478 0.103044 0.000000 +v 28.090170 14.122147 -5.000000 +vn -1.360350 0.785398 0.000000 +v 28.090170 14.122147 5.000000 +vn 1.297914 -0.421718 0.000000 +v 10.218524 22.079117 5.000000 +vn 0.196002 -0.063685 0.000000 +v 10.864545 24.067366 -5.000000 +vn 1.493916 -0.485403 0.000000 +v 10.864545 24.067366 5.000000 +vn 1.297914 -0.421718 0.000000 +v 10.864545 24.067366 -5.000000 +vn 0.196002 -0.063685 0.000000 +v 10.218524 22.079117 5.000000 +vn 1.493916 -0.485403 0.000000 +v 10.218524 22.079117 -5.000000 +vn 1.014175 0.913168 0.000000 +v 13.308694 12.568551 5.000000 +vn 0.153154 0.137900 0.000000 +v 11.909829 14.122147 -5.000000 +vn 1.167329 1.051068 0.000000 +v 11.909829 14.122147 5.000000 +vn 1.014175 0.913168 0.000000 +v 11.909829 14.122147 -5.000000 +vn 0.153154 0.137900 0.000000 +v 13.308694 12.568551 5.000000 +vn 1.167329 1.051068 0.000000 +v 13.308694 12.568551 -5.000000 +vn -1.014175 -0.913168 0.000000 +v 28.090170 25.877853 -5.000000 +vn -0.153154 -0.137900 0.000000 +v 26.691305 27.431448 5.000000 +vn -1.167328 -1.051069 0.000000 +v 26.691305 27.431448 -5.000000 +vn -1.014175 -0.913168 0.000000 +v 26.691305 27.431448 5.000000 +vn -0.153154 -0.137900 0.000000 +v 28.090170 25.877853 -5.000000 +vn -1.167328 -1.051069 0.000000 +v 28.090170 25.877853 5.000000 +vn 1.357232 -0.142651 0.000000 +v 10.000000 20.000000 5.000000 +vn 0.204960 -0.021542 0.000000 +v 10.218524 22.079117 -5.000000 +vn 1.562191 -0.164193 0.000000 +v 10.218524 22.079117 5.000000 +vn 1.357232 -0.142651 0.000000 +v 10.218524 22.079117 -5.000000 +vn 0.204960 -0.021542 0.000000 +v 10.000000 20.000000 5.000000 +vn 1.562191 -0.164193 0.000000 +v 10.000000 20.000000 -5.000000 +vn 0.042848 -0.201585 0.000000 +v 16.909828 29.510565 -5.000000 +vn 0.283737 -1.334885 0.000000 +v 18.954716 29.945217 5.000000 +vn 0.326586 -1.536471 0.000000 +v 16.909828 29.510565 5.000000 +vn 0.042848 -0.201585 0.000000 +v 18.954716 29.945217 5.000000 +vn 0.283737 -1.334885 0.000000 +v 16.909828 29.510565 -5.000000 +vn 0.326586 -1.536471 0.000000 +v 18.954716 29.945217 -5.000000 +vn -0.121136 -0.166729 0.000000 +v 25.000000 28.660254 -5.000000 +vn -0.802155 -1.104072 0.000000 +v 26.691305 27.431448 5.000000 +vn -0.923291 -1.270801 0.000000 +v 25.000000 28.660254 5.000000 +vn -0.121136 -0.166729 0.000000 +v 26.691305 27.431448 5.000000 +vn -0.802155 -1.104072 0.000000 +v 25.000000 28.660254 -5.000000 +vn -0.923291 -1.270801 0.000000 +v 26.691305 27.431448 -5.000000 +vn 0.083824 -0.188271 0.000000 +v 15.000000 28.660254 -5.000000 +vn 0.555077 -1.246722 0.000000 +v 16.909828 29.510565 5.000000 +vn 0.638901 -1.434994 0.000000 +v 15.000000 28.660254 5.000000 +vn 0.083824 -0.188271 0.000000 +v 16.909828 29.510565 5.000000 +vn 0.555077 -1.246722 0.000000 +v 15.000000 28.660254 -5.000000 +vn 0.638901 -1.434994 0.000000 +v 16.909828 29.510565 -5.000000 +vn -1.014175 0.913168 0.000000 +v 26.691305 12.568551 -5.000000 +vn -0.153154 0.137900 0.000000 +v 28.090170 14.122147 5.000000 +vn -1.167329 1.051068 0.000000 +v 28.090170 14.122147 -5.000000 +vn -1.014175 0.913168 0.000000 +v 28.090170 14.122147 5.000000 +vn -0.153154 0.137900 0.000000 +v 26.691305 12.568551 -5.000000 +vn -1.167329 1.051068 0.000000 +v 26.691305 12.568551 5.000000 +vn -0.083824 -0.188272 0.000000 +v 23.090170 29.510565 -5.000000 +vn -0.555077 -1.246722 0.000000 +v 25.000000 28.660254 5.000000 +vn -0.638901 -1.434994 0.000000 +v 23.090170 29.510565 5.000000 +vn -0.083824 -0.188272 0.000000 +v 25.000000 28.660254 5.000000 +vn -0.555077 -1.246722 0.000000 +v 23.090170 29.510565 -5.000000 +vn -0.638901 -1.434994 0.000000 +v 25.000000 28.660254 -5.000000 +vn 0.121136 0.166729 0.000000 +v 15.000000 11.339746 -5.000000 +vn 0.802155 1.104072 0.000000 +v 13.308694 12.568551 5.000000 +vn 0.923291 1.270801 0.000000 +v 15.000000 11.339746 5.000000 +vn 0.121136 0.166729 0.000000 +v 13.308694 12.568551 5.000000 +vn 0.802155 1.104072 0.000000 +v 15.000000 11.339746 -5.000000 +vn 0.923291 1.270801 0.000000 +v 13.308694 12.568551 -5.000000 +vn -0.121136 0.166729 0.000000 +v 26.691305 12.568551 -5.000000 +vn -0.802155 1.104072 0.000000 +v 25.000000 11.339746 5.000000 +vn -0.923291 1.270801 0.000000 +v 26.691305 12.568551 5.000000 +vn -0.121136 0.166729 0.000000 +v 25.000000 11.339746 5.000000 +vn -0.802155 1.104072 0.000000 +v 26.691305 12.568551 -5.000000 +vn -0.923291 1.270801 0.000000 +v 25.000000 11.339746 -5.000000 +vn -0.042848 0.201585 0.000000 +v 23.090170 10.489434 -5.000000 +vn -0.283738 1.334885 0.000000 +v 21.045284 10.054781 5.000000 +vn -0.326587 1.536471 0.000000 +v 23.090170 10.489434 5.000000 +vn -0.042848 0.201585 0.000000 +v 21.045284 10.054781 5.000000 +vn -0.283738 1.334885 0.000000 +v 23.090170 10.489434 -5.000000 +vn -0.326587 1.536471 0.000000 +v 21.045284 10.054781 -5.000000 +vn 0.121136 -0.166729 0.000000 +v 13.308694 27.431448 -5.000000 +vn 0.802155 -1.104072 0.000000 +v 15.000000 28.660254 5.000000 +vn 0.923291 -1.270801 0.000000 +v 13.308694 27.431448 5.000000 +vn 0.121136 -0.166729 0.000000 +v 15.000000 28.660254 5.000000 +vn 0.802155 -1.104072 0.000000 +v 13.308694 27.431448 -5.000000 +vn 0.923291 -1.270801 0.000000 +v 15.000000 28.660254 -5.000000 +vn 1.181872 0.682353 0.000000 +v 11.909829 14.122147 5.000000 +vn 0.178478 0.103044 0.000000 +v 10.864545 15.932633 -5.000000 +vn 1.360350 0.785398 0.000000 +v 10.864545 15.932633 5.000000 +vn 1.181872 0.682353 0.000000 +v 10.864545 15.932633 -5.000000 +vn 0.178478 0.103044 0.000000 +v 11.909829 14.122147 5.000000 +vn 1.360350 0.785398 0.000000 +v 11.909829 14.122147 -5.000000 +vn 1.357232 0.142651 0.000000 +v 10.218524 17.920883 5.000000 +vn 0.204960 0.021542 0.000000 +v 10.000000 20.000000 -5.000000 +vn 1.562191 0.164193 0.000000 +v 10.000000 20.000000 5.000000 +vn 1.357232 0.142651 0.000000 +v 10.000000 20.000000 -5.000000 +vn 0.204960 0.021542 0.000000 +v 10.218524 17.920883 5.000000 +vn 1.562191 0.164193 0.000000 +v 10.218524 17.920883 -5.000000 +vn 1.181872 -0.682353 0.000000 +v 10.864545 24.067366 5.000000 +vn 0.178478 -0.103044 0.000000 +v 11.909829 25.877853 -5.000000 +vn 1.360350 -0.785397 0.000000 +v 11.909829 25.877853 5.000000 +vn 1.181872 -0.682353 0.000000 +v 11.909829 25.877853 -5.000000 +vn 0.178478 -0.103044 0.000000 +v 10.864545 24.067366 5.000000 +vn 1.360350 -0.785397 0.000000 +v 10.864545 24.067366 -5.000000 +# 720 vertices, 0 vertices normals + +f 1//1 2//2 3//3 +f 4//4 5//5 6//6 +f 7//7 8//8 9//9 +f 10//10 11//11 12//12 +f 13//13 14//14 15//15 +f 16//16 17//17 18//18 +f 19//19 20//20 21//21 +f 22//22 23//23 24//24 +f 25//25 26//26 27//27 +f 28//28 29//29 30//30 +f 31//31 32//32 33//33 +f 34//34 35//35 36//36 +f 37//37 38//38 39//39 +f 40//40 41//41 42//42 +f 43//43 44//44 45//45 +f 46//46 47//47 48//48 +f 49//49 50//50 51//51 +f 52//52 53//53 54//54 +f 55//55 56//56 57//57 +f 58//58 59//59 60//60 +f 61//61 62//62 63//63 +f 64//64 65//65 66//66 +f 67//67 68//68 69//69 +f 70//70 71//71 72//72 +f 73//73 74//74 75//75 +f 76//76 77//77 78//78 +f 79//79 80//80 81//81 +f 82//82 83//83 84//84 +f 85//85 86//86 87//87 +f 88//88 89//89 90//90 +f 91//91 92//92 93//93 +f 94//94 95//95 96//96 +f 97//97 98//98 99//99 +f 100//100 101//101 102//102 +f 103//103 104//104 105//105 +f 106//106 107//107 108//108 +f 109//109 110//110 111//111 +f 112//112 113//113 114//114 +f 115//115 116//116 117//117 +f 118//118 119//119 120//120 +f 121//121 122//122 123//123 +f 124//124 125//125 126//126 +f 127//127 128//128 129//129 +f 130//130 131//131 132//132 +f 133//133 134//134 135//135 +f 136//136 137//137 138//138 +f 139//139 140//140 141//141 +f 142//142 143//143 144//144 +f 145//145 146//146 147//147 +f 148//148 149//149 150//150 +f 151//151 152//152 153//153 +f 154//154 155//155 156//156 +f 157//157 158//158 159//159 +f 160//160 161//161 162//162 +f 163//163 164//164 165//165 +f 166//166 167//167 168//168 +f 169//169 170//170 171//171 +f 172//172 173//173 174//174 +f 175//175 176//176 177//177 +f 178//178 179//179 180//180 +f 181//181 182//182 183//183 +f 184//184 185//185 186//186 +f 187//187 188//188 189//189 +f 190//190 191//191 192//192 +f 193//193 194//194 195//195 +f 196//196 197//197 198//198 +f 199//199 200//200 201//201 +f 202//202 203//203 204//204 +f 205//205 206//206 207//207 +f 208//208 209//209 210//210 +f 211//211 212//212 213//213 +f 214//214 215//215 216//216 +f 217//217 218//218 219//219 +f 220//220 221//221 222//222 +f 223//223 224//224 225//225 +f 226//226 227//227 228//228 +f 229//229 230//230 231//231 +f 232//232 233//233 234//234 +f 235//235 236//236 237//237 +f 238//238 239//239 240//240 +f 241//241 242//242 243//243 +f 244//244 245//245 246//246 +f 247//247 248//248 249//249 +f 250//250 251//251 252//252 +f 253//253 254//254 255//255 +f 256//256 257//257 258//258 +f 259//259 260//260 261//261 +f 262//262 263//263 264//264 +f 265//265 266//266 267//267 +f 268//268 269//269 270//270 +f 271//271 272//272 273//273 +f 274//274 275//275 276//276 +f 277//277 278//278 279//279 +f 280//280 281//281 282//282 +f 283//283 284//284 285//285 +f 286//286 287//287 288//288 +f 289//289 290//290 291//291 +f 292//292 293//293 294//294 +f 295//295 296//296 297//297 +f 298//298 299//299 300//300 +f 301//301 302//302 303//303 +f 304//304 305//305 306//306 +f 307//307 308//308 309//309 +f 310//310 311//311 312//312 +f 313//313 314//314 315//315 +f 316//316 317//317 318//318 +f 319//319 320//320 321//321 +f 322//322 323//323 324//324 +f 325//325 326//326 327//327 +f 328//328 329//329 330//330 +f 331//331 332//332 333//333 +f 334//334 335//335 336//336 +f 337//337 338//338 339//339 +f 340//340 341//341 342//342 +f 343//343 344//344 345//345 +f 346//346 347//347 348//348 +f 349//349 350//350 351//351 +f 352//352 353//353 354//354 +f 355//355 356//356 357//357 +f 358//358 359//359 360//360 +f 361//361 362//362 363//363 +f 364//364 365//365 366//366 +f 367//367 368//368 369//369 +f 370//370 371//371 372//372 +f 373//373 374//374 375//375 +f 376//376 377//377 378//378 +f 379//379 380//380 381//381 +f 382//382 383//383 384//384 +f 385//385 386//386 387//387 +f 388//388 389//389 390//390 +f 391//391 392//392 393//393 +f 394//394 395//395 396//396 +f 397//397 398//398 399//399 +f 400//400 401//401 402//402 +f 403//403 404//404 405//405 +f 406//406 407//407 408//408 +f 409//409 410//410 411//411 +f 412//412 413//413 414//414 +f 415//415 416//416 417//417 +f 418//418 419//419 420//420 +f 421//421 422//422 423//423 +f 424//424 425//425 426//426 +f 427//427 428//428 429//429 +f 430//430 431//431 432//432 +f 433//433 434//434 435//435 +f 436//436 437//437 438//438 +f 439//439 440//440 441//441 +f 442//442 443//443 444//444 +f 445//445 446//446 447//447 +f 448//448 449//449 450//450 +f 451//451 452//452 453//453 +f 454//454 455//455 456//456 +f 457//457 458//458 459//459 +f 460//460 461//461 462//462 +f 463//463 464//464 465//465 +f 466//466 467//467 468//468 +f 469//469 470//470 471//471 +f 472//472 473//473 474//474 +f 475//475 476//476 477//477 +f 478//478 479//479 480//480 +f 481//481 482//482 483//483 +f 484//484 485//485 486//486 +f 487//487 488//488 489//489 +f 490//490 491//491 492//492 +f 493//493 494//494 495//495 +f 496//496 497//497 498//498 +f 499//499 500//500 501//501 +f 502//502 503//503 504//504 +f 505//505 506//506 507//507 +f 508//508 509//509 510//510 +f 511//511 512//512 513//513 +f 514//514 515//515 516//516 +f 517//517 518//518 519//519 +f 520//520 521//521 522//522 +f 523//523 524//524 525//525 +f 526//526 527//527 528//528 +f 529//529 530//530 531//531 +f 532//532 533//533 534//534 +f 535//535 536//536 537//537 +f 538//538 539//539 540//540 +f 541//541 542//542 543//543 +f 544//544 545//545 546//546 +f 547//547 548//548 549//549 +f 550//550 551//551 552//552 +f 553//553 554//554 555//555 +f 556//556 557//557 558//558 +f 559//559 560//560 561//561 +f 562//562 563//563 564//564 +f 565//565 566//566 567//567 +f 568//568 569//569 570//570 +f 571//571 572//572 573//573 +f 574//574 575//575 576//576 +f 577//577 578//578 579//579 +f 580//580 581//581 582//582 +f 583//583 584//584 585//585 +f 586//586 587//587 588//588 +f 589//589 590//590 591//591 +f 592//592 593//593 594//594 +f 595//595 596//596 597//597 +f 598//598 599//599 600//600 +f 601//601 602//602 603//603 +f 604//604 605//605 606//606 +f 607//607 608//608 609//609 +f 610//610 611//611 612//612 +f 613//613 614//614 615//615 +f 616//616 617//617 618//618 +f 619//619 620//620 621//621 +f 622//622 623//623 624//624 +f 625//625 626//626 627//627 +f 628//628 629//629 630//630 +f 631//631 632//632 633//633 +f 634//634 635//635 636//636 +f 637//637 638//638 639//639 +f 640//640 641//641 642//642 +f 643//643 644//644 645//645 +f 646//646 647//647 648//648 +f 649//649 650//650 651//651 +f 652//652 653//653 654//654 +f 655//655 656//656 657//657 +f 658//658 659//659 660//660 +f 661//661 662//662 663//663 +f 664//664 665//665 666//666 +f 667//667 668//668 669//669 +f 670//670 671//671 672//672 +f 673//673 674//674 675//675 +f 676//676 677//677 678//678 +f 679//679 680//680 681//681 +f 682//682 683//683 684//684 +f 685//685 686//686 687//687 +f 688//688 689//689 690//690 +f 691//691 692//692 693//693 +f 694//694 695//695 696//696 +f 697//697 698//698 699//699 +f 700//700 701//701 702//702 +f 703//703 704//704 705//705 +f 706//706 707//707 708//708 +f 709//709 710//710 711//711 +f 712//712 713//713 714//714 +f 715//715 716//716 717//717 +f 718//718 719//719 720//720 +# 240 faces, 0 coords texture + +# End of File From 44bc8d8f5fb05f2d6e82aa5d098e5a73ca55c093 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 10:52:08 +0100 Subject: [PATCH 39/59] Small refactor and comments --- src/libslic3r/Optimize/NLoptOptimizer.hpp | 2 +- src/libslic3r/SLA/SupportTreeUtils.hpp | 84 +++++++++++++++-------- 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index f360c6753..9e423ff91 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -392,7 +392,7 @@ using AlgNLoptORIG_DIRECT = detail::NLoptAlg; using AlgNLoptISRES = detail::NLoptAlg; using AlgNLoptAGS = detail::NLoptAlg; -using AlgNLoptMLSL = detail::NLoptAlgComb; +using AlgNLoptMLSL_Subplx = detail::NLoptAlgComb; using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb; using AlgNLoptGenetic_Subplx = detail::NLoptAlgComb; diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 13586d937..78e9afb88 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -136,8 +136,11 @@ struct Beam_ { // Defines a set of rays displaced along a cone's surface using Beam = Beam_<8>; -template -Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam_ &beam, double sd) +template +Hit beam_mesh_hit(Ex policy, + const AABBMesh &mesh, + const Beam_ &beam, + double sd) { Vec3d src = beam.src; Vec3d dst = src + beam.dir; @@ -146,15 +149,15 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam_ &beam, double sd) Vec3d D = (dst - src); Vec3d dir = D.normalized(); - PointRing ring{dir}; + PointRing ring{dir}; using Hit = AABBMesh::hit_result; // Hit results - std::array hits; + std::array 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]; @@ -175,7 +178,7 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam_ &beam, double sd) } } else hit = hr; - }, std::min(execution::max_concurrency(ex), S)); + }, std::min(execution::max_concurrency(policy), RayCount)); return min_hit(hits.begin(), hits.end()); } @@ -359,7 +362,7 @@ bool optimize_pinhead_placement(Ex policy, // viable normal that doesn't collide with the model // geometry and its very close to the default. - Optimizer solver(get_criteria(m.cfg).stop_score(w).max_iterations(100)); + Optimizer solver(get_criteria(m.cfg).stop_score(w).max_iterations(100)); solver.seed(0); // we want deterministic behavior auto oresult = solver.to_max().optimize( @@ -483,21 +486,27 @@ constexpr bool IsWideningFn = std::is_invocable_r_v; +// A widening function can determine how many ray samples should a beam contain +// (see in beam_mesh_hit) template struct BeamSamples { static constexpr size_t Value = 8; }; template constexpr size_t BeamSamplesV = BeamSamples>::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> > Vec3d check_ground_route( Ex policy, const SupportableMesh &sm, - const Junction &source, - const Vec3d &dir, - double bridge_len, - WideningFn &&wideningfn, + 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 ) { @@ -556,6 +565,9 @@ Vec3d check_ground_route( return ret; } +// 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> > GroundConnection deepsearch_ground_connection( @@ -565,26 +577,35 @@ GroundConnection deepsearch_ground_connection( WideningFn &&wideningfn, const Vec3d &init_dir = DOWN) { + constexpr unsigned MaxIterationsGlobal = 5000; + constexpr unsigned MaxIterationsLocal = 100; + constexpr double RelScoreDiff = 0.05; + const auto gndlvl = ground_level(sm); - auto criteria = get_criteria(sm.cfg); - criteria.max_iterations(5000); + // 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) + + 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 criteria_loc = criteria; - criteria_loc.max_iterations(100); + criteria_loc.max_iterations(MaxIterationsLocal); criteria_loc.abs_score_diff(EPSILON); - criteria_loc.rel_score_diff(0.05); + criteria_loc.rel_score_diff(RelScoreDiff); - Optimizer solver(criteria); + Optimizer solver(criteria); solver.set_loc_criteria(criteria_loc); - solver.seed(0); + solver.seed(0); // require repeatability // 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, throught this bridge and an attached pillar. If there + // 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) { @@ -598,20 +619,22 @@ GroundConnection deepsearch_ground_connection( 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); - - // Saturate the polar angle to max tilt defined in config 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 + 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, like water flows down a - // surface. + // this search should arrive at a ground location. auto oresult = solver.to_min().optimize( z_fn, initvals({plr_init, azm_init, 0.}), @@ -628,7 +651,9 @@ GroundConnection deepsearch_ground_connection( // 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. + // 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::infinity(); while(zlvl > gndlvl && l <= l_max) { @@ -650,10 +675,14 @@ GroundConnection deepsearch_ground_connection( 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}; @@ -661,6 +690,7 @@ GroundConnection deepsearch_ground_connection( return conn; } +// Ground route search with a predefined end radius template GroundConnection deepsearch_ground_connection(Ex policy, const SupportableMesh &sm, From 7eb5ca7396ada2122f4a674167c3385a5eba1ab5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 12:06:16 +0100 Subject: [PATCH 40/59] Log support tree creation time --- src/libslic3r/SLA/SupportTree.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index 37c2e85e9..cfafdf7e9 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -18,6 +18,8 @@ #include #include +#include + //! 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(ctl); if (sm.cfg.enabled) { + Benchmark bench; + bench.start(); + switch (sm.cfg.tree_type) { case SupportTreeType::Default: { create_default_tree(*builder, sm); @@ -45,6 +50,12 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm, default:; } + bench.stop(); + + BOOST_LOG_TRIVIAL(info) << "Support tree creation took: " + << bench.getElapsedSec() + << " seconds"; + builder->merge_and_cleanup(); // clean metadata, leave only the meshes. } From ebb8f9bc80b4b1f8915ad7014146a1aab4eb8bfa Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 12:06:37 +0100 Subject: [PATCH 41/59] Disable parallel beam hits in branching tree --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 4230e38bb..5c7e751fa 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -13,6 +13,8 @@ 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; @@ -178,7 +180,7 @@ bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from, Vec3d fromd = from.pos.cast(), tod = to.pos.cast(); 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(); @@ -201,8 +203,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(); @@ -222,7 +224,7 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, sla::Junction j{from.pos.cast(), get_radius(from)}; Vec3d init_dir = (to.pos - from.pos).cast().normalized(); - auto conn = deepsearch_ground_connection(ex_tbb, m_sm, j, + auto conn = deepsearch_ground_connection(beam_ex_policy , m_sm, j, get_radius(to), init_dir); if (conn) { @@ -255,13 +257,13 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from, auto anchor = m_sm.cfg.ground_facing_only ? std::optional{} : // 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()); 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)) { From 58acc893b3c9da4af912a648f743afebba6709e3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 13:49:26 +0100 Subject: [PATCH 42/59] Solve mini pillar widening by enforcing min radius on bed points Use subtree rescue after all --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 5c7e751fa..d88bdb1b0 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -150,7 +150,7 @@ public: << " " << j.pos.y() << " " << j.pos.z(); // Discard all the support points connecting to this branch. - discard_subtree(j.id); + discard_subtree_rescure(j.id); } const std::vector& unroutable_pinheads() const @@ -336,6 +336,9 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s 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}; From d4a46d373a4b269b84b9b14ab85a4221795a1aea Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 13:50:12 +0100 Subject: [PATCH 43/59] Solve mini pillar widening in DefaultWideningStrategy --- src/libslic3r/SLA/SupportTreeUtils.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 78e9afb88..a47f8d662 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -723,7 +723,7 @@ struct DefaultWideningModel { "DefaultWideningModel is not a widening function"); double w = WIDENING_SCALE * sm.cfg.pillar_widening_factor * len; - return src.R + w; + return std::max(src.R, sm.cfg.head_back_radius_mm) + w; }; }; From e16c886a1adf2062e7bb34d75826bafa3dbe1c2a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 13:50:36 +0100 Subject: [PATCH 44/59] Remove dead code --- src/libslic3r/BranchingTree/PointCloud.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libslic3r/BranchingTree/PointCloud.cpp b/src/libslic3r/BranchingTree/PointCloud.cpp index 4075a20c2..1497f0894 100644 --- a/src/libslic3r/BranchingTree/PointCloud.cpp +++ b/src/libslic3r/BranchingTree/PointCloud.cpp @@ -74,8 +74,6 @@ std::vector sample_mesh(const indexed_triangle_set &its, double radius) std::vector sample_bed(const ExPolygons &bed, float z, double radius) { - std::vector ret; - auto triangles = triangulate_expolygons_3d(bed, z); indexed_triangle_set its; its.vertices.reserve(triangles.size()); From c79a46e6cb0dbb7591469354f9a2db90411cf5dd Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 14:03:25 +0100 Subject: [PATCH 45/59] Remove unnecessary stuff --- src/libslic3r/CMakeLists.txt | 2 - src/libslic3r/OrganicTree/OrganicTree.hpp | 95 - src/libslic3r/OrganicTree/OrganicTreeImpl.hpp | 11 - src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 4 - tests/data/disk_with_hole.obj | 1696 ----------------- 5 files changed, 1808 deletions(-) delete mode 100644 src/libslic3r/OrganicTree/OrganicTree.hpp delete mode 100644 src/libslic3r/OrganicTree/OrganicTreeImpl.hpp delete mode 100644 tests/data/disk_with_hole.obj diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 232285cee..7e02598d8 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -357,8 +357,6 @@ 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/OrganicTree/OrganicTree.hpp b/src/libslic3r/OrganicTree/OrganicTree.hpp deleted file mode 100644 index cf34c9eb3..000000000 --- a/src/libslic3r/OrganicTree/OrganicTree.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#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 deleted file mode 100644 index 6e18550da..000000000 --- a/src/libslic3r/OrganicTree/OrganicTreeImpl.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef ORGANICTREEIMPL_HPP -#define ORGANICTREEIMPL_HPP - -namespace Slic3r { namespace organictree { - - - - -}} - -#endif // ORGANICTREEIMPL_HPP diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index e0f3acb70..c22cdf606 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -12,8 +12,6 @@ #include #include -#include - #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_ObjectSettings.hpp" @@ -1021,8 +1019,6 @@ 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/disk_with_hole.obj b/tests/data/disk_with_hole.obj deleted file mode 100644 index f16cafb87..000000000 --- a/tests/data/disk_with_hole.obj +++ /dev/null @@ -1,1696 +0,0 @@ -#### -# -# OBJ File Generated by Meshlab -# -#### -# Object disk_with_hole.obj -# -# Vertices: 720 -# Faces: 240 -# -#### -vn -0.328454 -0.737720 0.000000 -v -25.000000 -43.301270 -5.000000 -vn -0.310446 -0.697273 0.000000 -v -15.450850 -47.552826 5.000000 -vn -0.638901 -1.434994 0.000000 -v -25.000000 -43.301270 5.000000 -vn -0.328454 -0.737720 0.000000 -v -15.450850 -47.552826 5.000000 -vn -0.310446 -0.697273 0.000000 -v -25.000000 -43.301270 -5.000000 -vn -0.638901 -1.434994 0.000000 -v -15.450850 -47.552826 -5.000000 -vn -0.725904 -0.235860 0.000000 -v -45.677273 -20.336832 -5.000000 -vn -0.768012 -0.249542 0.000000 -v -48.907379 -10.395584 5.000000 -vn -1.493916 -0.485403 0.000000 -v -48.907379 -10.395584 -5.000000 -vn -0.725904 -0.235860 0.000000 -v -48.907379 -10.395584 5.000000 -vn -0.768012 -0.249542 0.000000 -v -45.677273 -20.336832 -5.000000 -vn -1.493916 -0.485403 0.000000 -v -45.677273 -20.336832 5.000000 -vn 0.725904 0.235860 0.000000 -v 48.907379 10.395584 5.000000 -vn 0.768012 0.249542 0.000000 -v 45.677273 20.336832 -5.000000 -vn 1.493916 0.485403 0.000000 -v 45.677273 20.336832 5.000000 -vn 0.725904 0.235860 0.000000 -v 45.677273 20.336832 -5.000000 -vn 0.768012 0.249542 0.000000 -v 48.907379 10.395584 5.000000 -vn 1.493916 0.485403 0.000000 -v 48.907379 10.395584 -5.000000 -vn 0.759080 0.079783 0.000000 -v 50.000000 0.000000 5.000000 -vn 0.803112 0.084411 0.000000 -v 48.907379 10.395584 -5.000000 -vn 1.562191 0.164193 0.000000 -v 48.907379 10.395584 5.000000 -vn 0.759080 0.079783 0.000000 -v 48.907379 10.395584 -5.000000 -vn 0.803112 0.084411 0.000000 -v 50.000000 0.000000 5.000000 -vn 1.562191 0.164193 0.000000 -v 50.000000 0.000000 -5.000000 -vn 0.661003 0.381630 0.000000 -v 45.677273 20.336832 5.000000 -vn 0.699346 0.403768 0.000000 -v 40.450851 29.389263 -5.000000 -vn 1.360350 0.785398 0.000000 -v 40.450851 29.389263 5.000000 -vn 0.661003 0.381630 0.000000 -v 40.450851 29.389263 -5.000000 -vn 0.699346 0.403768 0.000000 -v 45.677273 20.336832 5.000000 -vn 1.360350 0.785398 0.000000 -v 45.677273 20.336832 -5.000000 -vn 0.474657 -0.653310 0.000000 -v 25.000000 -43.301270 -5.000000 -vn 0.448633 -0.617491 0.000000 -v 33.456528 -37.157242 5.000000 -vn 0.923291 -1.270801 0.000000 -v 25.000000 -43.301270 5.000000 -vn 0.474657 -0.653310 0.000000 -v 33.456528 -37.157242 5.000000 -vn 0.448633 -0.617491 0.000000 -v 25.000000 -43.301270 -5.000000 -vn 0.923291 -1.270801 0.000000 -v 33.456528 -37.157242 -5.000000 -vn 0.328454 0.737720 0.000000 -v 25.000000 43.301270 -5.000000 -vn 0.310446 0.697273 0.000000 -v 15.450850 47.552826 5.000000 -vn 0.638901 1.434994 0.000000 -v 25.000000 43.301270 5.000000 -vn 0.328454 0.737720 0.000000 -v 15.450850 47.552826 5.000000 -vn 0.310446 0.697273 0.000000 -v 25.000000 43.301270 -5.000000 -vn 0.638901 1.434994 0.000000 -v 15.450850 47.552826 -5.000000 -vn 0.759080 -0.079783 0.000000 -v 48.907379 -10.395584 5.000000 -vn 0.803112 -0.084411 0.000000 -v 50.000000 0.000000 -5.000000 -vn 1.562191 -0.164193 0.000000 -v 50.000000 0.000000 5.000000 -vn 0.759080 -0.079783 0.000000 -v 50.000000 0.000000 -5.000000 -vn 0.803112 -0.084411 0.000000 -v 48.907379 -10.395584 5.000000 -vn 1.562191 -0.164193 0.000000 -v 48.907379 -10.395584 -5.000000 -vn 0.567213 0.510721 0.000000 -v 40.450851 29.389263 5.000000 -vn 0.600116 0.540347 0.000000 -v 33.456528 37.157242 -5.000000 -vn 1.167329 1.051068 0.000000 -v 33.456528 37.157242 5.000000 -vn 0.567213 0.510721 0.000000 -v 33.456528 37.157242 -5.000000 -vn 0.600116 0.540347 0.000000 -v 40.450851 29.389263 5.000000 -vn 1.167329 1.051068 0.000000 -v 40.450851 29.389263 -5.000000 -vn -0.661003 0.381630 0.000000 -v -45.677273 20.336832 -5.000000 -vn -0.699346 0.403768 0.000000 -v -40.450851 29.389263 5.000000 -vn -1.360350 0.785398 0.000000 -v -40.450851 29.389263 -5.000000 -vn -0.661003 0.381630 0.000000 -v -40.450851 29.389263 5.000000 -vn -0.699346 0.403768 0.000000 -v -45.677273 20.336832 -5.000000 -vn -1.360350 0.785398 0.000000 -v -45.677273 20.336832 5.000000 -vn 0.567213 -0.510721 0.000000 -v 33.456528 -37.157242 5.000000 -vn 0.600116 -0.540347 0.000000 -v 40.450851 -29.389263 -5.000000 -vn 1.167329 -1.051068 0.000000 -v 40.450851 -29.389263 5.000000 -vn 0.567213 -0.510721 0.000000 -v 40.450851 -29.389263 -5.000000 -vn 0.600116 -0.540347 0.000000 -v 33.456528 -37.157242 5.000000 -vn 1.167329 -1.051068 0.000000 -v 33.456528 -37.157242 -5.000000 -vn -0.567213 0.510721 0.000000 -v -40.450851 29.389263 -5.000000 -vn -0.600116 0.540347 0.000000 -v -33.456528 37.157242 5.000000 -vn -1.167329 1.051068 0.000000 -v -33.456528 37.157242 -5.000000 -vn -0.567213 0.510721 0.000000 -v -33.456528 37.157242 5.000000 -vn -0.600116 0.540347 0.000000 -v -40.450851 29.389263 -5.000000 -vn -1.167329 1.051068 0.000000 -v -40.450851 29.389263 5.000000 -vn 0.661003 -0.381630 0.000000 -v 40.450851 -29.389263 5.000000 -vn 0.699346 -0.403768 0.000000 -v 45.677273 -20.336832 -5.000000 -vn 1.360350 -0.785398 0.000000 -v 45.677273 -20.336832 5.000000 -vn 0.661003 -0.381630 0.000000 -v 45.677273 -20.336832 -5.000000 -vn 0.699346 -0.403768 0.000000 -v 40.450851 -29.389263 5.000000 -vn 1.360350 -0.785398 0.000000 -v 40.450851 -29.389263 -5.000000 -vn -0.474657 0.653310 0.000000 -v -25.000000 43.301270 -5.000000 -vn -0.448633 0.617491 0.000000 -v -33.456528 37.157242 5.000000 -vn -0.923291 1.270801 0.000000 -v -25.000000 43.301270 5.000000 -vn -0.474657 0.653310 0.000000 -v -33.456528 37.157242 5.000000 -vn -0.448633 0.617491 0.000000 -v -25.000000 43.301270 -5.000000 -vn -0.923291 1.270801 0.000000 -v -33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 0.397030 -v 26.691305 12.568551 5.000000 -vn 0.000000 0.000000 0.971546 -v 50.000000 0.000000 5.000000 -vn 0.000000 0.000000 1.773017 -v 48.907379 10.395584 5.000000 -vn 0.000000 0.000000 0.533261 -v 29.135454 15.932633 5.000000 -vn 0.000000 0.000000 0.983586 -v 48.907379 10.395584 5.000000 -vn 0.000000 0.000000 1.624746 -v 45.677273 20.336832 5.000000 -vn 0.000000 0.000000 0.935258 -v 26.691305 12.568551 5.000000 -vn 0.000000 0.000000 0.079637 -v 48.907379 10.395584 5.000000 -vn 0.000000 0.000000 2.126698 -v 28.090170 14.122147 5.000000 -vn 0.000000 0.000000 0.710460 -v 30.000000 20.000000 5.000000 -vn 0.000000 0.000000 1.068680 -v 45.677273 20.336832 5.000000 -vn 0.000000 0.000000 1.362453 -v 40.450851 29.389263 5.000000 -vn 0.000000 0.000000 0.095914 -v 48.907379 10.395584 5.000000 -vn 0.000000 0.000000 1.821343 -v 29.135454 15.932633 5.000000 -vn 0.000000 0.000000 1.224335 -v 28.090170 14.122147 5.000000 -vn 0.000000 0.000000 0.849935 -v 28.090170 25.877853 5.000000 -vn 0.000000 0.000000 1.114545 -v 40.450851 29.389263 5.000000 -vn 0.000000 0.000000 1.177112 -v 33.456528 37.157242 5.000000 -vn 0.000000 0.000000 0.129351 -v 45.677273 20.336832 5.000000 -vn 0.000000 0.000000 1.696999 -v 30.000000 20.000000 5.000000 -vn 0.000000 0.000000 1.315244 -v 29.781475 17.920883 5.000000 -vn 0.000000 0.000000 0.109378 -v 45.677273 20.336832 5.000000 -vn 0.000000 0.000000 2.035788 -v 29.781475 17.920883 5.000000 -vn 0.000000 0.000000 0.996427 -v 29.135454 15.932633 5.000000 -vn 0.000000 0.000000 0.131250 -v 40.450851 29.389263 5.000000 -vn 0.000000 0.000000 2.066767 -v 29.781475 22.079117 5.000000 -vn 0.000000 0.000000 0.943575 -v 30.000000 20.000000 5.000000 -vn 0.000000 0.000000 0.161065 -v 40.450851 29.389263 5.000000 -vn 0.000000 0.000000 1.696263 -v 29.135454 24.067366 5.000000 -vn 0.000000 0.000000 1.284264 -v 29.781475 22.079117 5.000000 -vn 0.000000 0.000000 0.162839 -v 40.450851 29.389263 5.000000 -vn 0.000000 0.000000 1.323985 -v 28.090170 25.877853 5.000000 -vn 0.000000 0.000000 1.654769 -v 29.135454 24.067366 5.000000 -vn 0.000000 0.000000 0.163690 -v 33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.800790 -v 26.691305 27.431448 5.000000 -vn 0.000000 0.000000 1.177113 -v 28.090170 25.877853 5.000000 -vn 0.000000 0.000000 0.797639 -v 23.090170 29.510565 5.000000 -vn 0.000000 0.000000 1.263865 -v 33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.080089 -v 25.000000 43.301270 5.000000 -vn 0.000000 0.000000 0.175248 -v 33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.416103 -v 25.000000 28.660254 5.000000 -vn 0.000000 0.000000 1.550242 -v 26.691305 27.431448 5.000000 -vn 0.000000 0.000000 0.152239 -v 33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.054425 -v 23.090170 29.510565 5.000000 -vn 0.000000 0.000000 1.934929 -v 25.000000 28.660254 5.000000 -vn 0.000000 0.000000 0.150263 -v 25.000000 43.301270 5.000000 -vn 0.000000 0.000000 1.492361 -v 21.045284 29.945217 5.000000 -vn 0.000000 0.000000 1.498969 -v 23.090170 29.510565 5.000000 -vn 0.000000 0.000000 0.137161 -v 25.000000 43.301270 5.000000 -vn 0.000000 0.000000 1.145761 -v 18.954716 29.945217 5.000000 -vn 0.000000 0.000000 1.858670 -v 21.045284 29.945217 5.000000 -vn 0.000000 0.000000 0.955486 -v 15.450850 47.552826 5.000000 -vn 0.000000 0.000000 0.621466 -v 18.954716 29.945217 5.000000 -vn 0.000000 0.000000 1.564641 -v 25.000000 43.301270 5.000000 -vn 0.000000 0.000000 1.583804 -v 18.954716 29.945217 5.000000 -vn 0.000000 0.000000 0.115742 -v 15.450850 47.552826 5.000000 -vn 0.000000 0.000000 1.442047 -v 16.909828 29.510565 5.000000 -vn 0.000000 0.000000 0.104548 -v 15.450850 47.552826 5.000000 -vn 0.000000 0.000000 1.128057 -v 15.000000 28.660254 5.000000 -vn 0.000000 0.000000 1.908987 -v 16.909828 29.510565 5.000000 -vn 0.000000 0.000000 0.926960 -v 5.226422 49.726093 5.000000 -vn 0.000000 0.000000 0.458257 -v 15.000000 28.660254 5.000000 -vn 0.000000 0.000000 1.756376 -v 15.450850 47.552826 5.000000 -vn 0.000000 0.000000 1.764718 -v 15.000000 28.660254 5.000000 -vn 0.000000 0.000000 0.086613 -v 5.226422 49.726093 5.000000 -vn 0.000000 0.000000 1.290263 -v 13.308694 27.431448 5.000000 -vn 0.000000 0.000000 2.060768 -v 13.308694 27.431448 5.000000 -vn 0.000000 0.000000 0.074549 -v 5.226422 49.726093 5.000000 -vn 0.000000 0.000000 1.006277 -v 11.909829 25.877853 5.000000 -vn 0.000000 0.000000 0.947726 -v -5.226422 49.726093 5.000000 -vn 0.000000 0.000000 0.349832 -v 11.909829 25.877853 5.000000 -vn 0.000000 0.000000 1.844034 -v 5.226422 49.726093 5.000000 -vn 0.000000 0.000000 0.938074 -v -15.450850 47.552826 5.000000 -vn 0.000000 0.000000 0.282044 -v 10.864545 24.067366 5.000000 -vn 0.000000 0.000000 1.921476 -v -5.226422 49.726093 5.000000 -vn 0.000000 0.000000 1.994924 -v 11.909829 25.877853 5.000000 -vn 0.000000 0.000000 0.062953 -v -5.226422 49.726093 5.000000 -vn 0.000000 0.000000 1.083717 -v 10.864545 24.067366 5.000000 -vn 0.000000 0.000000 1.054154 -v 25.000000 11.339746 5.000000 -vn 0.000000 0.000000 0.068696 -v 50.000000 0.000000 5.000000 -vn 0.000000 0.000000 2.018744 -v 26.691305 12.568551 5.000000 -vn 0.000000 0.000000 1.891912 -v 50.000000 0.000000 5.000000 -vn 0.000000 0.000000 0.312011 -v 25.000000 11.339746 5.000000 -vn 0.000000 0.000000 0.937670 -v 48.907379 -10.395584 5.000000 -vn 0.000000 0.000000 1.099058 -v 23.090170 10.489434 5.000000 -vn 0.000000 0.000000 0.057667 -v 48.907379 -10.395584 5.000000 -vn 0.000000 0.000000 1.984868 -v 25.000000 11.339746 5.000000 -vn 0.000000 0.000000 1.936816 -v 48.907379 -10.395584 5.000000 -vn 0.000000 0.000000 0.258266 -v 23.090170 10.489434 5.000000 -vn 0.000000 0.000000 0.946511 -v 45.677273 -20.336832 5.000000 -vn 0.000000 0.000000 1.099133 -v 21.045284 10.054781 5.000000 -vn 0.000000 0.000000 0.048750 -v 45.677273 -20.336832 5.000000 -vn 0.000000 0.000000 1.993709 -v 23.090170 10.489434 5.000000 -vn 0.000000 0.000000 1.936892 -v 45.677273 -20.336832 5.000000 -vn 0.000000 0.000000 0.223894 -v 21.045284 10.054781 5.000000 -vn 0.000000 0.000000 0.980807 -v 40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 1.071817 -v 18.954716 10.054781 5.000000 -vn 0.000000 0.000000 0.041770 -v 40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 2.028005 -v 21.045284 10.054781 5.000000 -vn 0.000000 0.000000 1.909575 -v 40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 0.200963 -v 18.954716 10.054781 5.000000 -vn 0.000000 0.000000 1.031054 -v 33.456528 -37.157242 5.000000 -vn 0.000000 0.000000 1.901099 -v 33.456528 -37.157242 5.000000 -vn 0.000000 0.000000 0.185196 -v 18.954716 10.054781 5.000000 -vn 0.000000 0.000000 1.055297 -v 25.000000 -43.301270 5.000000 -vn 0.000000 0.000000 1.212075 -v 16.909828 10.489434 5.000000 -vn 0.000000 0.000000 0.036463 -v 25.000000 -43.301270 5.000000 -vn 0.000000 0.000000 1.893055 -v 18.954716 10.054781 5.000000 -vn 0.000000 0.000000 0.174414 -v 16.909828 10.489434 5.000000 -vn 0.000000 0.000000 1.126786 -v 15.450850 -47.552826 5.000000 -vn 0.000000 0.000000 1.840393 -v 25.000000 -43.301270 5.000000 -vn 0.000000 0.000000 0.032941 -v 5.226422 -49.726093 5.000000 -vn 0.000000 0.000000 1.798030 -v 16.909828 10.489434 5.000000 -vn 0.000000 0.000000 1.310621 -v 15.000000 11.339746 5.000000 -vn 0.000000 0.000000 0.031018 -v -15.450850 -47.552826 5.000000 -vn 0.000000 0.000000 1.721914 -v 15.000000 11.339746 5.000000 -vn 0.000000 0.000000 1.388663 -v 13.308694 12.568551 5.000000 -vn 0.000000 0.000000 0.030639 -v -40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 1.500477 -v 13.308694 12.568551 5.000000 -vn 0.000000 0.000000 1.610479 -v 11.909829 14.122147 5.000000 -vn 0.000000 0.000000 0.031757 -v -50.000000 0.000000 5.000000 -vn 0.000000 0.000000 1.271469 -v 11.909829 14.122147 5.000000 -vn 0.000000 0.000000 1.838368 -v 10.864545 15.932633 5.000000 -vn 0.000000 0.000000 0.034542 -v -45.677273 20.336832 5.000000 -vn 0.000000 0.000000 1.178902 -v 10.864545 15.932633 5.000000 -vn 0.000000 0.000000 1.928151 -v 10.218524 17.920883 5.000000 -vn 0.000000 0.000000 0.038851 -v -33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.051199 -v 10.218524 17.920883 5.000000 -vn 0.000000 0.000000 2.051544 -v 10.000000 20.000000 5.000000 -vn 0.000000 0.000000 1.985271 -v 10.864545 24.067366 5.000000 -vn 0.000000 0.000000 0.052938 -v -15.450850 47.552826 5.000000 -vn 0.000000 0.000000 1.103383 -v 10.218524 22.079117 5.000000 -vn 0.000000 0.000000 0.166514 -v 16.909828 10.489434 5.000000 -vn 0.000000 0.000000 1.169712 -v 5.226422 -49.726093 5.000000 -vn 0.000000 0.000000 1.805367 -v 15.450850 -47.552826 5.000000 -vn 0.000000 0.000000 0.961197 -v -25.000000 43.301270 5.000000 -vn 0.000000 0.000000 0.239254 -v 10.218524 22.079117 5.000000 -vn 0.000000 0.000000 1.941141 -v -15.450850 47.552826 5.000000 -vn 0.000000 0.000000 0.161147 -v 15.000000 11.339746 5.000000 -vn 0.000000 0.000000 1.250946 -v -5.226422 -49.726093 5.000000 -vn 0.000000 0.000000 1.729500 -v 5.226422 -49.726093 5.000000 -vn 0.000000 0.000000 2.008394 -v 10.218524 22.079117 5.000000 -vn 0.000000 0.000000 0.045050 -v -25.000000 43.301270 5.000000 -vn 0.000000 0.000000 1.088148 -v 10.000000 20.000000 5.000000 -vn 0.000000 0.000000 0.157350 -v 15.000000 11.339746 5.000000 -vn 0.000000 0.000000 1.303035 -v -15.450850 -47.552826 5.000000 -vn 0.000000 0.000000 1.681207 -v -5.226422 -49.726093 5.000000 -vn 0.000000 0.000000 1.004346 -v -33.456528 37.157242 5.000000 -vn 0.000000 0.000000 0.211341 -v 10.000000 20.000000 5.000000 -vn 0.000000 0.000000 1.925906 -v -25.000000 43.301270 5.000000 -vn 0.000000 0.000000 0.154864 -v 13.308694 12.568551 5.000000 -vn 0.000000 0.000000 1.388627 -v -25.000000 -43.301270 5.000000 -vn 0.000000 0.000000 1.598102 -v -15.450850 -47.552826 5.000000 -vn 0.000000 0.000000 0.192291 -v 10.218524 17.920883 5.000000 -vn 0.000000 0.000000 1.888957 -v -33.456528 37.157242 5.000000 -vn 0.000000 0.000000 1.060345 -v -40.450851 29.389263 5.000000 -vn 0.000000 0.000000 0.153677 -v 13.308694 12.568551 5.000000 -vn 0.000000 0.000000 1.444390 -v -33.456528 -37.157242 5.000000 -vn 0.000000 0.000000 1.543527 -v -25.000000 -43.301270 5.000000 -vn 0.000000 0.000000 0.179392 -v 10.218524 17.920883 5.000000 -vn 0.000000 0.000000 1.871808 -v -40.450851 29.389263 5.000000 -vn 0.000000 0.000000 1.090393 -v -45.677273 20.336832 5.000000 -vn 0.000000 0.000000 0.153352 -v 13.308694 12.568551 5.000000 -vn 0.000000 0.000000 1.500477 -v -40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 1.487764 -v -33.456528 -37.157242 5.000000 -vn 0.000000 0.000000 0.170109 -v 10.864545 15.932633 5.000000 -vn 0.000000 0.000000 1.807220 -v -45.677273 20.336832 5.000000 -vn 0.000000 0.000000 1.164264 -v -48.907379 10.395584 5.000000 -vn 0.000000 0.000000 0.154128 -v 11.909829 14.122147 5.000000 -vn 0.000000 0.000000 1.586425 -v -45.677273 -20.336832 5.000000 -vn 0.000000 0.000000 1.401039 -v -40.450851 -29.389263 5.000000 -vn 0.000000 0.000000 0.163654 -v 10.864545 15.932633 5.000000 -vn 0.000000 0.000000 1.767889 -v -48.907379 10.395584 5.000000 -vn 0.000000 0.000000 1.210049 -v -50.000000 0.000000 5.000000 -vn 0.000000 0.000000 0.156018 -v 11.909829 14.122147 5.000000 -vn 0.000000 0.000000 1.639846 -v -48.907379 -10.395584 5.000000 -vn 0.000000 0.000000 1.345728 -v -45.677273 -20.336832 5.000000 -vn 0.000000 0.000000 0.158937 -v 11.909829 14.122147 5.000000 -vn 0.000000 0.000000 1.690347 -v -50.000000 0.000000 5.000000 -vn 0.000000 0.000000 1.292307 -v -48.907379 -10.395584 5.000000 -vn 0.725904 -0.235860 0.000000 -v 45.677273 -20.336832 5.000000 -vn 0.768012 -0.249542 0.000000 -v 48.907379 -10.395584 -5.000000 -vn 1.493916 -0.485403 0.000000 -v 48.907379 -10.395584 5.000000 -vn 0.725904 -0.235860 0.000000 -v 48.907379 -10.395584 -5.000000 -vn 0.768012 -0.249542 0.000000 -v 45.677273 -20.336832 5.000000 -vn 1.493916 -0.485403 0.000000 -v 45.677273 -20.336832 -5.000000 -vn 0.000000 0.807535 0.000000 -v 5.226422 49.726093 -5.000000 -vn 0.000000 0.763261 0.000000 -v -5.226422 49.726093 5.000000 -vn 0.000000 1.570796 0.000000 -v 5.226422 49.726093 5.000000 -vn 0.000000 0.807535 0.000000 -v -5.226422 49.726093 5.000000 -vn 0.000000 0.763261 0.000000 -v 5.226422 49.726093 -5.000000 -vn 0.000000 1.570796 0.000000 -v -5.226422 49.726093 -5.000000 -vn -0.474657 -0.653310 0.000000 -v -33.456528 -37.157242 -5.000000 -vn -0.448633 -0.617491 0.000000 -v -25.000000 -43.301270 5.000000 -vn -0.923291 -1.270801 0.000000 -v -33.456528 -37.157242 5.000000 -vn -0.474657 -0.653310 0.000000 -v -25.000000 -43.301270 5.000000 -vn -0.448633 -0.617491 0.000000 -v -33.456528 -37.157242 -5.000000 -vn -0.923291 -1.270801 0.000000 -v -25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -0.312011 -v 25.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -1.891912 -v 50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -0.937670 -v 48.907379 -10.395584 -5.000000 -vn 0.000000 0.000000 -0.258266 -v 23.090170 10.489434 -5.000000 -vn 0.000000 0.000000 -1.936816 -v 48.907379 -10.395584 -5.000000 -vn 0.000000 0.000000 -0.946511 -v 45.677273 -20.336832 -5.000000 -vn 0.000000 0.000000 -0.068696 -v 50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -1.054154 -v 25.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -2.018744 -v 26.691305 12.568551 -5.000000 -vn 0.000000 0.000000 -0.223894 -v 21.045284 10.054781 -5.000000 -vn 0.000000 0.000000 -1.936892 -v 45.677273 -20.336832 -5.000000 -vn 0.000000 0.000000 -0.980807 -v 40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -0.079637 -v 48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -0.935258 -v 26.691305 12.568551 -5.000000 -vn 0.000000 0.000000 -2.126698 -v 28.090170 14.122147 -5.000000 -vn 0.000000 0.000000 -0.200963 -v 18.954716 10.054781 -5.000000 -vn 0.000000 0.000000 -1.909575 -v 40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -1.031054 -v 33.456528 -37.157242 -5.000000 -vn 0.000000 0.000000 -2.035788 -v 29.781475 17.920883 -5.000000 -vn 0.000000 0.000000 -0.109378 -v 45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -0.996427 -v 29.135454 15.932633 -5.000000 -vn 0.000000 0.000000 -1.068680 -v 45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -0.710460 -v 30.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -1.362453 -v 40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -2.066767 -v 29.781475 22.079117 -5.000000 -vn 0.000000 0.000000 -0.131250 -v 40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -0.943575 -v 30.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -1.696999 -v 30.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -0.129351 -v 45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -1.315244 -v 29.781475 17.920883 -5.000000 -vn 0.000000 0.000000 -0.983586 -v 48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -0.533261 -v 29.135454 15.932633 -5.000000 -vn 0.000000 0.000000 -1.624746 -v 45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -1.821343 -v 29.135454 15.932633 -5.000000 -vn 0.000000 0.000000 -0.095914 -v 48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -1.224335 -v 28.090170 14.122147 -5.000000 -vn 0.000000 0.000000 -0.185196 -v 18.954716 10.054781 -5.000000 -vn 0.000000 0.000000 -1.901099 -v 33.456528 -37.157242 -5.000000 -vn 0.000000 0.000000 -1.055297 -v 25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -0.971546 -v 50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -0.397030 -v 26.691305 12.568551 -5.000000 -vn 0.000000 0.000000 -1.773017 -v 48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -0.057667 -v 48.907379 -10.395584 -5.000000 -vn 0.000000 0.000000 -1.099058 -v 23.090170 10.489434 -5.000000 -vn 0.000000 0.000000 -1.984868 -v 25.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -0.048750 -v 45.677273 -20.336832 -5.000000 -vn 0.000000 0.000000 -1.099133 -v 21.045284 10.054781 -5.000000 -vn 0.000000 0.000000 -1.993709 -v 23.090170 10.489434 -5.000000 -vn 0.000000 0.000000 -0.041770 -v 40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -1.071817 -v 18.954716 10.054781 -5.000000 -vn 0.000000 0.000000 -2.028005 -v 21.045284 10.054781 -5.000000 -vn 0.000000 0.000000 -0.036463 -v 25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -1.212075 -v 16.909828 10.489434 -5.000000 -vn 0.000000 0.000000 -1.893055 -v 18.954716 10.054781 -5.000000 -vn 0.000000 0.000000 -1.126786 -v 15.450850 -47.552826 -5.000000 -vn 0.000000 0.000000 -0.174414 -v 16.909828 10.489434 -5.000000 -vn 0.000000 0.000000 -1.840393 -v 25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -1.169712 -v 5.226422 -49.726093 -5.000000 -vn 0.000000 0.000000 -0.166514 -v 16.909828 10.489434 -5.000000 -vn 0.000000 0.000000 -1.805367 -v 15.450850 -47.552826 -5.000000 -vn 0.000000 0.000000 -1.798030 -v 16.909828 10.489434 -5.000000 -vn 0.000000 0.000000 -0.032941 -v 5.226422 -49.726093 -5.000000 -vn 0.000000 0.000000 -1.310621 -v 15.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -1.250946 -v -5.226422 -49.726093 -5.000000 -vn 0.000000 0.000000 -0.161147 -v 15.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -1.729500 -v 5.226422 -49.726093 -5.000000 -vn 0.000000 0.000000 -1.303035 -v -15.450850 -47.552826 -5.000000 -vn 0.000000 0.000000 -0.157350 -v 15.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -1.681207 -v -5.226422 -49.726093 -5.000000 -vn 0.000000 0.000000 -1.721914 -v 15.000000 11.339746 -5.000000 -vn 0.000000 0.000000 -0.031018 -v -15.450850 -47.552826 -5.000000 -vn 0.000000 0.000000 -1.388663 -v 13.308694 12.568551 -5.000000 -vn 0.000000 0.000000 -1.388627 -v -25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -0.154864 -v 13.308694 12.568551 -5.000000 -vn 0.000000 0.000000 -1.598102 -v -15.450850 -47.552826 -5.000000 -vn 0.000000 0.000000 -1.444390 -v -33.456528 -37.157242 -5.000000 -vn 0.000000 0.000000 -0.153677 -v 13.308694 12.568551 -5.000000 -vn 0.000000 0.000000 -1.543527 -v -25.000000 -43.301270 -5.000000 -vn 0.000000 0.000000 -1.696263 -v 29.135454 24.067366 -5.000000 -vn 0.000000 0.000000 -0.161065 -v 40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -1.284264 -v 29.781475 22.079117 -5.000000 -vn 0.000000 0.000000 -1.323985 -v 28.090170 25.877853 -5.000000 -vn 0.000000 0.000000 -0.162839 -v 40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -1.654769 -v 29.135454 24.067366 -5.000000 -vn 0.000000 0.000000 -1.114545 -v 40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -0.849935 -v 28.090170 25.877853 -5.000000 -vn 0.000000 0.000000 -1.177112 -v 33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -1.800790 -v 26.691305 27.431448 -5.000000 -vn 0.000000 0.000000 -0.163690 -v 33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -1.177113 -v 28.090170 25.877853 -5.000000 -vn 0.000000 0.000000 -1.416103 -v 25.000000 28.660254 -5.000000 -vn 0.000000 0.000000 -0.175248 -v 33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -1.550242 -v 26.691305 27.431448 -5.000000 -vn 0.000000 0.000000 -1.054425 -v 23.090170 29.510565 -5.000000 -vn 0.000000 0.000000 -0.152239 -v 33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -1.934929 -v 25.000000 28.660254 -5.000000 -vn 0.000000 0.000000 -1.263865 -v 33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -0.797639 -v 23.090170 29.510565 -5.000000 -vn 0.000000 0.000000 -1.080089 -v 25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.492361 -v 21.045284 29.945217 -5.000000 -vn 0.000000 0.000000 -0.150263 -v 25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.498969 -v 23.090170 29.510565 -5.000000 -vn 0.000000 0.000000 -1.145761 -v 18.954716 29.945217 -5.000000 -vn 0.000000 0.000000 -0.137161 -v 25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.858670 -v 21.045284 29.945217 -5.000000 -vn 0.000000 0.000000 -0.115742 -v 15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.583804 -v 18.954716 29.945217 -5.000000 -vn 0.000000 0.000000 -1.442047 -v 16.909828 29.510565 -5.000000 -vn 0.000000 0.000000 -0.621466 -v 18.954716 29.945217 -5.000000 -vn 0.000000 0.000000 -0.955486 -v 15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.564641 -v 25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.128057 -v 15.000000 28.660254 -5.000000 -vn 0.000000 0.000000 -0.104548 -v 15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.908987 -v 16.909828 29.510565 -5.000000 -vn 0.000000 0.000000 -0.086613 -v 5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.764718 -v 15.000000 28.660254 -5.000000 -vn 0.000000 0.000000 -1.290263 -v 13.308694 27.431448 -5.000000 -vn 0.000000 0.000000 -0.074549 -v 5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -2.060768 -v 13.308694 27.431448 -5.000000 -vn 0.000000 0.000000 -1.006277 -v 11.909829 25.877853 -5.000000 -vn 0.000000 0.000000 -0.062953 -v -5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.994924 -v 11.909829 25.877853 -5.000000 -vn 0.000000 0.000000 -1.083717 -v 10.864545 24.067366 -5.000000 -vn 0.000000 0.000000 -0.052938 -v -15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.985271 -v 10.864545 24.067366 -5.000000 -vn 0.000000 0.000000 -1.103383 -v 10.218524 22.079117 -5.000000 -vn 0.000000 0.000000 -0.458257 -v 15.000000 28.660254 -5.000000 -vn 0.000000 0.000000 -0.926960 -v 5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.756376 -v 15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -0.045050 -v -25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -2.008394 -v 10.218524 22.079117 -5.000000 -vn 0.000000 0.000000 -1.088148 -v 10.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -1.500477 -v -40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -0.153352 -v 13.308694 12.568551 -5.000000 -vn 0.000000 0.000000 -1.487764 -v -33.456528 -37.157242 -5.000000 -vn 0.000000 0.000000 -1.500477 -v 13.308694 12.568551 -5.000000 -vn 0.000000 0.000000 -0.030639 -v -40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -1.610479 -v 11.909829 14.122147 -5.000000 -vn 0.000000 0.000000 -0.349832 -v 11.909829 25.877853 -5.000000 -vn 0.000000 0.000000 -0.947726 -v -5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.844034 -v 5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.586425 -v -45.677273 -20.336832 -5.000000 -vn 0.000000 0.000000 -0.154128 -v 11.909829 14.122147 -5.000000 -vn 0.000000 0.000000 -1.401039 -v -40.450851 -29.389263 -5.000000 -vn 0.000000 0.000000 -0.282044 -v 10.864545 24.067366 -5.000000 -vn 0.000000 0.000000 -0.938074 -v -15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.921476 -v -5.226422 49.726093 -5.000000 -vn 0.000000 0.000000 -1.639846 -v -48.907379 -10.395584 -5.000000 -vn 0.000000 0.000000 -0.156018 -v 11.909829 14.122147 -5.000000 -vn 0.000000 0.000000 -1.345728 -v -45.677273 -20.336832 -5.000000 -vn 0.000000 0.000000 -0.239254 -v 10.218524 22.079117 -5.000000 -vn 0.000000 0.000000 -0.961197 -v -25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.941141 -v -15.450850 47.552826 -5.000000 -vn 0.000000 0.000000 -1.690347 -v -50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -0.158937 -v 11.909829 14.122147 -5.000000 -vn 0.000000 0.000000 -1.292307 -v -48.907379 -10.395584 -5.000000 -vn 0.000000 0.000000 -0.211341 -v 10.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -1.004346 -v -33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -1.925906 -v -25.000000 43.301270 -5.000000 -vn 0.000000 0.000000 -1.271469 -v 11.909829 14.122147 -5.000000 -vn 0.000000 0.000000 -0.031757 -v -50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -1.838368 -v 10.864545 15.932633 -5.000000 -vn 0.000000 0.000000 -1.051199 -v 10.218524 17.920883 -5.000000 -vn 0.000000 0.000000 -0.038851 -v -33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -2.051544 -v 10.000000 20.000000 -5.000000 -vn 0.000000 0.000000 -1.767889 -v -48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -0.163654 -v 10.864545 15.932633 -5.000000 -vn 0.000000 0.000000 -1.210049 -v -50.000000 0.000000 -5.000000 -vn 0.000000 0.000000 -1.888957 -v -33.456528 37.157242 -5.000000 -vn 0.000000 0.000000 -0.192291 -v 10.218524 17.920883 -5.000000 -vn 0.000000 0.000000 -1.060345 -v -40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -1.807220 -v -45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -0.170109 -v 10.864545 15.932633 -5.000000 -vn 0.000000 0.000000 -1.164264 -v -48.907379 10.395584 -5.000000 -vn 0.000000 0.000000 -1.871808 -v -40.450851 29.389263 -5.000000 -vn 0.000000 0.000000 -0.179392 -v 10.218524 17.920883 -5.000000 -vn 0.000000 0.000000 -1.090393 -v -45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -1.178902 -v 10.864545 15.932633 -5.000000 -vn 0.000000 0.000000 -0.034542 -v -45.677273 20.336832 -5.000000 -vn 0.000000 0.000000 -1.928151 -v 10.218524 17.920883 -5.000000 -vn -0.328454 0.737720 0.000000 -v -15.450850 47.552826 -5.000000 -vn -0.310446 0.697273 0.000000 -v -25.000000 43.301270 5.000000 -vn -0.638901 1.434994 0.000000 -v -15.450850 47.552826 5.000000 -vn -0.328454 0.737720 0.000000 -v -25.000000 43.301270 5.000000 -vn -0.310446 0.697273 0.000000 -v -15.450850 47.552826 -5.000000 -vn -0.638901 1.434994 0.000000 -v -25.000000 43.301270 -5.000000 -vn -0.759080 -0.079783 0.000000 -v -48.907379 -10.395584 -5.000000 -vn -0.803112 -0.084411 0.000000 -v -50.000000 0.000000 5.000000 -vn -1.562191 -0.164193 0.000000 -v -50.000000 0.000000 -5.000000 -vn -0.759080 -0.079783 0.000000 -v -50.000000 0.000000 5.000000 -vn -0.803112 -0.084411 0.000000 -v -48.907379 -10.395584 -5.000000 -vn -1.562191 -0.164193 0.000000 -v -48.907379 -10.395584 5.000000 -vn -0.567213 -0.510721 0.000000 -v -33.456528 -37.157242 -5.000000 -vn -0.600116 -0.540347 0.000000 -v -40.450851 -29.389263 5.000000 -vn -1.167329 -1.051068 0.000000 -v -40.450851 -29.389263 -5.000000 -vn -0.567213 -0.510721 0.000000 -v -40.450851 -29.389263 5.000000 -vn -0.600116 -0.540347 0.000000 -v -33.456528 -37.157242 -5.000000 -vn -1.167329 -1.051068 0.000000 -v -33.456528 -37.157242 5.000000 -vn -0.661003 -0.381630 0.000000 -v -40.450851 -29.389263 -5.000000 -vn -0.699346 -0.403768 0.000000 -v -45.677273 -20.336832 5.000000 -vn -1.360350 -0.785398 0.000000 -v -45.677273 -20.336832 -5.000000 -vn -0.661003 -0.381630 0.000000 -v -45.677273 -20.336832 5.000000 -vn -0.699346 -0.403768 0.000000 -v -40.450851 -29.389263 -5.000000 -vn -1.360350 -0.785398 0.000000 -v -40.450851 -29.389263 5.000000 -vn 0.167896 -0.789889 0.000000 -v 5.226422 -49.726093 -5.000000 -vn 0.158691 -0.746582 0.000000 -v 15.450850 -47.552826 5.000000 -vn 0.326587 -1.536471 0.000000 -v 5.226422 -49.726093 5.000000 -vn 0.167896 -0.789889 0.000000 -v 15.450850 -47.552826 5.000000 -vn 0.158691 -0.746582 0.000000 -v 5.226422 -49.726093 -5.000000 -vn 0.326587 -1.536471 0.000000 -v 15.450850 -47.552826 -5.000000 -vn -0.759080 0.079783 0.000000 -v -50.000000 0.000000 -5.000000 -vn -0.803112 0.084411 0.000000 -v -48.907379 10.395584 5.000000 -vn -1.562191 0.164193 0.000000 -v -48.907379 10.395584 -5.000000 -vn -0.759080 0.079783 0.000000 -v -48.907379 10.395584 5.000000 -vn -0.803112 0.084411 0.000000 -v -50.000000 0.000000 -5.000000 -vn -1.562191 0.164193 0.000000 -v -50.000000 0.000000 5.000000 -vn 0.167896 0.789889 0.000000 -v 15.450850 47.552826 -5.000000 -vn 0.158691 0.746582 0.000000 -v 5.226422 49.726093 5.000000 -vn 0.326587 1.536471 0.000000 -v 15.450850 47.552826 5.000000 -vn 0.167896 0.789889 0.000000 -v 5.226422 49.726093 5.000000 -vn 0.158691 0.746582 0.000000 -v 15.450850 47.552826 -5.000000 -vn 0.326587 1.536471 0.000000 -v 5.226422 49.726093 -5.000000 -vn 0.474657 0.653310 0.000000 -v 33.456528 37.157242 -5.000000 -vn 0.448633 0.617491 0.000000 -v 25.000000 43.301270 5.000000 -vn 0.923291 1.270801 0.000000 -v 33.456528 37.157242 5.000000 -vn 0.474657 0.653310 0.000000 -v 25.000000 43.301270 5.000000 -vn 0.448633 0.617491 0.000000 -v 33.456528 37.157242 -5.000000 -vn 0.923291 1.270801 0.000000 -v 25.000000 43.301270 -5.000000 -vn -0.167896 0.789889 0.000000 -v -5.226422 49.726093 -5.000000 -vn -0.158691 0.746582 0.000000 -v -15.450850 47.552826 5.000000 -vn -0.326587 1.536471 0.000000 -v -5.226422 49.726093 5.000000 -vn -0.167896 0.789889 0.000000 -v -15.450850 47.552826 5.000000 -vn -0.158691 0.746582 0.000000 -v -5.226422 49.726093 -5.000000 -vn -0.326587 1.536471 0.000000 -v -15.450850 47.552826 -5.000000 -vn 0.328454 -0.737720 0.000000 -v 15.450850 -47.552826 -5.000000 -vn 0.310446 -0.697273 0.000000 -v 25.000000 -43.301270 5.000000 -vn 0.638901 -1.434994 0.000000 -v 15.450850 -47.552826 5.000000 -vn 0.328454 -0.737720 0.000000 -v 25.000000 -43.301270 5.000000 -vn 0.310446 -0.697273 0.000000 -v 15.450850 -47.552826 -5.000000 -vn 0.638901 -1.434994 0.000000 -v 25.000000 -43.301270 -5.000000 -vn 0.000000 -0.807535 0.000000 -v -5.226422 -49.726093 -5.000000 -vn 0.000000 -0.763261 0.000000 -v 5.226422 -49.726093 5.000000 -vn 0.000000 -1.570796 0.000000 -v -5.226422 -49.726093 5.000000 -vn 0.000000 -0.807535 0.000000 -v 5.226422 -49.726093 5.000000 -vn 0.000000 -0.763261 0.000000 -v -5.226422 -49.726093 -5.000000 -vn 0.000000 -1.570796 0.000000 -v 5.226422 -49.726093 -5.000000 -vn -0.167896 -0.789889 0.000000 -v -15.450850 -47.552826 -5.000000 -vn -0.158691 -0.746582 0.000000 -v -5.226422 -49.726093 5.000000 -vn -0.326587 -1.536471 0.000000 -v -15.450850 -47.552826 5.000000 -vn -0.167896 -0.789889 0.000000 -v -5.226422 -49.726093 5.000000 -vn -0.158691 -0.746582 0.000000 -v -15.450850 -47.552826 -5.000000 -vn -0.326587 -1.536471 0.000000 -v -5.226422 -49.726093 -5.000000 -vn -0.725904 0.235860 0.000000 -v -48.907379 10.395584 -5.000000 -vn -0.768012 0.249542 0.000000 -v -45.677273 20.336832 5.000000 -vn -1.493916 0.485403 0.000000 -v -45.677273 20.336832 -5.000000 -vn -0.725904 0.235860 0.000000 -v -45.677273 20.336832 5.000000 -vn -0.768012 0.249542 0.000000 -v -48.907379 10.395584 -5.000000 -vn -1.493916 0.485403 0.000000 -v -48.907379 10.395584 5.000000 -vn -1.297914 -0.421718 0.000000 -v 29.781475 22.079117 -5.000000 -vn -0.196002 -0.063685 0.000000 -v 29.135454 24.067366 5.000000 -vn -1.493916 -0.485403 0.000000 -v 29.135454 24.067366 -5.000000 -vn -1.297914 -0.421718 0.000000 -v 29.135454 24.067366 5.000000 -vn -0.196002 -0.063685 0.000000 -v 29.781475 22.079117 -5.000000 -vn -1.493916 -0.485403 0.000000 -v 29.781475 22.079117 5.000000 -vn -1.357231 -0.142651 0.000000 -v 30.000000 20.000000 -5.000000 -vn -0.204960 -0.021542 0.000000 -v 29.781475 22.079117 5.000000 -vn -1.562191 -0.164194 0.000000 -v 29.781475 22.079117 -5.000000 -vn -1.357231 -0.142651 0.000000 -v 29.781475 22.079117 5.000000 -vn -0.204960 -0.021542 0.000000 -v 30.000000 20.000000 -5.000000 -vn -1.562191 -0.164194 0.000000 -v 30.000000 20.000000 5.000000 -vn -1.181872 -0.682353 0.000000 -v 29.135454 24.067366 -5.000000 -vn -0.178478 -0.103044 0.000000 -v 28.090170 25.877853 5.000000 -vn -1.360350 -0.785397 0.000000 -v 28.090170 25.877853 -5.000000 -vn -1.181872 -0.682353 0.000000 -v 28.090170 25.877853 5.000000 -vn -0.178478 -0.103044 0.000000 -v 29.135454 24.067366 -5.000000 -vn -1.360350 -0.785397 0.000000 -v 29.135454 24.067366 5.000000 -vn 0.000000 0.206089 0.000000 -v 21.045284 10.054781 -5.000000 -vn 0.000000 1.364708 0.000000 -v 18.954716 10.054781 5.000000 -vn 0.000000 1.570796 0.000000 -v 21.045284 10.054781 5.000000 -vn 0.000000 0.206089 0.000000 -v 18.954716 10.054781 5.000000 -vn 0.000000 1.364708 0.000000 -v 21.045284 10.054781 -5.000000 -vn 0.000000 1.570796 0.000000 -v 18.954716 10.054781 -5.000000 -vn -0.083824 0.188272 0.000000 -v 25.000000 11.339746 -5.000000 -vn -0.555077 1.246722 0.000000 -v 23.090170 10.489434 5.000000 -vn -0.638901 1.434994 0.000000 -v 25.000000 11.339746 5.000000 -vn -0.083824 0.188272 0.000000 -v 23.090170 10.489434 5.000000 -vn -0.555077 1.246722 0.000000 -v 25.000000 11.339746 -5.000000 -vn -0.638901 1.434994 0.000000 -v 23.090170 10.489434 -5.000000 -vn 1.297914 0.421717 0.000000 -v 10.864545 15.932633 5.000000 -vn 0.196002 0.063685 0.000000 -v 10.218524 17.920883 -5.000000 -vn 1.493916 0.485402 0.000000 -v 10.218524 17.920883 5.000000 -vn 1.297914 0.421717 0.000000 -v 10.218524 17.920883 -5.000000 -vn 0.196002 0.063685 0.000000 -v 10.864545 15.932633 5.000000 -vn 1.493916 0.485402 0.000000 -v 10.864545 15.932633 -5.000000 -vn -1.357231 0.142651 0.000000 -v 29.781475 17.920883 -5.000000 -vn -0.204960 0.021542 0.000000 -v 30.000000 20.000000 5.000000 -vn -1.562191 0.164194 0.000000 -v 30.000000 20.000000 -5.000000 -vn -1.357231 0.142651 0.000000 -v 30.000000 20.000000 5.000000 -vn -0.204960 0.021542 0.000000 -v 29.781475 17.920883 -5.000000 -vn -1.562191 0.164194 0.000000 -v 29.781475 17.920883 5.000000 -vn 1.014175 -0.913168 0.000000 -v 11.909829 25.877853 5.000000 -vn 0.153154 -0.137900 0.000000 -v 13.308694 27.431448 -5.000000 -vn 1.167328 -1.051069 0.000000 -v 13.308694 27.431448 5.000000 -vn 1.014175 -0.913168 0.000000 -v 13.308694 27.431448 -5.000000 -vn 0.153154 -0.137900 0.000000 -v 11.909829 25.877853 5.000000 -vn 1.167328 -1.051069 0.000000 -v 11.909829 25.877853 -5.000000 -vn 0.042848 0.201585 0.000000 -v 18.954716 10.054781 -5.000000 -vn 0.283738 1.334885 0.000000 -v 16.909828 10.489434 5.000000 -vn 0.326586 1.536471 0.000000 -v 18.954716 10.054781 5.000000 -vn 0.042848 0.201585 0.000000 -v 16.909828 10.489434 5.000000 -vn 0.283738 1.334885 0.000000 -v 18.954716 10.054781 -5.000000 -vn 0.326586 1.536471 0.000000 -v 16.909828 10.489434 -5.000000 -vn -1.297914 0.421717 0.000000 -v 29.135454 15.932633 -5.000000 -vn -0.196002 0.063685 0.000000 -v 29.781475 17.920883 5.000000 -vn -1.493916 0.485402 0.000000 -v 29.781475 17.920883 -5.000000 -vn -1.297914 0.421717 0.000000 -v 29.781475 17.920883 5.000000 -vn -0.196002 0.063685 0.000000 -v 29.135454 15.932633 -5.000000 -vn -1.493916 0.485402 0.000000 -v 29.135454 15.932633 5.000000 -vn 0.083824 0.188271 0.000000 -v 16.909828 10.489434 -5.000000 -vn 0.555077 1.246722 0.000000 -v 15.000000 11.339746 5.000000 -vn 0.638901 1.434994 0.000000 -v 16.909828 10.489434 5.000000 -vn 0.083824 0.188271 0.000000 -v 15.000000 11.339746 5.000000 -vn 0.555077 1.246722 0.000000 -v 16.909828 10.489434 -5.000000 -vn 0.638901 1.434994 0.000000 -v 15.000000 11.339746 -5.000000 -vn -0.042848 -0.201585 0.000000 -v 21.045284 29.945217 -5.000000 -vn -0.283738 -1.334886 0.000000 -v 23.090170 29.510565 5.000000 -vn -0.326586 -1.536471 0.000000 -v 21.045284 29.945217 5.000000 -vn -0.042848 -0.201585 0.000000 -v 23.090170 29.510565 5.000000 -vn -0.283738 -1.334886 0.000000 -v 21.045284 29.945217 -5.000000 -vn -0.326586 -1.536471 0.000000 -v 23.090170 29.510565 -5.000000 -vn 0.000000 -0.206089 0.000000 -v 18.954716 29.945217 -5.000000 -vn 0.000000 -1.364708 0.000000 -v 21.045284 29.945217 5.000000 -vn 0.000000 -1.570796 0.000000 -v 18.954716 29.945217 5.000000 -vn 0.000000 -0.206089 0.000000 -v 21.045284 29.945217 5.000000 -vn 0.000000 -1.364708 0.000000 -v 18.954716 29.945217 -5.000000 -vn 0.000000 -1.570796 0.000000 -v 21.045284 29.945217 -5.000000 -vn -1.181872 0.682353 0.000000 -v 28.090170 14.122147 -5.000000 -vn -0.178478 0.103044 0.000000 -v 29.135454 15.932633 5.000000 -vn -1.360350 0.785398 0.000000 -v 29.135454 15.932633 -5.000000 -vn -1.181872 0.682353 0.000000 -v 29.135454 15.932633 5.000000 -vn -0.178478 0.103044 0.000000 -v 28.090170 14.122147 -5.000000 -vn -1.360350 0.785398 0.000000 -v 28.090170 14.122147 5.000000 -vn 1.297914 -0.421718 0.000000 -v 10.218524 22.079117 5.000000 -vn 0.196002 -0.063685 0.000000 -v 10.864545 24.067366 -5.000000 -vn 1.493916 -0.485403 0.000000 -v 10.864545 24.067366 5.000000 -vn 1.297914 -0.421718 0.000000 -v 10.864545 24.067366 -5.000000 -vn 0.196002 -0.063685 0.000000 -v 10.218524 22.079117 5.000000 -vn 1.493916 -0.485403 0.000000 -v 10.218524 22.079117 -5.000000 -vn 1.014175 0.913168 0.000000 -v 13.308694 12.568551 5.000000 -vn 0.153154 0.137900 0.000000 -v 11.909829 14.122147 -5.000000 -vn 1.167329 1.051068 0.000000 -v 11.909829 14.122147 5.000000 -vn 1.014175 0.913168 0.000000 -v 11.909829 14.122147 -5.000000 -vn 0.153154 0.137900 0.000000 -v 13.308694 12.568551 5.000000 -vn 1.167329 1.051068 0.000000 -v 13.308694 12.568551 -5.000000 -vn -1.014175 -0.913168 0.000000 -v 28.090170 25.877853 -5.000000 -vn -0.153154 -0.137900 0.000000 -v 26.691305 27.431448 5.000000 -vn -1.167328 -1.051069 0.000000 -v 26.691305 27.431448 -5.000000 -vn -1.014175 -0.913168 0.000000 -v 26.691305 27.431448 5.000000 -vn -0.153154 -0.137900 0.000000 -v 28.090170 25.877853 -5.000000 -vn -1.167328 -1.051069 0.000000 -v 28.090170 25.877853 5.000000 -vn 1.357232 -0.142651 0.000000 -v 10.000000 20.000000 5.000000 -vn 0.204960 -0.021542 0.000000 -v 10.218524 22.079117 -5.000000 -vn 1.562191 -0.164193 0.000000 -v 10.218524 22.079117 5.000000 -vn 1.357232 -0.142651 0.000000 -v 10.218524 22.079117 -5.000000 -vn 0.204960 -0.021542 0.000000 -v 10.000000 20.000000 5.000000 -vn 1.562191 -0.164193 0.000000 -v 10.000000 20.000000 -5.000000 -vn 0.042848 -0.201585 0.000000 -v 16.909828 29.510565 -5.000000 -vn 0.283737 -1.334885 0.000000 -v 18.954716 29.945217 5.000000 -vn 0.326586 -1.536471 0.000000 -v 16.909828 29.510565 5.000000 -vn 0.042848 -0.201585 0.000000 -v 18.954716 29.945217 5.000000 -vn 0.283737 -1.334885 0.000000 -v 16.909828 29.510565 -5.000000 -vn 0.326586 -1.536471 0.000000 -v 18.954716 29.945217 -5.000000 -vn -0.121136 -0.166729 0.000000 -v 25.000000 28.660254 -5.000000 -vn -0.802155 -1.104072 0.000000 -v 26.691305 27.431448 5.000000 -vn -0.923291 -1.270801 0.000000 -v 25.000000 28.660254 5.000000 -vn -0.121136 -0.166729 0.000000 -v 26.691305 27.431448 5.000000 -vn -0.802155 -1.104072 0.000000 -v 25.000000 28.660254 -5.000000 -vn -0.923291 -1.270801 0.000000 -v 26.691305 27.431448 -5.000000 -vn 0.083824 -0.188271 0.000000 -v 15.000000 28.660254 -5.000000 -vn 0.555077 -1.246722 0.000000 -v 16.909828 29.510565 5.000000 -vn 0.638901 -1.434994 0.000000 -v 15.000000 28.660254 5.000000 -vn 0.083824 -0.188271 0.000000 -v 16.909828 29.510565 5.000000 -vn 0.555077 -1.246722 0.000000 -v 15.000000 28.660254 -5.000000 -vn 0.638901 -1.434994 0.000000 -v 16.909828 29.510565 -5.000000 -vn -1.014175 0.913168 0.000000 -v 26.691305 12.568551 -5.000000 -vn -0.153154 0.137900 0.000000 -v 28.090170 14.122147 5.000000 -vn -1.167329 1.051068 0.000000 -v 28.090170 14.122147 -5.000000 -vn -1.014175 0.913168 0.000000 -v 28.090170 14.122147 5.000000 -vn -0.153154 0.137900 0.000000 -v 26.691305 12.568551 -5.000000 -vn -1.167329 1.051068 0.000000 -v 26.691305 12.568551 5.000000 -vn -0.083824 -0.188272 0.000000 -v 23.090170 29.510565 -5.000000 -vn -0.555077 -1.246722 0.000000 -v 25.000000 28.660254 5.000000 -vn -0.638901 -1.434994 0.000000 -v 23.090170 29.510565 5.000000 -vn -0.083824 -0.188272 0.000000 -v 25.000000 28.660254 5.000000 -vn -0.555077 -1.246722 0.000000 -v 23.090170 29.510565 -5.000000 -vn -0.638901 -1.434994 0.000000 -v 25.000000 28.660254 -5.000000 -vn 0.121136 0.166729 0.000000 -v 15.000000 11.339746 -5.000000 -vn 0.802155 1.104072 0.000000 -v 13.308694 12.568551 5.000000 -vn 0.923291 1.270801 0.000000 -v 15.000000 11.339746 5.000000 -vn 0.121136 0.166729 0.000000 -v 13.308694 12.568551 5.000000 -vn 0.802155 1.104072 0.000000 -v 15.000000 11.339746 -5.000000 -vn 0.923291 1.270801 0.000000 -v 13.308694 12.568551 -5.000000 -vn -0.121136 0.166729 0.000000 -v 26.691305 12.568551 -5.000000 -vn -0.802155 1.104072 0.000000 -v 25.000000 11.339746 5.000000 -vn -0.923291 1.270801 0.000000 -v 26.691305 12.568551 5.000000 -vn -0.121136 0.166729 0.000000 -v 25.000000 11.339746 5.000000 -vn -0.802155 1.104072 0.000000 -v 26.691305 12.568551 -5.000000 -vn -0.923291 1.270801 0.000000 -v 25.000000 11.339746 -5.000000 -vn -0.042848 0.201585 0.000000 -v 23.090170 10.489434 -5.000000 -vn -0.283738 1.334885 0.000000 -v 21.045284 10.054781 5.000000 -vn -0.326587 1.536471 0.000000 -v 23.090170 10.489434 5.000000 -vn -0.042848 0.201585 0.000000 -v 21.045284 10.054781 5.000000 -vn -0.283738 1.334885 0.000000 -v 23.090170 10.489434 -5.000000 -vn -0.326587 1.536471 0.000000 -v 21.045284 10.054781 -5.000000 -vn 0.121136 -0.166729 0.000000 -v 13.308694 27.431448 -5.000000 -vn 0.802155 -1.104072 0.000000 -v 15.000000 28.660254 5.000000 -vn 0.923291 -1.270801 0.000000 -v 13.308694 27.431448 5.000000 -vn 0.121136 -0.166729 0.000000 -v 15.000000 28.660254 5.000000 -vn 0.802155 -1.104072 0.000000 -v 13.308694 27.431448 -5.000000 -vn 0.923291 -1.270801 0.000000 -v 15.000000 28.660254 -5.000000 -vn 1.181872 0.682353 0.000000 -v 11.909829 14.122147 5.000000 -vn 0.178478 0.103044 0.000000 -v 10.864545 15.932633 -5.000000 -vn 1.360350 0.785398 0.000000 -v 10.864545 15.932633 5.000000 -vn 1.181872 0.682353 0.000000 -v 10.864545 15.932633 -5.000000 -vn 0.178478 0.103044 0.000000 -v 11.909829 14.122147 5.000000 -vn 1.360350 0.785398 0.000000 -v 11.909829 14.122147 -5.000000 -vn 1.357232 0.142651 0.000000 -v 10.218524 17.920883 5.000000 -vn 0.204960 0.021542 0.000000 -v 10.000000 20.000000 -5.000000 -vn 1.562191 0.164193 0.000000 -v 10.000000 20.000000 5.000000 -vn 1.357232 0.142651 0.000000 -v 10.000000 20.000000 -5.000000 -vn 0.204960 0.021542 0.000000 -v 10.218524 17.920883 5.000000 -vn 1.562191 0.164193 0.000000 -v 10.218524 17.920883 -5.000000 -vn 1.181872 -0.682353 0.000000 -v 10.864545 24.067366 5.000000 -vn 0.178478 -0.103044 0.000000 -v 11.909829 25.877853 -5.000000 -vn 1.360350 -0.785397 0.000000 -v 11.909829 25.877853 5.000000 -vn 1.181872 -0.682353 0.000000 -v 11.909829 25.877853 -5.000000 -vn 0.178478 -0.103044 0.000000 -v 10.864545 24.067366 5.000000 -vn 1.360350 -0.785397 0.000000 -v 10.864545 24.067366 -5.000000 -# 720 vertices, 0 vertices normals - -f 1//1 2//2 3//3 -f 4//4 5//5 6//6 -f 7//7 8//8 9//9 -f 10//10 11//11 12//12 -f 13//13 14//14 15//15 -f 16//16 17//17 18//18 -f 19//19 20//20 21//21 -f 22//22 23//23 24//24 -f 25//25 26//26 27//27 -f 28//28 29//29 30//30 -f 31//31 32//32 33//33 -f 34//34 35//35 36//36 -f 37//37 38//38 39//39 -f 40//40 41//41 42//42 -f 43//43 44//44 45//45 -f 46//46 47//47 48//48 -f 49//49 50//50 51//51 -f 52//52 53//53 54//54 -f 55//55 56//56 57//57 -f 58//58 59//59 60//60 -f 61//61 62//62 63//63 -f 64//64 65//65 66//66 -f 67//67 68//68 69//69 -f 70//70 71//71 72//72 -f 73//73 74//74 75//75 -f 76//76 77//77 78//78 -f 79//79 80//80 81//81 -f 82//82 83//83 84//84 -f 85//85 86//86 87//87 -f 88//88 89//89 90//90 -f 91//91 92//92 93//93 -f 94//94 95//95 96//96 -f 97//97 98//98 99//99 -f 100//100 101//101 102//102 -f 103//103 104//104 105//105 -f 106//106 107//107 108//108 -f 109//109 110//110 111//111 -f 112//112 113//113 114//114 -f 115//115 116//116 117//117 -f 118//118 119//119 120//120 -f 121//121 122//122 123//123 -f 124//124 125//125 126//126 -f 127//127 128//128 129//129 -f 130//130 131//131 132//132 -f 133//133 134//134 135//135 -f 136//136 137//137 138//138 -f 139//139 140//140 141//141 -f 142//142 143//143 144//144 -f 145//145 146//146 147//147 -f 148//148 149//149 150//150 -f 151//151 152//152 153//153 -f 154//154 155//155 156//156 -f 157//157 158//158 159//159 -f 160//160 161//161 162//162 -f 163//163 164//164 165//165 -f 166//166 167//167 168//168 -f 169//169 170//170 171//171 -f 172//172 173//173 174//174 -f 175//175 176//176 177//177 -f 178//178 179//179 180//180 -f 181//181 182//182 183//183 -f 184//184 185//185 186//186 -f 187//187 188//188 189//189 -f 190//190 191//191 192//192 -f 193//193 194//194 195//195 -f 196//196 197//197 198//198 -f 199//199 200//200 201//201 -f 202//202 203//203 204//204 -f 205//205 206//206 207//207 -f 208//208 209//209 210//210 -f 211//211 212//212 213//213 -f 214//214 215//215 216//216 -f 217//217 218//218 219//219 -f 220//220 221//221 222//222 -f 223//223 224//224 225//225 -f 226//226 227//227 228//228 -f 229//229 230//230 231//231 -f 232//232 233//233 234//234 -f 235//235 236//236 237//237 -f 238//238 239//239 240//240 -f 241//241 242//242 243//243 -f 244//244 245//245 246//246 -f 247//247 248//248 249//249 -f 250//250 251//251 252//252 -f 253//253 254//254 255//255 -f 256//256 257//257 258//258 -f 259//259 260//260 261//261 -f 262//262 263//263 264//264 -f 265//265 266//266 267//267 -f 268//268 269//269 270//270 -f 271//271 272//272 273//273 -f 274//274 275//275 276//276 -f 277//277 278//278 279//279 -f 280//280 281//281 282//282 -f 283//283 284//284 285//285 -f 286//286 287//287 288//288 -f 289//289 290//290 291//291 -f 292//292 293//293 294//294 -f 295//295 296//296 297//297 -f 298//298 299//299 300//300 -f 301//301 302//302 303//303 -f 304//304 305//305 306//306 -f 307//307 308//308 309//309 -f 310//310 311//311 312//312 -f 313//313 314//314 315//315 -f 316//316 317//317 318//318 -f 319//319 320//320 321//321 -f 322//322 323//323 324//324 -f 325//325 326//326 327//327 -f 328//328 329//329 330//330 -f 331//331 332//332 333//333 -f 334//334 335//335 336//336 -f 337//337 338//338 339//339 -f 340//340 341//341 342//342 -f 343//343 344//344 345//345 -f 346//346 347//347 348//348 -f 349//349 350//350 351//351 -f 352//352 353//353 354//354 -f 355//355 356//356 357//357 -f 358//358 359//359 360//360 -f 361//361 362//362 363//363 -f 364//364 365//365 366//366 -f 367//367 368//368 369//369 -f 370//370 371//371 372//372 -f 373//373 374//374 375//375 -f 376//376 377//377 378//378 -f 379//379 380//380 381//381 -f 382//382 383//383 384//384 -f 385//385 386//386 387//387 -f 388//388 389//389 390//390 -f 391//391 392//392 393//393 -f 394//394 395//395 396//396 -f 397//397 398//398 399//399 -f 400//400 401//401 402//402 -f 403//403 404//404 405//405 -f 406//406 407//407 408//408 -f 409//409 410//410 411//411 -f 412//412 413//413 414//414 -f 415//415 416//416 417//417 -f 418//418 419//419 420//420 -f 421//421 422//422 423//423 -f 424//424 425//425 426//426 -f 427//427 428//428 429//429 -f 430//430 431//431 432//432 -f 433//433 434//434 435//435 -f 436//436 437//437 438//438 -f 439//439 440//440 441//441 -f 442//442 443//443 444//444 -f 445//445 446//446 447//447 -f 448//448 449//449 450//450 -f 451//451 452//452 453//453 -f 454//454 455//455 456//456 -f 457//457 458//458 459//459 -f 460//460 461//461 462//462 -f 463//463 464//464 465//465 -f 466//466 467//467 468//468 -f 469//469 470//470 471//471 -f 472//472 473//473 474//474 -f 475//475 476//476 477//477 -f 478//478 479//479 480//480 -f 481//481 482//482 483//483 -f 484//484 485//485 486//486 -f 487//487 488//488 489//489 -f 490//490 491//491 492//492 -f 493//493 494//494 495//495 -f 496//496 497//497 498//498 -f 499//499 500//500 501//501 -f 502//502 503//503 504//504 -f 505//505 506//506 507//507 -f 508//508 509//509 510//510 -f 511//511 512//512 513//513 -f 514//514 515//515 516//516 -f 517//517 518//518 519//519 -f 520//520 521//521 522//522 -f 523//523 524//524 525//525 -f 526//526 527//527 528//528 -f 529//529 530//530 531//531 -f 532//532 533//533 534//534 -f 535//535 536//536 537//537 -f 538//538 539//539 540//540 -f 541//541 542//542 543//543 -f 544//544 545//545 546//546 -f 547//547 548//548 549//549 -f 550//550 551//551 552//552 -f 553//553 554//554 555//555 -f 556//556 557//557 558//558 -f 559//559 560//560 561//561 -f 562//562 563//563 564//564 -f 565//565 566//566 567//567 -f 568//568 569//569 570//570 -f 571//571 572//572 573//573 -f 574//574 575//575 576//576 -f 577//577 578//578 579//579 -f 580//580 581//581 582//582 -f 583//583 584//584 585//585 -f 586//586 587//587 588//588 -f 589//589 590//590 591//591 -f 592//592 593//593 594//594 -f 595//595 596//596 597//597 -f 598//598 599//599 600//600 -f 601//601 602//602 603//603 -f 604//604 605//605 606//606 -f 607//607 608//608 609//609 -f 610//610 611//611 612//612 -f 613//613 614//614 615//615 -f 616//616 617//617 618//618 -f 619//619 620//620 621//621 -f 622//622 623//623 624//624 -f 625//625 626//626 627//627 -f 628//628 629//629 630//630 -f 631//631 632//632 633//633 -f 634//634 635//635 636//636 -f 637//637 638//638 639//639 -f 640//640 641//641 642//642 -f 643//643 644//644 645//645 -f 646//646 647//647 648//648 -f 649//649 650//650 651//651 -f 652//652 653//653 654//654 -f 655//655 656//656 657//657 -f 658//658 659//659 660//660 -f 661//661 662//662 663//663 -f 664//664 665//665 666//666 -f 667//667 668//668 669//669 -f 670//670 671//671 672//672 -f 673//673 674//674 675//675 -f 676//676 677//677 678//678 -f 679//679 680//680 681//681 -f 682//682 683//683 684//684 -f 685//685 686//686 687//687 -f 688//688 689//689 690//690 -f 691//691 692//692 693//693 -f 694//694 695//695 696//696 -f 697//697 698//698 699//699 -f 700//700 701//701 702//702 -f 703//703 704//704 705//705 -f 706//706 707//707 708//708 -f 709//709 710//710 711//711 -f 712//712 713//713 714//714 -f 715//715 716//716 717//717 -f 718//718 719//719 720//720 -# 240 faces, 0 coords texture - -# End of File From 878f3b30ddb41b6ffee37c96a4ff1e220b2d3951 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Dec 2022 17:46:27 +0100 Subject: [PATCH 46/59] wip adding separate config values for support tree algorithms --- src/libslic3r/Preset.cpp | 20 ++ src/libslic3r/PrintConfig.cpp | 366 +++++++++++++------------- src/libslic3r/PrintConfig.hpp | 57 ++++ src/libslic3r/SLAPrint.cpp | 80 ++++-- src/slic3r/GUI/ConfigManipulation.cpp | 55 ++-- src/slic3r/GUI/Tab.cpp | 96 +++++-- src/slic3r/GUI/Tab.hpp | 5 +- 7 files changed, 432 insertions(+), 247 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ce3c27bf6..9dad49d4c 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -495,6 +495,7 @@ static std::vector s_Preset_sla_print_options { "faded_layers", "supports_enable", "support_tree_type", + "support_head_front_diameter", "support_head_penetration", "support_head_width", @@ -512,6 +513,25 @@ static std::vector 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", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ebdfd2ff6..f39857759 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3273,6 +3273,191 @@ 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; + + 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)); + + def = this->add(prefix + "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(prefix + "support_max_weight_on_model", coFloat); + def->label = L("Max weight on model"); + def->category = L("Supports"); + def->tooltip = 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.)); + + def = this->add(prefix + "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::get_enum_values(); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values = ConfigOptionEnum::get_enum_names(); + def->enum_labels = ConfigOptionEnum::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"); + 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.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; + def->set_default_value(new ConfigOptionFloat(15.0)); + + def = this->add(prefix + "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(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; @@ -3619,185 +3804,8 @@ void PrintConfigDef::init_sla_params() def->mode = comAdvanced; 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_max_weight_on_model", coFloat); - def->label = L("Max weight on model"); - def->category = L("Supports"); - def->tooltip = 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.)); - - 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::get_enum_values(); - def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values = ConfigOptionEnum::get_enum_names(); - def->enum_labels = ConfigOptionEnum::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"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 47c43148a..5dee704ba 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -185,6 +185,7 @@ private: void init_fff_params(); void init_extruder_option_keys(); void init_sla_params(); + void init_sla_support_params(const std::string &method_prefix); std::vector m_extruder_option_keys; std::vector m_extruder_retract_keys; @@ -891,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, 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)) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 21895f943..6bdf415f9 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -44,29 +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()); - scfg.max_weight_on_model_support = c.support_max_weight_on_model.getFloat(); + + 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; } diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 952d453a2..9dd854029 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -374,25 +374,45 @@ 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_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); + toggle_field("support_pillar_widening_factor", supports_en && is_default_tree); + toggle_field("support_max_weight_on_model", supports_en && is_default_tree); + toggle_field("support_points_density_relative", supports_en && is_default_tree); + toggle_field("support_points_minimal_distance", supports_en && is_default_tree); + + 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", supports_en && is_branching_tree); + toggle_field("branchingsupport_pillar_connection_mode", supports_en && is_branching_tree); + 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", supports_en && is_branching_tree); + 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("branchingsupport_points_density_relative", supports_en && is_branching_tree); + toggle_field("branchingsupport_points_minimal_distance", supports_en && is_branching_tree); bool pad_en = config->opt_bool("pad_enable"); @@ -407,7 +427,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); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 67f603434..60742dd4d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4821,6 +4821,43 @@ void TabSLAMaterial::update() wxGetApp().mainframe->on_config_changed(m_config); } +void TabSLAPrint::build_sla_support_params(const std::string &prefix, + const Slic3r::GUI::PageShp &page) +{ + auto optgroup = page->new_optgroup(L("Support head")); + optgroup->append_single_option_line(prefix + "support_head_front_diameter"); + optgroup->append_single_option_line(prefix + "support_head_penetration"); + optgroup->append_single_option_line(prefix + "support_head_width"); + + optgroup = page->new_optgroup(L("Support pillar")); + optgroup->append_single_option_line(prefix + "support_pillar_diameter"); + optgroup->append_single_option_line(prefix + "support_small_pillar_diameter_percent"); + optgroup->append_single_option_line(prefix + "support_max_bridges_on_pillar"); + + optgroup->append_single_option_line(prefix + "support_pillar_connection_mode"); + optgroup->append_single_option_line(prefix + "support_buildplate_only"); + optgroup->append_single_option_line(prefix + "support_pillar_widening_factor"); + optgroup->append_single_option_line(prefix + "support_max_weight_on_model"); + optgroup->append_single_option_line(prefix + "support_base_diameter"); + optgroup->append_single_option_line(prefix + "support_base_height"); + optgroup->append_single_option_line(prefix + "support_base_safety_distance"); + + // Mirrored parameter from Pad page for toggling elevation on the same page + optgroup->append_single_option_line(prefix + "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(prefix + "support_critical_angle"); + optgroup->append_single_option_line(prefix + "support_max_bridge_length"); + optgroup->append_single_option_line(prefix + "support_max_pillar_link_distance"); +} + void TabSLAPrint::build() { m_presets = &m_preset_bundle->sla_prints; @@ -4833,42 +4870,47 @@ void TabSLAPrint::build() optgroup->append_single_option_line("faded_layers"); page = add_options_page(L("Supports"), "support"/*"sla_supports"*/); + +// page = add_options_page(L("Branching"), "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"); + build_sla_support_params("", page); + build_sla_support_params("branching", page); +// 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 = 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_max_weight_on_model"); - 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"); +// 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_max_weight_on_model"); +// 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"); +// // 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); +// 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"); +// 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"); optgroup = page->new_optgroup(L("Automatic generation")); optgroup->append_single_option_line("support_points_density_relative"); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index f5dd4c522..e95050f33 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -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,9 @@ public: class TabSLAPrint : public Tab { + void build_sla_support_params(const std::string &prefix, + const Slic3r::GUI::PageShp &page); + public: TabSLAPrint(wxBookCtrlBase* parent) : Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_SLA_PRINT) {} From 6238595ac6c80fb57961ee5ed34418b7040d5e8b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 14 Dec 2022 13:14:25 +0100 Subject: [PATCH 47/59] adding separate config values for support tree algorithms Realize config matrix on supports tab --- src/libslic3r/PrintConfig.cpp | 52 ++++++++++++---- src/libslic3r/libslic3r.h | 2 + src/slic3r/GUI/ConfigManipulation.cpp | 17 +++-- src/slic3r/GUI/Tab.cpp | 90 +++++++++++---------------- src/slic3r/GUI/Tab.hpp | 5 +- 5 files changed, 90 insertions(+), 76 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f39857759..926cfd186 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3277,6 +3277,9 @@ 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"); @@ -3326,20 +3329,28 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) 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 = L( + 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(3)); + 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 = L( + 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"); @@ -3347,9 +3358,13 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) 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 = L("Controls the bridge type between two neighboring pillars." + 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."); @@ -3373,12 +3388,16 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) def = this->add(prefix + "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 " + + 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. What is garanteed is that thickness will not " - "exceed \"support_base_diameter\""); + "change in the future."); def->min = 0; def->max = 1; @@ -3434,13 +3453,22 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) def->sidetext = L("mm"); def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(15.0)); + + 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 = L("The max distance of two pillars to get linked with each other." - " A zero value will prohibit pillar cascading."); + 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; @@ -3801,7 +3829,7 @@ void PrintConfigDef::init_sla_params() def->enum_labels[0] = L("Default"); def->enum_labels[1] = L("Branching"); // TODO: def->enum_labels[2] = L("Organic"); - def->mode = comAdvanced; + def->mode = comSimple; def->set_default_value(new ConfigOptionEnum(sla::SupportTreeType::Default)); init_sla_support_params(""); diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index d5a21cf21..79945867b 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -380,6 +380,8 @@ inline IntegerOnly fast_round_up(double a) return a == 0.49999999999999994 ? I(0) : I(floor(a + 0.5)); } +template using SamePair = std::pair; + } // namespace Slic3r #endif // _libslic3r_h_ diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 9dd854029..85fe3e53e 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -390,29 +390,28 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) 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_default_tree); - toggle_field("support_max_weight_on_model", supports_en && is_default_tree); - toggle_field("support_points_density_relative", supports_en && is_default_tree); - toggle_field("support_points_minimal_distance", supports_en && is_default_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", supports_en && is_branching_tree); - toggle_field("branchingsupport_pillar_connection_mode", 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", 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("branchingsupport_points_density_relative", supports_en && is_branching_tree); - toggle_field("branchingsupport_points_minimal_distance", supports_en && is_branching_tree); + + toggle_field("support_points_density_relative", supports_en); + toggle_field("support_points_minimal_distance", supports_en); bool pad_en = config->opt_bool("pad_enable"); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 60742dd4d..481b16222 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4821,29 +4821,46 @@ void TabSLAMaterial::update() wxGetApp().mainframe->on_config_changed(m_config); } -void TabSLAPrint::build_sla_support_params(const std::string &prefix, +static void add_options_into_line(ConfigOptionsGroupShp &optgroup, + const std::vector> &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> &prefixes, const Slic3r::GUI::PageShp &page) { + auto optgroup = page->new_optgroup(L("Support head")); - optgroup->append_single_option_line(prefix + "support_head_front_diameter"); - optgroup->append_single_option_line(prefix + "support_head_penetration"); - optgroup->append_single_option_line(prefix + "support_head_width"); + 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")); - optgroup->append_single_option_line(prefix + "support_pillar_diameter"); - optgroup->append_single_option_line(prefix + "support_small_pillar_diameter_percent"); - optgroup->append_single_option_line(prefix + "support_max_bridges_on_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"); - optgroup->append_single_option_line(prefix + "support_pillar_connection_mode"); - optgroup->append_single_option_line(prefix + "support_buildplate_only"); - optgroup->append_single_option_line(prefix + "support_pillar_widening_factor"); - optgroup->append_single_option_line(prefix + "support_max_weight_on_model"); - optgroup->append_single_option_line(prefix + "support_base_diameter"); - optgroup->append_single_option_line(prefix + "support_base_height"); - optgroup->append_single_option_line(prefix + "support_base_safety_distance"); + 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 - optgroup->append_single_option_line(prefix + "support_object_elevation"); + add_options_into_line(optgroup, prefixes, "support_object_elevation"); Line line{ "", "" }; line.full_width = 1; @@ -4853,9 +4870,9 @@ void TabSLAPrint::build_sla_support_params(const std::string &prefix, optgroup->append_line(line); optgroup = page->new_optgroup(L("Connection of the support sticks and junctions")); - optgroup->append_single_option_line(prefix + "support_critical_angle"); - optgroup->append_single_option_line(prefix + "support_max_bridge_length"); - optgroup->append_single_option_line(prefix + "support_max_pillar_link_distance"); + 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() @@ -4871,46 +4888,11 @@ void TabSLAPrint::build() page = add_options_page(L("Supports"), "support"/*"sla_supports"*/); -// page = add_options_page(L("Branching"), "supports"); - optgroup = page->new_optgroup(L("Supports")); optgroup->append_single_option_line("supports_enable"); optgroup->append_single_option_line("support_tree_type"); - build_sla_support_params("", page); - build_sla_support_params("branching", page); -// 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_max_weight_on_model"); -// 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"); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index e95050f33..c060eb7fd 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -526,7 +526,10 @@ public: class TabSLAPrint : public Tab { - void build_sla_support_params(const std::string &prefix, + // 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> &methods, const Slic3r::GUI::PageShp &page); public: From 234167534bcc0597b02feed48530ff7972469c63 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 15 Dec 2022 16:25:31 +0100 Subject: [PATCH 48/59] wip on branching tree avoidance again --- src/libslic3r/BranchingTree/BranchingTree.cpp | 12 +++++- src/libslic3r/BranchingTree/BranchingTree.hpp | 6 +++ src/libslic3r/SLA/BranchingTreeSLA.cpp | 43 +++++++++++++++++-- src/libslic3r/SLA/SupportTreeUtils.hpp | 2 +- src/libslic3r/SLAPrint.cpp | 19 ++++++++ 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 72ecf8c86..8f1d322a1 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -76,7 +76,17 @@ void build_tree(PointCloud &nodes, Builder &builder) switch (type) { case BED: { closest_node.weight = w; - if ((routed = builder.add_ground_bridge(node, closest_node))) { + 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; + 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))) { closest_node.left = closest_node.right = node_id; nodes.get(closest_node_id) = closest_node; nodes.mark_unreachable(closest_node_id); diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index 2d372452b..06ee3ee14 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -105,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 suggest_avoidance(const Node &from, + float max_bridge_len) + { + return {}; + } + // Report nodes that can not be routed to an endpoint (model or ground) virtual void report_unroutable(const Node &j) = 0; diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index d88bdb1b0..d4778a0ac 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -29,7 +29,7 @@ class BranchingTreeBuilder: public branchingtree::Builder { // 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) const { @@ -99,9 +99,10 @@ class BranchingTreeBuilder: public branchingtree::Builder { 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.weight += node.pos.z(); - dst.Rmin = std::max(node.Rmin, dst.Rmin); + 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; @@ -144,8 +145,18 @@ public: bool add_mesh_bridge(const branchingtree::Node &from, const branchingtree::Node &to) override; + std::optional suggest_avoidance(const branchingtree::Node &from, + float max_bridge_len) override;; + void report_unroutable(const branchingtree::Node &j) override { + 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(); @@ -279,6 +290,32 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from, return bool(anchor); } +static std::optional get_avoidance(const GroundConnection &conn, + float maxdist) +{ + return {}; +} + +std::optional BranchingTreeBuilder::suggest_avoidance( + const branchingtree::Node &from, float max_bridge_len) +{ + double glvl = ground_level(m_sm); + branchingtree::Node dst = from; + dst.pos.z() = glvl; + dst.weight += from.pos.z() - glvl; + bool succ = add_ground_bridge(from, dst); + + std::optional ret; + + if (succ) { + auto it = m_gnd_connections.find(from.id); + if (it != m_gnd_connections.end()) + ret = get_avoidance(it->second, max_bridge_len); + } + + return ret; +} + inline void build_pillars(SupportTreeBuilder &builder, BranchingTreeBuilder &vbuilder, const SupportableMesh &sm) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index a47f8d662..6c1707953 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -547,7 +547,7 @@ Vec3d check_ground_route( auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - if (std::isinf(gndhit.distance()) && sm.cfg.object_elevation_mm < EPSILON) { + if (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)); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 6bdf415f9..d991dfe05 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -862,6 +862,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector Date: Mon, 2 Jan 2023 17:48:41 +0100 Subject: [PATCH 49/59] Still WIP on branching tree avoidance --- src/libslic3r/BranchingTree/BranchingTree.cpp | 24 ++++++-- src/libslic3r/SLA/BranchingTreeSLA.cpp | 55 ++++++++++++++++--- 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 8f1d322a1..01f4b2724 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -76,16 +76,28 @@ 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 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).squaredNorm(); + 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); } +// 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; +// 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))) { closest_node.left = closest_node.right = node_id; nodes.get(closest_node_id) = closest_node; diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index d4778a0ac..7f4b9853c 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -146,7 +146,7 @@ public: const branchingtree::Node &to) override; std::optional suggest_avoidance(const branchingtree::Node &from, - float max_bridge_len) override;; + float max_bridge_len) override; void report_unroutable(const branchingtree::Node &j) override { @@ -293,27 +293,64 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from, static std::optional get_avoidance(const GroundConnection &conn, float maxdist) { - return {}; + std::optional ret; + + if (conn) { + if (conn.path.size() > 1) { + ret = conn.path[1].pos.cast(); + } else { + Vec3f pbeg = conn.path[0].pos.cast(); + Vec3f pend = conn.pillar_base->pos.cast(); + pbeg.z() = std::max(pbeg.z() - maxdist, pend.z()); + ret = pbeg; + } + } + + return ret; } std::optional BranchingTreeBuilder::suggest_avoidance( const branchingtree::Node &from, float max_bridge_len) { + std::optional ret; + double glvl = ground_level(m_sm); branchingtree::Node dst = from; dst.pos.z() = glvl; dst.weight += from.pos.z() - glvl; - bool succ = add_ground_bridge(from, dst); + sla::Junction j{from.pos.cast(), get_radius(from)}; - std::optional ret; +// auto found_it = m_ground_mem.find(from.id); +// if (found_it != m_ground_mem.end()) { +// // TODO look up the conn object +// } +// else if (auto conn = deepsearch_ground_connection( +// beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN)) { +// ret = get_avoidance(conn, max_bridge_len); +// } - if (succ) { - auto it = m_gnd_connections.find(from.id); - if (it != m_gnd_connections.end()) - ret = get_avoidance(it->second, max_bridge_len); - } + auto conn = deepsearch_ground_connection( + beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN); + + ret = get_avoidance(conn, max_bridge_len); return ret; + +// double glvl = ground_level(m_sm); +// branchingtree::Node dst = from; +// dst.pos.z() = glvl; +// dst.weight += from.pos.z() - glvl; +// bool succ = add_ground_bridge(from, dst); + +// std::optional ret; + +// if (succ) { +// auto it = m_gnd_connections.find(m_pillars.size() - 1); +// if (it != m_gnd_connections.end()) +// ret = get_avoidance(it->second, max_bridge_len); +// } + +// return ret; } inline void build_pillars(SupportTreeBuilder &builder, From 816371f37ce7d8abdf6385df82f01b4938f230d7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 3 Jan 2023 17:51:20 +0100 Subject: [PATCH 50/59] Use avoidance suggestion when ground point is too far --- src/libslic3r/BranchingTree/BranchingTree.cpp | 11 +--- src/libslic3r/SLA/BranchingTreeSLA.cpp | 63 ++++++------------- src/libslic3r/SLA/SupportTreeUtils.hpp | 3 +- 3 files changed, 23 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 01f4b2724..98261311b 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -83,21 +83,12 @@ void build_tree(PointCloud &nodes, Builder &builder) break; Node new_node {*avo, node.Rmin}; - new_node.weight = nodes.get(node_id).weight + (node.pos - *avo).squaredNorm(); + 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); } -// 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; -// 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))) { closest_node.left = closest_node.right = node_id; nodes.get(closest_node_id) = closest_node; diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index 7f4b9853c..e851fd437 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -20,12 +20,10 @@ class BranchingTreeBuilder: public branchingtree::Builder { const SupportableMesh &m_sm; const branchingtree::PointCloud &m_cloud; - std::set m_ground_mem; - std::vector m_pillars; // to put an index over them // cache succesfull ground connections - std::map m_gnd_connections; + std::map m_gnd_connections; // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour @@ -162,6 +160,7 @@ public: // Discard all the support points connecting to this branch. discard_subtree_rescure(j.id); +// discard_subtree(j.id); } const std::vector& unroutable_pinheads() const @@ -177,7 +176,7 @@ public: { const GroundConnection *ret = nullptr; - auto it = m_gnd_connections.find(pillar); + auto it = m_gnd_connections.find(m_pillars[pillar].id); if (it != m_gnd_connections.end()) ret = &it->second; @@ -230,25 +229,23 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, namespace bgi = boost::geometry::index; - auto it = m_ground_mem.find(from.id); - if (it == m_ground_mem.end()) { + auto it = m_gnd_connections.find(from.id); + if (it == m_gnd_connections.end()) { sla::Junction j{from.pos.cast(), get_radius(from)}; Vec3d init_dir = (to.pos - from.pos).cast().normalized(); auto conn = deepsearch_ground_connection(beam_ex_policy , m_sm, j, get_radius(to), init_dir); - if (conn) { - m_pillars.emplace_back(from); - m_gnd_connections[m_pillars.size() - 1] = conn; - - ret = true; - } - // 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 - m_ground_mem.insert(from.id); + m_gnd_connections[from.id] = conn; + + if (conn) { + m_pillars.emplace_back(from); + ret = true; + } } if (ret) { @@ -320,37 +317,17 @@ std::optional BranchingTreeBuilder::suggest_avoidance( dst.weight += from.pos.z() - glvl; sla::Junction j{from.pos.cast(), get_radius(from)}; -// auto found_it = m_ground_mem.find(from.id); -// if (found_it != m_ground_mem.end()) { -// // TODO look up the conn object -// } -// else if (auto conn = deepsearch_ground_connection( -// beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN)) { -// ret = get_avoidance(conn, max_bridge_len); -// } - - auto conn = deepsearch_ground_connection( - beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN); - - ret = get_avoidance(conn, max_bridge_len); + auto 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); + m_gnd_connections[from.id] = conn; + ret = get_avoidance(conn, max_bridge_len); + } return ret; - -// double glvl = ground_level(m_sm); -// branchingtree::Node dst = from; -// dst.pos.z() = glvl; -// dst.weight += from.pos.z() - glvl; -// bool succ = add_ground_bridge(from, dst); - -// std::optional ret; - -// if (succ) { -// auto it = m_gnd_connections.find(m_pillars.size() - 1); -// if (it != m_gnd_connections.end()) -// ret = get_avoidance(it->second, max_bridge_len); -// } - -// return ret; } inline void build_pillars(SupportTreeBuilder &builder, diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 6c1707953..8f27ab914 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -646,6 +646,7 @@ GroundConnection deepsearch_ground_connection( // 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); // 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 @@ -654,7 +655,7 @@ GroundConnection deepsearch_ground_connection( // 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 l = 0., l_max = sm.cfg.max_bridge_length_mm; double zlvl = std::numeric_limits::infinity(); while(zlvl > gndlvl && l <= l_max) { From 9a33537b1d3e0f3a1f8d58b2b859d694dd4a80cb Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 4 Jan 2023 11:27:24 +0100 Subject: [PATCH 51/59] Slight performance improvement With parallel avoidance search for leaf nodes --- src/libslic3r/BranchingTree/BranchingTree.hpp | 2 +- src/libslic3r/SLA/BranchingTreeSLA.cpp | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index 06ee3ee14..7e59e6f1a 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -106,7 +106,7 @@ public: virtual bool add_mesh_bridge(const Node &from, const Node &to) = 0; virtual std::optional suggest_avoidance(const Node &from, - float max_bridge_len) + float max_bridge_len) const { return {}; } diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index e851fd437..b3c7d2944 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -23,7 +23,8 @@ class BranchingTreeBuilder: public branchingtree::Builder { std::vector m_pillars; // to put an index over them // cache succesfull ground connections - std::map m_gnd_connections; + mutable std::map m_gnd_connections; + mutable execution::SpinningMutex m_gnd_connections_mtx; // Scaling of the input value 'widening_factor:<0, 1>' to produce resonable // widening behaviour @@ -144,7 +145,7 @@ public: const branchingtree::Node &to) override; std::optional suggest_avoidance(const branchingtree::Node &from, - float max_bridge_len) override; + float max_bridge_len) const override; void report_unroutable(const branchingtree::Node &j) override { @@ -307,7 +308,7 @@ static std::optional get_avoidance(const GroundConnection &conn, } std::optional BranchingTreeBuilder::suggest_avoidance( - const branchingtree::Node &from, float max_bridge_len) + const branchingtree::Node &from, float max_bridge_len) const { std::optional ret; @@ -317,13 +318,23 @@ std::optional BranchingTreeBuilder::suggest_avoidance( dst.weight += from.pos.z() - glvl; sla::Junction j{from.pos.cast(), get_radius(from)}; - auto found_it = m_gnd_connections.find(from.id); + 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); - m_gnd_connections[from.id] = conn; + + { + std::lock_guard lk{m_gnd_connections_mtx}; + m_gnd_connections[from.id] = conn; + } + ret = get_avoidance(conn, max_bridge_len); } @@ -394,6 +405,15 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s 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); From 32e323c64ca7e81d8eff0fd3da5198a01b484539 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 4 Jan 2023 12:59:05 +0100 Subject: [PATCH 52/59] Fix supports below ground --- src/libslic3r/SLA/SupportTreeUtils.hpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 8f27ab914..a7220ce0e 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -517,6 +517,11 @@ Vec3d check_ground_route( 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; @@ -648,6 +653,9 @@ GroundConnection deepsearch_ground_connection( 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. @@ -655,7 +663,7 @@ GroundConnection deepsearch_ground_connection( // 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 = sm.cfg.max_bridge_length_mm; + double l = 0., l_max = bridge_l; double zlvl = std::numeric_limits::infinity(); while(zlvl > gndlvl && l <= l_max) { From aec0c4a0dcc7a0491f88b4cac9a3674212aa9437 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 12:40:09 +0100 Subject: [PATCH 53/59] Fix sidebar combobox behavior for support routing "support_buildplate_only" was toggled only for default supports --- src/libslic3r/PrintConfig.cpp | 17 +++++++++++++++++ src/libslic3r/PrintConfig.hpp | 2 ++ src/slic3r/GUI/Plater.cpp | 7 +++++-- src/slic3r/GUI/Tab.cpp | 7 +++++-- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 926cfd186..cf2753b18 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4802,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>("support_tree_type"); + std::string slatree = ""; + if (suptreetype) { + auto ttype = static_cast(suptreetype->getInt()); + switch (ttype) { + case sla::SupportTreeType::Branching: slatree = "branching"; break; + case sla::SupportTreeType::Organic: slatree = "organic"; break; + default: + ; + } + } + + return slatree; +} + } // namespace Slic3r #include diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 5dee704ba..ca6679051 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1167,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 diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1ab23b344..0da9e5b83 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -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); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 481b16222..ad7b92e2e 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -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")) From add0f89728e35aab50aa5784c2c46bd466260eb3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 15:20:35 +0100 Subject: [PATCH 54/59] Fix floating point divisions by zero when ground route has no bridge --- src/libslic3r/SLA/SupportTreeUtils.hpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index a7220ce0e..472b9ec40 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -125,8 +125,11 @@ struct Beam_ { // Defines a set of rays displaced along a cone's surface 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) @@ -542,7 +545,7 @@ Vec3d check_ground_route( if (brhit_dist < bridge_len) { ret = (source.pos + brhit_dist * dir); - } else { + } 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( @@ -565,6 +568,8 @@ Vec3d check_ground_route( } ret = Vec3d{bridge_end.x(), bridge_end.y(), bridge_end.z() - gnd_hit_d}; + } else { + ret = bridge_end; } return ret; @@ -709,6 +714,9 @@ GroundConnection deepsearch_ground_connection(Ex policy, { 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; From f72984f18ef48a544188b980ff32532caa04bb22 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 15:21:05 +0100 Subject: [PATCH 55/59] Fix broken caching of pillar routes --- src/libslic3r/SLA/BranchingTreeSLA.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index b3c7d2944..bb9712616 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -231,6 +231,8 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, 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(), get_radius(from)}; Vec3d init_dir = (to.pos - from.pos).cast().normalized(); @@ -241,15 +243,14 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from, // 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 - m_gnd_connections[from.id] = conn; - - if (conn) { - m_pillars.emplace_back(from); - ret = true; - } + connptr = &(m_gnd_connections[from.id] = conn); + } else { + connptr = &(it->second); } - if (ret) { + if (connptr && *connptr) { + m_pillars.emplace_back(from); + ret = true; build_subtree(from.id); } From 47a824d1310b83ffb7156e98d4698bc4e30eb1a3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 15:23:55 +0100 Subject: [PATCH 56/59] Remove unused member in DefaultSupportTree Also fix for loop that is copying int vector in each iteration --- src/libslic3r/SLA/DefaultSupportTree.cpp | 3 +-- src/libslic3r/SLA/DefaultSupportTree.hpp | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 53475542a..a0a12d32b 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -372,7 +372,6 @@ void DefaultSupportTree::add_pinheads() 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()); @@ -602,7 +601,7 @@ void DefaultSupportTree::routing_to_ground() // 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 &cl : m_pillar_clusters) { m_thr(); auto cidx = cl_centroids[ci++]; diff --git a/src/libslic3r/SLA/DefaultSupportTree.hpp b/src/libslic3r/SLA/DefaultSupportTree.hpp index ee58e9ded..08990a8df 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.hpp +++ b/src/libslic3r/SLA/DefaultSupportTree.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 m_head_to_ground_scans; From 8207433b81e1afe176cca8a7fba8df79590161b4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 15:24:41 +0100 Subject: [PATCH 57/59] Fix up whitespace for comments in DefaultSupportTree This commit only deals with white space --- src/libslic3r/SLA/DefaultSupportTree.cpp | 143 +++++++++++------------ 1 file changed, 71 insertions(+), 72 deletions(-) diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index a0a12d32b..429cd45c6 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -365,8 +365,8 @@ 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; @@ -377,7 +377,7 @@ void DefaultSupportTree::add_pinheads() 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); @@ -406,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; @@ -430,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 @@ -511,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(); @@ -532,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(); @@ -561,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; @@ -597,9 +596,9 @@ 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 (const std::vector &cl : m_pillar_clusters) { m_thr(); @@ -665,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; @@ -774,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 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) { @@ -798,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; @@ -810,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); @@ -822,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); @@ -849,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) @@ -888,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 canplace(needpillars, false); std::vector spts(needpillars); // vector of starting points @@ -917,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; @@ -931,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; } From 4620dd5a3d37d4f48a85c5ee2a7f52a2305c820e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 9 Jan 2023 16:04:48 +0100 Subject: [PATCH 58/59] WIP on small pillar fixes --- src/libslic3r/SLA/SupportTreeUtils.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index 472b9ec40..e4c03da6c 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -469,6 +469,9 @@ inline long build_ground_connection(SupportTreeBuilder &builder, gp.z() = ground_level(sm); double h = conn.path.back().pos.z() - gp.z(); + if (conn.pillar_base->r_top < sm.cfg.head_back_radius_mm) + h += sm.pad_cfg.wall_thickness_mm; + // TODO: does not work yet // if (conn.path.back().id < 0) { // // this is a head @@ -477,7 +480,8 @@ inline long build_ground_connection(SupportTreeBuilder &builder, // } else ret = builder.add_pillar(gp, h, conn.path.back().r, conn.pillar_base->r_top); - builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom); + 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; } @@ -555,7 +559,7 @@ Vec3d check_ground_route( auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd); double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON); - if (gndhit.distance() > down_l && sm.cfg.object_elevation_mm < 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)); From d6fe5767e0d59e56c643707102fb8259733664f9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 17 Jan 2023 16:41:27 +0100 Subject: [PATCH 59/59] Small supports now go through the pad to always reach the bed They will not hang in the air if they end up in the gap between the "around object pad" and the object --- src/libslic3r/SLA/SupportTreeUtils.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeUtils.hpp b/src/libslic3r/SLA/SupportTreeUtils.hpp index e4c03da6c..93f370c32 100644 --- a/src/libslic3r/SLA/SupportTreeUtils.hpp +++ b/src/libslic3r/SLA/SupportTreeUtils.hpp @@ -469,8 +469,10 @@ inline long build_ground_connection(SupportTreeBuilder &builder, gp.z() = ground_level(sm); double h = conn.path.back().pos.z() - gp.z(); - if (conn.pillar_base->r_top < sm.cfg.head_back_radius_mm) + 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; + } // TODO: does not work yet // if (conn.path.back().id < 0) { @@ -478,7 +480,8 @@ 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.pillar_base->r_top); + + 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);