Bugfixes and new tests for pillar search
This commit is contained in:
parent
a20659fc2d
commit
0bbd50eaa0
6 changed files with 277 additions and 505 deletions
|
@ -1,4 +1,3 @@
|
|||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <random>
|
||||
#include <numeric>
|
||||
|
@ -32,13 +31,6 @@ const char *const SUPPORT_TEST_MODELS[] = {
|
|||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Pillar pairhash should be unique", "[SLASupportGeneration]") {
|
||||
test_pairhash<int, int>();
|
||||
test_pairhash<int, long>();
|
||||
test_pairhash<unsigned, unsigned>();
|
||||
test_pairhash<unsigned, unsigned long>();
|
||||
}
|
||||
|
||||
TEST_CASE("Support point generator should be deterministic if seeded",
|
||||
"[SLASupportGeneration], [SLAPointGen]") {
|
||||
TriangleMesh mesh = load_model("A_upsidedown.obj");
|
||||
|
|
|
@ -1,8 +1,158 @@
|
|||
#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]")
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <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 = sla::pairhash<I, II>(a, b);
|
||||
II hash_ba = 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);
|
||||
}
|
||||
}
|
||||
|
||||
// SLA Raster test utils:
|
||||
|
||||
using TPixel = uint8_t;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue