PrusaSlicer-NonPlainar/tests/sla_print/sla_supptreeutils_tests.cpp
2023-01-18 09:26:28 +01:00

378 lines
12 KiB
C++

#include <catch2/catch.hpp>
#include <test_utils.hpp>
#include <unordered_set>
#include "libslic3r/Execution/ExecutionSeq.hpp"
#include "libslic3r/SLA/SupportTreeUtils.hpp"
#include "libslic3r/SLA/SupportTreeUtilsLegacy.hpp"
// Test pair hash for 'nums' random number pairs.
template <class I, class II> void test_pairhash()
{
const constexpr size_t nums = 1000;
I A[nums] = {0}, B[nums] = {0};
std::unordered_set<I> CH;
std::unordered_map<II, std::pair<I, I>> ints;
std::random_device rd;
std::mt19937 gen(rd());
const I Ibits = int(sizeof(I) * CHAR_BIT);
const II IIbits = int(sizeof(II) * CHAR_BIT);
int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits;
if (std::is_signed<I>::value) bits -= 1;
const I Imin = 0;
const I Imax = I(std::pow(2., bits) - 1);
std::uniform_int_distribution<I> dis(Imin, Imax);
for (size_t i = 0; i < nums;) {
I a = dis(gen);
if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; }
}
for (size_t i = 0; i < nums;) {
I b = dis(gen);
if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; }
}
for (size_t i = 0; i < nums; ++i) {
I a = A[i], b = B[i];
REQUIRE(a != b);
II hash_ab = Slic3r::sla::pairhash<I, II>(a, b);
II hash_ba = Slic3r::sla::pairhash<I, II>(b, a);
REQUIRE(hash_ab == hash_ba);
auto it = ints.find(hash_ab);
if (it != ints.end()) {
REQUIRE((
(it->second.first == a && it->second.second == b) ||
(it->second.first == b && it->second.second == a)
));
} else
ints[hash_ab] = std::make_pair(a, b);
}
}
TEST_CASE("Pillar pairhash should be unique", "[suptreeutils]") {
test_pairhash<int, int>();
test_pairhash<int, long>();
test_pairhash<unsigned, unsigned>();
test_pairhash<unsigned, unsigned long>();
}
static void eval_ground_conn(const Slic3r::sla::GroundConnection &conn,
const Slic3r::sla::SupportableMesh &sm,
const Slic3r::sla::Junction &j,
double end_r,
const std::string &stl_fname = "output.stl")
{
using namespace Slic3r;
#ifndef NDEBUG
sla::SupportTreeBuilder builder;
if (!conn)
builder.add_junction(j);
sla::build_ground_connection(builder, sm, conn);
indexed_triangle_set mesh = *sm.emesh.get_triangle_mesh();
its_merge(mesh, builder.merged_mesh());
its_write_stl_ascii(stl_fname.c_str(), "stl_fname", mesh);
#endif
REQUIRE(bool(conn));
// The route should include the source and one avoidance junction.
REQUIRE(conn.path.size() == 2);
// Check if the radius increases with each node
REQUIRE(conn.path.front().r < conn.path.back().r);
REQUIRE(conn.path.back().r < conn.pillar_base->r_top);
// The end radius and the pillar base's upper radius should match
REQUIRE(conn.pillar_base->r_top == Approx(end_r));
}
TEST_CASE("Pillar search dumb case", "[suptreeutils]") {
using namespace Slic3r;
constexpr double FromR = 0.5;
auto j = sla::Junction{Vec3d::Zero(), FromR};
SECTION("with empty mesh") {
sla::SupportableMesh sm{indexed_triangle_set{},
sla::SupportPoints{},
sla::SupportTreeConfig{}};
constexpr double EndR = 1.;
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN);
REQUIRE(conn);
REQUIRE(conn.path.size() == 1);
REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm)));
}
SECTION("with zero R source and destination") {
sla::SupportableMesh sm{indexed_triangle_set{},
sla::SupportPoints{},
sla::SupportTreeConfig{}};
j.r = 0.;
constexpr double EndR = 0.;
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, sla::DOWN);
REQUIRE(conn);
REQUIRE(conn.path.size() == 1);
REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm)));
REQUIRE(conn.pillar_base->r_top == Approx(0.));
}
SECTION("with zero init direction") {
sla::SupportableMesh sm{indexed_triangle_set{},
sla::SupportPoints{},
sla::SupportTreeConfig{}};
constexpr double EndR = 1.;
Vec3d init_dir = Vec3d::Zero();
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_seq, sm, j, EndR, init_dir);
REQUIRE(conn);
REQUIRE(conn.path.size() == 1);
REQUIRE(conn.pillar_base->pos.z() == Approx(ground_level(sm)));
}
}
TEST_CASE("Avoid disk below junction", "[suptreeutils]")
{
// In this test there will be a disk mesh with some radius, centered at
// (0, 0, 0) and above the disk, a junction from which the support pillar
// should be routed. The algorithm needs to find an avoidance route.
using namespace Slic3r;
constexpr double FromRadius = .5;
constexpr double EndRadius = 1.;
constexpr double CylRadius = 4.;
constexpr double CylHeight = 1.;
sla::SupportTreeConfig cfg;
indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight);
// 2.5 * CyRadius height should be enough to be able to insert a bridge
// with 45 degree tilt above the disk.
sla::Junction j{Vec3d{0., 0., 2.5 * CylRadius}, FromRadius};
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
SECTION("without elevation") {
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
eval_ground_conn(conn, sm, j, EndRadius, "disk.stl");
// Check if the avoidance junction is indeed outside of the disk barrier's
// edge.
auto p = conn.path.back().pos;
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
REQUIRE(pR + FromRadius > CylRadius);
}
SECTION("with elevation") {
sm.cfg.object_elevation_mm = 0.;
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
eval_ground_conn(conn, sm, j, EndRadius, "disk_ze.stl");
// Check if the avoidance junction is indeed outside of the disk barrier's
// edge.
auto p = conn.path.back().pos;
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
REQUIRE(pR + FromRadius > CylRadius);
}
}
TEST_CASE("Avoid disk below junction with barrier on the side", "[suptreeutils]")
{
// In this test there will be a disk mesh with some radius, centered at
// (0, 0, 0) and above the disk, a junction from which the support pillar
// should be routed. The algorithm needs to find an avoidance route.
using namespace Slic3r;
constexpr double FromRadius = .5;
constexpr double EndRadius = 1.;
constexpr double CylRadius = 4.;
constexpr double CylHeight = 1.;
constexpr double JElevX = 2.5;
sla::SupportTreeConfig cfg;
indexed_triangle_set disk = its_make_cylinder(CylRadius, CylHeight);
indexed_triangle_set wall = its_make_cube(1., 2 * CylRadius, JElevX * CylRadius);
its_translate(wall, Vec3f{float(FromRadius), -float(CylRadius), 0.f});
its_merge(disk, wall);
// 2.5 * CyRadius height should be enough to be able to insert a bridge
// with 45 degree tilt above the disk.
sla::Junction j{Vec3d{0., 0., JElevX * CylRadius}, FromRadius};
sla::SupportableMesh sm{disk, sla::SupportPoints{}, cfg};
SECTION("without elevation") {
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
eval_ground_conn(conn, sm, j, EndRadius, "disk_with_barrier.stl");
// Check if the avoidance junction is indeed outside of the disk barrier's
// edge.
auto p = conn.path.back().pos;
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
REQUIRE(pR + FromRadius > CylRadius);
}
SECTION("without elevation") {
sm.cfg.object_elevation_mm = 0.;
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_seq, sm, j, EndRadius, sla::DOWN);
eval_ground_conn(conn, sm, j, EndRadius, "disk_with_barrier_ze.stl");
// Check if the avoidance junction is indeed outside of the disk barrier's
// edge.
auto p = conn.path.back().pos;
double pR = std::sqrt(p.x() * p.x()) + std::sqrt(p.y() * p.y());
REQUIRE(pR + FromRadius > CylRadius);
}
}
TEST_CASE("Find ground route just above ground", "[suptreeutils]") {
using namespace Slic3r;
sla::SupportTreeConfig cfg;
cfg.object_elevation_mm = 0.;
sla::Junction j{Vec3d{0., 0., 2. * cfg.head_back_radius_mm}, cfg.head_back_radius_mm};
sla::SupportableMesh sm{{}, sla::SupportPoints{}, cfg};
sla::GroundConnection conn =
sla::deepsearch_ground_connection(ex_seq, sm, j, Geometry::spheric_to_dir(3 * PI/ 4, PI));
REQUIRE(conn);
REQUIRE(conn.pillar_base->pos.z() >= Approx(ground_level(sm)));
}
TEST_CASE("BranchingSupports::MergePointFinder", "[suptreeutils]") {
using namespace Slic3r;
SECTION("Identical points have the same merge point") {
Vec3f a{0.f, 0.f, 0.f}, b = a;
auto slope = float(PI / 4.);
auto mergept = sla::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
REQUIRE((*mergept - b).norm() < EPSILON);
REQUIRE((*mergept - a).norm() < EPSILON);
}
// ^ Z
// | a *
// |
// | b * <= mergept
SECTION("Points at different heights have the lower point as mergepoint") {
Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 0.f, -1.f};
auto slope = float(PI / 4.);
auto mergept = sla::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
REQUIRE((*mergept - b).squaredNorm() < 2 * EPSILON);
}
// -|---------> X
// a b
// * *
// * <= mergept
SECTION("Points at different X have mergept in the middle at lower Z") {
Vec3f a{0.f, 0.f, 0.f}, b = {1.f, 0.f, 0.f};
auto slope = float(PI / 4.);
auto mergept = sla::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
// Distance of mergept should be equal from both input points
float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm());
REQUIRE(D < EPSILON);
REQUIRE(!sla::is_outside_support_cone(a, *mergept, slope));
REQUIRE(!sla::is_outside_support_cone(b, *mergept, slope));
}
// -|---------> Y
// a b
// * *
// * <= mergept
SECTION("Points at different Y have mergept in the middle at lower Z") {
Vec3f a{0.f, 0.f, 0.f}, b = {0.f, 1.f, 0.f};
auto slope = float(PI / 4.);
auto mergept = sla::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
// Distance of mergept should be equal from both input points
float D = std::abs((*mergept - b).squaredNorm() - (*mergept - a).squaredNorm());
REQUIRE(D < EPSILON);
REQUIRE(!sla::is_outside_support_cone(a, *mergept, slope));
REQUIRE(!sla::is_outside_support_cone(b, *mergept, slope));
}
SECTION("Points separated by less than critical angle have the lower point as mergept") {
Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -2.f};
auto slope = float(PI / 4.);
auto mergept = sla::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
REQUIRE((*mergept - b).norm() < 2 * EPSILON);
}
// -|----------------------------> Y
// a b
// * * <= mergept *
//
SECTION("Points at same height have mergepoint in the middle if critical angle is zero ") {
Vec3f a{-1.f, -1.f, -1.f}, b = {-1.5f, -1.5f, -1.f};
auto slope = EPSILON;
auto mergept = sla::find_merge_pt(a, b, slope);
REQUIRE(bool(mergept));
Vec3f middle = (b + a) / 2.;
REQUIRE((*mergept - middle).norm() < 4 * EPSILON);
}
}