From a20659fc2d3f8b497cd4b0659fe378184452801d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 15 Nov 2022 15:08:28 +0100 Subject: [PATCH] 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: {