QIDISlicer1.0.0

This commit is contained in:
sunsets
2023-06-10 10:14:12 +08:00
parent f2e20e1a90
commit b4cd486f2d
3475 changed files with 1973675 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
add_executable(${_TEST_NAME}_tests
${_TEST_NAME}_tests.cpp
test_3mf.cpp
test_aabbindirect.cpp
test_kdtreeindirect.cpp
test_arachne.cpp
test_clipper_offset.cpp
test_clipper_utils.cpp
test_color.cpp
test_config.cpp
test_curve_fitting.cpp
test_cut_surface.cpp
test_elephant_foot_compensation.cpp
test_expolygon.cpp
test_geometry.cpp
test_placeholder_parser.cpp
test_polygon.cpp
test_polyline.cpp
test_mutable_polygon.cpp
test_mutable_priority_queue.cpp
test_stl.cpp
test_meshboolean.cpp
test_marchingsquares.cpp
test_region_expansion.cpp
test_timeutils.cpp
test_utils.cpp
test_voronoi.cpp
test_optimizers.cpp
test_png_io.cpp
test_surface_mesh.cpp
test_timeutils.cpp
test_quadric_edge_collapse.cpp
test_triangulation.cpp
test_emboss.cpp
test_indexed_triangle_set.cpp
test_astar.cpp
test_jump_point_search.cpp
../libnest2d/printer_parts.cpp
)
if (TARGET OpenVDB::openvdb)
target_sources(${_TEST_NAME}_tests PRIVATE test_hollowing.cpp)
endif()
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
if (WIN32)
qidislicer_copy_dlls(${_TEST_NAME}_tests)
endif()
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS})

View File

@@ -0,0 +1,88 @@
#include <catch_main.hpp>
#include "libslic3r/Utils.hpp"
// bimap test
#include <string_view>
#include <boost/bimap.hpp>
#include <boost/assign.hpp>
namespace {
TEST_CASE("sort_remove_duplicates", "[utils]") {
std::vector<int> data_src = { 3, 0, 2, 1, 15, 3, 5, 6, 3, 1, 0 };
std::vector<int> data_dst = { 0, 1, 2, 3, 5, 6, 15 };
Slic3r::sort_remove_duplicates(data_src);
REQUIRE(data_src == data_dst);
}
TEST_CASE("string_printf", "[utils]") {
SECTION("Empty format with empty data should return empty string") {
std::string outs = Slic3r::string_printf("");
REQUIRE(outs.empty());
}
SECTION("String output length should be the same as input") {
std::string outs = Slic3r::string_printf("1234");
REQUIRE(outs.size() == 4);
}
SECTION("String format should be interpreted as with sprintf") {
std::string outs = Slic3r::string_printf("%d %f %s", 10, 11.4, " This is a string");
char buffer[1024];
sprintf(buffer, "%d %f %s", 10, 11.4, " This is a string");
REQUIRE(outs.compare(buffer) == 0);
}
SECTION("String format should survive large input data") {
std::string input(2048, 'A');
std::string outs = Slic3r::string_printf("%s", input.c_str());
REQUIRE(outs.compare(input) == 0);
}
}
TEST_CASE("Bimap duplicity behavior") {
enum class number {
one = 1,
three = 3,
tri = 3 // ONLY alias
};
using BimapType = boost::bimap<std::string_view, number>;
BimapType bimap = boost::assign::list_of<BimapType::relation>
("one", number::one)
("three", number::three)
("tri", number::tri) // no matter if it is there
;
const auto& to_type = bimap.left;
auto item_number1 = to_type.find("one");
REQUIRE(item_number1 != to_type.end());
CHECK(item_number1->second == number::one);
auto item_number3 = to_type.find("three");
REQUIRE(item_number3 != to_type.end());
CHECK(item_number3->second == number::three);
// to_type.find("tri"); // not in map
const auto &to_name = bimap.right;
auto it1 = to_name.find(number::one);
REQUIRE(it1 != to_name.end());
CHECK(it1->second == "one");
auto it2 = to_name.find(number::three);
REQUIRE(it2 != to_name.end());
CHECK(it2->second == "three");
auto it3 = to_name.find(number::tri);
REQUIRE(it3 != to_name.end());
REQUIRE(number::three == number::tri);
CHECK(it3->second == "three");
}
} // end namespace

View File

@@ -0,0 +1,133 @@
#include <catch2/catch.hpp>
#include "libslic3r/Model.hpp"
#include "libslic3r/Format/3mf.hpp"
#include "libslic3r/Format/STL.hpp"
#include <boost/filesystem/operations.hpp>
using namespace Slic3r;
SCENARIO("Reading 3mf file", "[3mf]") {
GIVEN("umlauts in the path of the file") {
Model model;
WHEN("3mf model is read") {
std::string path = std::string(TEST_DATA_DIR) + "/test_3mf/Geräte/Büchse.3mf";
DynamicPrintConfig config;
ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable };
bool ret = load_3mf(path.c_str(), config, ctxt, &model, false);
THEN("load should succeed") {
REQUIRE(ret);
}
}
}
}
SCENARIO("Export+Import geometry to/from 3mf file cycle", "[3mf]") {
GIVEN("world vertices coordinates before save") {
// load a model from stl file
Model src_model;
std::string src_file = std::string(TEST_DATA_DIR) + "/test_3mf/QIDI.stl";
load_stl(src_file.c_str(), &src_model);
src_model.add_default_instances();
ModelObject* src_object = src_model.objects.front();
// apply generic transformation to the 1st volume
Geometry::Transformation src_volume_transform;
src_volume_transform.set_offset({ 10.0, 20.0, 0.0 });
src_volume_transform.set_rotation({ Geometry::deg2rad(25.0), Geometry::deg2rad(35.0), Geometry::deg2rad(45.0) });
src_volume_transform.set_scaling_factor({ 1.1, 1.2, 1.3 });
src_volume_transform.set_mirror({ -1.0, 1.0, -1.0 });
src_object->volumes.front()->set_transformation(src_volume_transform);
// apply generic transformation to the 1st instance
Geometry::Transformation src_instance_transform;
src_instance_transform.set_offset({ 5.0, 10.0, 0.0 });
src_instance_transform.set_rotation({ Geometry::deg2rad(12.0), Geometry::deg2rad(13.0), Geometry::deg2rad(14.0) });
src_instance_transform.set_scaling_factor({ 0.9, 0.8, 0.7 });
src_instance_transform.set_mirror({ 1.0, -1.0, -1.0 });
src_object->instances.front()->set_transformation(src_instance_transform);
WHEN("model is saved+loaded to/from 3mf file") {
// save the model to 3mf file
std::string test_file = std::string(TEST_DATA_DIR) + "/test_3mf/qidi.3mf";
store_3mf(test_file.c_str(), &src_model, nullptr, false);
// load back the model from the 3mf file
Model dst_model;
DynamicPrintConfig dst_config;
{
ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable };
load_3mf(test_file.c_str(), dst_config, ctxt, &dst_model, false);
}
boost::filesystem::remove(test_file);
// compare meshes
TriangleMesh src_mesh = src_model.mesh();
TriangleMesh dst_mesh = dst_model.mesh();
bool res = src_mesh.its.vertices.size() == dst_mesh.its.vertices.size();
if (res) {
for (size_t i = 0; i < dst_mesh.its.vertices.size(); ++i) {
res &= dst_mesh.its.vertices[i].isApprox(src_mesh.its.vertices[i]);
}
}
THEN("world vertices coordinates after load match") {
REQUIRE(res);
}
}
}
}
SCENARIO("2D convex hull of sinking object", "[3mf]") {
GIVEN("model") {
// load a model
Model model;
std::string src_file = std::string(TEST_DATA_DIR) + "/test_3mf/QIDI.stl";
load_stl(src_file.c_str(), &model);
model.add_default_instances();
WHEN("model is rotated, scaled and set as sinking") {
ModelObject* object = model.objects.front();
object->center_around_origin(false);
// set instance's attitude so that it is rotated, scaled and sinking
ModelInstance* instance = object->instances.front();
instance->set_rotation(X, -M_PI / 4.0);
instance->set_offset(Vec3d::Zero());
instance->set_scaling_factor({ 2.0, 2.0, 2.0 });
// calculate 2D convex hull
Polygon hull_2d = object->convex_hull_2d(instance->get_transformation().get_matrix());
// verify result
Points result = {
{ -91501496, -15914144 },
{ 91501496, -15914144 },
{ 91501496, 4243 },
{ 78229680, 4246883 },
{ 56898100, 4246883 },
{ -85501496, 4242641 },
{ -91501496, 4243 }
};
// Allow 1um error due to floating point rounding.
bool res = hull_2d.points.size() == result.size();
if (res)
for (size_t i = 0; i < result.size(); ++ i) {
const Point &p1 = result[i];
const Point &p2 = hull_2d.points[i];
if (std::abs(p1.x() - p2.x()) > 1 || std::abs(p1.y() - p2.y()) > 1) {
res = false;
break;
}
}
THEN("2D convex hull should match with reference") {
REQUIRE(res);
}
}
}
}

View File

@@ -0,0 +1,409 @@
#include <algorithm>
#include <catch2/catch.hpp>
#include <test_utils.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/AABBTreeIndirect.hpp>
#include <libslic3r/AABBTreeLines.hpp>
using namespace Slic3r;
TEST_CASE("Building a tree over a box, ray caster and closest query", "[AABBIndirect]")
{
TriangleMesh tmesh = make_cube(1., 1., 1.);
auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(tmesh.its.vertices, tmesh.its.indices);
REQUIRE(! tree.empty());
igl::Hit hit;
bool intersected = AABBTreeIndirect::intersect_ray_first_hit(
tmesh.its.vertices, tmesh.its.indices,
tree,
Vec3d(0.5, 0.5, -5.),
Vec3d(0., 0., 1.),
hit);
REQUIRE(intersected);
REQUIRE(hit.t == Approx(5.));
std::vector<igl::Hit> hits;
bool intersected2 = AABBTreeIndirect::intersect_ray_all_hits(
tmesh.its.vertices, tmesh.its.indices,
tree,
Vec3d(0.3, 0.5, -5.),
Vec3d(0., 0., 1.),
hits);
REQUIRE(intersected2);
REQUIRE(hits.size() == 2);
REQUIRE(hits.front().t == Approx(5.));
REQUIRE(hits.back().t == Approx(6.));
size_t hit_idx;
Vec3d closest_point;
double squared_distance = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
tmesh.its.vertices, tmesh.its.indices,
tree,
Vec3d(0.3, 0.5, -5.),
hit_idx, closest_point);
REQUIRE(squared_distance == Approx(5. * 5.));
REQUIRE(closest_point.x() == Approx(0.3));
REQUIRE(closest_point.y() == Approx(0.5));
REQUIRE(closest_point.z() == Approx(0.));
squared_distance = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
tmesh.its.vertices, tmesh.its.indices,
tree,
Vec3d(0.3, 0.5, 5.),
hit_idx, closest_point);
REQUIRE(squared_distance == Approx(4. * 4.));
REQUIRE(closest_point.x() == Approx(0.3));
REQUIRE(closest_point.y() == Approx(0.5));
REQUIRE(closest_point.z() == Approx(1.));
}
TEST_CASE("Creating a several 2d lines, testing closest point query", "[AABBIndirect]")
{
std::vector<Linef> lines { };
lines.push_back(Linef(Vec2d(0.0, 0.0), Vec2d(1.0, 0.0)));
lines.push_back(Linef(Vec2d(1.0, 0.0), Vec2d(1.0, 1.0)));
lines.push_back(Linef(Vec2d(1.0, 1.0), Vec2d(0.0, 1.0)));
lines.push_back(Linef(Vec2d(0.0, 1.0), Vec2d(0.0, 0.0)));
auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines);
size_t hit_idx_out;
Vec2d hit_point_out;
auto sqr_dist = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, Vec2d(0.0, 0.0), hit_idx_out,
hit_point_out);
REQUIRE(sqr_dist == Approx(0.0));
REQUIRE((hit_idx_out == 0 || hit_idx_out == 3));
REQUIRE(hit_point_out.x() == Approx(0.0));
REQUIRE(hit_point_out.y() == Approx(0.0));
sqr_dist = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, Vec2d(1.5, 0.5), hit_idx_out,
hit_point_out);
REQUIRE(sqr_dist == Approx(0.25));
REQUIRE(hit_idx_out == 1);
REQUIRE(hit_point_out.x() == Approx(1.0));
REQUIRE(hit_point_out.y() == Approx(0.5));
}
TEST_CASE("Creating a several 2d lines, testing all lines in radius query", "[AABBIndirect]")
{
std::vector<Linef> lines { };
lines.push_back(Linef(Vec2d(0.0, 0.0), Vec2d(10.0, 0.0)));
lines.push_back(Linef(Vec2d(-10.0, 10.0), Vec2d(10.0, -10.0)));
lines.push_back(Linef(Vec2d(-2.0, -1.0), Vec2d(-2.0, 1.0)));
lines.push_back(Linef(Vec2d(-1.0, -1.0), Vec2d(-1.0, -1.0)));
lines.push_back(Linef(Vec2d(1.0, 1.0), Vec2d(1.0, 1.0)));
auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines);
auto indices = AABBTreeLines::all_lines_in_radius(lines, tree, Vec2d{1.0,1.0}, 4.0);
REQUIRE(std::find(indices.begin(),indices.end(), 0) != indices.end());
REQUIRE(std::find(indices.begin(),indices.end(), 1) != indices.end());
REQUIRE(std::find(indices.begin(),indices.end(), 4) != indices.end());
REQUIRE(indices.size() == 3);
}
TEST_CASE("Find the closest point from ExPolys", "[ClosestPoint]") {
//////////////////////////////
// 0 - 3
// |Ex0| 0 - 3
// | |p |Ex1|
// 1 - 2 | |
// 1 - 2
//[0,0]
///////////////////
ExPolygons ex_polys{
/*Ex0*/ {{0, 4}, {0, 1}, {2, 1}, {2, 4}},
/*Ex1*/ {{4, 3}, {4, 0}, {6, 0}, {6, 3}}
};
Vec2d p{2.5, 3.5};
std::vector<Linef> lines;
auto add_lines = [&lines](const Polygon& poly) {
for (const auto &line : poly.lines())
lines.emplace_back(
line.a.cast<double>(),
line.b.cast<double>());
};
for (const ExPolygon &ex_poly : ex_polys) {
add_lines(ex_poly.contour);
for (const Polygon &hole : ex_poly.holes)
add_lines(hole);
}
AABBTreeIndirect::Tree<2, double> tree =
AABBTreeLines::build_aabb_tree_over_indexed_lines(lines);
size_t hit_idx_out = std::numeric_limits<size_t>::max();
Vec2d hit_point_out;
[[maybe_unused]] double distance_sq =
AABBTreeLines::squared_distance_to_indexed_lines(
lines, tree, p, hit_idx_out, hit_point_out, 0.24/* < (0.5*0.5) */);
CHECK(hit_idx_out == std::numeric_limits<size_t>::max());
distance_sq = AABBTreeLines::squared_distance_to_indexed_lines(
lines, tree, p, hit_idx_out, hit_point_out, 0.26);
CHECK(hit_idx_out != std::numeric_limits<size_t>::max());
//double distance = sqrt(distance_sq);
//const Linef &line = lines[hit_idx_out];
}
#if 0
#include "libslic3r/EdgeGrid.hpp"
#include <iostream>
#include <ctime>
#include <ratio>
#include <chrono>
TEST_CASE("AABBTreeLines vs SignedDistanceGrid time Benchmark", "[AABBIndirect]")
{
std::vector<Points> lines { Points { } };
std::vector<Linef> linesf { };
Vec2d prevf { };
// NOTE: max coord value of the lines is approx 83 mm
for (int r = 1; r < 1000; ++r) {
lines[0].push_back(Point::new_scale(Vec2d(exp(0.005f * r) * cos(r), exp(0.005f * r) * cos(r))));
linesf.emplace_back(prevf, Vec2d(exp(0.005f * r) * cos(r), exp(0.005f * r) * cos(r)));
prevf = linesf.back().b;
}
int build_num = 10000;
using namespace std::chrono;
{
std::cout << "building the tree " << build_num << " times..." << std::endl;
high_resolution_clock::time_point t1 = high_resolution_clock::now();
for (int i = 0; i < build_num; ++i) {
volatile auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(linesf);
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
std::cout << "It took " << time_span.count() << " seconds." << std::endl << std::endl;
}
{
std::cout << "building the grid res 1mm ONLY " << build_num/100 << " !!! times..." << std::endl;
high_resolution_clock::time_point t1 = high_resolution_clock::now();
for (int i = 0; i < build_num/100; ++i) {
EdgeGrid::Grid grid { };
grid.create(lines, scaled(1.0), true);
grid.calculate_sdf();
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
std::cout << "It took " << time_span.count() << " seconds." << std::endl << std::endl;
}
{
std::cout << "building the grid res 10mm " << build_num << " times..." << std::endl;
high_resolution_clock::time_point t1 = high_resolution_clock::now();
for (int i = 0; i < build_num; ++i) {
EdgeGrid::Grid grid { };
grid.create(lines, scaled(10.0), true);
grid.calculate_sdf();
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
std::cout << "It took " << time_span.count() << " seconds." << std::endl << std::endl;
}
EdgeGrid::Grid grid10 { };
grid10.create(lines, scaled(10.0), true);
coord_t query10_res = scaled(10.0);
grid10.calculate_sdf();
EdgeGrid::Grid grid1 { };
grid1.create(lines, scaled(1.0), true);
coord_t query1_res = scaled(1.0);
grid1.calculate_sdf();
auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(linesf);
int query_num = 10000;
Points query_points { };
std::vector<Vec2d> query_pointsf { };
for (int x = 0; x < query_num; ++x) {
Vec2d qp { rand() / (double(RAND_MAX) + 1.0f) * 200.0 - 100.0, rand() / (double(RAND_MAX) + 1.0f) * 200.0
- 100.0 };
query_pointsf.push_back(qp);
query_points.push_back(Point::new_scale(qp));
}
{
std::cout << "querying tree " << query_num << " times..." << std::endl;
high_resolution_clock::time_point t1 = high_resolution_clock::now();
for (const Vec2d &qp : query_pointsf) {
size_t hit_idx_out;
Vec2d hit_point_out;
AABBTreeLines::squared_distance_to_indexed_lines(linesf, tree, qp, hit_idx_out, hit_point_out);
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
std::cout << "It took " << time_span.count() << " seconds." << std::endl << std::endl;
}
{
std::cout << "querying grid res 1mm " << query_num << " times..." << std::endl;
high_resolution_clock::time_point t1 = high_resolution_clock::now();
for (const Point &qp : query_points) {
volatile auto dist = grid1.closest_point_signed_distance(qp, query1_res);
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
std::cout << "It took " << time_span.count() << " seconds." << std::endl << std::endl;
}
{
std::cout << "querying grid res 10mm " << query_num << " times..." << std::endl;
high_resolution_clock::time_point t1 = high_resolution_clock::now();
for (const Point &qp : query_points) {
volatile auto dist = grid10.closest_point_signed_distance(qp, query10_res);
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
std::cout << "It took " << time_span.count() << " seconds." << std::endl << std::endl;
}
std::cout << "Test build and queries together - same number of contour points and query points" << std::endl << std::endl;
std::vector<int> point_counts { 100, 300, 500, 1000, 3000 };
for (auto count : point_counts) {
std::vector<Points> lines { Points { } };
std::vector<Linef> linesf { };
Vec2d prevf { };
Points query_points { };
std::vector<Vec2d> query_pointsf { };
for (int x = 0; x < count; ++x) {
Vec2d cp { rand() / (double(RAND_MAX) + 1.0f) * 200.0 - 100.0, rand() / (double(RAND_MAX) + 1.0f) * 200.0
- 100.0 };
lines[0].push_back(Point::new_scale(cp));
linesf.emplace_back(prevf, cp);
prevf = linesf.back().b;
Vec2d qp { rand() / (double(RAND_MAX) + 1.0f) * 200.0 - 100.0, rand() / (double(RAND_MAX) + 1.0f) * 200.0
- 100.0 };
query_pointsf.push_back(qp);
query_points.push_back(Point::new_scale(qp));
}
std::cout << "Test for point count: " << count << std::endl;
{
high_resolution_clock::time_point t1 = high_resolution_clock::now();
auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(linesf);
for (const Vec2d &qp : query_pointsf) {
size_t hit_idx_out;
Vec2d hit_point_out;
AABBTreeLines::squared_distance_to_indexed_lines(linesf, tree, qp, hit_idx_out, hit_point_out);
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
std::cout << " Tree took " << time_span.count() << " seconds." << std::endl;
}
{
high_resolution_clock::time_point t1 = high_resolution_clock::now();
EdgeGrid::Grid grid1 { };
grid1.create(lines, scaled(1.0), true);
coord_t query1_res = scaled(1.0);
grid1.calculate_sdf();
for (const Point &qp : query_points) {
volatile auto dist = grid1.closest_point_signed_distance(qp, query1_res);
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
std::cout << " Grid 1mm took " << time_span.count() << " seconds." << std::endl;
}
{
high_resolution_clock::time_point t1 = high_resolution_clock::now();
EdgeGrid::Grid grid10 { };
grid10.create(lines, scaled(10.0), true);
coord_t query10_res = scaled(10.0);
grid10.calculate_sdf();
for (const Point &qp : query_points) {
volatile auto dist = grid10.closest_point_signed_distance(qp, query10_res);
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
std::cout << " Grid 10mm took " << time_span.count() << " seconds." << std::endl;
}
}
std::cout << "Test build and queries together - same number of contour points and query points" << std::endl <<
"And with limited contour edge length to 4mm " << std::endl;
for (auto count : point_counts) {
std::vector<Points> lines { Points { } };
std::vector<Linef> linesf { };
Vec2d prevf { };
Points query_points { };
std::vector<Vec2d> query_pointsf { };
for (int x = 0; x < count; ++x) {
Vec2d cp { rand() / (double(RAND_MAX) + 1.0f) * 200.0 - 100.0, rand() / (double(RAND_MAX) + 1.0f) * 200.0
- 100.0 };
Vec2d contour = prevf + cp.normalized()*4.0; // limits the cnotour edge len to 4mm
lines[0].push_back(Point::new_scale(contour));
linesf.emplace_back(prevf, contour);
prevf = linesf.back().b;
Vec2d qp { rand() / (double(RAND_MAX) + 1.0f) * 200.0 - 100.0, rand() / (double(RAND_MAX) + 1.0f) * 200.0
- 100.0 };
query_pointsf.push_back(qp);
query_points.push_back(Point::new_scale(qp));
}
std::cout << "Test for point count: " << count << std::endl;
{
high_resolution_clock::time_point t1 = high_resolution_clock::now();
auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(linesf);
for (const Vec2d &qp : query_pointsf) {
size_t hit_idx_out;
Vec2d hit_point_out;
AABBTreeLines::squared_distance_to_indexed_lines(linesf, tree, qp, hit_idx_out, hit_point_out);
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
std::cout << " Tree took " << time_span.count() << " seconds." << std::endl;
}
{
high_resolution_clock::time_point t1 = high_resolution_clock::now();
EdgeGrid::Grid grid1 { };
grid1.create(lines, scaled(1.0), true);
coord_t query1_res = scaled(1.0);
grid1.calculate_sdf();
for (const Point &qp : query_points) {
volatile auto dist = grid1.closest_point_signed_distance(qp, query1_res);
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
std::cout << " Grid 1mm took " << time_span.count() << " seconds." << std::endl;
}
{
high_resolution_clock::time_point t1 = high_resolution_clock::now();
EdgeGrid::Grid grid10 { };
grid10.create(lines, scaled(10.0), true);
coord_t query10_res = scaled(10.0);
grid10.calculate_sdf();
for (const Point &qp : query_points) {
volatile auto dist = grid10.closest_point_signed_distance(qp, query10_res);
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
std::cout << " Grid 10mm took " << time_span.count() << " seconds." << std::endl;
}
}
}
#endif

View File

@@ -0,0 +1,746 @@
#include <catch2/catch.hpp>
#include "libslic3r/Arachne/WallToolPaths.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/SVG.hpp"
#include "libslic3r/Utils.hpp"
using namespace Slic3r;
using namespace Slic3r::Arachne;
//#define ARACHNE_DEBUG_OUT
#ifdef ARACHNE_DEBUG_OUT
static void export_perimeters_to_svg(const std::string &path, const Polygons &contours, const std::vector<Arachne::VariableWidthLines> &perimeters, const ExPolygons &infill_area)
{
coordf_t stroke_width = scale_(0.03);
BoundingBox bbox = get_extents(contours);
bbox.offset(scale_(1.));
::Slic3r::SVG svg(path.c_str(), bbox);
svg.draw(infill_area, "cyan");
for (const Arachne::VariableWidthLines &perimeter : perimeters)
for (const Arachne::ExtrusionLine &extrusion_line : perimeter) {
ThickPolyline thick_polyline = to_thick_polyline(extrusion_line);
svg.draw({thick_polyline}, "green", "blue", stroke_width);
}
for (const Line &line : to_lines(contours))
svg.draw(line, "red", stroke_width);
}
#endif
TEST_CASE("Arachne - Closed ExtrusionLine", "[ArachneClosedExtrusionLine]") {
Polygon poly = {
Point(-40000000, 10000000),
Point(-62480000, 10000000),
Point(-62480000, -7410000),
Point(-58430000, -7330000),
Point(-58400000, -5420000),
Point(-58720000, -4710000),
Point(-58940000, -3870000),
Point(-59020000, -3000000),
};
Polygons polygons = {poly};
coord_t spacing = 407079;
coord_t inset_count = 5;
Arachne::WallToolPaths wallToolPaths(polygons, spacing, spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults());
wallToolPaths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-closed-extrusion-line.svg"), polygons, perimeters, union_ex(wallToolPaths.getInnerContour()));
#endif
for (VariableWidthLines &perimeter : perimeters)
for (ExtrusionLine &el : perimeter)
if (el.is_closed) {
REQUIRE(el.junctions.front().p == el.junctions.back().p);
}
}
// This test case was distilled from GitHub issue #8472.
// Where for wall_distribution_count == 3 sometime middle perimeter was missing.
TEST_CASE("Arachne - Missing perimeter - #8472", "[ArachneMissingPerimeter8472]") {
Polygon poly = {
Point(-9000000, 8054793),
Point( 7000000, 8054793),
Point( 7000000, 10211874),
Point(-8700000, 10211874),
Point(-9000000, 9824444)
};
Polygons polygons = {poly};
coord_t spacing = 437079;
coord_t inset_count = 3;
PrintObjectConfig print_object_config = PrintObjectConfig::defaults();
print_object_config.wall_distribution_count.setInt(3);
Arachne::WallToolPaths wallToolPaths(polygons, spacing, spacing, inset_count, 0, 0.2, print_object_config, PrintConfig::defaults());
wallToolPaths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-missing-perimeter-8472.svg"), polygons, perimeters, union_ex(wallToolPaths.getInnerContour()));
#endif
REQUIRE(perimeters.size() == 3);
}
// This test case was distilled from GitHub issue #8593.
// Where on the symmetrical model, there were missing parts of extrusions in gear teeth based on model rotation.
TEST_CASE("Arachne - #8593 - Missing a part of the extrusion", "[ArachneMissingPartOfExtrusion8593]") {
const Polygon poly_orig = {
Point( 1800000, 28500000),
Point( 1100000, 30000000),
Point( 1000000, 30900000),
Point( 600000, 32300000),
Point( -600000, 32300000),
Point(-1000000, 30900000),
Point(-1100000, 30000000),
Point(-1800000, 29000000),
};
coord_t spacing = 377079;
coord_t inset_count = 3;
PrintObjectConfig print_object_config = PrintObjectConfig::defaults();
print_object_config.min_bead_width = ConfigOptionFloatOrPercent(0.315, false);
print_object_config.wall_transition_angle = ConfigOptionFloat(40.);
print_object_config.wall_transition_length = ConfigOptionFloatOrPercent(1., false);
// This behavior seems to be related to the rotation of the input polygon.
// There are specific angles in which this behavior is always triggered.
for (const double angle : {0., -PI / 2., -PI / 15.}) {
Polygon poly = poly_orig;
if (angle != 0.)
poly.rotate(angle);
Polygons polygons = {poly};
Arachne::WallToolPaths wall_tool_paths(polygons, spacing, spacing, inset_count, 0, 0.2, print_object_config, PrintConfig::defaults());
wall_tool_paths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
{
static int iRun = 0;
export_perimeters_to_svg(debug_out_path("arachne-missing-part-of-extrusion-8593-%d.svg", iRun++), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
}
#endif
}
}
// This test case was distilled from GitHub issue #8573.
TEST_CASE("Arachne - #8573 - A gap in the perimeter - 1", "[ArachneGapInPerimeter8573_1]") {
const Polygon poly = {
Point(13960000, 500000),
Point(13920000, 1210000),
Point(13490000, 2270000),
Point(12960000, 3400000),
Point(12470000, 4320000),
Point(12160000, 4630000),
Point(12460000, 3780000),
Point(12700000, 2850000),
Point(12880000, 1910000),
Point(12950000, 1270000),
Point(13000000, 500000),
};
Polygons polygons = {poly};
coord_t spacing = 407079;
coord_t inset_count = 2;
PrintObjectConfig print_object_config = PrintObjectConfig::defaults();
// print_object_config.wall_transition_angle = ConfigOptionFloat(20.);
Arachne::WallToolPaths wallToolPaths(polygons, spacing, spacing, inset_count, 0, 0.2, print_object_config, PrintConfig::defaults());
wallToolPaths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-gap-in-perimeter-1-8573.svg"), polygons, perimeters, union_ex(wallToolPaths.getInnerContour()));
#endif
}
// This test case was distilled from GitHub issue #8444.
TEST_CASE("Arachne - #8444 - A gap in the perimeter - 2", "[ArachneGapInPerimeter8444_2]") {
const Polygon poly = {
Point(14413938, 3825902),
Point(16817613, 711749),
Point(19653030, 67154),
Point(20075592, 925370),
Point(20245428, 1339788),
Point(20493219, 2121894),
Point(20570295, 2486625),
Point(20616559, 2835232),
Point(20631964, 3166882),
Point(20591800, 3858877),
Point(19928267, 2153012),
Point(19723020, 1829802),
Point(19482017, 1612364),
Point(19344810, 1542433),
Point(19200249, 1500902),
Point(19047680, 1487200),
Point(18631073, 1520777),
Point(18377524, 1567627),
Point(18132517, 1641174),
Point(17896307, 1741360),
Point(17669042, 1868075),
Point(17449999, 2021790),
};
Polygons polygons = {poly};
coord_t spacing = 594159;
coord_t inset_count = 2;
PrintObjectConfig print_object_config = PrintObjectConfig::defaults();
// print_object_config.wall_transition_angle = ConfigOptionFloat(20.);
Arachne::WallToolPaths wallToolPaths(polygons, spacing, spacing, inset_count, 0, 0.4, print_object_config, PrintConfig::defaults());
wallToolPaths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-gap-in-perimeter-2-8444.svg"), polygons, perimeters, union_ex(wallToolPaths.getInnerContour()));
#endif
}
// This test case was distilled from GitHub issue #8528.
// There is a hole in the place where the number of perimeters is changing from 6 perimeters to 7 perimeters.
TEST_CASE("Arachne - #8528 - A hole when number of perimeters is changing", "[ArachneHoleOnPerimetersChange8528]") {
const Polygon poly = {
Point(-30000000, 27650000),
Point(-30000000, 33500000),
Point(-40000000, 33500000),
Point(-40500000, 33500000),
Point(-41100000, 33400000),
Point(-41600000, 33200000),
Point(-42100000, 32900000),
Point(-42600000, 32600000),
Point(-43000000, 32200000),
Point(-43300000, 31700000),
Point(-43600000, 31200000),
Point(-43800000, 30700000),
Point(-43900000, 30100000),
Point(-43900000, 29600000),
Point(-43957080, 25000000),
Point(-39042920, 25000000),
Point(-39042920, 27650000),
};
Polygons polygons = {poly};
coord_t spacing = 814159;
coord_t inset_count = 5;
PrintObjectConfig print_object_config = PrintObjectConfig::defaults();
print_object_config.min_bead_width = ConfigOptionFloatOrPercent(0.68, false);
// Changing min_bead_width to 0.66 seems that resolve this issue, at least in this case.
print_object_config.min_bead_width = ConfigOptionFloatOrPercent(0.66, false);
Arachne::WallToolPaths wallToolPaths(polygons, spacing, spacing, inset_count, 0, 0.4, print_object_config, PrintConfig::defaults());
wallToolPaths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-hole-on-perimeters-change-8528.svg"), polygons, perimeters, union_ex(wallToolPaths.getInnerContour()));
#endif
}
// This test case was distilled from GitHub issue #8528.
// There is an inconsistency between layers in length of the single perimeters.
TEST_CASE("Arachne - #8555 - Inconsistent single perimeter", "[ArachneInconsistentSinglePerimeter8555]") {
const Polygon poly_0 = {
Point(5527411, -38490007),
Point(11118814, -36631169),
Point(13529600, -36167120),
Point(11300145, -36114514),
Point(10484024, -36113916),
Point(5037323, -37985945),
Point(4097054, -39978866)
};
const Polygon poly_1 = {
Point(5566841, -38517205),
Point(11185208, -36649404),
Point(13462719, -36211009),
Point(11357290, -36161329),
Point(10583855, -36160763),
Point(5105952, -38043516),
Point(4222019, -39917031)
};
const Polygon poly_2 = {
Point(5606269, -38544404),
Point(11251599, -36667638),
Point(13391666, -36255700),
Point(10683552, -36207653),
Point(5174580, -38101085),
Point(4346981, -39855197)
};
const Polygon poly_3 = {
Point(5645699, -38571603),
Point(11317993, -36685873),
Point(13324786, -36299588),
Point(10783383, -36254499),
Point(5243209, -38158655),
Point(4471947, -39793362)
};
const Polygon poly_4 = {
Point(5685128, -38598801),
Point(11384385, -36704108),
Point(13257907, -36343476),
Point(10883211, -36301345),
Point(5311836, -38216224),
Point(4596909, -39731528)
};
const Polygon poly_5 = {
Point(5724558, -38626000),
Point(11450778, -36722343),
Point(13191026, -36387365),
Point(10983042, -36348191),
Point(5380466, -38273795),
Point(4721874, -39669693)
};
Polygons polygons = {poly_0, poly_1, poly_2, poly_3, poly_4, poly_5};
coord_t spacing = 417809;
coord_t inset_count = 2;
for (size_t poly_idx = 0; poly_idx < polygons.size(); ++poly_idx) {
Polygons input_polygons{polygons[poly_idx]};
Arachne::WallToolPaths wallToolPaths(input_polygons, spacing, spacing, inset_count, 0, 0.15, PrintObjectConfig::defaults(), PrintConfig::defaults());
wallToolPaths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-inconsistent-single-perimeter-8555-%d.svg", poly_idx), input_polygons, perimeters, union_ex(wallToolPaths.getInnerContour()));
#endif
}
}
// This test case was distilled from GitHub issue #8633.
// Open perimeter extrusion is shorter on endpoints in comparison to closed perimeter.
TEST_CASE("Arachne - #8633 - Shorter open perimeter", "[ArachneShorterOpenPerimeter8633]") {
const Polygon poly_0 = {
Point(6507498, 4189461),
Point(6460382, 3601960),
Point(6390896, 3181097),
Point(6294072, 2765838),
Point(6170293, 2357794),
Point(7090581, 2045388),
Point(7232821, 2514293),
Point(7344089, 2991501),
Point(7423910, 3474969),
Point(7471937, 3962592),
Point(7487443, 4436235),
Point(6515575, 4436235),
};
const Polygon poly_1 = {
Point(6507498, 4189461),
Point(6460382, 3601960),
Point(6390896, 3181097),
Point(6294072, 2765838),
Point(6170293, 2357794),
Point(6917958, 1586830),
Point(7090552, 2045398),
Point(7232821, 2514293),
Point(7344089, 2991501),
Point(7423910, 3474969),
Point(7471937, 3962592),
Point(7487443, 4436235),
Point(6515575, 4436235),
};
Polygons polygons = {poly_0, poly_1};
coord_t spacing = 617809;
coord_t inset_count = 1;
PrintObjectConfig print_object_config = PrintObjectConfig::defaults();
print_object_config.min_bead_width = ConfigOptionFloatOrPercent(0.51, false);
print_object_config.min_feature_size = ConfigOptionFloatOrPercent(0.15, false);
print_object_config.wall_transition_length = ConfigOptionFloatOrPercent(0.6, false);
for (size_t poly_idx = 0; poly_idx < polygons.size(); ++poly_idx) {
Polygons input_polygons{polygons[poly_idx]};
Arachne::WallToolPaths wallToolPaths(input_polygons, spacing, spacing, inset_count, 0, 0.15, print_object_config, PrintConfig::defaults());
wallToolPaths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-shorter-open-perimeter-8633-%d.svg", poly_idx), input_polygons, perimeters, union_ex(wallToolPaths.getInnerContour()));
#endif
}
}
// This test case was distilled from GitHub issue #8597.
// There was just an issue with decrementing std::vector::begin() in a specific case.
TEST_CASE("Arachne - #8597 - removeSmallAreas", "[ArachneRemoveSmallAreas8597]") {
const Polygon poly_0 = {
Point(-38768167, -3636556),
Point(-38763631, -3617883),
Point(-38763925, -3617820),
Point(-38990169, -3919539),
Point(-38928506, -3919539),
};
const Polygon poly_1 = {
Point(-39521732, -4480560),
Point(-39383333, -4398498),
Point(-39119825, -3925307),
Point(-39165608, -3926212),
Point(-39302205, -3959445),
Point(-39578719, -4537002),
};
Polygons polygons = {poly_0, poly_1};
coord_t spacing = 407079;
coord_t inset_count = 2;
Arachne::WallToolPaths wallToolPaths(polygons, spacing, spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults());
wallToolPaths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-remove-small-areas-8597.svg"), polygons, perimeters, union_ex(wallToolPaths.getInnerContour()));
#endif
REQUIRE(perimeters.size() == 1);
}
// Test case for missing infill that is probably caused by PolylineStitcher, which produced an open polyline.
TEST_CASE("Arachne - Missing infill", "[ArachneMissingInfill]") {
const Polygon poly_0 = {
Point( 5525881, 3649657),
Point( 452351, -2035297),
Point(-1014702, -2144286),
Point(-5142096, -9101108),
Point( 5525882, -9101108),
};
const Polygon poly_1 = {
Point(1415524, -2217520),
Point(1854189, -2113857),
Point(1566974, -2408538),
};
const Polygon poly_2 = {
Point(-42854, -3771357),
Point(310500, -3783332),
Point( 77735, -4059215),
};
Polygons polygons = {poly_0, poly_1, poly_2};
coord_t spacing = 357079;
coord_t inset_count = 2;
Arachne::WallToolPaths wallToolPaths(polygons, spacing, spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults());
wallToolPaths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-missing-infill.svg"), polygons, perimeters, union_ex(wallToolPaths.getInnerContour()));
#endif
// REQUIRE(wallToolPaths.getInnerContour().size() == 1);
}
// This test case was distilled from GitHub issue #8849.
// Missing part of the model after simplifying generated tool-paths by simplifyToolPaths.
TEST_CASE("Arachne - #8849 - Missing part of model", "[ArachneMissingPart8849]") {
const Polygon poly_0 = {
Point(-29700000, -10600000),
Point(-28200000, -10600000),
Point( 20000000, -10600000),
Point( 20000000, - 9900000),
Point(-28200000, - 9900000),
Point(-28200000, 0),
Point(-29700000, 0),
};
Polygons polygons = {poly_0};
coord_t ext_perimeter_spacing = 449999;
coord_t perimeter_spacing = 757079;
coord_t inset_count = 2;
Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.32, PrintObjectConfig::defaults(), PrintConfig::defaults());
wall_tool_paths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-missing-part-8849.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
#endif
int64_t total_extrusion_length = 0;
for (Arachne::VariableWidthLines &perimeter : perimeters)
for (Arachne::ExtrusionLine &extrusion_line : perimeter)
total_extrusion_length += extrusion_line.getLength();
// Total extrusion length should be around 30mm when the part is missing and around 120 when everything is ok.
// REQUIRE(total_extrusion_length >= scaled<int64_t>(120.));
}
// This test case was distilled from GitHub issue #8446.
// Boost Voronoi generator produces non-planar Voronoi diagram with two intersecting linear Voronoi edges.
// Those intersecting edges are causing that perimeters are also generated in places where they shouldn't be.
TEST_CASE("Arachne - #8446 - Degenerated Voronoi diagram - Linear edges", "[ArachneDegeneratedDiagram8446LinearEdges]") {
Polygon poly_0 = {
Point( 42240656, 9020315),
Point( 4474248, 42960681),
Point( -4474248, 42960681),
Point( -4474248, 23193537),
Point( -6677407, 22661038),
Point( -8830542, 21906307),
Point( -9702935, 21539826),
Point(-13110431, 19607811),
Point(-18105334, 15167780),
Point(-20675743, 11422461),
Point(-39475413, 17530840),
Point(-42240653, 9020315)
};
Polygons polygons = {poly_0};
coord_t ext_perimeter_spacing = 407079;
coord_t perimeter_spacing = 407079;
coord_t inset_count = 1;
Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults());
wall_tool_paths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-degenerated-diagram-8446-linear-edges.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
#endif
int64_t total_extrusion_length = 0;
for (Arachne::VariableWidthLines &perimeter : perimeters)
for (Arachne::ExtrusionLine &extrusion_line : perimeter)
total_extrusion_length += extrusion_line.getLength();
// Total extrusion length should be around 211.2mm when the part is ok and 212.1mm when it has perimeters in places where they shouldn't be.
REQUIRE(total_extrusion_length <= scaled<int64_t>(211.5));
}
// This test case was distilled from GitHub issue #8846.
// Boost Voronoi generator produces degenerated Voronoi diagram with one parabolic edge intersecting linear Voronoi edge.
// Those intersecting edges are causing that perimeters are also generated in places where they shouldn't be.
TEST_CASE("Arachne - #8846 - Degenerated Voronoi diagram - One Parabola", "[ArachneDegeneratedDiagram8846OneParabola]") {
const Polygon poly_0 = {
Point(101978540, -41304489), Point(101978540, 41304489),
Point(94709788, 42514051), Point(94709788, 48052315),
Point(93352716, 48052315), Point(93352716, 42514052),
Point(75903540, 42514051), Point(75903540, 48052315),
Point(74546460, 48052315), Point(74546460, 42514052),
Point(69634788, 42514051), Point(69634788, 48052315),
Point(68277708, 48052315), Point(68277708, 42514051),
Point(63366040, 42514051), Point(63366040, 48052315),
Point(62008960, 48052315), Point(62008960, 42514051),
Point(57097292, 42514051), Point(57097292, 48052315),
Point(55740212, 48052315), Point(55740212, 42514052),
Point(50828540, 42514052), Point(50828540, 48052315),
Point(49471460, 48052315), Point(49471460, 42514051),
Point(25753540, 42514051), Point(25753540, 48052315),
Point(24396460, 48052315), Point(24396460, 42514051),
Point(19484790, 42514052), Point(19484790, 48052315),
Point(18127710, 48052315), Point(18127710, 42514051),
Point(-5590210, 42514051), Point(-5590210, 48052315),
Point(-6947290, 48052315), Point(-6947290, 42514051),
Point(-11858960, 42514051), Point(-11858960, 48052315),
Point(-13216040, 48052315), Point(-13216040, 42514051),
Point(-18127710, 42514051), Point(-18127710, 48052315),
Point(-19484790, 48052315), Point(-19484790, 42514052),
Point(-49471460, 42514051), Point(-49471460, 48052315),
Point(-50828540, 48052315), Point(-50828540, 42514052),
Point(-55740212, 42514052), Point(-55740212, 48052315),
Point(-57097292, 48052315), Point(-57097292, 42514051),
Point(-68277708, 42514051), Point(-68277708, 48052315),
Point(-69634788, 48052315), Point(-69634788, 42514051),
Point(-74546460, 42514052), Point(-74546460, 48052315),
Point(-75903540, 48052315), Point(-75903540, 42514051),
Point(-80815204, 42514051), Point(-80815204, 48052315),
Point(-82172292, 48052315), Point(-82172292, 42514051),
Point(-87083956, 42514051), Point(-87083956, 48052315),
Point(-88441044, 48052315), Point(-88441044, 42514051),
Point(-99621460, 42514051), Point(-99621460, 48052315),
Point(-100978540, 48052315), Point(-100978540, 42528248),
Point(-101978540, 41304489), Point(-101978540, -41304489),
Point(-100978540, -48052315), Point(-99621460, -48052315),
};
Polygon poly_1 = {
Point(-100671460, -40092775),
Point(-100671460, 40092775),
Point(100671460, 40092775),
Point(100671460, -40092775),
};
Polygons polygons = {poly_0, poly_1};
coord_t ext_perimeter_spacing = 607079;
coord_t perimeter_spacing = 607079;
coord_t inset_count = 1;
Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults());
wall_tool_paths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-degenerated-diagram-8846-one-parabola.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
#endif
int64_t total_extrusion_length = 0;
for (Arachne::VariableWidthLines &perimeter : perimeters)
for (Arachne::ExtrusionLine &extrusion_line : perimeter)
total_extrusion_length += extrusion_line.getLength();
// Total extrusion length should be around 1335mm when the part is ok and 1347mm when it has perimeters in places where they shouldn't be.
REQUIRE(total_extrusion_length <= scaled<int64_t>(1335.));
}
// This test case was distilled from GitHub issue #9357.
// Boost Voronoi generator produces degenerated Voronoi diagram with two intersecting parabolic Voronoi edges.
// Those intersecting edges are causing that perimeters are also generated in places where they shouldn't be.
TEST_CASE("Arachne - #9357 - Degenerated Voronoi diagram - Two parabolas", "[ArachneDegeneratedDiagram9357TwoParabolas]") {
const Polygon poly_0 = {
Point(78998946, -11733905),
Point(40069507, -7401251),
Point(39983905, -6751055),
Point(39983905, 8251054),
Point(79750000, 10522762),
Point(79983905, 10756667),
Point(79983905, 12248946),
Point(79950248, 12504617),
Point(79709032, 12928156),
Point(79491729, 13102031),
Point(78998946, 13233905),
Point(38501054, 13233905),
Point(37258117, 12901005),
Point(36349000, 11991885),
Point(36100868, 11392844),
Point(36016095, 10748947),
Point(36016095, -6751054),
Point(35930493, -7401249),
Point(4685798, -11733905),
};
Polygons polygons = {poly_0};
coord_t ext_perimeter_spacing = 407079;
coord_t perimeter_spacing = 407079;
coord_t inset_count = 1;
Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults());
wall_tool_paths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-degenerated-diagram-9357-two-parabolas.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
#endif
int64_t total_extrusion_length = 0;
for (Arachne::VariableWidthLines &perimeter : perimeters)
for (Arachne::ExtrusionLine &extrusion_line : perimeter)
total_extrusion_length += extrusion_line.getLength();
// Total extrusion length should be around 256mm when the part is ok and 293mm when it has perimeters in places where they shouldn't be.
REQUIRE(total_extrusion_length <= scaled<int64_t>(256.));
}
// This test case was distilled from GitHub issue #8846.
// Boost Voronoi generator produces degenerated Voronoi diagram with some Voronoi edges intersecting input segments.
// Those Voronoi edges intersecting input segments are causing that perimeters are also generated in places where they shouldn't be.
TEST_CASE("Arachne - #8846 - Degenerated Voronoi diagram - Voronoi edges intersecting input segment", "[ArachneDegeneratedDiagram8846IntersectingInputSegment]") {
const Polygon poly_0 = {
Point( 60000000, 58000000),
Point(-20000000, 53229451),
Point( 49312250, 53229452),
Point( 49443687, 53666225),
Point( 55358348, 50908580),
Point( 53666223, 49443687),
Point( 53229452, 49312250),
Point( 53229452, -49312250),
Point( 53666014, -49443623),
Point(-10000000, -58000000),
Point( 60000000, -58000000),
};
Polygons polygons = {poly_0};
coord_t ext_perimeter_spacing = 407079;
coord_t perimeter_spacing = 407079;
coord_t inset_count = 1;
Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.32, PrintObjectConfig::defaults(), PrintConfig::defaults());
wall_tool_paths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-degenerated-diagram-8846-intersecting-input-segment.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
#endif
int64_t total_extrusion_length = 0;
for (Arachne::VariableWidthLines &perimeter : perimeters)
for (Arachne::ExtrusionLine &extrusion_line : perimeter)
total_extrusion_length += extrusion_line.getLength();
// Total extrusion length should be around 500mm when the part is ok and 680mm when it has perimeters in places where they shouldn't be.
REQUIRE(total_extrusion_length <= scaled<int64_t>(500.));
}
// This test case was distilled from GitHub issue #10034.
// In this test case previous rotation by PI / 6 wasn't able to fix non-planar Voronoi diagram.
TEST_CASE("Arachne - #10034 - Degenerated Voronoi diagram - That wasn't fixed by rotation by PI / 6", "[ArachneDegeneratedDiagram10034RotationNotWorks]") {
Polygon poly_0 = {
Point(43612632, -25179766), Point(58456010, 529710), Point(51074898, 17305660), Point(49390982, 21042355),
Point(48102357, 23840161), Point(46769686, 26629546), Point(45835761, 28472742), Point(45205450, 29623133),
Point(45107431, 29878059), Point(45069846, 30174950), Point(45069846, 50759533), Point(-45069846, 50759533),
Point(-45069852, 29630557), Point(-45105780, 29339980), Point(-45179725, 29130704), Point(-46443313, 26398986),
Point(-52272109, 13471493), Point(-58205450, 95724), Point(-29075091, -50359531), Point(29075086, -50359531),
};
Polygon poly_1 = {
Point(-37733905, 45070445), Point(-37813254, 45116257), Point(-39353851, 47784650), Point(-39353851, 47876274),
Point(-38632470, 49125743), Point(-38553121, 49171555), Point(-33833475, 49171555), Point(-33754126, 49125743),
Point(-33032747, 47876277), Point(-33032747, 47784653), Point(-34007855, 46095721), Point(-34573350, 45116257),
Point(-34652699, 45070445),
};
Polygon poly_2 = {
Point(-44016799, 40706401), Point(-44116953, 40806555), Point(-44116953, 46126289), Point(-44016799, 46226443),
Point(-42211438, 46226443), Point(-42132089, 46180631), Point(-40591492, 43512233), Point(-40591492, 43420609),
Point(-41800123, 41327194), Point(-42132089, 40752213), Point(-42211438, 40706401),
};
Polygon poly_3 = {
Point(6218189, 10966609), Point(6138840, 11012421), Point(4598238, 13680817), Point(4598238, 13772441), Point(6138840, 16440843),
Point(6218189, 16486655), Point(9299389, 16486655), Point(9378738, 16440843), Point(10919340, 13772441), Point(10919340, 13680817),
Point(10149039, 12346618), Point(9378738, 11012421), Point(9299389, 10966609),
};
Polygon poly_4 = {
Point(13576879, 6718065), Point(13497530, 6763877), Point(11956926, 9432278), Point(11956926, 9523902),
Point(13497528, 12192302), Point(13576877, 12238114), Point(16658079, 12238112), Point(16737428, 12192300),
Point(18278031, 9523904), Point(18278031, 9432280), Point(17507729, 8098077), Point(16737428, 6763877),
Point(16658079, 6718065),
};
Polygons polygons = {
poly_0, poly_1, poly_2, poly_3, poly_4,
};
coord_t ext_perimeter_spacing = 407079;
coord_t perimeter_spacing = 407079;
coord_t inset_count = 1;
Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults());
wall_tool_paths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-degenerated-diagram-10034-rotation-not-works.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
#endif
}

View File

@@ -0,0 +1,406 @@
#include <catch2/catch.hpp>
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/AStar.hpp"
#include "libslic3r/Execution/ExecutionSeq.hpp"
#include "libslic3r/PointGrid.hpp"
using namespace Slic3r;
TEST_CASE("Testing basic invariants of AStar", "[AStar]") {
struct DummyTracer {
using Node = int;
int goal = 0;
float distance(int a, int b) const { return a - b; }
float goal_heuristic(int n) const { return n == goal ? -1.f : 0.f; }
size_t unique_id(int n) const { return n; }
void foreach_reachable(int, std::function<bool(int)>) const {}
};
std::vector<int> out;
SECTION("Output is empty when source is also the destination") {
bool found = astar::search_route(DummyTracer{}, 0, std::back_inserter(out));
REQUIRE(out.empty());
REQUIRE(found);
}
SECTION("Return false when there is no route to destination") {
bool found = astar::search_route(DummyTracer{}, 1, std::back_inserter(out));
REQUIRE(!found);
REQUIRE(out.empty());
}
}
struct PointGridTracer3D {
using Node = size_t;
const PointGrid<float> &grid;
size_t final;
PointGridTracer3D(const PointGrid<float> &g, size_t goal) :
grid{g}, final{goal} {}
template<class Fn>
void foreach_reachable(size_t from, Fn &&fn) const
{
Vec3i from_crd = grid.get_coord(from);
REQUIRE(grid.get_idx(from_crd) == from);
if (size_t i = grid.get_idx(from_crd + Vec3i{ 1, 0, 0}); i < grid.point_count()) fn(i);
if (size_t i = grid.get_idx(from_crd + Vec3i{ 0, 1, 0}); i < grid.point_count()) fn(i);
if (size_t i = grid.get_idx(from_crd + Vec3i{ 0, 0, 1}); i < grid.point_count()) fn(i);
if (size_t i = grid.get_idx(from_crd + Vec3i{ 1, 1, 0}); i < grid.point_count()) fn(i);
if (size_t i = grid.get_idx(from_crd + Vec3i{ 0, 1, 1}); i < grid.point_count()) fn(i);
if (size_t i = grid.get_idx(from_crd + Vec3i{ 1, 1, 1}); i < grid.point_count()) fn(i);
if (size_t i = grid.get_idx(from_crd + Vec3i{-1, 0, 0}); from_crd.x() > 0 && i < grid.point_count()) fn(i);
if (size_t i = grid.get_idx(from_crd + Vec3i{ 0, -1, 0}); from_crd.y() > 0 && i < grid.point_count()) fn(i);
if (size_t i = grid.get_idx(from_crd + Vec3i{ 0, 0, -1}); from_crd.z() > 0 && i < grid.point_count()) fn(i);
if (size_t i = grid.get_idx(from_crd + Vec3i{-1, -1, 0}); from_crd.x() > 0 && from_crd.y() > 0 && i < grid.point_count()) fn(i);
if (size_t i = grid.get_idx(from_crd + Vec3i{ 0, -1, -1}); from_crd.y() > 0 && from_crd.z() && i < grid.point_count()) fn(i);
if (size_t i = grid.get_idx(from_crd + Vec3i{-1, -1, -1}); from_crd.x() > 0 && from_crd.y() > 0 && from_crd.z() && i < grid.point_count()) fn(i);
}
float distance(size_t a, size_t b) const
{
return (grid.get(a) - grid.get(b)).squaredNorm();
}
float goal_heuristic(size_t n) const
{
return n == final ? -1.f : (grid.get(n) - grid.get(final)).squaredNorm();
}
size_t unique_id(size_t n) const { return n; }
};
template<class Node, class Cmp = std::less<Node>>
bool has_duplicates(const std::vector<Node> &res, Cmp cmp = {})
{
auto cpy = res;
std::sort(cpy.begin(), cpy.end(), cmp);
auto it = std::unique(cpy.begin(), cpy.end());
return it != cpy.end();
}
TEST_CASE("astar algorithm test over 3D point grid", "[AStar]") {
auto vol = BoundingBox3Base<Vec3f>{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}};
auto pgrid = point_grid(ex_seq, vol, {0.1f, 0.1f, 0.1f});
size_t target = pgrid.point_count() - 1;
PointGridTracer3D pgt{pgrid, target};
std::vector<size_t> out;
bool found = astar::search_route(pgt, 0, std::back_inserter(out));
REQUIRE(found);
REQUIRE(!out.empty());
REQUIRE(out.front() == target);
#ifndef NDEBUG
std::cout << "Route taken: ";
for (auto it = out.rbegin(); it != out.rend(); ++it) {
std::cout << "(" << pgrid.get_coord(*it).transpose() << ") ";
}
std::cout << std::endl;
#endif
REQUIRE(!has_duplicates(out)); // No duplicates in output
}
enum CellValue {ON, OFF};
struct CellGridTracer2D_AllDirs {
using Node = Vec2i;
static constexpr auto Cols = size_t(5);
static constexpr auto Rows = size_t(8);
static constexpr size_t GridSize = Cols * Rows;
const std::array<std::array<CellValue, Cols>, Rows> &grid;
Vec2i goal;
CellGridTracer2D_AllDirs(const std::array<std::array<CellValue, Cols>, Rows> &g,
const Vec2i &goal_)
: grid{g}, goal{goal_}
{}
template<class Fn>
void foreach_reachable(const Vec2i &src, Fn &&fn) const
{
auto is_inside = [](const Vec2i& v) { return v.x() >= 0 && v.x() < int(Cols) && v.y() >= 0 && v.y() < int(Rows); };
if (Vec2i crd = src + Vec2i{0, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
if (Vec2i crd = src + Vec2i{1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
if (Vec2i crd = src + Vec2i{1, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
if (Vec2i crd = src + Vec2i{0, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
if (Vec2i crd = src + Vec2i{-1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
if (Vec2i crd = src + Vec2i{-1, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
if (Vec2i crd = src + Vec2i{1, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
if (Vec2i crd = src + Vec2i{-1, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
}
float distance(const Vec2i & a, const Vec2i & b) const { return (a - b).squaredNorm(); }
float goal_heuristic(const Vec2i & n) const { return n == goal ? -1.f : (n - goal).squaredNorm(); }
size_t unique_id(const Vec2i & n) const { return n.y() * Cols + n.x(); }
};
struct CellGridTracer2D_Axis {
using Node = Vec2i;
static constexpr auto Cols = size_t(5);
static constexpr auto Rows = size_t(8);
static constexpr size_t GridSize = Cols * Rows;
const std::array<std::array<CellValue, Cols>, Rows> &grid;
Vec2i goal;
CellGridTracer2D_Axis(
const std::array<std::array<CellValue, Cols>, Rows> &g,
const Vec2i &goal_)
: grid{g}, goal{goal_}
{}
template<class Fn>
void foreach_reachable(const Vec2i &src, Fn &&fn) const
{
auto is_inside = [](const Vec2i& v) { return v.x() >= 0 && v.x() < int(Cols) && v.y() >= 0 && v.y() < int(Rows); };
if (Vec2i crd = src + Vec2i{0, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
if (Vec2i crd = src + Vec2i{0, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
if (Vec2i crd = src + Vec2i{1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
if (Vec2i crd = src + Vec2i{-1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
}
float distance(const Vec2i & a, const Vec2i & b) const { return (a - b).squaredNorm(); }
float goal_heuristic(const Vec2i &n) const
{
int manhattan_dst = std::abs(n.x() - goal.x()) +
std::abs(n.y() - goal.y());
return n == goal ? -1.f : manhattan_dst;
}
size_t unique_id(const Vec2i & n) const { return n.y() * Cols + n.x(); }
};
using TestClasses = std::tuple< CellGridTracer2D_AllDirs, CellGridTracer2D_Axis >;
TEMPLATE_LIST_TEST_CASE("Astar should avoid simple barrier", "[AStar]", TestClasses) {
std::array<std::array<CellValue, 5>, 8> grid = {{
{ON , ON , ON , ON , ON},
{ON , ON , ON , ON , ON},
{ON , ON , ON , ON , ON},
{ON , ON , ON , ON , ON},
{ON , ON , ON , ON , ON},
{ON , OFF, OFF, OFF, ON},
{ON , ON , ON , ON , ON},
{ON , ON , ON , ON , ON}
}};
Vec2i dst = {2, 0};
TestType cgt{grid, dst};
std::vector<Vec2i> out;
bool found = astar::search_route(cgt, {2, 7}, std::back_inserter(out));
REQUIRE(found);
REQUIRE(!out.empty());
REQUIRE(out.front() == dst);
REQUIRE(!has_duplicates(out, [](const Vec2i &a, const Vec2i &b) {
return a.x() == b.x() ? a.y() < b.y() : a.x() < b.x();
}));
#ifndef NDEBUG
std::cout << "Route taken: ";
for (auto it = out.rbegin(); it != out.rend(); ++it) {
std::cout << "(" << it->transpose() << ") ";
}
std::cout << std::endl;
#endif
}
TEMPLATE_LIST_TEST_CASE("Astar should manage to avoid arbitrary barriers", "[AStar]", TestClasses) {
std::array<std::array<CellValue, 5>, 8> grid = {{
{ON , ON , ON , ON , ON},
{ON , ON , ON , OFF, ON},
{OFF, OFF, ON , OFF, ON},
{ON , ON , ON , OFF, ON},
{ON , OFF, ON , OFF, ON},
{ON , OFF, ON , ON , ON},
{ON , OFF, ON , OFF, ON},
{ON , ON , ON , ON , ON}
}};
Vec2i dst = {0, 0};
TestType cgt{grid, dst};
std::vector<Vec2i> out;
bool found = astar::search_route(cgt, {0, 7}, std::back_inserter(out));
REQUIRE(found);
REQUIRE(!out.empty());
REQUIRE(out.front() == dst);
REQUIRE(!has_duplicates(out, [](const Vec2i &a, const Vec2i &b) {
return a.x() == b.x() ? a.y() < b.y() : a.x() < b.x();
}));
#ifndef NDEBUG
std::cout << "Route taken: ";
for (auto it = out.rbegin(); it != out.rend(); ++it) {
std::cout << "(" << it->transpose() << ") ";
}
std::cout << std::endl;
#endif
}
TEMPLATE_LIST_TEST_CASE("Astar should find the way out of a labyrinth", "[AStar]", TestClasses) {
std::array<std::array<CellValue, 5>, 8> grid = {{
{ON , ON , ON , ON , ON },
{ON , OFF, OFF, OFF, OFF},
{ON , ON , ON , ON , ON },
{OFF, OFF, OFF, OFF, ON },
{ON , ON , ON , ON , ON },
{ON , OFF, OFF, OFF, OFF},
{ON , ON , ON , ON , ON },
{OFF, OFF, OFF, OFF, ON }
}};
Vec2i dst = {4, 0};
TestType cgt{grid, dst};
std::vector<Vec2i> out;
bool found = astar::search_route(cgt, {4, 7}, std::back_inserter(out));
REQUIRE(found);
REQUIRE(!out.empty());
REQUIRE(out.front() == dst);
REQUIRE(!has_duplicates(out, [](const Vec2i &a, const Vec2i &b) {
return a.x() == b.x() ? a.y() < b.y() : a.x() < b.x();
}));
#ifndef NDEBUG
std::cout << "Route taken: ";
for (auto it = out.rbegin(); it != out.rend(); ++it) {
std::cout << "(" << it->transpose() << ") ";
}
std::cout << std::endl;
#endif
}
TEST_CASE("Zero heuristic function should result in dijsktra's algo", "[AStar]")
{
struct GraphTracer {
using Node = size_t;
using QNode = astar::QNode<GraphTracer>;
struct Edge
{
size_t to_id = size_t(-1);
float cost = 0.f;
bool operator <(const Edge &e) const { return to_id < e.to_id; }
};
struct ENode: public QNode {
std::vector<Edge> edges;
ENode(size_t node_id, std::initializer_list<Edge> edgelist)
: QNode{node_id}, edges(edgelist)
{}
ENode &operator=(const QNode &q)
{
assert(node == q.node);
g = q.g;
h = q.h;
parent = q.parent;
queue_id = q.queue_id;
return *this;
}
};
// Example graph from
// https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-greedy-algo-7/?ref=lbp
std::vector<ENode> nodes = {
{0, {{1, 4.f}, {7, 8.f}}},
{1, {{0, 4.f}, {2, 8.f}, {7, 11.f}}},
{2, {{1, 8.f}, {3, 7.f}, {5, 4.f}, {8, 2.f}}},
{3, {{2, 7.f}, {4, 9.f}, {5, 14.f}}},
{4, {{3, 9.f}, {5, 10.f}}},
{5, {{2, 4.f}, {3, 14.f}, {4, 10.f}, {6, 2.f}}},
{6, {{5, 2.f}, {7, 1.f}, {8, 6.f}}},
{7, {{0, 8.f}, {1, 11.f}, {6, 1.f}, {8, 7.f}}},
{8, {{2, 2.f}, {6, 6.f}, {7, 7.f}}}
};
float distance(size_t a, size_t b) const {
float ret = std::numeric_limits<float>::infinity();
if (a < nodes.size()) {
auto it = std::lower_bound(nodes[a].edges.begin(),
nodes[a].edges.end(),
Edge{b, 0.f});
if (it != nodes[a].edges.end()) {
ret = it->cost;
}
}
return ret;
}
float goal_heuristic(size_t) const { return 0.f; }
size_t unique_id(size_t n) const { return n; }
void foreach_reachable(size_t n, std::function<bool(int)> fn) const
{
if (n < nodes.size()) {
for (const Edge &e : nodes[n].edges)
fn(e.to_id);
}
}
} graph;
std::vector<size_t> out;
// 'graph.nodes' is able to be a node cache (it simulates an associative container)
bool found = astar::search_route(graph, size_t(0), std::back_inserter(out), graph.nodes);
// But should not crash or loop infinitely.
REQUIRE(!found);
// Without a destination, there is no output. But the algorithm should halt.
REQUIRE(out.empty());
// Source node should have it's parent unset
REQUIRE(graph.nodes[0].parent == astar::Unassigned);
// All other nodes should have their parents set
for (size_t i = 1; i < graph.nodes.size(); ++i)
REQUIRE(graph.nodes[i].parent != astar::Unassigned);
std::array<float, 9> ref_distances = {0.f, 4.f, 12.f, 19.f, 21.f,
11.f, 9.f, 8.f, 14.f};
// Try to trace each node back to the source node. Each of them should
// arrive to the source within less hops than the full number of nodes.
for (size_t i = 0, k = 0; i < graph.nodes.size(); ++i, k = 0) {
GraphTracer::QNode *q = &graph.nodes[i];
REQUIRE(q->g == Approx(ref_distances[i]));
while (k++ < graph.nodes.size() && q->parent != astar::Unassigned)
q = &graph.nodes[q->parent];
REQUIRE(q->parent == astar::Unassigned);
}
}

View File

@@ -0,0 +1,214 @@
#include <catch2/catch.hpp>
#include <iostream>
#include <boost/filesystem.hpp>
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/SVG.hpp"
using namespace Slic3r;
// #define TESTS_EXPORT_SVGS
SCENARIO("Constant offset", "[ClipperUtils]") {
coord_t s = 1000000;
GIVEN("20mm box") {
ExPolygon box20mm;
box20mm.contour.points = { Vec2crd{ 0, 0 }, Vec2crd{ 20 * s, 0 }, Vec2crd{ 20 * s, 20 * s}, Vec2crd{ 0, 20 * s} };
std::vector<float> deltas_plus(box20mm.contour.points.size(), 1. * s);
std::vector<float> deltas_minus(box20mm.contour.points.size(), - 1. * s);
Polygons output;
WHEN("Slic3r::offset()") {
for (double miter : { 2.0, 1.5, 1.2 }) {
DYNAMIC_SECTION("plus 1mm, miter " << miter << "x") {
output = Slic3r::offset(box20mm, 1. * s, ClipperLib::jtMiter, miter);
#ifdef TESTS_EXPORT_SVGS
{
SVG svg(debug_out_path("constant_offset_box20mm_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
svg.draw(box20mm, "blue");
svg.draw_outline(output, "black", coord_t(scale_(0.01)));
}
#endif
THEN("Area is 22^2mm2") {
REQUIRE(output.size() == 1);
REQUIRE(output.front().area() == Approx(22. * 22. * s * s));
}
}
DYNAMIC_SECTION("minus 1mm, miter " << miter << "x") {
output = Slic3r::offset(box20mm, - 1. * s, ClipperLib::jtMiter, miter);
#ifdef TESTS_EXPORT_SVGS
{
SVG svg(debug_out_path("constant_offset_box20mm_minus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
svg.draw(box20mm, "blue");
svg.draw_outline(output, "black", coord_t(scale_(0.01)));
}
#endif
THEN("Area is 18^2mm2") {
REQUIRE(output.size() == 1);
REQUIRE(output.front().area() == Approx(18. * 18. * s * s));
}
}
}
}
WHEN("Slic3r::variable_offset_outer/inner") {
for (double miter : { 2.0, 1.5, 1.2 }) {
DYNAMIC_SECTION("plus 1mm, miter " << miter << "x") {
output = Slic3r::variable_offset_outer(box20mm, { deltas_plus }, miter);
#ifdef TESTS_EXPORT_SVGS
{
SVG svg(debug_out_path("variable_offset_box20mm_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
svg.draw(box20mm, "blue");
svg.draw_outline(output, "black", coord_t(scale_(0.01)));
}
#endif
THEN("Area is 22^2mm2") {
REQUIRE(output.size() == 1);
REQUIRE(output.front().area() == Approx(22. * 22. * s * s));
}
}
DYNAMIC_SECTION("minus 1mm, miter " << miter << "x") {
output = Slic3r::variable_offset_inner(box20mm, { deltas_minus }, miter);
#ifdef TESTS_EXPORT_SVGS
{
SVG svg(debug_out_path("variable_offset_box20mm_minus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
svg.draw(box20mm, "blue");
svg.draw_outline(output, "black", coord_t(scale_(0.01)));
}
#endif
THEN("Area is 18^2mm2") {
REQUIRE(output.size() == 1);
REQUIRE(output.front().area() == Approx(18. * 18. * s * s));
}
}
}
}
}
GIVEN("20mm box with 10mm hole") {
ExPolygon box20mm;
box20mm.contour.points = { Vec2crd{ 0, 0 }, Vec2crd{ 20 * s, 0 }, Vec2crd{ 20 * s, 20 * s}, Vec2crd{ 0, 20 * s} };
box20mm.holes.emplace_back(Slic3r::Polygon({ Vec2crd{ 5 * s, 5 * s }, Vec2crd{ 5 * s, 15 * s}, Vec2crd{ 15 * s, 15 * s}, Vec2crd{ 15 * s, 5 * s } }));
std::vector<float> deltas_plus(box20mm.contour.points.size(), 1. * s);
std::vector<float> deltas_minus(box20mm.contour.points.size(), -1. * s);
ExPolygons output;
SECTION("Slic3r::offset()") {
for (double miter : { 2.0, 1.5, 1.2 }) {
DYNAMIC_SECTION("miter " << miter << "x") {
WHEN("plus 1mm") {
output = Slic3r::offset_ex(box20mm, 1. * s, ClipperLib::jtMiter, miter);
#ifdef TESTS_EXPORT_SVGS
{
SVG svg(debug_out_path("constant_offset_box20mm_10mm_hole_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
svg.draw(box20mm, "blue");
svg.draw_outline(to_polygons(output), "black", coord_t(scale_(0.01)));
}
#endif
THEN("Area is 22^2-8^2 mm2") {
REQUIRE(output.size() == 1);
REQUIRE(output.front().area() == Approx((22. * 22. - 8. * 8.) * s * s));
}
}
WHEN("minus 1mm") {
output = Slic3r::offset_ex(box20mm, - 1. * s, ClipperLib::jtMiter, miter);
#ifdef TESTS_EXPORT_SVGS
{
SVG svg(debug_out_path("constant_offset_box20mm_10mm_hole_minus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
svg.draw(box20mm, "blue");
svg.draw_outline(to_polygons(output), "black", coord_t(scale_(0.01)));
}
#endif
THEN("Area is 18^2-12^2 mm2") {
REQUIRE(output.size() == 1);
REQUIRE(output.front().area() == Approx((18. * 18. - 12. * 12.) * s * s));
}
}
}
}
}
SECTION("Slic3r::variable_offset_outer()") {
for (double miter : { 2.0, 1.5, 1.2 }) {
DYNAMIC_SECTION("miter " << miter << "x") {
WHEN("plus 1mm") {
output = Slic3r::variable_offset_outer_ex(box20mm, { deltas_plus, deltas_plus }, miter);
#ifdef TESTS_EXPORT_SVGS
{
SVG svg(debug_out_path("variable_offset_box20mm_10mm_hole_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
svg.draw(box20mm, "blue");
svg.draw_outline(to_polygons(output), "black", coord_t(scale_(0.01)));
}
#endif
THEN("Area is 22^2-8^2 mm2") {
REQUIRE(output.size() == 1);
REQUIRE(output.front().area() == Approx((22. * 22. - 8. * 8.) * s * s));
}
}
WHEN("minus 1mm") {
output = Slic3r::variable_offset_inner_ex(box20mm, { deltas_minus, deltas_minus }, miter);
#ifdef TESTS_EXPORT_SVGS
{
SVG svg(debug_out_path("variable_offset_box20mm_10mm_hole_minus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
svg.draw(box20mm, "blue");
svg.draw_outline(to_polygons(output), "black", coord_t(scale_(0.01)));
}
#endif
THEN("Area is 18^2-12^2 mm2") {
REQUIRE(output.size() == 1);
REQUIRE(output.front().area() == Approx((18. * 18. - 12. * 12.) * s * s));
}
}
}
}
}
}
GIVEN("20mm right angle triangle") {
ExPolygon triangle20mm;
triangle20mm.contour.points = { Vec2crd{ 0, 0 }, Vec2crd{ 20 * s, 0 }, Vec2crd{ 0, 20 * s } };
Polygons output;
double offset = 1.;
// Angle of the sharp corner bisector.
double angle_bisector = M_PI / 8.;
// Area tapered by mitering one sharp corner.
double area_tapered = pow(offset * (1. / sin(angle_bisector) - 1.), 2.) * tan(angle_bisector);
double l_triangle_side_offsetted = 20. + offset * (1. + 1. / tan(angle_bisector));
double area_offsetted = (0.5 * l_triangle_side_offsetted * l_triangle_side_offsetted - 2. * area_tapered) * s * s;
SECTION("Slic3r::offset()") {
for (double miter : { 2.0, 1.5, 1.2 }) {
DYNAMIC_SECTION("Outer offset 1mm, miter " << miter << "x") {
output = Slic3r::offset(triangle20mm, offset * s, ClipperLib::jtMiter, 2.0);
#ifdef TESTS_EXPORT_SVGS
{
SVG svg(debug_out_path("constant_offset_triangle20mm_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
svg.draw(triangle20mm, "blue");
svg.draw_outline(output, "black", coord_t(scale_(0.01)));
}
#endif
THEN("Area matches") {
REQUIRE(output.size() == 1);
REQUIRE(output.front().area() == Approx(area_offsetted));
}
}
}
}
SECTION("Slic3r::variable_offset_outer()") {
std::vector<float> deltas(triangle20mm.contour.points.size(), 1. * s);
for (double miter : { 2.0, 1.5, 1.2 }) {
DYNAMIC_SECTION("Outer offset 1mm, miter " << miter << "x") {
output = Slic3r::variable_offset_outer(triangle20mm, { deltas }, 2.0);
#ifdef TESTS_EXPORT_SVGS
{
SVG svg(debug_out_path("variable_offset_triangle20mm_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
svg.draw(triangle20mm, "blue");
svg.draw_outline(output, "black", coord_t(scale_(0.01)));
}
#endif
THEN("Area matches") {
REQUIRE(output.size() == 1);
REQUIRE(output.front().area() == Approx(area_offsetted));
}
}
}
}
}
}

View File

@@ -0,0 +1,384 @@
#include <catch2/catch.hpp>
#include <numeric>
#include <iostream>
#include <boost/filesystem.hpp>
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/SVG.hpp"
using namespace Slic3r;
SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") {
// CCW oriented contour
Slic3r::Polygon square{ { 200, 100 }, {200, 200}, {100, 200}, {100, 100} };
// CW oriented contour
Slic3r::Polygon hole_in_square{ { 160, 140 }, { 140, 140 }, { 140, 160 }, { 160, 160 } };
Slic3r::ExPolygon square_with_hole(square, hole_in_square);
GIVEN("square_with_hole") {
WHEN("offset") {
Polygons result = Slic3r::offset(square_with_hole, 5.f);
THEN("offset matches") {
REQUIRE(result == Polygons {
{ { 205, 205 }, { 95, 205 }, { 95, 95 }, { 205, 95 }, },
{ { 155, 145 }, { 145, 145 }, { 145, 155 }, { 155, 155 } } });
}
}
WHEN("offset_ex") {
ExPolygons result = Slic3r::offset_ex(square_with_hole, 5.f);
THEN("offset matches") {
REQUIRE(result == ExPolygons { {
{ { 205, 205 }, { 95, 205 }, { 95, 95 }, { 205, 95 }, },
{ { 145, 145 }, { 145, 155 }, { 155, 155 }, { 155, 145 } } } } );
}
}
WHEN("offset2_ex") {
ExPolygons result = Slic3r::offset2_ex({ square_with_hole }, 5.f, -2.f);
THEN("offset matches") {
REQUIRE(result == ExPolygons { {
{ { 203, 203 }, { 97, 203 }, { 97, 97 }, { 203, 97 } },
{ { 143, 143 }, { 143, 157 }, { 157, 157 }, { 157, 143 } } } } );
}
}
}
GIVEN("square_with_hole 2") {
Slic3r::ExPolygon square_with_hole(
{ { 20000000, 20000000 }, { 0, 20000000 }, { 0, 0 }, { 20000000, 0 } },
{ { 5000000, 15000000 }, { 15000000, 15000000 }, { 15000000, 5000000 }, { 5000000, 5000000 } });
WHEN("offset2_ex") {
Slic3r::ExPolygons result = Slic3r::offset2_ex(ExPolygons { square_with_hole }, -1.f, 1.f);
THEN("offset matches") {
REQUIRE(result.size() == 1);
REQUIRE(square_with_hole.area() == result.front().area());
}
}
}
GIVEN("square and hole") {
WHEN("diff_ex") {
ExPolygons result = Slic3r::diff_ex(Polygons{ square }, Polygons{ hole_in_square });
THEN("hole is created") {
REQUIRE(result.size() == 1);
REQUIRE(square_with_hole.area() == result.front().area());
}
}
}
GIVEN("polyline") {
Polyline polyline { { 50, 150 }, { 300, 150 } };
WHEN("intersection_pl") {
Polylines result = Slic3r::intersection_pl(polyline, ExPolygon{ square, hole_in_square });
THEN("correct number of result lines") {
REQUIRE(result.size() == 2);
}
THEN("result lines have correct length") {
// results are in no particular order
REQUIRE(result[0].length() == 40);
REQUIRE(result[1].length() == 40);
}
}
WHEN("diff_pl") {
Polylines result = Slic3r::diff_pl({ polyline }, Polygons{ square, hole_in_square });
THEN("correct number of result lines") {
REQUIRE(result.size() == 3);
}
// results are in no particular order
THEN("the left result line has correct length") {
REQUIRE(std::count_if(result.begin(), result.end(), [](const Polyline &pl) { return pl.length() == 50; }) == 1);
}
THEN("the right result line has correct length") {
REQUIRE(std::count_if(result.begin(), result.end(), [](const Polyline &pl) { return pl.length() == 100; }) == 1);
}
THEN("the central result line has correct length") {
REQUIRE(std::count_if(result.begin(), result.end(), [](const Polyline &pl) { return pl.length() == 20; }) == 1);
}
}
}
GIVEN("Clipper bug #96 / Slic3r issue #2028") {
Slic3r::Polyline subject{
{ 44735000, 31936670 }, { 55270000, 31936670 }, { 55270000, 25270000 }, { 74730000, 25270000 }, { 74730000, 44730000 }, { 68063296, 44730000 }, { 68063296, 55270000 }, { 74730000, 55270000 },
{ 74730000, 74730000 }, { 55270000, 74730000 }, { 55270000, 68063296 }, { 44730000, 68063296 }, { 44730000, 74730000 }, { 25270000, 74730000 }, { 25270000, 55270000 }, { 31936670, 55270000 },
{ 31936670, 44730000 }, { 25270000, 44730000 }, { 25270000, 25270000 }, { 44730000, 25270000 }, { 44730000, 31936670 } };
Slic3r::Polygon clip { {75200000, 45200000}, {54800000, 45200000}, {54800000, 24800000}, {75200000, 24800000} };
Slic3r::Polylines result = Slic3r::intersection_pl(subject, ExPolygon{ clip });
THEN("intersection_pl - result is not empty") {
REQUIRE(result.size() == 1);
}
}
GIVEN("Clipper bug #122") {
Slic3r::Polyline subject { { 1975, 1975 }, { 25, 1975 }, { 25, 25 }, { 1975, 25 }, { 1975, 1975 } };
Slic3r::Polygons clip { { { 2025, 2025 }, { -25, 2025 } , { -25, -25 }, { 2025, -25 } },
{ { 525, 525 }, { 525, 1475 }, { 1475, 1475 }, { 1475, 525 } } };
Slic3r::Polylines result = Slic3r::intersection_pl({ subject }, clip);
THEN("intersection_pl - result is not empty") {
REQUIRE(result.size() == 1);
REQUIRE(result.front().points.size() == 5);
}
}
GIVEN("Clipper bug #126") {
Slic3r::Polyline subject { { 200000, 19799999 }, { 200000, 200000 }, { 24304692, 200000 }, { 15102879, 17506106 }, { 13883200, 19799999 }, { 200000, 19799999 } };
Slic3r::Polygon clip { { 15257205, 18493894 }, { 14350057, 20200000 }, { -200000, 20200000 }, { -200000, -200000 }, { 25196917, -200000 } };
Slic3r::Polylines result = Slic3r::intersection_pl(subject, ExPolygon{ clip });
THEN("intersection_pl - result is not empty") {
REQUIRE(result.size() == 1);
}
THEN("intersection_pl - result has same length as subject polyline") {
REQUIRE(result.front().length() == Approx(subject.length()));
}
}
#if 0
{
# Clipper does not preserve polyline orientation
my $polyline = Slic3r::Polyline->new([50, 150], [300, 150]);
my $result = Slic3r::Geometry::Clipper::intersection_pl([$polyline], [$square]);
is scalar(@$result), 1, 'intersection_pl - correct number of result lines';
is_deeply $result->[0]->pp, [[100, 150], [200, 150]], 'clipped line orientation is preserved';
}
{
# Clipper does not preserve polyline orientation
my $polyline = Slic3r::Polyline->new([300, 150], [50, 150]);
my $result = Slic3r::Geometry::Clipper::intersection_pl([$polyline], [$square]);
is scalar(@$result), 1, 'intersection_pl - correct number of result lines';
is_deeply $result->[0]->pp, [[200, 150], [100, 150]], 'clipped line orientation is preserved';
}
{
# Disabled until Clipper bug #127 is fixed
my $subject = [
Slic3r::Polyline->new([-90000000, -100000000], [-90000000, 100000000]), # vertical
Slic3r::Polyline->new([-100000000, -10000000], [100000000, -10000000]), # horizontal
Slic3r::Polyline->new([-100000000, 0], [100000000, 0]), # horizontal
Slic3r::Polyline->new([-100000000, 10000000], [100000000, 10000000]), # horizontal
];
my $clip = Slic3r::Polygon->new(# a circular, convex, polygon
[99452190, 10452846], [97814760, 20791169], [95105652, 30901699], [91354546, 40673664], [86602540, 50000000],
[80901699, 58778525], [74314483, 66913061], [66913061, 74314483], [58778525, 80901699], [50000000, 86602540],
[40673664, 91354546], [30901699, 95105652], [20791169, 97814760], [10452846, 99452190], [0, 100000000],
[-10452846, 99452190], [-20791169, 97814760], [-30901699, 95105652], [-40673664, 91354546],
[-50000000, 86602540], [-58778525, 80901699], [-66913061, 74314483], [-74314483, 66913061],
[-80901699, 58778525], [-86602540, 50000000], [-91354546, 40673664], [-95105652, 30901699],
[-97814760, 20791169], [-99452190, 10452846], [-100000000, 0], [-99452190, -10452846],
[-97814760, -20791169], [-95105652, -30901699], [-91354546, -40673664], [-86602540, -50000000],
[-80901699, -58778525], [-74314483, -66913061], [-66913061, -74314483], [-58778525, -80901699],
[-50000000, -86602540], [-40673664, -91354546], [-30901699, -95105652], [-20791169, -97814760],
[-10452846, -99452190], [0, -100000000], [10452846, -99452190], [20791169, -97814760],
[30901699, -95105652], [40673664, -91354546], [50000000, -86602540], [58778525, -80901699],
[66913061, -74314483], [74314483, -66913061], [80901699, -58778525], [86602540, -50000000],
[91354546, -40673664], [95105652, -30901699], [97814760, -20791169], [99452190, -10452846], [100000000, 0]
);
my $result = Slic3r::Geometry::Clipper::intersection_pl($subject, [$clip]);
is scalar(@$result), scalar(@$subject), 'intersection_pl - expected number of polylines';
is sum(map scalar(@$_), @$result), scalar(@$subject) * 2, 'intersection_pl - expected number of points in polylines';
}
#endif
}
SCENARIO("Various Clipper operations - t/clipper.t", "[ClipperUtils]") {
GIVEN("square with hole") {
// CCW oriented contour
Slic3r::Polygon square { { 10, 10 }, { 20, 10 }, { 20, 20 }, { 10, 20 } };
Slic3r::Polygon square2 { { 5, 12 }, { 25, 12 }, { 25, 18 }, { 5, 18 } };
// CW oriented contour
Slic3r::Polygon hole_in_square { { 14, 14 }, { 14, 16 }, { 16, 16 }, { 16, 14 } };
WHEN("intersection_ex with another square") {
ExPolygons intersection = Slic3r::intersection_ex(Polygons{ square, hole_in_square }, Polygons{ square2 });
THEN("intersection area matches (hole is preserved)") {
ExPolygon match({ { 20, 18 }, { 10, 18 }, { 10, 12 }, { 20, 12 } },
{ { 14, 16 }, { 16, 16 }, { 16, 14 }, { 14, 14 } });
REQUIRE(intersection.size() == 1);
REQUIRE(intersection.front().area() == Approx(match.area()));
}
}
ExPolygons expolygons { ExPolygon { square, hole_in_square } };
WHEN("Clipping line 1") {
Polylines intersection = intersection_pl({ Polyline { { 15, 18 }, { 15, 15 } } }, expolygons);
THEN("line is clipped to square with hole") {
REQUIRE((Vec2f(15, 18) - Vec2f(15, 16)).norm() == Approx(intersection.front().length()));
}
}
WHEN("Clipping line 2") {
Polylines intersection = intersection_pl({ Polyline { { 15, 15 }, { 15, 12 } } }, expolygons);
THEN("line is clipped to square with hole") {
REQUIRE((Vec2f(15, 14) - Vec2f(15, 12)).norm() == Approx(intersection.front().length()));
}
}
WHEN("Clipping line 3") {
Polylines intersection = intersection_pl({ Polyline { { 12, 18 }, { 18, 18 } } }, expolygons);
THEN("line is clipped to square with hole") {
REQUIRE((Vec2f(18, 18) - Vec2f(12, 18)).norm() == Approx(intersection.front().length()));
}
}
WHEN("Clipping line 4") {
Polylines intersection = intersection_pl({ Polyline { { 5, 15 }, { 30, 15 } } }, expolygons);
THEN("line is clipped to square with hole") {
REQUIRE((Vec2f(14, 15) - Vec2f(10, 15)).norm() == Approx(intersection.front().length()));
REQUIRE((Vec2f(20, 15) - Vec2f(16, 15)).norm() == Approx(intersection[1].length()));
}
}
WHEN("Clipping line 5") {
Polylines intersection = intersection_pl({ Polyline { { 30, 15 }, { 5, 15 } } }, expolygons);
THEN("reverse line is clipped to square with hole") {
REQUIRE((Vec2f(20, 15) - Vec2f(16, 15)).norm() == Approx(intersection.front().length()));
REQUIRE((Vec2f(14, 15) - Vec2f(10, 15)).norm() == Approx(intersection[1].length()));
}
}
WHEN("Clipping line 6") {
Polylines intersection = intersection_pl({ Polyline { { 10, 18 }, { 20, 18 } } }, expolygons);
THEN("tangent line is clipped to square with hole") {
REQUIRE((Vec2f(20, 18) - Vec2f(10, 18)).norm() == Approx(intersection.front().length()));
}
}
}
GIVEN("square with hole 2") {
// CCW oriented contour
Slic3r::Polygon square { { 0, 0 }, { 40, 0 }, { 40, 40 }, { 0, 40 } };
Slic3r::Polygon square2 { { 10, 10 }, { 30, 10 }, { 30, 30 }, { 10, 30 } };
// CW oriented contour
Slic3r::Polygon hole { { 15, 15 }, { 15, 25 }, { 25, 25 }, {25, 15 } };
WHEN("union_ex with another square") {
ExPolygons union_ = Slic3r::union_ex({ square, square2, hole });
THEN("union of two ccw and one cw is a contour with no holes") {
REQUIRE(union_.size() == 1);
REQUIRE(union_.front() == ExPolygon { { 40, 40 }, { 0, 40 }, { 0, 0 }, { 40, 0 } } );
}
}
WHEN("diff_ex with another square") {
ExPolygons diff = Slic3r::diff_ex(Polygons{ square, square2 }, Polygons{ hole });
THEN("difference of a cw from two ccw is a contour with one hole") {
REQUIRE(diff.size() == 1);
REQUIRE(diff.front().area() == Approx(ExPolygon({ {40, 40}, {0, 40}, {0, 0}, {40, 0} }, { {15, 25}, {25, 25}, {25, 15}, {15, 15} }).area()));
}
}
}
GIVEN("yet another square") {
Slic3r::Polygon square { { 10, 10 }, { 20, 10 }, { 20, 20 }, { 10, 20 } };
Slic3r::Polyline square_pl = square.split_at_first_point();
WHEN("no-op diff_pl") {
Slic3r::Polylines res = Slic3r::diff_pl({ square_pl }, Polygons{});
THEN("returns the right number of polylines") {
REQUIRE(res.size() == 1);
}
THEN("returns the unmodified input polyline") {
REQUIRE(res.front().points.size() == square_pl.points.size());
}
}
}
GIVEN("circle") {
Slic3r::ExPolygon circle_with_hole { Polygon::new_scale({
{ 151.8639,288.1192 }, {133.2778,284.6011}, { 115.0091,279.6997 }, { 98.2859,270.8606 }, { 82.2734,260.7933 },
{ 68.8974,247.4181 }, { 56.5622,233.0777 }, { 47.7228,216.3558 }, { 40.1617,199.0172 }, { 36.6431,180.4328 },
{ 34.932,165.2312 }, { 37.5567,165.1101 }, { 41.0547,142.9903 }, { 36.9056,141.4295 }, { 40.199,124.1277 },
{ 47.7776,106.7972 }, { 56.6335,90.084 }, { 68.9831,75.7557 }, { 82.3712,62.3948 }, { 98.395,52.3429 },
{ 115.1281,43.5199 }, { 133.4004,38.6374 }, { 151.9884,35.1378 }, { 170.8905,35.8571 }, { 189.6847,37.991 },
{ 207.5349,44.2488 }, { 224.8662,51.8273 }, { 240.0786,63.067 }, { 254.407,75.4169 }, { 265.6311,90.6406 },
{ 275.6832,106.6636 }, { 281.9225,124.52 }, { 286.8064,142.795 }, { 287.5061,161.696 }, { 286.7874,180.5972 },
{ 281.8856,198.8664 }, { 275.6283,216.7169 }, { 265.5604,232.7294 }, { 254.3211,247.942 }, { 239.9802,260.2776 },
{ 224.757,271.5022 }, { 207.4179,279.0635 }, { 189.5605,285.3035 }, { 170.7649,287.4188 }
}) };
circle_with_hole.holes = { Polygon::new_scale({
{ 158.227,215.9007 }, { 164.5136,215.9007 }, { 175.15,214.5007 }, { 184.5576,210.6044 }, { 190.2268,207.8743 },
{ 199.1462,201.0306 }, { 209.0146,188.346 }, { 213.5135,177.4829 }, { 214.6979,168.4866 }, { 216.1025,162.3325 },
{ 214.6463,151.2703 }, { 213.2471,145.1399 }, { 209.0146,134.9203 }, { 199.1462,122.2357 }, { 189.8944,115.1366 },
{ 181.2504,111.5567 }, { 175.5684,108.8205 }, { 164.5136,107.3655 }, { 158.2269,107.3655 }, { 147.5907,108.7656 },
{ 138.183,112.6616 }, { 132.5135,115.3919 }, { 123.5943,122.2357 }, { 113.7259,134.92 }, { 109.2269,145.7834 },
{ 108.0426,154.7799 }, { 106.638,160.9339 }, { 108.0941,171.9957 }, { 109.4933,178.1264 }, { 113.7259,188.3463 },
{ 123.5943,201.0306 }, { 132.8461,208.1296 }, { 141.4901,211.7094 }, { 147.172,214.4458 }
}) };
THEN("contour is counter-clockwise") {
REQUIRE(circle_with_hole.contour.is_counter_clockwise());
}
THEN("hole is counter-clockwise") {
REQUIRE(circle_with_hole.holes.size() == 1);
REQUIRE(circle_with_hole.holes.front().is_clockwise());
}
WHEN("clipping a line") {
auto line = Polyline::new_scale({ { 152.742,288.086671142818 }, { 152.742,34.166466971035 } });
Polylines intersection = intersection_pl(line, to_polygons(circle_with_hole));
THEN("clipped to two pieces") {
REQUIRE(intersection.front().length() == Approx((Vec2d(152742000, 215178843) - Vec2d(152742000, 288086661)).norm()));
REQUIRE(intersection[1].length() == Approx((Vec2d(152742000, 35166477) - Vec2d(152742000, 108087507)).norm()));
}
}
}
GIVEN("line") {
THEN("expand by 5") {
REQUIRE(offset(Polyline({10,10}, {20,10}), 5).front().area() == Polygon({ {10,5}, {20,5}, {20,15}, {10,15} }).area());
}
}
}
template<e_ordering o = e_ordering::OFF, class P, class P_Alloc, class Tree>
double polytree_area(const Tree &tree, std::vector<P, P_Alloc> *out)
{
traverse_pt<o>(tree, out);
return std::accumulate(out->begin(), out->end(), 0.0,
[](double a, const P &p) { return a + p.area(); });
}
size_t count_polys(const ExPolygons& expolys)
{
size_t c = 0;
for (auto &ep : expolys) c += ep.holes.size() + 1;
return c;
}
TEST_CASE("Traversing Clipper PolyTree", "[ClipperUtils]") {
// Create a polygon representing unit box
Polygon unitbox;
const auto UNIT = coord_t(1. / SCALING_FACTOR);
unitbox.points = { Vec2crd{0, 0}, Vec2crd{UNIT, 0}, Vec2crd{UNIT, UNIT}, Vec2crd{0, UNIT}};
Polygon box_frame = unitbox;
box_frame.scale(20, 10);
Polygon hole_left = unitbox;
hole_left.scale(8);
hole_left.translate(UNIT, UNIT);
hole_left.reverse();
Polygon hole_right = hole_left;
hole_right.translate(UNIT * 10, 0);
Polygon inner_left = unitbox;
inner_left.scale(4);
inner_left.translate(UNIT * 3, UNIT * 3);
Polygon inner_right = inner_left;
inner_right.translate(UNIT * 10, 0);
Polygons reference = union_({box_frame, hole_left, hole_right, inner_left, inner_right});
ClipperLib::PolyTree tree = union_pt(reference);
double area_sum = box_frame.area() + hole_left.area() +
hole_right.area() + inner_left.area() +
inner_right.area();
REQUIRE(area_sum > 0);
SECTION("Traverse into Polygons WITHOUT spatial ordering") {
Polygons output;
REQUIRE(area_sum == Approx(polytree_area(tree.GetFirst(), &output)));
REQUIRE(output.size() == reference.size());
}
SECTION("Traverse into ExPolygons WITHOUT spatial ordering") {
ExPolygons output;
REQUIRE(area_sum == Approx(polytree_area(tree.GetFirst(), &output)));
REQUIRE(count_polys(output) == reference.size());
}
SECTION("Traverse into Polygons WITH spatial ordering") {
Polygons output;
REQUIRE(area_sum == Approx(polytree_area<e_ordering::ON>(tree.GetFirst(), &output)));
REQUIRE(output.size() == reference.size());
}
SECTION("Traverse into ExPolygons WITH spatial ordering") {
ExPolygons output;
REQUIRE(area_sum == Approx(polytree_area<e_ordering::ON>(tree.GetFirst(), &output)));
REQUIRE(count_polys(output) == reference.size());
}
}

View File

@@ -0,0 +1,23 @@
#include <catch2/catch.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Color.hpp"
using namespace Slic3r;
SCENARIO("Color encoding/decoding cycle", "[Color]") {
GIVEN("Color") {
const ColorRGB src_rgb(static_cast<unsigned char>(255), static_cast<unsigned char>(127), static_cast<unsigned char>(63));
WHEN("apply encode/decode cycle") {
const std::string encoded = encode_color(src_rgb);
ColorRGB res_rgb;
decode_color(encoded, res_rgb);
const bool ret = res_rgb.r_uchar() == src_rgb.r_uchar() && res_rgb.g_uchar() == src_rgb.g_uchar() && res_rgb.b_uchar() == src_rgb.b_uchar();
THEN("result matches source") {
REQUIRE(ret);
}
}
}
}

View File

@@ -0,0 +1,247 @@
#include <catch2/catch.hpp>
#include "libslic3r/Config.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/LocalesUtils.hpp"
#include <cereal/types/polymorphic.hpp>
#include <cereal/types/string.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/archives/binary.hpp>
using namespace Slic3r;
SCENARIO("Generic config validation performs as expected.", "[Config]") {
GIVEN("A config generated from default options") {
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
WHEN("perimeter_extrusion_width is set to 250%, a valid value") {
config.set_deserialize_strict("perimeter_extrusion_width", "250%");
THEN( "The config is read as valid.") {
REQUIRE(config.validate().empty());
}
}
WHEN("perimeter_extrusion_width is set to -10, an invalid value") {
config.set("perimeter_extrusion_width", -10);
THEN( "Validate returns error") {
REQUIRE(! config.validate().empty());
}
}
WHEN("perimeters is set to -10, an invalid value") {
config.set("perimeters", -10);
THEN( "Validate returns error") {
REQUIRE(! config.validate().empty());
}
}
}
}
SCENARIO("Config accessor functions perform as expected.", "[Config]") {
auto test = [](ConfigBase &config) {
WHEN("A boolean option is set to a boolean value") {
REQUIRE_NOTHROW(config.set("gcode_comments", true));
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionBool>("gcode_comments")->getBool() == true);
}
}
WHEN("A boolean option is set to a string value representing a 0 or 1") {
CHECK_NOTHROW(config.set_deserialize_strict("gcode_comments", "1"));
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionBool>("gcode_comments")->getBool() == true);
}
}
WHEN("A boolean option is set to a string value representing something other than 0 or 1") {
THEN("A BadOptionTypeException exception is thrown.") {
REQUIRE_THROWS_AS(config.set("gcode_comments", "Z"), BadOptionTypeException);
}
AND_THEN("Value is unchanged.") {
REQUIRE(config.opt<ConfigOptionBool>("gcode_comments")->getBool() == false);
}
}
WHEN("A boolean option is set to an int value") {
THEN("A BadOptionTypeException exception is thrown.") {
REQUIRE_THROWS_AS(config.set("gcode_comments", 1), BadOptionTypeException);
}
}
WHEN("A numeric option is set from serialized string") {
config.set_deserialize_strict("bed_temperature", "100");
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionInts>("bed_temperature")->get_at(0) == 100);
}
}
#if 0
//FIXME better design accessors for vector elements.
WHEN("An integer-based option is set through the integer interface") {
config.set("bed_temperature", 100);
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionInts>("bed_temperature")->get_at(0) == 100);
}
}
#endif
WHEN("An floating-point option is set through the integer interface") {
config.set("perimeter_speed", 10);
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionFloat>("perimeter_speed")->getFloat() == 10.0);
}
}
WHEN("A floating-point option is set through the double interface") {
config.set("perimeter_speed", 5.5);
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionFloat>("perimeter_speed")->getFloat() == 5.5);
}
}
WHEN("An integer-based option is set through the double interface") {
THEN("A BadOptionTypeException exception is thrown.") {
REQUIRE_THROWS_AS(config.set("bed_temperature", 5.5), BadOptionTypeException);
}
}
WHEN("A numeric option is set to a non-numeric value.") {
THEN("A BadOptionTypeException exception is thown.") {
REQUIRE_THROWS_AS(config.set_deserialize_strict("perimeter_speed", "zzzz"), BadOptionValueException);
}
THEN("The value does not change.") {
REQUIRE(config.opt<ConfigOptionFloat>("perimeter_speed")->getFloat() == 60.0);
}
}
WHEN("A string option is set through the string interface") {
config.set("end_gcode", "100");
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionString>("end_gcode")->value == "100");
}
}
WHEN("A string option is set through the integer interface") {
config.set("end_gcode", 100);
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionString>("end_gcode")->value == "100");
}
}
WHEN("A string option is set through the double interface") {
config.set("end_gcode", 100.5);
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionString>("end_gcode")->value == float_to_string_decimal_point(100.5));
}
}
WHEN("A float or percent is set as a percent through the string interface.") {
config.set_deserialize_strict("first_layer_extrusion_width", "100%");
THEN("Value and percent flag are 100/true") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
REQUIRE(tmp->percent == true);
REQUIRE(tmp->value == 100);
}
}
WHEN("A float or percent is set as a float through the string interface.") {
config.set_deserialize_strict("first_layer_extrusion_width", "100");
THEN("Value and percent flag are 100/false") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
REQUIRE(tmp->percent == false);
REQUIRE(tmp->value == 100);
}
}
WHEN("A float or percent is set as a float through the int interface.") {
config.set("first_layer_extrusion_width", 100);
THEN("Value and percent flag are 100/false") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
REQUIRE(tmp->percent == false);
REQUIRE(tmp->value == 100);
}
}
WHEN("A float or percent is set as a float through the double interface.") {
config.set("first_layer_extrusion_width", 100.5);
THEN("Value and percent flag are 100.5/false") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
REQUIRE(tmp->percent == false);
REQUIRE(tmp->value == 100.5);
}
}
WHEN("An invalid option is requested during set.") {
THEN("A BadOptionTypeException exception is thrown.") {
REQUIRE_THROWS_AS(config.set("deadbeef_invalid_option", 1), UnknownOptionException);
REQUIRE_THROWS_AS(config.set("deadbeef_invalid_option", 1.0), UnknownOptionException);
REQUIRE_THROWS_AS(config.set("deadbeef_invalid_option", "1"), UnknownOptionException);
REQUIRE_THROWS_AS(config.set("deadbeef_invalid_option", true), UnknownOptionException);
}
}
WHEN("An invalid option is requested during get.") {
THEN("A UnknownOptionException exception is thrown.") {
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionString>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionFloat>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionInt>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionBool>("deadbeef_invalid_option", false), UnknownOptionException);
}
}
WHEN("An invalid option is requested during opt.") {
THEN("A UnknownOptionException exception is thrown.") {
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionString>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionFloat>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionInt>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionBool>("deadbeef_invalid_option", false), UnknownOptionException);
}
}
WHEN("getX called on an unset option.") {
THEN("The default is returned.") {
REQUIRE(config.opt_float("layer_height") == 0.3);
REQUIRE(config.opt_int("raft_layers") == 0);
REQUIRE(config.opt_bool("support_material") == false);
}
}
WHEN("getFloat called on an option that has been set.") {
config.set("layer_height", 0.5);
THEN("The set value is returned.") {
REQUIRE(config.opt_float("layer_height") == 0.5);
}
}
};
GIVEN("DynamicPrintConfig generated from default options") {
auto config = Slic3r::DynamicPrintConfig::full_print_config();
test(config);
}
GIVEN("FullPrintConfig generated from default options") {
Slic3r::FullPrintConfig config;
test(config);
}
}
SCENARIO("Config ini load/save interface", "[Config]") {
WHEN("new_from_ini is called") {
Slic3r::DynamicPrintConfig config;
std::string path = std::string(TEST_DATA_DIR) + "/test_config/new_from_ini.ini";
config.load_from_ini(path, ForwardCompatibilitySubstitutionRule::Disable);
THEN("Config object contains ini file options.") {
REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.size() == 1);
REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.front() == "#ABCD");
}
}
}
SCENARIO("DynamicPrintConfig serialization", "[Config]") {
WHEN("DynamicPrintConfig is serialized and deserialized") {
FullPrintConfig full_print_config;
DynamicPrintConfig cfg;
cfg.apply(full_print_config, false);
std::string serialized;
try {
std::ostringstream ss;
cereal::BinaryOutputArchive oarchive(ss);
oarchive(cfg);
serialized = ss.str();
} catch (const std::runtime_error & /* e */) {
// e.what();
}
THEN("Config object contains ini file options.") {
DynamicPrintConfig cfg2;
try {
std::stringstream ss(serialized);
cereal::BinaryInputArchive iarchive(ss);
iarchive(cfg2);
} catch (const std::runtime_error & /* e */) {
// e.what();
}
REQUIRE(cfg == cfg2);
}
}
}

View File

@@ -0,0 +1,118 @@
#include <catch2/catch.hpp>
#include <test_utils.hpp>
#include <libslic3r/Geometry/Curves.hpp>
#include <libslic3r/Utils.hpp>
#include <libslic3r/SVG.hpp>
TEST_CASE("Curves: cubic b spline fit test", "[Curves]") {
using namespace Slic3r;
using namespace Slic3r::Geometry;
auto fx = [&](size_t index) {
return float(index) / 200.0f;
};
auto fy = [&](size_t index) {
return 1.0f;
};
std::vector<Vec<1, float>> observations { };
std::vector<float> observation_points { };
std::vector<float> weights { };
for (size_t index = 0; index < 200; ++index) {
observations.push_back(Vec<1, float> { fy(index) });
observation_points.push_back(fx(index));
weights.push_back(1);
}
Vec2f fmin { fx(0), fy(0) };
Vec2f fmax { fx(200), fy(200) };
auto bspline = fit_cubic_bspline(observations, observation_points, weights, 1);
Approx ap(1.0f);
ap.epsilon(0.1f);
for (int p = 0; p < 200; ++p) {
float fitted_val = bspline.get_fitted_value(fx(p))(0);
float expected = fy(p);
REQUIRE(fitted_val == ap(expected));
}
}
TEST_CASE("Curves: quadratic f cubic b spline fit test", "[Curves]") {
using namespace Slic3r;
using namespace Slic3r::Geometry;
auto fx = [&](size_t index) {
return float(index) / 100.0f;
};
auto fy = [&](size_t index) {
return (fx(index) - 1) * (fx(index) - 1);
};
std::vector<Vec<1, float>> observations { };
std::vector<float> observation_points { };
std::vector<float> weights { };
for (size_t index = 0; index < 200; ++index) {
observations.push_back(Vec<1, float> { fy(index) });
observation_points.push_back(fx(index));
weights.push_back(1);
}
Vec2f fmin { fx(0), fy(0) };
Vec2f fmax { fx(200), fy(200) };
auto bspline = fit_cubic_bspline(observations, observation_points, weights, 10);
for (int p = 0; p < 200; ++p) {
float fitted_val = bspline.get_fitted_value(fx(p))(0);
float expected = fy(p);
auto check = [](float a, float b) {
return abs(a - b) < 0.2f;
};
//Note: checking is problematic, splines will not perfectly align
REQUIRE(check(fitted_val, expected));
}
}
TEST_CASE("Curves: polynomial fit test", "[Curves]") {
using namespace Slic3r;
using namespace Slic3r::Geometry;
auto fx = [&](size_t index) {
return float(index) / 100.0f;
};
auto fy = [&](size_t index) {
return (fx(index) - 1) * (fx(index) - 1);
};
std::vector<Vec<1, float>> observations { };
std::vector<float> observation_points { };
std::vector<float> weights { };
for (size_t index = 0; index < 200; ++index) {
observations.push_back(Vec<1, float> { fy(index) });
observation_points.push_back(fx(index));
weights.push_back(1);
}
Vec2f fmin { fx(0), fy(0) };
Vec2f fmax { fx(200), fy(200) };
Approx ap(1.0f);
ap.epsilon(0.1f);
auto poly = fit_polynomial(observations, observation_points, weights, 2);
REQUIRE(poly.coefficients(0, 0) == ap(1));
REQUIRE(poly.coefficients(0, 1) == ap(-2));
REQUIRE(poly.coefficients(0, 2) == ap(1));
}

View File

@@ -0,0 +1,171 @@
#include <catch2/catch.hpp>
#include <libslic3r/CutSurface.hpp>
#include <libslic3r/TriangleMesh.hpp> // its_make_cube + its_merge
using namespace Slic3r;
TEST_CASE("Cut character from surface", "[]")
{
std::string font_path = std::string(TEST_DATA_DIR) +
"/../../resources/fonts/NotoSans-Regular.ttf";
char letter = '%';
float flatness = 2.;
unsigned int font_index = 0; // collection
double z_depth = 50.f; // projection size
auto font = Emboss::create_font_file(font_path.c_str());
REQUIRE(font != nullptr);
std::optional<Emboss::Glyph> glyph =
Emboss::letter2glyph(*font, font_index, letter, flatness);
REQUIRE(glyph.has_value());
ExPolygons shapes = glyph->shape;
REQUIRE(!shapes.empty());
Transform3d tr = Transform3d::Identity();
tr.translate(Vec3d(0., 0., -z_depth));
tr.scale(Emboss::SHAPE_SCALE);
Emboss::OrthoProject cut_projection(tr, Vec3d(0., 0., z_depth));
auto object = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5);
its_translate(object, Vec3f(49 - 25, -10 - 25, -40));
auto cube2 = object; // copy
its_translate(cube2, Vec3f(100, -40, 7.5));
its_merge(object, std::move(cube2));
std::vector<indexed_triangle_set> objects{object};
// Call core function for cut surface
auto surfaces = cut_surface(shapes, objects, cut_projection, 0.5);
CHECK(!surfaces.empty());
Emboss::OrthoProject projection(Transform3d::Identity(),
Vec3d(0.f, 0.f, 10.f));
its_translate(surfaces, Vec3f(0.f, 0.f, 10));
indexed_triangle_set its = cut2model(surfaces, projection);
CHECK(!its.empty());
// its_write_obj(its, "C:/data/temp/projected.obj");
}
//#define DEBUG_3MF
#ifdef DEBUG_3MF
// Test load of 3mf
#include "libslic3r/Format/3mf.hpp"
#include "libslic3r/Model.hpp"
static std::vector<indexed_triangle_set> transform_volumes(ModelVolume *mv) {
const auto &volumes = mv->get_object()->volumes;
std::vector<indexed_triangle_set> results;
results.reserve(volumes.size());
// Improve create object from part or use gl_volume
// Get first model part in object
for (const ModelVolume *v : volumes) {
if (v->id() == mv->id()) continue;
if (!v->is_model_part()) continue;
const TriangleMesh &tm = v->mesh();
if (tm.empty()) continue;
if (tm.its.empty()) continue;
results.push_back(tm.its); // copy: indexed_triangle_set
indexed_triangle_set& its = results.back();
its_transform(its,v->get_matrix());
}
return results;
}
static Emboss::OrthoProject create_projection_for_cut(
Transform3d tr,
double shape_scale,
const BoundingBox &shape_bb,
const std::pair<float, float> &z_range)
{
// create sure that emboss object is bigger than source object
const float safe_extension = 1.0f;
double min_z = z_range.first - safe_extension;
double max_z = z_range.second + safe_extension;
assert(min_z < max_z);
// range between min and max value
double projection_size = max_z - min_z;
Matrix3d transformation_for_vector = tr.linear();
// Projection must be negative value.
// System of text coordinate
// X .. from left to right
// Y .. from bottom to top
// Z .. from text to eye
Vec3d untransformed_direction(0., 0., projection_size);
Vec3d project_direction = transformation_for_vector * untransformed_direction;
// Projection is in direction from far plane
tr.translate(Vec3d(0., 0., min_z));
tr.scale(shape_scale);
// Text alignemnt to center 2D
Vec2d move = -(shape_bb.max + shape_bb.min).cast<double>() / 2.;
// Vec2d move = -shape_bb.center().cast<double>(); // not precisse
tr.translate(Vec3d(move.x(), move.y(), 0.));
return Emboss::OrthoProject(tr, project_direction);
}
TEST_CASE("CutSurface in 3mf", "[Emboss]")
{
//std::string path_to_3mf = "C:/Users/Filip Sykala/Downloads/EmbossFromMultiVolumes.3mf";
//int object_id = 0;
//int text_volume_id = 2;
//std::string path_to_3mf = "C:/Users/Filip Sykala/Downloads/treefrog.3mf";
//int object_id = 0;
//int text_volume_id = 1;
std::string path_to_3mf = "C:/Users/Filip Sykala/Downloads/cube_test.3mf";
int object_id = 1;
int text_volume_id = 2;
Model model;
DynamicPrintConfig config;
ConfigSubstitutionContext ctxt{ForwardCompatibilitySubstitutionRule::Disable};
CHECK(load_3mf(path_to_3mf.c_str(), config, ctxt, &model, false));
CHECK(object_id >= 0);
CHECK((size_t)object_id < model.objects.size());
ModelObject* mo = model.objects[object_id];
CHECK(mo != nullptr);
CHECK(text_volume_id >= 0);
CHECK((size_t)text_volume_id < mo->volumes.size());
ModelVolume *mv_text = mo->volumes[text_volume_id];
CHECK(mv_text != nullptr);
CHECK(mv_text->text_configuration.has_value());
TextConfiguration &tc = *mv_text->text_configuration;
/* // Need GUI to load font by wx
std::optional<wxFont> wx_font = GUI::WxFontUtils::load_wxFont(tc.style.path);
CHECK(wx_font.has_value());
Emboss::FontFileWithCache ff(GUI::WxFontUtils::create_font_file(*wx_font));
CHECK(ff.font_file != nullptr);
/*/ // end use GUI
// start use fake font
std::string font_path = std::string(TEST_DATA_DIR) +
"/../../resources/fonts/NotoSans-Regular.ttf";
Emboss::FontFileWithCache ff(Emboss::create_font_file(font_path.c_str()));
// */ // end use fake font
CHECK(ff.has_value());
std::vector<indexed_triangle_set> its = transform_volumes(mv_text);
BoundingBoxf3 bb;
for (auto &i : its) bb.merge(Slic3r::bounding_box(i));
Transform3d cut_projection_tr = mv_text->get_matrix() * tc.fix_3mf_tr->inverse();
Transform3d emboss_tr = cut_projection_tr.inverse();
BoundingBoxf3 mesh_bb_tr = bb.transformed(emboss_tr);
std::pair<float, float> z_range{mesh_bb_tr.min.z(), mesh_bb_tr.max.z()};
FontProp fp = tc.style.prop;
ExPolygons shapes = Emboss::text2shapes(ff, tc.text.c_str(), fp);
double shape_scale = Emboss::get_shape_scale(fp, *ff.font_file);
Emboss::OrthoProject projection = create_projection_for_cut(
cut_projection_tr, shape_scale, get_extents(shapes), z_range);
float projection_ratio = -z_range.first / (z_range.second - z_range.first);
SurfaceCut cut = cut_surface(shapes, its, projection, projection_ratio);
its_write_obj(cut, "C:/data/temp/cutSurface/result_cut.obj");
}
#endif // DEBUG_3MF

View File

@@ -0,0 +1,610 @@
#include <catch2/catch.hpp>
#include <iostream>
#include <boost/filesystem.hpp>
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/ElephantFootCompensation.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/Flow.hpp"
#include "libslic3r/SVG.hpp"
using namespace Slic3r;
// #define TESTS_EXPORT_SVGS
namespace Slic3r {
ClipperLib::Path mittered_offset_path_scaled(const Points& contour, const std::vector<float>& deltas, double miter_limit);
}
static ExPolygon spirograph_gear_1mm()
{
ExPolygon out;
out.contour.points = { { 8989059, 1015976 }, { 9012502, 1051010 }, { 9224741, 1786512 }, { 9232060, 1811874 }, { 9222459, 2132217 }, { 10263301, 2241715 }, { 10318693, 1936696 }, { 10320603, 1926178 }, { 10680972, 1250945 }, { 10693399, 1227661 }, { 10723616, 1198273 }, { 11599898, 346008 }, { 11616108, 351267 }, { 12086183, 503769 }, { 12293780, 1708518 }, { 12300939, 1750061 }, { 12195899, 2508234 }, { 12192277, 2534378 }, { 12053161, 2823089 }, { 12959357, 3346344 }, { 13133980, 3090414 }, { 13140002, 3081589 }, { 13160830, 3065371 }, { 13764842, 2595047 }, { 13804400, 2580484 }, { 14951581, 2158173 }, { 14964243, 2169573 }, { 15331439, 2500198 }, { 15031347, 3685330 }, { 15020999, 3726196 }, { 14616409, 4376044 }, { 14602458, 4398453 }, { 14594311, 4405358 }, { 14358060, 4605591 }, { 14973020, 5452271 }, { 15245662, 5283768 }, { 15271287, 5277427 }, { 16014420, 5093552 }, { 16056481, 5096336 }, { 17276242, 5177094 }, { 17477040, 5628611 }, { 17483964, 5644181 }, { 16727991, 6604475 }, { 16701923, 6637589 }, { 16680060, 6652386 }, { 16046043, 7081528 }, { 16035789, 7084529 }, { 15738421, 7171570 }, { 15955998, 8195191 }, { 16273777, 8152008 }, { 16299760, 8156636 }, { 17053280, 8290848 }, { 17090572, 8310500 }, { 18172024, 8880417 }, { 18172024, 9391815 }, { 18134732, 9411467 }, { 17053280, 9981369 }, { 17027297, 9985997 }, { 16273777, 10120209 }, { 16263184, 10118770 }, { 15955998, 10077026 }, { 15738421, 11100647 }, { 16046043, 11190704 }, { 16067906, 11205502 }, { 16701923, 11634644 }, { 17457896, 12594938 }, { 17483964, 12628052 }, { 17283166, 13079569 }, { 17276242, 13095139 }, { 17234181, 13097923 }, { 16014420, 13178665 }, { 15988795, 13172324 }, { 15245662, 12988449 }, { 15236574, 12982832 }, { 14973020, 12819946 }, { 14358060, 13666641 }, { 14602458, 13873764 }, { 15007048, 14523627 }, { 15020999, 14546036 }, { 15321091, 15731152 }, { 15331439, 15772018 }, { 15318777, 15783419 }, { 14951581, 16114059 }, { 14912023, 16099496 }, { 13764842, 15677170 }, { 13744014, 15660952 }, { 13140002, 15190628 }, { 12959357, 14925887 }, { 12053161, 15449127 }, { 12187640, 15728230 }, { 12192277, 15737854 }, { 12297317, 16496013 }, { 12300939, 16522156 }, { 12093342, 17726920 }, { 12086183, 17768464 }, { 12069973, 17773722 }, { 11599898, 17926208 }, { 11569681, 17896820 }, { 10693399, 17044556 }, { 10333030, 16369337 }, { 10320603, 16346054 }, { 10263301, 16030502 }, { 9222459, 16140015 }, { 9231740, 16449664 }, { 9232060, 16460342 }, { 9019821, 17195859 }, { 9012502, 17221222 }, { 8332646, 18237183 }, { 8309203, 18272216 }, { 8292260, 18270438 }, { 7800922, 18218872 }, { 7347225, 17083700 }, { 7331580, 17044556 }, { 7276730, 16280940 }, { 7274839, 16254608 }, { 7350758, 15943314 }, { 6355663, 15619904 }, { 6238078, 15906603 }, { 6234023, 15916489 }, { 5741039, 16501967 }, { 5724040, 16522156 }, { 5688377, 16544630 }, { 4654144, 17196380 }, { 4639383, 17187857 }, { 4211318, 16940704 }, { 4258533, 15719288 }, { 4260162, 15677170 }, { 4520697, 14957214 }, { 4529681, 14932388 }, { 4725821, 14678955 }, { 3948022, 13978775 }, { 3716296, 14200317 }, { 3692552, 14211841 }, { 3003980, 14546036 }, { 2962267, 14552057 }, { 1752599, 14726654 }, { 1462059, 14326969 }, { 1452041, 14313187 }, { 1991943, 13216482 }, { 2010560, 13178665 }, { 2541453, 12627039 }, { 2559760, 12608017 }, { 2569167, 12602956 }, { 2841979, 12456177 }, { 2416404, 11500290 }, { 2114701, 11608368 }, { 2088313, 11609244 }, { 1323058, 11634644 }, { 1282503, 11623175 }, { 106399, 11290588 }, { 3546, 10807167 }, { -1, 10790497 }, { 32389, 10763526 }, { 971700, 9981369 }, { 996159, 9971434 }, { 1705482, 9683334 }, { 1716131, 9682534 }, { 2024962, 9659348 }, { 2024962, 8612869 }, { 1705482, 8588898 }, { 1681022, 8578963 }, { 971700, 8290848 }, { 939310, 8263878 }, { -1, 7481735 }, { 102852, 6998299 }, { 106399, 6981629 }, { 146954, 6970160 }, { 1323058, 6637589 }, { 1349446, 6638464 }, { 2114701, 6663849 }, { 2124758, 6667452 }, { 2416404, 6771927 }, { 2841979, 5816056 }, { 2559760, 5664200 }, { 2028867, 5112573 }, { 2010560, 5093552 }, { 1470658, 3996848 }, { 1452041, 3959030 }, { 1742580, 3559360 }, { 1752599, 3545578 }, { 1794312, 3551599 }, { 3003980, 3726196 }, { 3027724, 3737720 }, { 3716296, 4071915 }, { 3724020, 4079299 }, { 3948022, 4293442 }, { 4725822, 3593262 }, { 4536219, 3348276 }, { 4529681, 3339829 }, { 4269146, 2619873 }, { 4260162, 2595047 }, { 4212946, 1373645 }, { 4211318, 1331528 }, { 4226079, 1323005 }, { 4654144, 1075852 }, { 4689807, 1098325 }, { 5724040, 1750061 }, { 6217024, 2335539 }, { 6234023, 2355728 }, { 6355663, 2652329 }, { 7350759, 2328903 }, { 7277369, 2027985 }, { 7274839, 2017609 }, { 7329689, 1253993 }, { 7331580, 1227661 }, { 7785277, 92503 }, { 7800922, 53360 }, { 7817864, 51581 }, { 8309203, 0 } };
out.holes.emplace_back(Slic3r::Points({ {8982039, 9119734}, {8675233, 9160126}, {8654832, 9168577}, {8368934, 9287003}, {8351415, 9300446}, {8105907, 9488831}, {7917523, 9734328}, {7904081, 9751846}, {7785658, 10037750}, {7777208, 10058151}, {7736814, 10364949}, {7733932, 10386841}, {7774325, 10693653}, {7777208, 10715546}, {7895630, 11001450}, {7904081, 11021851}, {8092464, 11267363}, {8105907, 11284882}, {8123425, 11298325}, {8368934, 11486710}, {8389335, 11495160}, {8675233, 11613571}, {8697126, 11616453}, {9003932, 11656845}, {9025825, 11653963}, {9332633, 11613571}, {9353034, 11605121}, {9638932, 11486710}, {9656451, 11473267}, {9901958, 11284882}, {10090343, 11039370}, {10103786, 11021851}, {10222209, 10735947}, {10230659, 10715546}, {10271050, 10408734}, {10273932, 10386841}, {10233541, 10080043}, {10230659, 10058151}, {10112236, 9772247}, {10103786, 9751846}, {9915401, 9506349}, {9901958, 9488831}, {9884439, 9475388}, {9638932, 9287003}, {9618531, 9278552}, {9332633, 9160126}, {9310740, 9157244}, {9003932, 9116852} }));
out.holes.emplace_back(Slic3r::Points({ {5301863, 6863631}, {4995055, 6904022}, {4974654, 6912473}, {4688756, 7030899}, {4671237, 7044342}, {4425731, 7232727}, {4237345, 7478225}, {4223903, 7495743}, {4105480, 7781646}, {4097030, 7802048}, {4056638, 8108859}, {4053756, 8130753}, {4094147, 8437550}, {4097030, 8459442}, {4215452, 8745346}, {4223903, 8765747}, {4412288, 9011259}, {4425731, 9028778}, {4443249, 9042221}, {4688756, 9230606}, {4709157, 9239057}, {4995055, 9357483}, {5016948, 9360365}, {5323756, 9400757}, {5345649, 9397875}, {5652456, 9357483}, {5672856, 9349032}, {5958755, 9230606}, {5976273, 9217163}, {6221782, 9028778}, {6410165, 8783266}, {6423608, 8765747}, {6542031, 8479843}, {6550481, 8459442}, {6590874, 8152645}, {6593757, 8130753}, {6553363, 7823941}, {6550481, 7802048}, {6432058, 7516144}, {6423608, 7495743}, {6235224, 7250245}, {6221782, 7232727}, {6204263, 7219284}, {5958755, 7030899}, {5938354, 7022448}, {5652456, 6904022}, {5630563, 6901140}, {5323756, 6860749} }));
out.holes.emplace_back(Slic3r::Points({ {10306044, 5682112}, {9999236, 5722504}, {9978835, 5730953}, {9692937, 5849365}, {9675418, 5862808}, {9429912, 6051194}, {9241527, 6296691}, {9228084, 6314209}, {9109661, 6600113}, {9101211, 6620514}, {9060819, 6927326}, {9057937, 6949219}, {9098329, 7256016}, {9101211, 7277909}, {9219634, 7563812}, {9228084, 7584214}, {9416469, 7829725}, {9429912, 7847245}, {9447431, 7860687}, {9692937, 8049073}, {9713338, 8057523}, {9999236, 8175949}, {10021129, 8178831}, {10327937, 8219223}, {10349830, 8216341}, {10656638, 8175949}, {10677039, 8167498}, {10962937, 8049073}, {10980456, 8035630}, {11225963, 7847245}, {11414346, 7601733}, {11427789, 7584214}, {11546212, 7298310}, {11554662, 7277909}, {11595056, 6971111}, {11597938, 6949219}, {11557544, 6642407}, {11554662, 6620514}, {11436239, 6334610}, {11427789, 6314209}, {11239406, 6068712}, {11225963, 6051194}, {11208444, 6037751}, {10962937, 5849365}, {10942536, 5840915}, {10656638, 5722504}, {10634745, 5719621}, {10327937, 5679230} }));
return out;
}
// Contour from GH issue #2998.
static ExPolygon box_with_hole_close_to_wall()
{
ExPolygon out;
out.contour.points = { { 20000000, 20000000}, { 0, 20000000}, { 0, 0}, { 20000000, 0} };
out.holes.emplace_back(Slic3r::Points( {
{ 9905173, 501406}, { 9895707, 501967}, { 9715853, 512640}, { 9706437, 513762}, { 9527531, 535071}, { 9518198, 536749}, { 9340868, 568619}, { 9331651, 570846}, { 9156521, 613166},
{ 9147452, 615935}, { 8975137, 668555}, { 8966248, 671857}, { 8797352, 734593}, { 8788674, 738416}, { 8623792, 811047}, { 8615356, 815377}, { 8455065, 897648}, { 8446900, 902470},
{ 8291765, 994093}, { 8283900, 999390}, { 8134465, 1100042}, { 8126928, 1105796}, { 7983719, 1215124}, { 7976536, 1221315}, { 7840055, 1338934}, { 7833251, 1345539}, { 7703977, 1471037},
{ 7697576, 1478034}, { 7575964, 1610970}, { 7569989, 1618333}, { 7456466, 1758240}, { 7450937, 1765944}, { 7345902, 1912331}, { 7340840, 1920349}, { 7244661, 2072701}, { 7240082, 2081005},
{ 7153097, 2238787}, { 7149019, 2247348}, { 7071534, 2410005}, { 7067970, 2418793}, { 7000257, 2585755}, { 6997220, 2594738}, { 6939517, 2765418}, { 6937018, 2774565},
{ 6889527, 2948365}, { 6887574, 2957644}, { 6850462, 3133951}, { 6849062, 3143330}, { 6822461, 3321526}, { 6821618, 3330971}, { 6805620, 3510430}, { 6805339, 3519909},
{ 6800000, 3700000}, { 6800281, 3709478}, { 6805620, 3889570}, { 6806462, 3899015}, { 6822461, 4078474}, { 6823861, 4087853}, { 6850462, 4266049}, { 6852415, 4275328},
{ 6889527, 4451636}, { 6892027, 4460783}, { 6939517, 4634582}, { 6942554, 4643565}, { 7000257, 4814245}, { 7003821, 4823033}, { 7071534, 4989995}, { 7075612, 4998556},
{ 7153097, 5161214}, { 7157675, 5169518}, { 7244661, 5327300}, { 7249723, 5335318}, { 7345902, 5487670}, { 7351430, 5495374}, { 7456466, 5641761}, { 7462440, 5649124},
{ 7575964, 5789031}, { 7582365, 5796027}, { 7703977, 5928963}, { 7710780, 5935568}, { 7840055, 6061067}, { 7847238, 6067257}, { 7983719, 6184877}, { 7991256, 6190631},
{ 8134465, 6299958}, { 8142330, 6305255}, { 8291765, 6405907}, { 8299930, 6410729}, { 8455065, 6502352}, { 8463501, 6506682}, { 8623792, 6588953}, { 8632470, 6592776},
{ 8797352, 6665407}, { 8806241, 6668708}, { 8975137, 6731445}, { 8984206, 6734214}, { 9156521, 6786834}, { 9165738, 6789061}, { 9340868, 6831381}, { 9350201, 6833058},
{ 9527531, 6864929}, { 9536947, 6866050}, { 9715853, 6887360}, { 9725319, 6887921}, { 9905173, 6898595}, { 10094827, 6898595}, { 10104293, 6898033}, { 10284147, 6887360},
{ 10293563, 6886238}, { 10472469, 6864929}, { 10481802, 6863251}, { 10659132, 6831381}, { 10668349, 6829154}, { 10843479, 6786834}, { 10852548, 6784065}, { 11024863, 6731445},
{ 11033752, 6728143}, { 11202648, 6665407}, { 11211326, 6661584}, { 11376208, 6588953}, { 11384644, 6584623}, { 11544935, 6502352}, { 11553100, 6497530}, { 11708235, 6405907},
{ 11716100, 6400610}, { 11865535, 6299958}, { 11873072, 6294204}, { 12016281, 6184877}, { 12023464, 6178686}, { 12159946, 6061067}, { 12166750, 6054461}, { 12296023, 5928963},
{ 12302424, 5921966}, { 12424036, 5789031}, { 12430011, 5781667}, { 12543534, 5641761}, { 12549062, 5634056}, { 12654099, 5487670}, { 12659161, 5479651}, { 12755340, 5327300},
{ 12759918, 5318995}, { 12846903, 5161214}, { 12850981, 5152653}, { 12928466, 4989995}, { 12932030, 4981208}, { 12999743, 4814245}, { 13002780, 4805262}, { 13060483, 4634582},
{ 13062983, 4625434}, { 13110474, 4451636}, { 13112427, 4442356}, { 13149538, 4266049}, { 13150938, 4256670}, { 13177540, 4078474}, { 13178382, 4069029}, { 13194380, 3889570},
{ 13194661, 3880092}, { 13200000, 3700000}, { 13199719, 3690521}, { 13194380, 3510430}, { 13193538, 3500985}, { 13177540, 3321526}, { 13176140, 3312147}, { 13149538, 3133951},
{ 13147585, 3124672}, { 13110474, 2948365}, { 13107974, 2939217}, { 13060483, 2765418}, { 13057446, 2756435}, { 12999743, 2585755}, { 12996179, 2576968}, { 12928466, 2410005},
{ 12924388, 2401444}, { 12846903, 2238787}, { 12842325, 2230482}, { 12755340, 2072701}, { 12750278, 2064682}, { 12654099, 1912331}, { 12648571, 1904626}, { 12543534, 1758240},
{ 12537559, 1750876}, { 12424036, 1610970}, { 12417635, 1603973}, { 12296023, 1471037}, { 12289219, 1464432}, { 12159946, 1338934}, { 12152763, 1332744}, { 12016281, 1215124},
{ 12008744, 1209370}, { 11865535, 1100042}, { 11857670, 1094745}, { 11708235, 994093}, { 11700070, 989271}, { 11544935, 897648}, { 11536499, 893318}, { 11376208, 811047},
{ 11367530, 807224}, { 11202648, 734593}, { 11193759, 731291}, { 11024863, 668555}, { 11015794, 665786}, { 10843479, 613166}, { 10834262, 610939}, { 10659132, 568619},
{ 10649799, 566941}, { 10472469, 535071}, { 10463053, 533950}, { 10284147, 512640}, { 10274681, 512078}, { 10094827, 501406}
}));
return out;
}
// Contour from GH issue #2085.
static ExPolygon thin_ring()
{
ExPolygon out;
out.contour.points = {
{ 7805980, 147}, { 8182728, 9400}, { 8188694, 9840}, { 8564533, 37560}, { 8570470, 38292}, { 8944500, 84420}, { 8950394, 85443}, { 9321700, 149880},
{ 9327537, 151191}, { 9695240, 233760}, { 9701005, 235356}, { 10064220, 335870}, { 10069900, 337747}, { 10427740, 455960}, { 10433321, 458113}, { 10784930, 593740},
{ 10790399, 596164}, { 11134930, 748880}, { 11140273, 751570}, { 11476891, 921010}, { 11482096, 923959}, { 11810000, 1109720}, { 11815054, 1112921}, { 12133450, 1314540},
{ 12138341, 1317985}, { 12446450, 1534980}, { 12451166, 1538661}, { 12748270, 1770520}, { 12752800, 1774427}, { 13038160, 2020580}, { 13042492, 2024705}, { 13315430, 2284570},
{ 13575295, 2557508}, { 13579420, 2561840}, { 13825573, 2847201}, { 13829480, 2851730}, { 14061340, 3148834}, { 14065020, 3153550}, { 14282016, 3461660}, { 14285460, 3466550},
{ 14487080, 3784946}, { 14490280, 3790000}, { 14676041, 4117905}, { 14678990, 4123110}, { 14848430, 4459727}, { 14851120, 4465071}, { 15003836, 4809601}, { 15006260, 4815070},
{ 15141887, 5166679}, { 15144040, 5172261}, { 15262254, 5530100}, { 15264130, 5535780}, { 15364645, 5898995}, { 15366240, 5904761}, { 15448809, 6272464}, { 15450120, 6278301},
{ 15514557, 6649607}, { 15515580, 6655501}, { 15561709, 7029530}, { 15562441, 7035467}, { 15590160, 7411306}, { 15590600, 7417272}, { 15599853, 7794020}, { 15600000, 7800000},
{ 15590747, 8176748}, { 15590600, 8182728}, { 15562881, 8558567}, { 15562441, 8564533}, { 15516312, 8938563}, { 15515580, 8944500}, { 15451143, 9315806}, { 15450120, 9321700},
{ 15367551, 9689403}, { 15366240, 9695240}, { 15265725, 10058455}, { 15264130, 10064220}, { 15145916, 10422060}, { 15144040, 10427740}, { 15008413, 10779349}, { 15006260, 10784930},
{ 14853544, 11129461}, { 14851120, 11134930}, { 14681680, 11471548}, { 14678990, 11476891}, { 14493229, 11804795}, { 14490280, 11810000}, { 14288660, 12128396}, { 14285460, 12133450},
{ 14068464, 12441559}, { 14065020, 12446450}, { 13833160, 12743554}, { 13829480, 12748270}, { 13583327, 13033630}, { 13579420, 13038160}, { 13319555, 13311098}, { 13315430, 13315430},
{ 13311098, 13319555}, { 13038160, 13579420}, { 13033630, 13583327}, { 12748270, 13829480}, { 12743554, 13833160}, { 12446450, 14065020}, { 12441559, 14068464}, { 12133450, 14285460},
{ 12128396, 14288660}, { 11810000, 14490280}, { 11804795, 14493229}, { 11476891, 14678990}, { 11471548, 14681680}, { 11134930, 14851120}, { 11129461, 14853544}, { 10784930, 15006260},
{ 10779349, 15008413}, { 10427740, 15144040}, { 10422060, 15145916}, { 10064220, 15264130}, { 10058455, 15265725}, { 9695240, 15366240}, { 9689403, 15367551}, { 9321700, 15450120},
{ 9315806, 15451143}, { 8944500, 15515580}, { 8938563, 15516312}, { 8564533, 15562441}, { 8558567, 15562881}, { 8182728, 15590600}, { 8176748, 15590747}, { 7800000, 15600000},
{ 7794020, 15599853}, { 7417272, 15590600}, { 7411306, 15590160}, { 7035467, 15562441}, { 7029530, 15561709}, { 6655501, 15515580}, { 6649607, 15514557}, { 6278301, 15450120},
{ 6272464, 15448809}, { 5904761, 15366240}, { 5898995, 15364645}, { 5535780, 15264130}, { 5530100, 15262254}, { 5172261, 15144040}, { 5166679, 15141887}, { 4815070, 15006260},
{ 4809601, 15003836}, { 4465071, 14851120}, { 4459727, 14848430}, { 4123110, 14678990}, { 4117905, 14676041}, { 3790000, 14490280}, { 3784946, 14487080}, { 3466550, 14285460},
{ 3461660, 14282016}, { 3153550, 14065020}, { 3148834, 14061340}, { 2851730, 13829480}, { 2847201, 13825573}, { 2561840, 13579420}, { 2557508, 13575295}, { 2284570, 13315430},
{ 2024705, 13042492}, { 2020580, 13038160}, { 1774427, 12752800}, { 1770520, 12748270}, { 1538661, 12451166}, { 1534980, 12446450}, { 1317985, 12138341}, { 1314540, 12133450},
{ 1112921, 11815054}, { 1109720, 11810000}, { 923959, 11482096}, { 921010, 11476891}, { 751570, 11140273}, { 748880, 11134930}, { 596164, 10790399}, { 593740, 10784930},
{ 458113, 10433321}, { 455960, 10427740}, { 337747, 10069900}, { 335870, 10064220}, { 235356, 9701005}, { 233760, 9695240}, { 151191, 9327537}, { 149880, 9321700}, { 85443, 8950394},
{ 84420, 8944500}, { 38292, 8570470}, { 37560, 8564533}, { 9840, 8188694}, { 9400, 8182728}, { 147, 7805980}, { 0, 7800000}, { 9253, 7423252}, { 9400, 7417272}, { 37120, 7041433},
{ 37560, 7035467}, { 83688, 6661437}, { 84420, 6655501}, { 148858, 6284194}, { 149880, 6278301}, { 232450, 5910597}, { 233760, 5904761}, { 334275, 5541545}, { 335870, 5535780},
{ 454084, 5177940}, { 455960, 5172261}, { 591587, 4820651}, { 593740, 4815070}, { 746456, 4470539}, { 748880, 4465071}, { 918320, 4128453}, { 921010, 4123110}, { 1106772, 3795205},
{ 1109720, 3790000}, { 1311340, 3471604}, { 1314540, 3466550}, { 1531536, 3158441}, { 1534980, 3153550}, { 1766840, 2856446}, { 1770520, 2851730}, { 2016673, 2566370}, { 2020580, 2561840},
{ 2280445, 2288903}, { 2284570, 2284570}, { 2288903, 2280445}, { 2561840, 2020580}, { 2566370, 2016673}, { 2851730, 1770520}, { 2856446, 1766840}, { 3153550, 1534980}, { 3158441, 1531536},
{ 3466550, 1314540}, { 3471604, 1311340}, { 3790000, 1109720}, { 3795205, 1106772}, { 4123110, 921010}, { 4128453, 918320}, { 4465071, 748880}, { 4470539, 746456}, { 4815070, 593740},
{ 4820651, 591587}, { 5172261, 455960}, { 5177940, 454084}, { 5535780, 335870}, { 5541545, 334275}, { 5904761, 233760}, { 5910597, 232450}, { 6278301, 149880}, { 6284194, 148858},
{ 6655501, 84420}, { 6661437, 83688}, { 7035467, 37560}, { 7041433, 37120}, { 7417272, 9400}, { 7423252, 9253}, { 7800000, 0}
};
out.holes.emplace_back(Slic3r::Points( {
{ 7794921, 1002175}, { 7466441, 1010240}, { 7461374, 1010614}, { 7133685, 1034780}, { 7128642, 1035402}, { 6802534, 1075630}, { 6797528, 1076499}, { 6473790, 1132670},
{ 6468832, 1133784}, { 6148230, 1205780}, { 6143333, 1207135}, { 5826660, 1294770}, { 5821835, 1296364}, { 5509840, 1399430}, { 5505100, 1401259}, { 5198540, 1519510},
{ 5193895, 1521569}, { 4893501, 1654720}, { 4888962, 1657005}, { 4595471, 1804740}, { 4591050, 1807245}, { 4305150, 1969200}, { 4300857, 1971918}, { 4023260, 2147710},
{ 4019106, 2150636}, { 3750470, 2339831}, { 3746465, 2342956}, { 3487430, 2545110}, { 3483583, 2548429}, { 3234780, 2763050}, { 3231100, 2766553}, { 2993120, 2993120},
{ 2766553, 3231100}, { 2763050, 3234780}, { 2548429, 3483583}, { 2545110, 3487430}, { 2342956, 3746465}, { 2339831, 3750470}, { 2150636, 4019106}, { 2147710, 4023260},
{ 1971918, 4300857}, { 1969200, 4305150}, { 1807245, 4591050}, { 1804740, 4595471}, { 1657005, 4888962}, { 1654720, 4893501}, { 1521569, 5193895}, { 1519510, 5198540},
{ 1401259, 5505100}, { 1399430, 5509840}, { 1296364, 5821835}, { 1294770, 5826660}, { 1207135, 6143333}, { 1205780, 6148230}, { 1133784, 6468832}, { 1132670, 6473790},
{ 1076499, 6797528}, { 1075630, 6802534}, { 1035402, 7128642}, { 1034780, 7133685}, { 1010614, 7461374}, { 1010240, 7466441}, { 1002175, 7794921}, { 1002050, 7800000},
{ 1010115, 8128480}, { 1010240, 8133559}, { 1034406, 8461248}, { 1034780, 8466315}, { 1075008, 8792423}, { 1075630, 8797466}, { 1131802, 9121204}, { 1132670, 9126210},
{ 1204667, 9446812}, { 1205780, 9451770}, { 1293415, 9768443}, { 1294770, 9773340}, { 1397836, 10085335}, { 1399430, 10090160}, { 1517682, 10396721}, { 1519510, 10401461},
{ 1652661, 10701855}, { 1654720, 10706500}, { 1802456, 10999992}, { 1804740, 11004530}, { 1966696, 11290429}, { 1969200, 11294850}, { 2144992, 11572447}, { 2147710, 11576740},
{ 2336905, 11845376}, { 2339831, 11849530}, { 2541984, 12108564}, { 2545110, 12112570}, { 2759731, 12361373}, { 2763050, 12365220}, { 2989617, 12603200}, { 2993120, 12606880},
{ 2996800, 12610383}, { 3234780, 12836950}, { 3238628, 12840269}, { 3487430, 13054890}, { 3491436, 13058016}, { 3750470, 13260170}, { 3754624, 13263096}, { 4023260, 13452290},
{ 4027553, 13455008}, { 4305150, 13630800}, { 4309571, 13633304}, { 4595471, 13795260}, { 4600009, 13797544}, { 4893501, 13945280}, { 4898146, 13947339}, { 5198540, 14080490},
{ 5203280, 14082319}, { 5509840, 14200570}, { 5514665, 14202164}, { 5826660, 14305230}, { 5831557, 14306585}, { 6148230, 14394220}, { 6153188, 14395333}, { 6473790, 14467330},
{ 6478796, 14468199}, { 6802534, 14524370}, { 6807577, 14524992}, { 7133685, 14565220}, { 7138752, 14565594}, { 7466441, 14589760}, { 7471520, 14589885}, { 7800000, 14597950},
{ 7805079, 14597825}, { 8133559, 14589760}, { 8138626, 14589386}, { 8466315, 14565220}, { 8471358, 14564598}, { 8797466, 14524370}, { 8802472, 14523501}, { 9126210, 14467330},
{ 9131168, 14466217}, { 9451770, 14394220}, { 9456667, 14392865}, { 9773340, 14305230}, { 9778165, 14303636}, { 10090160, 14200570}, { 10094900, 14198741}, { 10401461, 14080490},
{ 10406106, 14078431}, { 10706500, 13945280}, { 10711038, 13942996}, { 11004530, 13795260}, { 11008951, 13792756}, { 11294850, 13630800}, { 11299143, 13628082}, { 11576740, 13452290},
{ 11580894, 13449364}, { 11849530, 13260170}, { 11853536, 13257044}, { 12112570, 13054890}, { 12116417, 13051571}, { 12365220, 12836950}, { 12368900, 12833447}, { 12606880, 12606880},
{ 12833447, 12368900}, { 12836950, 12365220}, { 13051571, 12116417}, { 13054890, 12112570}, { 13257044, 11853536}, { 13260170, 11849530}, { 13449364, 11580894}, { 13452290, 11576740},
{ 13628082, 11299143}, { 13630800, 11294850}, { 13792756, 11008951}, { 13795260, 11004530}, { 13942996, 10711038}, { 13945280, 10706500}, { 14078431, 10406106}, { 14080490, 10401461},
{ 14198741, 10094900}, { 14200570, 10090160}, { 14303636, 9778165}, { 14305230, 9773340}, { 14392865, 9456667}, { 14394220, 9451770}, { 14466217, 9131168}, { 14467330, 9126210},
{ 14523501, 8802472}, { 14524370, 8797466}, { 14564598, 8471358}, { 14565220, 8466315}, { 14589386, 8138626}, { 14589760, 8133559}, { 14597825, 7805079}, { 14597950, 7800000},
{ 14589885, 7471520}, { 14589760, 7466441}, { 14565594, 7138752}, { 14565220, 7133685}, { 14524992, 6807577}, { 14524370, 6802534}, { 14468199, 6478796}, { 14467330, 6473790},
{ 14395333, 6153188}, { 14394220, 6148230}, { 14306585, 5831557}, { 14305230, 5826660}, { 14202164, 5514665}, { 14200570, 5509840}, { 14082319, 5203280}, { 14080490, 5198540},
{ 13947339, 4898146}, { 13945280, 4893501}, { 13797544, 4600009}, { 13795260, 4595471}, { 13633304, 4309571}, { 13630800, 4305150}, { 13455008, 4027553}, { 13452290, 4023260},
{ 13263096, 3754624}, { 13260170, 3750470}, { 13058016, 3491436}, { 13054890, 3487430}, { 12840269, 3238628}, { 12836950, 3234780}, { 12610383, 2996800}, { 12606880, 2993120},
{ 12603200, 2989617}, { 12365220, 2763050}, { 12361373, 2759731}, { 12112570, 2545110}, { 12108564, 2541984}, { 11849530, 2339831}, { 11845376, 2336905}, { 11576740, 2147710},
{ 11572447, 2144992}, { 11294850, 1969200}, { 11290429, 1966696}, { 11004530, 1804740}, { 10999992, 1802456}, { 10706500, 1654720}, { 10701855, 1652661}, { 10401461, 1519510},
{ 10396721, 1517682}, { 10090160, 1399430}, { 10085335, 1397836}, { 9773340, 1294770}, { 9768443, 1293415}, { 9451770, 1205780}, { 9446812, 1204667}, { 9126210, 1132670},
{ 9121204, 1131802}, { 8797466, 1075630}, { 8792423, 1075008}, { 8466315, 1034780}, { 8461248, 1034406}, { 8133559, 1010240}, { 8128480, 1010115}, { 7800000, 1002050}
} ));
return out;
}
static ExPolygon vase_with_fins()
{
ExPolygon out;
out.contour.points = {
{27431106, 489754}, {27436907, 489850}, {27457500, 489724}, {27457500, 5510510}, {28343327, 5565859}, {28351400, 5566288}, {28389945, 5568336}, {28394790, 5568765}, {28420177, 5571613}, {28901163, 5629918},
{29903776, 5750412}, {30416384, 2513976}, {30682801, 831878}, {30688548, 795593}, {31507808, 939183}, {31513523, 940185}, {31533883, 943282}, {30775577, 5731079}, {30768824, 5773720}, {30748466, 5902252},
{31614726, 6095505}, {31622633, 6097191}, {31660382, 6105244}, {31665100, 6106426}, {31689729, 6113210}, {32155671, 6246039}, {33127094, 6521893}, {34139670, 3405493}, {34665944, 1785782}, {34677296, 1750843},
{35464012, 2020824}, {35469500, 2022707}, {35489124, 2028950}, {33991170, 6639179}, {33977829, 6680238}, {33937615, 6804003}, {34762987, 7130382}, {34770532, 7133285}, {34806557, 7147144}, {34811033, 7149049},
{34834297, 7159603}, {35273721, 7363683}, {36190026, 7788101}, {37677657, 4868472}, {38450834, 3351031}, {38467513, 3318298}, {39202308, 3708028}, {39207434, 3710747}, {39225840, 3719984}, {37025125, 8039112},
{37005525, 8077579}, {36946446, 8193529}, {37710592, 8645011}, {37717591, 8649059}, {37751004, 8668383}, {37755126, 8670965}, {37776453, 8685028}, {38178545, 8955338}, {39017176, 9517879}, {40943217, 6866906},
{41944249, 5489097}, {41965843, 5459376}, {42630625, 5959265}, {42635262, 5962752}, {42651996, 5974755}, {39802725, 9896448}, {39777349, 9931375}, {39700858, 10036656}, {40384973, 10602104}, {40391252, 10607196},
{40421232, 10631509}, {40424899, 10634704}, {40443764, 10651931}, {40798616, 10981815}, {41538921, 11668622}, {43855948, 9351592}, {45060194, 8147345}, {45086172, 8121368}, {45664563, 8719082}, {45668598, 8723251},
{45683249, 8737724}, {42255579, 12165422}, {42225051, 12195949}, {42133032, 12287968}, {42720262, 12953467}, {42725667, 12959479}, {42751474, 12988183}, {42754596, 12991912}, {42770534, 13011877}, {43069412, 13393211},
{43693167, 14187377}, {46344137, 12261333}, {47721948, 11260299}, {47751670, 11238705}, {48229435, 11919543}, {48232767, 11924292}, {48244974, 11940879}, {44323286, 14790155}, {44288359, 14815531}, {44183078, 14892022},
{44658973, 15641210}, {44663371, 15647994}, {44684370, 15680381}, {44686871, 15684553}, {44699489, 15706766}, {44935035, 16130156}, {45426863, 17012121}, {48346505, 15524481}, {49863946, 14751306}, {49896680, 14734627},
{50262068, 15481841}, {50264616, 15487053}, {50274078, 15505344}, {45954933, 17706046}, {45916466, 17725646}, {45800515, 17784726}, {46153358, 18599135}, {46156641, 18606523}, {46172315, 18641796}, {46174132, 18646308},
{46183120, 18670221}, {46349534, 19125250}, {46697342, 20073284}, {49813754, 19060715}, {51433464, 18534440}, {51468404, 18523087}, {51712400, 19318239}, {51714102, 19323786}, {51720585, 19343332}, {47110355, 20841293},
{47069295, 20854634}, {46945530, 20894847}, {47166614, 21754409}, {47168701, 21762220}, {47178664, 21799510}, {47179753, 21804251}, {47184889, 21829276}, {47278074, 22304738}, {47473309, 23295520}, {50709741, 22782917},
{52391837, 22516497}, {52428122, 22510750}, {52544737, 23334291}, {52545550, 23340036}, {52548897, 23360356}, {47761090, 24118668}, {47718449, 24125422}, {47589917, 24145780}, {47673812, 25029360}, {47674651, 25037401},
{47678657, 25075792}, {47678992, 25080644}, {47680151, 25106164}, {47697809, 25590347}, {47735642, 26599468}, {52752230, 26599468}, {52738564, 27431106}, {52738469, 27436907}, {52738595, 27457500}, {47717808, 27457500},
{47662461, 28343321}, {47662032, 28351394}, {47659983, 28389938}, {47659554, 28394784}, {47656706, 28420171}, {47598401, 28901157}, {47477907, 29903774}, {50714338, 30416378}, {52396434, 30682795}, {52432719, 30688542},
{52289144, 31507800}, {52288143, 31513515}, {52285046, 31533875}, {47497239, 30775569}, {47454598, 30768816}, {47326067, 30748458}, {47132809, 31614720}, {47131122, 31622626}, {47123069, 31660376}, {47121887, 31665094},
{47115103, 31689724}, {46982279, 32155664}, {46706424, 33127087}, {49822834, 34139662}, {51442545, 34665936}, {51477485, 34677289}, {51207490, 35464012}, {51205607, 35469500}, {51199363, 35489124}, {46589140, 33991162},
{46548081, 33977821}, {46424316, 33937607}, {46097945, 34762979}, {46095042, 34770524}, {46081183, 34806549}, {46079278, 34811025}, {46068724, 34834289}, {45864641, 35273715}, {45440218, 36190023}, {48359847, 37677651},
{49877288, 38450826}, {49910022, 38467505}, {49520291, 39202300}, {49517572, 39207426}, {49508336, 39225832}, {45189199, 37025117}, {45150732, 37005517}, {45034781, 36946438}, {44583309, 37710592}, {44579262, 37717591},
{44559938, 37751004}, {44557356, 37755126}, {44543292, 37776453}, {44272982, 38178543}, {43710441, 39017170}, {46361413, 40943214}, {47739222, 41944249}, {47768943, 41965843}, {47269053, 42630624}, {47265566, 42635262},
{47253564, 42651996}, {43331872, 39802717}, {43296945, 39777341}, {43191664, 39700850}, {42626221, 40384973}, {42621129, 40391252}, {42596816, 40421232}, {42593621, 40424899}, {42576394, 40443764}, {42246510, 40798616},
{41559699, 41538918}, {43876735, 43855948}, {45080983, 45060194}, {45106960, 45086172}, {44509231, 45664571}, {44505061, 45668605}, {44490589, 45683256}, {41062903, 42255578}, {40940357, 42133032}, {40274856, 42720258},
{40268844, 42725663}, {40240140, 42751470}, {40236411, 42754592}, {40216446, 42770530}, {39835112, 43069407}, {39040953, 43693161}, {40966991, 46344124}, {41968025, 47721932}, {41989619, 47751654}, {41308783, 48229434},
{41304034, 48232767}, {41287447, 48244973}, {38438168, 44323278}, {38412792, 44288351}, {38336302, 44183071}, {37587122, 44658973}, {37580338, 44663371}, {37547951, 44684370}, {37543779, 44686871}, {37521566, 44699489},
{37098171, 44935029}, {36216213, 45426864}, {37703841, 48346500}, {38477019, 49863946}, {38493698, 49896680}, {37746484, 50262052}, {37741272, 50264600}, {37722981, 50274062}, {35522285, 45954933}, {35502686, 45916466},
{35443606, 45800515}, {34629191, 46153350}, {34621803, 46156633}, {34586530, 46172307}, {34582018, 46174124}, {34558105, 46183112}, {34103078, 46349526}, {33155041, 46697341}, {34167619, 49813746}, {34693894, 51433456},
{34705246, 51468395}, {33910086, 51712399}, {33904540, 51714102}, {33884994, 51720585}, {32387039, 47110355}, {32373698, 47069295}, {32333485, 46945530}, {31473915, 47166622}, {31466104, 47168709}, {31428813, 47178672},
{31424073, 47179760}, {31399048, 47184897}, {30923586, 47278079}, {29932800, 47473310}, {30445407, 50709741}, {30711827, 52391837}, {30717574, 52428122}, {29894033, 52544729}, {29888288, 52545543}, {29867968, 52548889},
{29109657, 47761082}, {29102904, 47718441}, {29082546, 47589909}, {28198964, 47673827}, {28190923, 47674666}, {28152532, 47678673}, {28147680, 47679007}, {28122160, 47680166}, {27637977, 47697820}, {26628861, 47735648},
{26628861, 51012422}, {26628864, 52715485}, {26628864, 52752222}, {25797210, 52738556}, {25791409, 52738461}, {25770816, 52738587}, {25770816, 47717800}, {24884998, 47662453}, {24876924, 47662024}, {24838380, 47659975},
{24833534, 47659546}, {24808147, 47656698}, {24327161, 47598396}, {23324548, 47477901}, {22811940, 50714338}, {22545523, 52396434}, {22539776, 52432719}, {21720525, 52289129}, {21714811, 52288127}, {21694451, 52285030},
{22452755, 47497223}, {22459508, 47454583}, {22479866, 47326051}, {21613606, 47132816}, {21605699, 47131129}, {21567950, 47123077}, {21563232, 47121895}, {21538602, 47115110}, {21072662, 46982279}, {20101239, 46706425},
{19088664, 49822824}, {18562390, 51442538}, {18551037, 51477477}, {17764314, 51207498}, {17758826, 51205614}, {17739202, 51199371}, {19237154, 46589140}, {19250495, 46548081}, {19290709, 46424316}, {18465339, 46097937},
{18457794, 46095035}, {18421769, 46081175}, {18417293, 46079270}, {18394029, 46068716}, {17954603, 45864634}, {17038299, 45440211}, {15550671, 48359845}, {14777498, 49877288}, {14760820, 49910022}, {14026023, 49520291},
{14020897, 49517572}, {14002491, 49508335}, {16203201, 45189191}, {16222801, 45150724}, {16281880, 45034773}, {15517740, 44583309}, {15510741, 44579261}, {15477328, 44559938}, {15473206, 44557356}, {15451878, 44543292},
{15049787, 44272982}, {14211153, 43710440}, {12285115, 46361403}, {11284082, 47739206}, {11262488, 47768928}, {10597703, 47269053}, {10593066, 47265566}, {10576332, 47253563}, {13425609, 43331872}, {13450985, 43296945},
{13527476, 43191664}, {12843352, 42626213}, {12837073, 42621121}, {12807093, 42596808}, {12803426, 42593613}, {12784561, 42576386}, {12429709, 42246502}, {11689410, 41559693}, {9372373, 43876727}, {8168126, 45080975},
{8142148, 45106952}, {7563757, 44509222}, {7559722, 44505053}, {7545071, 44490581}, {10972747, 41062911}, {11003274, 41032383}, {11095293, 40940365}, {10508063, 40274848}, {10502658, 40268836}, {10476851, 40240132},
{10473729, 40236403}, {10457791, 40216438}, {10158911, 39835107}, {9535160, 39040950}, {6884192, 40966991}, {5506386, 41968025}, {5476665, 41989618}, {4998885, 41308775}, {4995553, 41304026}, {4983346, 41287439},
{8905039, 38438168}, {8939966, 38412792}, {9045247, 38336301}, {8569356, 37587114}, {8564958, 37580330}, {8543959, 37547943}, {8541458, 37543771}, {8528840, 37521558}, {8293293, 37098166}, {7801454, 36216208},
{4881822, 37703836}, {3364381, 38477011}, {3331647, 38493690}, {2966260, 37746484}, {2963712, 37741272}, {2954250, 37722981}, {7273379, 35522270}, {7311845, 35502670}, {7427796, 35443590}, {7074968, 34629191},
{7071686, 34621803}, {7056012, 34586530}, {7054194, 34582018}, {7045206, 34558105}, {6878792, 34103076}, {6530980, 33155036}, {3414573, 34167611}, {1794864, 34693885}, {1759924, 34705238}, {1515921, 33910079},
{1514219, 33904532}, {1507735, 33884986}, {6117964, 32387033}, {6159023, 32373692}, {6282789, 32333479}, {6061704, 31473909}, {6059617, 31466099}, {6049654, 31428807}, {6048565, 31424067}, {6043429, 31399042},
{5950245, 30923582}, {5755014, 29932799}, {2518579, 30445403}, {836483, 30711821}, {800198, 30717568}, {683591, 29894033}, {682777, 29888288}, {679431, 29867968}, {5467236, 29109657}, {5509877, 29102904}, {5638409, 29082546},
{5554499, 28198964}, {5553660, 28190923}, {5549653, 28152532}, {5549319, 28147680}, {5548160, 28122159}, {5530507, 27637975}, {5492679, 26628853}, {2215900, 26628853}, {512834, 26628856}, {476096, 26628856}, {489754, 25797218},
{489850, 25791417}, {489724, 25770824}, {5510510, 25770824}, {5565867, 24884990}, {5566296, 24876916}, {5568344, 24838372}, {5568773, 24833527}, {5571621, 24808139}, {5629923, 24327156}, {5750418, 23324543}, {2513981, 22811940},
{831886, 22545523}, {795600, 22539776}, {939191, 21720518}, {940192, 21714803}, {943289, 21694443}, {5731087, 22452754}, {5773728, 22459508}, {5902260, 22479865}, {6095512, 21613598}, {6097199, 21605691}, {6105252, 21567942},
{6106434, 21563224}, {6113218, 21538594}, {6246044, 21072654}, {6521898, 20101231}, {3405493, 19088662}, {1785783, 18562390}, {1750843, 18551037}, {2020831, 17764306}, {2022714, 17758819}, {2028958, 17739194}, {6639187, 19237147},
{6680246, 19250488}, {6804011, 19290701}, {7130382, 18465339}, {7133285, 18457794}, {7147144, 18421769}, {7149049, 18417293}, {7159603, 18394029}, {7363683, 17954605}, {7788110, 17038301}, {4868477, 15550669}, {3351039, 14777491},
{3318305, 14760812}, {3708029, 14026016}, {3710747, 14020890}, {3719984, 14002484}, {8039120, 16203201}, {8077586, 16222801}, {8193537, 16281881}, {8645019, 15517733}, {8649067, 15510734}, {8668391, 15477321}, {8670973, 15473199},
{8685036, 15451871}, {8955346, 15049780}, {9517887, 14211149}, {6866919, 12285108}, {5489112, 11284075}, {5459391, 11262481}, {5959259, 10597695}, {5962745, 10593058}, {5974747, 10576324}, {9896454, 13425601}, {9931382, 13450977},
{10036663, 13527468}, {10602111, 12843352}, {10607203, 12837073}, {10631516, 12807093}, {10634711, 12803426}, {10651937, 12784561}, {10981820, 12429709}, {11668626, 11689407}, {8147345, 8168126}, {8121368, 8142148}, {8719089, 7563749},
{8723258, 7559715}, {8737731, 7545064}, {12165414, 10972746}, {12195941, 11003274}, {12287960, 11095293}, {12953467, 10508056}, {12959479, 10502650}, {12988183, 10476843}, {12991912, 10473721}, {13011878, 10457783}, {13393211, 10158903},
{14187378, 9535150}, {12261338, 6884179}, {11260306, 5506371}, {11238712, 5476650}, {11919550, 4998885}, {11924299, 4995552}, {11940886, 4983346}, {14790161, 8905032}, {14815537, 8939959}, {14892028, 9045240}, {15641210, 8569348},
{15647994, 8564950}, {15680381, 8543951}, {15684553, 8541450}, {15706766, 8528832}, {16130159, 8293285}, {17012123, 7801449}, {15524489, 4881814}, {14751314, 3364373}, {14734635, 3331640}, {15481841, 2966253}, {15487053, 2963704},
{15505344, 2954242}, {17706054, 7273386}, {17725654, 7311852}, {17784734, 7427803}, {18599135, 7074961}, {18606523, 7071678}, {18641796, 7056004}, {18646308, 7054187}, {18670222, 7045199}, {19125250, 6878787}, {20073289, 6530975},
{19060715, 3414573}, {18534440, 1794864}, {18523088, 1759924}, {19318247, 1515921}, {19323794, 1514219}, {19343340, 1507736}, {20841293, 6117964}, {20854634, 6159023}, {20894848, 6282789}, {21754417, 6061696}, {21762228, 6059609},
{21799518, 6049647}, {21804259, 6048557}, {21829284, 6043421}, {22304743, 5950237}, {23295525, 5755007}, {22782917, 2518572}, {22516497, 836476}, {22510750, 800190}, {23334299, 683591}, {23340043, 682777}, {23360363, 679431},
{24118676, 5467229}, {24125430, 5509869}, {24145787, 5638402}, {25029368, 5554507}, {25037409, 5553668}, {25075799, 5549661}, {25080652, 5549327}, {25106172, 5548168}, {25590355, 5530509}, {26599476, 5492671}, {26599476, 476096}
};
return out;
}
static ExPolygon contour_with_hole()
{
ExPolygon out;
out.contour.points = {
{ 23302819, 108248}, { 23410179, 157624}, { 23451825, 176777}, { 24106418, 478750}, { 24704172, 811512}, { 24883849, 911534}, { 25980045, 1530217}, { 26591038, 1897423}, { 26829981, 2041022}, { 27158523, 2249848}, { 27618921, 2584465},
{ 27896903, 2786507}, { 28144524, 2978990}, { 28815685, 3551061}, { 28909975, 3628821}, { 29371498, 4009409}, { 29402087, 4037084}, { 29493584, 4119861}, { 29765627, 4382947}, { 30607836, 5197449}, { 30934687, 5508413}, { 31019374, 5593546},
{ 31075807, 5655861}, { 31235879, 5823254}, { 31667505, 6274618}, { 31976596, 6656087}, { 32328364, 7055603}, { 32440973, 7183484}, { 32491346, 7249288}, { 33179667, 8148478}, { 33575401, 8717521}, { 33835875, 9075811}, { 34010014, 9315332},
{ 34304500, 9781688}, { 34369165, 9898535}, { 34397842, 9950359}, { 34494651, 10316439}, { 34501993, 10344190}, { 34385828, 10617514}, { 34331252, 10651174}, { 34084812, 10803186}, { 33894353, 10899665}, { 33398927, 11326583},
{ 33183121, 11494200}, { 32195826, 12261037}, { 31686925, 12719913}, { 31571718, 12807396}, { 31250995, 13050935}, { 31207108, 13086856}, { 31130381, 13149671}, { 31070741, 13206732}, { 30967095, 13305896}, { 30228082, 14071658},
{ 30116771, 14212337}, { 30044101, 14304176}, { 29567520, 14906137}, { 29043350, 15664879}, { 28911161, 15871189}, { 28855871, 15957479}, { 28714334, 16227582}, { 28650159, 16350050}, { 28364584, 16899765}, { 28240857, 17235607},
{ 28151371, 17509658}, { 28114198, 17623503}, { 28309361, 17730441}, { 28370394, 17763884}, { 28488974, 17847025}, { 28525745, 17872806}, { 29082248, 18281292}, { 29152930, 18376480}, { 29168058, 18396855}, { 29173722, 18656366},
{ 29176206, 18770149}, { 29167406, 18857292}, { 29104337, 19029141}, { 29049428, 19178752}, { 28907061, 19434701}, { 28857790, 19523283}, { 28715480, 19775043}, { 28630622, 20043684}, { 28609342, 20111052}, { 28573760, 20267045},
{ 28403454, 21103762}, { 28370165, 21230085}, { 28332310, 21373746}, { 28315057, 21418891}, { 28294569, 21472487}, { 28334157, 21579715}, { 28561468, 21814880}, { 28854906, 22118451}, { 29225599, 22499341}, { 29285205, 22617454},
{ 29324833, 22695983}, { 29313473, 22800767}, { 29312583, 22808982}, { 29272380, 22876835}, { 28829469, 23460472}, { 28817999, 23488286}, { 28796393, 23540675}, { 28775618, 23627381}, { 28732328, 23808034}, { 28661140, 24177335},
{ 28645731, 24834289}, { 28625222, 25202417}, { 28579034, 26031478}, { 28586310, 26420529}, { 28633240, 26560504}, { 28664456, 26653603}, { 28740916, 26788014}, { 28797005, 26886614}, { 28812464, 26950783}, { 28858428, 27009579},
{ 28975940, 26859631}, { 29022419, 26805440}, { 29115451, 26696972}, { 29135739, 26685915}, { 29155135, 26675346}, { 29408332, 26616458}, { 29592642, 26573591}, { 29614928, 26568091}, { 29711634, 26559197}, { 30723503, 26466299},
{ 31183646, 26470661}, { 31550568, 26550771}, { 31777556, 26600329}, { 32014697, 26671604}, { 32334931, 26854665}, { 32449353, 26920987}, { 32657873, 27041843}, { 32701539, 27084927}, { 32750872, 27133602}, { 33434549, 27790306},
{ 33487600, 27817659}, { 33548673, 27849142}, { 33793150, 28109624}, { 33877574, 28164293}, { 33965395, 28221161}, { 33999067, 28249986}, { 34024398, 28271673}, { 34059690, 28329572}, { 34087359, 28374972}, { 34181544, 28710471},
{ 34170186, 28732578}, { 34134947, 28801161}, { 34092867, 29064916}, { 33950784, 29233310}, { 33878646, 29318804}, { 33721956, 29672399}, { 33660358, 29727949}, { 33620108, 29764243}, { 33393624, 30270577}, { 33094597, 30771032},
{ 33063116, 30812704}, { 32973928, 30930779}, { 32608081, 31341847}, { 32393317, 31544017}, { 32206520, 31719862}, { 31997581, 31894374}, { 31972538, 31942583}, { 32059002, 32025240}, { 32171917, 32133182}, { 32501317, 32311025},
{ 32715593, 32426714}, { 32802065, 32479231}, { 32956210, 32574312}, { 33249042, 32770899}, { 33946833, 33239350}, { 34445301, 33680139}, { 34778020, 33974357}, { 35230994, 34391224}, { 35341113, 34460366}, { 35450459, 34529022},
{ 35625170, 34673345}, { 35764733, 34757179}, { 35775747, 34633947}, { 35846476, 34564107}, { 35965365, 34446723}, { 36038088, 34379954}, { 36151170, 34276133}, { 36426218, 34106680}, { 36531666, 34187969}, { 36695885, 34314565},
{ 37011093, 34586835}, { 37067557, 34150814}, { 37052506, 33989541}, { 37037043, 33823855}, { 37069574, 33661923}, { 37083653, 33591851}, { 37186706, 33497192}, { 37521634, 33288703}, { 37617140, 33275082}, { 37684699, 33219614},
{ 37821418, 33228393}, { 37938489, 33235910}, { 38091617, 33138918}, { 38155158, 33060873}, { 38213556, 32989142}, { 38727086, 32659362}, { 38746459, 32654507}, { 38809135, 32638806}, { 38820634, 32624462}, { 38855007, 32581573},
{ 39134002, 32235481}, { 39392850, 32163442}, { 39569189, 32115608}, { 39686862, 32083692}, { 39744314, 32146839}, { 39840707, 31963655}, { 39973169, 31711932}, { 40025735, 31592644}, { 40157184, 31465080}, { 40313010, 31313863},
{ 40390192, 31223588}, { 40418596, 31230809}, { 40594404, 31186692}, { 40732045, 31068306}, { 40746151, 30846139}, { 40761255, 30608300}, { 40853394, 30223426}, { 40876768, 30095588}, { 40895496, 29993166}, { 40968240, 29949606},
{ 41197066, 29989787}, { 41412367, 30027591}, { 41472384, 29977101}, { 41695297, 29659954}, { 41890516, 29382211}, { 42157410, 28987811}, { 42408947, 28616097}, { 42669462, 28292349}, { 42683144, 28275345}, { 42919982, 27924149},
{ 43162781, 27628506}, { 43527344, 27260325}, { 43847191, 27036250}, { 44057061, 26922424}, { 44231096, 26828037}, { 44301999, 26795490}, { 44327421, 26804561}, { 44319287, 26913761}, { 44143507, 27648484}, { 44107324, 27729499},
{ 44074236, 27803580}, { 44025541, 27932083}, { 43944121, 28146941}, { 43877811, 28710269}, { 43895199, 28764671}, { 43933238, 28883702}, { 43919165, 29004140}, { 43888109, 29269841}, { 43825852, 29576752}, { 43811824, 29609468},
{ 43748820, 29756420}, { 43763658, 29837769}, { 43832567, 30215488}, { 44075125, 29807258}, { 44209233, 29804204}, { 44310228, 29813855}, { 44365586, 29958259}, { 43873534, 30271247}, { 44003187, 30330249}, { 44617279, 30687869},
{ 44694113, 31070182}, { 44941015, 31257544}, { 45130334, 31171398}, { 45147836, 31132029}, { 45242053, 31070592}, { 45345637, 31033061}, { 45565937, 30953238}, { 45609517, 30857448}, { 45651888, 30764320}, { 45660681, 30754094},
{ 45822750, 30772646}, { 45944979, 30753042}, { 45964326, 30749938}, { 46054945, 30795588}, { 46577640, 31130668}, { 46870296, 31313313}, { 46976414, 31379541}, { 46998128, 31406087}, { 47008874, 31439291}, { 47031018, 31569281},
{ 47031214, 31576854}, { 47036334, 31774677}, { 47193705, 31889293}, { 47353245, 32029772}, { 47484683, 32145510}, { 47534251, 32233847}, { 47538509, 32241438}, { 47602626, 32453825}, { 47622648, 32465115}, { 47701707, 32575250},
{ 47776955, 33122018}, { 47677092, 33345574}, { 47630772, 33380015}, { 47572757, 33423150}, { 47328653, 33537512}, { 47343826, 33612940}, { 47462219, 33617810}, { 47578431, 33622591}, { 47808035, 33604884}, { 47842258, 33885890},
{ 47847000, 34154765}, { 47852298, 34455418}, { 47806556, 34798342}, { 47804979, 34803470}, { 47795265, 34835122}, { 47811501, 34879922}, { 47843100, 35247684}, { 47839663, 35481904}, { 47833503, 35902474}, { 47803910, 36044010},
{ 47819598, 36077879}, { 47841934, 36100587}, { 47854870, 36165755}, { 47911856, 36452861}, { 47927332, 36616382}, { 47936929, 36717785}, { 47770423, 36987292}, { 47699764, 37101659}, { 47671115, 37157488}, { 47423375, 37424772},
{ 47616349, 37518717}, { 47680621, 37550006}, { 47836151, 37632587}, { 47811936, 37777743}, { 47716954, 38113916}, { 47654340, 38250491}, { 47533407, 38514290}, { 47431515, 38674036}, { 47367427, 38987733}, { 47348164, 39043625},
{ 47298533, 39187606}, { 47279676, 39231940}, { 47252411, 39296047}, { 47246894, 39304927}, { 47238746, 39318037}, { 47232029, 39335258}, { 47220194, 39365593}, { 47196053, 39429922}, { 47159408, 39527585}, { 47041654, 39691835},
{ 47002148, 39908798}, { 46964248, 39997937}, { 46895728, 40159083}, { 46826610, 40301043}, { 46763479, 40430710}, { 46514929, 40884923}, { 46474179, 40918994}, { 46440818, 40946888}, { 46433233, 40992821}, { 46426528, 41033401},
{ 46108271, 41626808}, { 46056215, 41723876}, { 45997871, 41855066}, { 45755987, 42227269}, { 45653183, 42385466}, { 45444848, 42652871}, { 45380966, 42654262}, { 45336326, 42655238}, { 45326382, 42763461}, { 45318953, 42844333},
{ 45175146, 43086382}, { 45086585, 43235443}, { 45055897, 43281060}, { 44968051, 43418247}, { 44470500, 44195272}, { 44413430, 44364401}, { 44390221, 44433179}, { 44309502, 44528273}, { 44199667, 44604532}, { 43887229, 44833256},
{ 43815081, 44886070}, { 43726552, 44932547}, { 43689058, 44928887}, { 43686137, 44927822}, { 43280111, 44871367}, { 43249704, 44937548}, { 43324977, 45004000}, { 43046101, 45224515}, { 42898716, 45341059}, { 42838343, 45382240},
{ 42721108, 45493632}, { 42470119, 45669357}, { 42359756, 45746630}, { 42073412, 45910212}, { 42022050, 45926905}, { 41907133, 46027394}, { 41144940, 46559849}, { 40902566, 46683907}, { 40884989, 46688481}, { 40811763, 46707548},
{ 40768612, 46786655}, { 40675645, 46871372}, { 40548269, 46985681}, { 40382460, 47085920}, { 40082094, 47267510}, { 39768380, 47413990}, { 39734614, 47420931}, { 39586801, 47437916}, { 39408498, 47458403}, { 39355630, 47574767},
{ 39281498, 47737937}, { 39251009, 47783502}, { 39152882, 47890727}, { 39013408, 48043132}, { 38921577, 48100514}, { 38896008, 48108330}, { 38727116, 48102492}, { 38692428, 48101294}, { 38425261, 48075982}, { 38342344, 48047392},
{ 38336010, 48154957}, { 38151978, 48395628}, { 37811687, 48488990}, { 37804084, 48490379}, { 37674998, 48513979}, { 37674196, 48513196}, { 37658712, 48498074}, { 37592273, 48482371}, { 37336907, 48659173}, { 37140701, 48741338},
{ 37129466, 48764064}, { 37075599, 48873013}, { 36739574, 48838715}, { 36721697, 48864552}, { 36456161, 49171298}, { 36442740, 49184060}, { 36436660, 49212679}, { 36300951, 49585030}, { 36223897, 49727927}, { 36150156, 49864671},
{ 35924446, 50245885}, { 35769083, 50508275}, { 35750118, 50514284}, { 35323137, 50653609}, { 34050908, 50703703}, { 33864494, 50706292}, { 33666152, 50709051}, { 33813201, 50839130}, { 33884905, 50893350}, { 33912037, 50913867},
{ 34282238, 51132740}, { 35016181, 51605972}, { 35027459, 51615787}, { 35030754, 51618656}, { 35108803, 51693454}, { 35137469, 51720927}, { 34948522, 51872654}, { 34658613, 52064227}, { 34464997, 52192175}, { 34289189, 52285353},
{ 34219119, 52312637}, { 33847969, 52428212}, { 33681538, 52480036}, { 33407178, 52510887}, { 33421683, 52685666}, { 33428342, 52765908}, { 33392094, 53146294}, { 33371466, 53362761}, { 33253040, 54291767}, { 33196142, 54612534},
{ 33128154, 54815569}, { 33095559, 54912904}, { 32570427, 55111061}, { 32525706, 55125923}, { 32458612, 55148214}, { 32385063, 55163161}, { 32282016, 55184108}, { 32241393, 55188603}, { 32190544, 55194226}, { 32027959, 55217259},
{ 32011561, 56072729}, { 32003567, 57064095}, { 31997637, 57799631}, { 32015577, 60287161}, { 32014290, 61201940}, { 32012996, 62120667}, { 32007630, 62197246}, { 32002828, 62265761}, { 32003310, 62373952}, { 32003630, 62444825},
{ 31951202, 63100419}, { 31935103, 63301732}, { 31937490, 63354807}, { 31968533, 64124669}, { 32071989, 64767136}, { 32091323, 64947492}, { 32101518, 65042609}, { 32140486, 65216353}, { 32159835, 65302616}, { 32422071, 66001036},
{ 32441049, 66056128}, { 32463003, 66119864}, { 32483582, 66164217}, { 32504016, 66208251}, { 32702117, 66557895}, { 32734168, 66611648}, { 32759723, 66654509}, { 32985249, 66546464}, { 33208649, 66439436}, { 33424955, 66330151},
{ 33554797, 66263457}, { 33891385, 66090564}, { 34622897, 65616004}, { 34819546, 65471063}, { 34988926, 65346218}, { 35739513, 64794843}, { 36421629, 64150515}, { 36944662, 63656452}, { 36959929, 63643292}, { 36964174, 63639854},
{ 36973615, 63630686}, { 37023366, 63597643}, { 37652255, 63172287}, { 37804320, 63100590}, { 37939211, 63174238}, { 37949998, 63178562}, { 38147664, 63257792}, { 38147652, 63269386}, { 38147521, 63403665}, { 38150429, 63418056},
{ 38177182, 63550576}, { 38159827, 64298859}, { 38153585, 64520174}, { 38146482, 64771937}, { 38142126, 64820836}, { 38138239, 64839298}, { 38115242, 65010431}, { 38113231, 65025393}, { 37912271, 66372984}, { 37841830, 66687479},
{ 37674277, 67228175}, { 37551047, 67593509}, { 37497098, 67727333}, { 37392268, 67951311}, { 36986640, 68817980}, { 36604483, 69575518}, { 36479686, 69769345}, { 36265058, 70102690}, { 36332308, 70163400}, { 36398395, 70223058},
{ 36718105, 70645723}, { 36714573, 70708131}, { 36707947, 70825274}, { 36665865, 71083146}, { 36295751, 71910509}, { 36243731, 72020144}, { 36010145, 72512434}, { 35364761, 74115820}, { 35327445, 74206370}, { 35287332, 74303707},
{ 35262905, 74370595}, { 35235816, 74444782}, { 35006275, 75142899}, { 34758612, 75896141}, { 34609479, 76324076}, { 34534936, 76598593}, { 34419529, 77019735}, { 34125782, 78091675}, { 34270135, 78023153}, { 34366481, 77977415},
{ 34669421, 77827427}, { 35532698, 77282412}, { 35875762, 77065829}, { 35924952, 77041288}, { 35981906, 77004141}, { 36227708, 76899428}, { 36700108, 76693284}, { 36835857, 76657801}, { 36942059, 76731654}, { 36959107, 76741135},
{ 37155031, 76850094}, { 37152161, 76868751}, { 37133420, 76990662}, { 37135224, 77014721}, { 37144331, 77136260}, { 37029215, 77783623}, { 36994547, 77984972}, { 36957442, 78200506}, { 36949745, 78231593}, { 36945059, 78243379},
{ 36909925, 78358183}, { 36908693, 78362210}, { 36517584, 79569608}, { 36400200, 79852238}, { 36160758, 80317591}, { 36001388, 80606724}, { 35929263, 80720331}, { 35803937, 80894237}, { 35313741, 81574455}, { 34810829, 82211118},
{ 34636165, 82398130}, { 34424143, 82625140}, { 34177218, 82875584}, { 34001320, 83053991}, { 33330876, 83686990}, { 33313615, 83940131}, { 33257889, 84757318}, { 33154596, 86125618}, { 33050414, 87930914}, { 33037323, 88157771},
{ 32996151, 88791902}, { 33122354, 88720953}, { 34042644, 88195810}, { 34854618, 87571171}, { 35217422, 87292077}, { 35240201, 87279017}, { 35256654, 87268145}, { 35304044, 87230648}, { 35465557, 87154377}, { 35979874, 86886707},
{ 36162994, 86833255}, { 36213131, 86859811}, { 36328089, 86920714}, { 36446386, 87103899}, { 36444792, 87129675}, { 36435583, 87278561}, { 36439166, 87306042}, { 36455346, 87430153}, { 36439626, 87577638}, { 36363937, 88287781},
{ 36334385, 88516418}, { 36324472, 88550288}, { 36266923, 88831775}, { 36258817, 88871412}, { 36009099, 90001153}, { 35925390, 90278389}, { 35742522, 90743063}, { 35584494, 91114154}, { 35511233, 91260521}, { 35378328, 91493626},
{ 34896978, 92337857}, { 34592698, 92829175}, { 34534101, 92906355}, { 34379904, 93109443}, { 34292029, 93224277}, { 34181322, 93368951}, { 33996695, 93594059}, { 33791238, 93844563}, { 33350304, 94350448}, { 32679061, 95059135},
{ 32663276, 95383974}, { 32630835, 96051559}, { 32623715, 96162432}, { 32625261, 96184173}, { 32631760, 96253789}, { 32637940, 96319986}, { 32671334, 96831435}, { 32555999, 97073603}, { 32552956, 97110111}, { 32549772, 97148299},
{ 32339278, 100576678}, { 32333722, 100685777}, { 32330348, 100752035}, { 32315766, 101012907}, { 32604225, 100816839}, { 32833219, 100661190}, { 33690734, 100037568}, { 33947721, 99810841}, { 34263306, 99532414}, { 34709871, 99161136},
{ 35458100, 98470566}, { 35535202, 98409290}, { 35673889, 98299068}, { 35825183, 98230993}, { 36031300, 98138241}, { 36364183, 98058757}, { 36389853, 98099020}, { 36443213, 98182736}, { 36495776, 98421334}, { 36464592, 98534766},
{ 36403262, 98757832}, { 36433188, 98786253}, { 36468201, 98819516}, { 36427877, 99135414}, { 36380139, 99509425}, { 36367327, 99566653}, { 36130997, 100458902}, { 36092849, 100736616}, { 35993189, 101207413}, { 35961980, 101354843},
{ 35901824, 101565944}, { 35599001, 102436249}, { 35598486, 102437494}, { 35525627, 102612717}, { 35498238, 102672427}, { 35179093, 103368204}, { 34902420, 103873765}, { 34074371, 105280790}, { 33796945, 105666257}, { 33430747, 106175067},
{ 32757675, 107021332}, { 32288404, 107611357}, { 32147333, 107782229}, { 32045181, 107903768}, { 32013865, 108446053}, { 32004365, 108597331}, { 31933356, 109727991}, { 31929556, 109801743}, { 31921205, 109963885}, { 31919950, 109998202},
{ 31917378, 110068478}, { 31935487, 110174763}, { 31962352, 110332410}, { 31868759, 110776536}, { 31779274, 112901692}, { 31772558, 113008639}, { 31763520, 113152580}, { 31760914, 113226796}, { 31757613, 113320828}, { 31878079, 113245898},
{ 32056600, 113134847}, { 32205325, 113028281}, { 32417383, 112876331}, { 32791706, 112611586}, { 33374891, 112199137}, { 34043729, 111739447}, { 34299836, 111533282}, { 34686259, 111194925}, { 35041202, 110899316}, { 36153161, 109973245},
{ 36489565, 109732139}, { 36935134, 109547251}, { 36998142, 109523782}, { 37285208, 109416845}, { 37303575, 109443686}, { 37380657, 109556352}, { 37429339, 109768662}, { 37389406, 109896075}, { 37312708, 110140778}, { 37330397, 110173101},
{ 37358669, 110224762}, { 37347970, 110508588}, { 37343682, 110622428}, { 37233824, 111039422}, { 36974286, 111866215}, { 36941457, 112104350}, { 36810462, 112600390}, { 36763361, 112778757}, { 36685333, 113003686}, { 36304140, 113929965},
{ 36303227, 113931942}, { 36219925, 114112998}, { 36185254, 114177524}, { 35766113, 114957538}, { 35699185, 115058398}, { 35271549, 115739102}, { 34529522, 116832154}, { 34230604, 117226448}, { 34152175, 117323267}, { 33753453, 117815498},
{ 33688745, 117896887}, { 33515149, 118115220}, { 33167360, 118505862}, { 32252839, 119533076}, { 31951224, 119865885}, { 31856676, 119967574}, { 31811772, 120013039}, { 31820788, 120150853}, { 31837447, 120637820}, { 31884548, 122014628},
{ 31884879, 122025348}, { 31884889, 122025915}, { 31884859, 122030715}, { 31853727, 124752378}, { 31852710, 125798379}, { 32040109, 125687330}, { 32336721, 125511560}, { 33050838, 125039566}, { 33741293, 124531865}, { 34004877, 124347492},
{ 34706008, 123857040}, { 34900746, 123695124}, { 35248769, 123405773}, { 35958009, 122839178}, { 36752647, 122139217}, { 36794698, 122103853}, { 36926183, 121993279}, { 37041929, 121900209}, { 37364281, 121641009}, { 37506782, 121535931},
{ 37599623, 121475276}, { 37805210, 121390600}, { 38274450, 121197339}, { 38429386, 121137935}, { 38611951, 121409191}, { 38647554, 121490884}, { 38558179, 121763354}, { 38544345, 121816126}, { 38504735, 121967178}, { 38540287, 122025777},
{ 38533522, 122225637}, { 38527834, 122393821}, { 38490015, 122574939}, { 38335371, 123023448}, { 38226910, 123422167}, { 38128017, 123785706}, { 38110062, 123913558}, { 38039445, 124196782}, { 37811751, 125109983}, { 37795287, 125159401},
{ 37789856, 125175267}, { 37747302, 125281671}, { 37678378, 125454008}, { 37326009, 126304036}, { 37280379, 126403545}, { 36723741, 127438116}, { 36607591, 127622339}, { 35307172, 129556108}, { 34960577, 130042788}, { 34625146, 130457962},
{ 34244496, 130929114}, { 33616736, 131638592}, { 33126427, 132192717}, { 32289044, 133098400}, { 32128210, 133254928}, { 32114672, 133265860}, { 32051379, 133303244}, { 31973610, 133349175}, { 32021296, 134019482}, { 32078232, 134927829},
{ 32192915, 136757391}, { 32250210, 137342897}, { 32301584, 137908848}, { 32406571, 139065434}, { 32456488, 139422175}, { 32511513, 139855909}, { 32587723, 140456611}, { 33065481, 140191593}, { 33323332, 140063408}, { 33766028, 139843340},
{ 33978698, 139717429}, { 34224999, 139571601}, { 35090288, 139002456}, { 36098536, 138270161}, { 36204726, 138196467}, { 36870073, 137734734}, { 36937868, 137678015}, { 37269439, 137400662}, { 38224552, 136636721}, { 39248462, 135736109},
{ 39262231, 135724978}, { 39431206, 135588270}, { 39558286, 135491389}, { 40066663, 135103831}, { 40597978, 134876486}, { 40913397, 134752602}, { 41009750, 134730971}, { 41033440, 134769160}, { 41137853, 134937472}, { 41236776, 135135656},
{ 41185372, 135392011}, { 41170368, 135466840}, { 41117848, 135629223}, { 41128977, 135726643}, { 41112112, 135925316}, { 41028443, 136275112}, { 40892177, 136737346}, { 40715316, 137337282}, { 40625973, 137862286}, { 40571054, 138077826},
{ 40413004, 138698127}, { 40307787, 139028628}, { 40280705, 139108396}, { 40108570, 139542037}, { 39781168, 140366808}, { 39776747, 140377453}, { 39771298, 140388940}, { 39694209, 140532631}, { 39126953, 141589960}, { 39112976, 141613526},
{ 38864787, 141998169}, { 38780359, 142124163}, { 37534211, 143983846}, { 36837998, 144898691}, { 36749607, 145008489}, { 36437049, 145396720}, { 36308895, 145540735}, { 35926199, 145970826}, { 35104551, 146848709}, { 34756762, 147234955},
{ 34428436, 147599589}, { 34120556, 147908106}, { 34059694, 147944671}, { 33992021, 147971830}, { 33888925, 148013197}, { 33994002, 148234139}, { 34102871, 148463060}, { 34260406, 148815390}, { 34505558, 149252538}, { 34649150, 149539737},
{ 34875213, 149991894}, { 34913367, 150060689}, { 34939834, 150108425}, { 35009188, 150222655}, { 35057146, 150301638}, { 35531716, 151039155}, { 35961908, 151607166}, { 36198106, 151919026}, { 37112008, 151466356}, { 37122527, 151461129},
{ 37143274, 151448455}, { 37793852, 151104327}, { 38753278, 150462096}, { 39057095, 150265965}, { 39387132, 150052914}, { 39992757, 149578233}, { 40209373, 149410006}, { 40448656, 149224173}, { 41648972, 148150708}, { 41827582, 147994189},
{ 42089284, 147764870}, { 42281920, 147557241}, { 42535672, 147283737}, { 43211137, 146606344}, { 43969650, 145734949}, { 44008274, 145690567}, { 44434382, 145256367}, { 44673165, 145036231}, { 44753304, 144976343}, { 44941707, 144886575},
{ 45449136, 144644796}, { 45533221, 144617860}, { 45594657, 144672684}, { 45686988, 144755077}, { 45821054, 144894151}, { 45845698, 144928928}, { 45802394, 145256827}, { 45801968, 145263145}, { 45793099, 145396327}, { 45826083, 145436911},
{ 45827387, 145448733}, { 45852550, 145676686}, { 45846396, 146183080}, { 45801072, 146729105}, { 45751200, 147329993}, { 45765306, 147565974}, { 45765766, 147690105}, { 45758629, 147823920}, { 45717918, 148587045}, { 45669293, 148998256},
{ 45657164, 149090109}, { 45565455, 149517107}, { 45390903, 150329829}, { 45380310, 150370709}, { 45303883, 150599765}, { 45049477, 151362234}, { 45041081, 151384892}, { 44988127, 151512567}, { 44899898, 151709940}, { 44188361, 153301702},
{ 43960091, 153807492}, { 43687530, 154326968}, { 43680264, 154339888}, { 43428400, 154787836}, { 43418419, 154804941}, { 43222756, 155140257}, { 43211901, 155157187}, { 43019606, 155457042}, { 42439201, 156284412}, { 42742998, 156320854},
{ 42946786, 156345296}, { 43218356, 156408139}, { 43490220, 156548626}, { 43600789, 156605776}, { 43616758, 156616967}, { 43638494, 156675797}, { 43689725, 156874320}, { 43697411, 156939181}, { 43667792, 157194800}, { 43663112, 157219786},
{ 43589483, 157612846}, { 43578259, 157650201}, { 43503908, 157897703}, { 43271842, 158586008}, { 43026656, 159112379}, { 42680049, 159768278}, { 42229097, 160621619}, { 41614538, 161818913}, { 41602009, 161838594}, { 41549009, 161921905},
{ 41366702, 162195210}, { 41089703, 162610457}, { 41051349, 162661598}, { 40028827, 163938879}, { 39981539, 163995316}, { 39859709, 164140726}, { 39557928, 164489623}, { 38840108, 165319487}, { 38817977, 165343409}, { 36508721, 167822791},
{ 35803734, 168527171}, { 35265129, 169065323}, { 35217638, 169111343}, { 35182142, 169143335}, { 34143283, 170051242}, { 34091092, 170092305}, { 33992346, 170169987}, { 32820222, 171015261}, { 32596277, 171172367}, { 32366414, 171333625},
{ 30949741, 172256683}, { 30776429, 172369214}, { 30685231, 172428426}, { 29784929, 172978028}, { 29711510, 173022900}, { 29649347, 173060901}, { 29626880, 173084470}, { 29607989, 173104288}, { 29476620, 173372906}, { 29166644, 173374167},
{ 29105869, 173396269}, { 29066168, 173410694}, { 28480959, 173773359}, { 28318456, 173874074}, { 28236958, 173920336}, { 28053468, 174015451}, { 27663961, 174212865}, { 26444009, 174781179}, { 25128636, 175292014}, { 24833691, 175404475},
{ 24567873, 175499255}, { 23673660, 175815148}, { 23263816, 175959931}, { 22989484, 175996217}, { 22919277, 176005507}, { 22821755, 176011321}, { 22593369, 175931875}, { 22197778, 175796707}, { 20895444, 175329856}, { 20562493, 175210506},
{ 20357518, 175131409}, { 19431901, 174778687}, { 19227774, 174700914}, { 17432818, 173805114}, { 17355249, 173765680}, { 17340552, 173757060}, { 17293649, 173727963}, { 15176003, 172414266}, { 14987901, 172296594}, { 14897452, 172240019},
{ 14730104, 172123866}, { 14649567, 172067971}, { 12604451, 170685425}, { 12582065, 170669040}, { 12501564, 170610143}, { 12483411, 170595498}, { 12418519, 170543227}, { 11146256, 169546467}, { 11131285, 169533173}, { 10973608, 169393198},
{ 10963368, 169383375}, { 10855356, 169279681}, { 9350332, 167783891}, { 9237755, 167663880}, { 9038028, 167450975}, { 7554140, 165846157}, { 6510331, 164717307}, { 6450301, 164645790}, { 4792198, 162599032}, { 4711896, 162499401},
{ 4702892, 162486484}, { 4689884, 162466018}, { 4689721, 162465748}, { 4111625, 161512044}, { 3262811, 159825639}, { 3085907, 159501392}, { 2964224, 159278374}, { 2880198, 159123098}, { 2827825, 159026309}, { 2730830, 158798250},
{ 2662597, 158637824}, { 2461794, 158144454}, { 2258655, 157377436}, { 2232776, 156966420}, { 2227381, 156880727}, { 2229842, 156800001}, { 2404803, 156571898}, { 2502593, 156512353}, { 2571069, 156470646}, { 3012355, 156329121},
{ 3172690, 156317433}, { 3263007, 156310852}, { 3448807, 156270050}, { 2933268, 155537448}, { 2932334, 155536119}, { 2690506, 155171633}, { 2473838, 154800417}, { 2214521, 154335871}, { 1956160, 153843466}, { 1643404, 153150964},
{ 936422, 151585583}, { 886715, 151471650}, { 881872, 151459055}, { 835673, 151315362}, { 420381, 150023686}, { 415543, 150006511}, { 411493, 149986474}, { 371105, 149740432}, { 184472, 148603483}, { 176976, 148544106}, { 143829, 148268525},
{ 141423, 148213179}, { 118798, 147692447}, { 141994, 147109270}, { 96664, 146619882}, { 46940, 146083025}, { 34028, 145778412}, { 32148, 145734124}, { 50580, 145571914}, { 79797, 145477573}, { 59893, 144996644}, { 53607, 144916874},
{ 75632, 144881102}, { 170230, 144783356}, { 367047, 144609349}, { 495089, 144649841}, { 696206, 144748339}, { 861062, 144829070}, { 1202743, 145013350}, { 1665932, 145467720}, { 1738044, 145542186}, { 1871110, 145679584}, { 2233705, 146073631},
{ 2875888, 146771504}, { 2976802, 146887761}, { 3008358, 146918708}, { 3105019, 147016992}, { 3562844, 147482514}, { 3900940, 147829488}, { 3926192, 147851556}, { 5456634, 149216502}, { 5473415, 149229592}, { 5678115, 149389248},
{ 6416516, 149979537}, { 6693887, 150160404}, { 7011978, 150367823}, { 8034093, 151060650}, { 8245822, 151174920}, { 8663509, 151400371}, { 8734568, 151444233}, { 9700825, 151913516}, { 10314440, 151101573}, { 10876143, 150241580},
{ 10937084, 150142918}, { 10989872, 150057455}, { 11012110, 150016058}, { 11029139, 149984364}, { 11202330, 149640866}, { 11331097, 149385460}, { 11601540, 148893595}, { 11801542, 148453984}, { 11867898, 148312015}, { 12006182, 148016156},
{ 11936334, 147987685}, { 11846756, 147951181}, { 11775937, 147908929}, { 11448318, 147578728}, { 10005162, 146006655}, { 9941330, 145934468}, { 9420742, 145345782}, { 9364739, 145276533}, { 9005053, 144831776}, { 8354706, 143947082},
{ 7741954, 143034251}, { 7046776, 141998616}, { 6866979, 141726486}, { 6759755, 141551328}, { 6581042, 141228382}, { 6214827, 140566592}, { 6160332, 140464737}, { 6154984, 140452943}, { 6118667, 140365627}, { 5608006, 139066930},
{ 5576877, 138974353}, { 5449821, 138579522}, { 5423448, 138473840}, { 5269717, 137857830}, { 5183256, 137323221}, { 5051763, 136885377}, { 4900390, 136381329}, { 4788716, 135926420}, { 4778542, 135832434}, { 4751278, 135580592},
{ 4759133, 135551850}, { 4772567, 135502722}, { 4750760, 135428400}, { 4689711, 135220325}, { 4720284, 134950229}, { 4772938, 134876983}, { 4872059, 134739100}, { 4907041, 134734799}, { 4988166, 134747911}, { 5187996, 134827143},
{ 5324282, 134881173}, { 5823633, 135095262}, { 6457261, 135576778}, { 6468046, 135585394}, { 6645027, 135726930}, { 7665807, 136625984}, { 8014871, 136908364}, { 8760642, 137511681}, { 9070115, 137764153}, { 9505207, 138067027},
{ 9692018, 138199840}, { 10866067, 139034528}, { 10974854, 139102654}, { 11199174, 139243162}, { 11980766, 139757269}, { 12820102, 140204762}, { 13013724, 140301821}, { 13307713, 140449197}, { 13339465, 140204984}, { 13387908, 139832384},
{ 13476326, 139229254}, { 13545245, 138464294}, { 13637934, 137435521}, { 13704650, 136750183}, { 13756310, 135946981}, { 13854009, 134427968}, { 13931781, 133352665}, { 13880515, 133326641}, { 13806176, 133288914}, { 13608773, 133095964},
{ 13229938, 132676064}, { 12917088, 132329299}, { 12475540, 131854762}, { 12234438, 131582242}, { 11645945, 130917061}, { 11435343, 130656410}, { 11256705, 130435328}, { 11087956, 130227341}, { 10943531, 130049329}, { 10660547, 129658000},
{ 9884836, 128504691}, { 9363495, 127729593}, { 9183437, 127445707}, { 8613352, 126392173}, { 8569664, 126295529}, { 8233135, 125484892}, { 8100143, 125150567}, { 8091324, 125125230}, { 8068370, 125055541}, { 8047369, 124966573},
{ 7827878, 124036734}, { 7815999, 123941440}, { 7743138, 123689990}, { 7467916, 122740178}, { 7381012, 122383130}, { 7365871, 122250909}, { 7330956, 121946008}, { 7347071, 121910652}, { 7366239, 121868607}, { 7337555, 121775565},
{ 7275180, 121573218}, { 7357784, 121255913}, { 7363162, 121248563}, { 7433561, 121152362}, { 7492882, 121172887}, { 8152120, 121400901}, { 8296078, 121458859}, { 8337642, 121483827}, { 8428744, 121552386}, { 8461373, 121578560},
{ 9113408, 122101612}, { 9968838, 122858025}, { 10418874, 123221408}, { 11203964, 123855334}, { 11236475, 123882487}, { 11264272, 123901717}, { 12869603, 125041315}, { 13619004, 125547677}, { 13833945, 125671552}, { 14049136, 125795572},
{ 14042979, 124631730}, { 14031124, 123791039}, { 14029618, 123425913}, { 14024871, 122275773}, { 14024680, 122240909}, { 14024300, 122172017}, { 14024368, 122132419}, { 14024494, 122058437}, { 14025750, 122003675}, { 14028093, 121901540},
{ 14053706, 121051621}, { 14084937, 120015176}, { 13976495, 119893307}, { 12808105, 118596333}, { 12632795, 118395530}, { 12332420, 118051483}, { 12010936, 117651678}, { 11662489, 117218341}, { 11286185, 116695820}, { 10542401, 115590915},
{ 10484664, 115505145}, { 10085127, 114875400}, { 9677465, 114107097}, { 9676038, 114103997}, { 9587011, 113910478}, { 9572058, 113874387}, { 9221672, 113028545}, { 9132465, 112762183}, { 8929936, 112011523}, { 8896027, 111773355},
{ 8763540, 111338847}, { 8591711, 110775312}, { 8585822, 110750616}, { 8583286, 110726469}, { 8532504, 110242770}, { 8561517, 110201837}, { 8589689, 110162093}, { 8539283, 109999835}, { 8459773, 109743891}, { 8476274, 109635698},
{ 8539247, 109532026}, { 8559299, 109499015}, { 8639538, 109407427}, { 8837219, 109481673}, { 9374636, 109713713}, { 9614985, 109884378}, { 9895885, 110108176}, { 10150796, 110311272}, { 10647433, 110745796}, { 11163900, 111149653},
{ 11435641, 111378216}, { 11952173, 111812662}, { 12063358, 111892355}, { 12195941, 111987389}, { 13754894, 113077948}, { 13965930, 113207021}, { 14143358, 113315534}, { 14095680, 112195851}, { 14075275, 111736247}, { 14031684, 110754424},
{ 13949266, 109698295}, { 13931155, 109374956}, { 13907232, 108947887}, { 13903305, 108820557}, { 13899752, 108705317}, { 13898286, 108692370}, { 13896892, 108680054}, { 13882077, 108455610}, { 13866991, 108227067}, { 13852378, 107897586},
{ 13627196, 107630194}, { 13249326, 107173733}, { 13128837, 107021896}, { 12504668, 106235327}, { 12449045, 106156712}, { 12301165, 105947708}, { 12240927, 105864439}, { 12071292, 105629917}, { 11741182, 105140360}, { 11102902, 104050785},
{ 11009874, 103891983}, { 10724262, 103375048}, { 10370561, 102607103}, { 10302463, 102446702}, { 9995869, 101563023}, { 9933827, 101340326}, { 9788639, 100674614}, { 9761576, 100425516}, { 9620310, 99895785}, { 9572074, 99714909},
{ 9473316, 99261511}, { 9457110, 98860065}, { 9475422, 98813097}, { 9491516, 98771818}, { 9454445, 98628574}, { 9395112, 98399301}, { 9430018, 98201406}, { 9448015, 98172416}, { 9519385, 98057456}, { 9858391, 98155219}, { 10045563, 98209192},
{ 10217386, 98274096}, { 10328458, 98365757}, { 11168922, 99136589}, { 11517095, 99428522}, { 11782963, 99664460}, { 12152171, 99992110}, { 12543518, 100270019}, { 12914813, 100533689}, { 13199749, 100744460}, { 13324020, 100835567},
{ 13585579, 101027330}, { 13575682, 100826649}, { 13569447, 100700201}, { 13562345, 100567361}, { 13559065, 100506021}, { 13429751, 98521010}, { 13371150, 97621467}, { 13343156, 97180710}, { 13333987, 97039073}, { 13207473, 95084673},
{ 13138184, 95005008}, { 13017680, 94866468}, { 12083312, 93848129}, { 12022705, 93771797}, { 11862461, 93569995}, { 11784430, 93470508}, { 11589381, 93221813}, { 11309567, 92840780}, { 10844778, 92098029}, { 10775191, 91976786},
{ 10496881, 91491862}, { 10185086, 90849349}, { 10144137, 90764963}, { 10074833, 90600171}, { 9828579, 89857830}, { 9703614, 89075796}, { 9674971, 88969502}, { 9495272, 88102892}, { 9475468, 87916753}, { 9440640, 87589408}, { 9465676, 87528619},
{ 9487914, 87474617}, { 9465041, 87357340}, { 9428525, 87170118}, { 9490390, 86904119}, { 9512256, 86883153}, { 9574632, 86823334}, { 9727402, 86841642}, { 10166330, 86894255}, { 10190151, 86899193}, { 10198409, 86903284}, { 10249971, 86942095},
{ 10299758, 86980568}, { 11788945, 88131376}, { 11901024, 88196968}, { 12050012, 88284161}, { 12770268, 88697024}, { 12893258, 88767518}, { 12865978, 88340499}, { 12755514, 86383203}, { 12590001, 84209400}, { 12584956, 84143148},
{ 12549052, 83666692}, { 11929877, 83107725}, { 11390770, 82556851}, { 11083660, 82243035}, { 10537284, 81546957}, { 10424674, 81403496}, { 10079867, 80926984}, { 9689286, 80270083}, { 9687616, 80267071}, { 9615613, 80136762},
{ 9601056, 80104215}, { 9309849, 79453353}, { 9259598, 79312241}, { 9118888, 78788995}, { 9088297, 78585733}, { 8994447, 78301695}, { 8881493, 77959840}, { 8828452, 77771748}, { 8812025, 77688113}, { 8733303, 77287329}, { 8744431, 77274990},
{ 8786674, 77228142}, { 8770800, 77179143}, { 8746495, 77100409}, { 8709758, 76981397}, { 8765710, 76725562}, { 8780763, 76703647}, { 8821796, 76643902}, { 9050198, 76667553}, { 9430163, 76706910}, { 9505621, 76721216}, { 9535183, 76740071},
{ 9561982, 76753134}, { 11021709, 77681521}, { 11271938, 77809531}, { 11740477, 78049225}, { 11713323, 77940287}, { 11500961, 77088300}, { 11491445, 77055518}, { 11468672, 76977103}, { 11007894, 75454998}, { 10625858, 74342820},
{ 10581531, 74223613}, { 10547931, 74133250}, { 9872558, 72487409}, { 9785055, 72279833}, { 9735127, 72161397}, { 9661531, 72007947}, { 9614591, 71910070}, { 9234200, 71112437}, { 9147114, 70727104}, { 9142261, 70702843}, { 9138267, 70682891},
{ 9286224, 70443224}, { 9461343, 70247284}, { 9556416, 70162672}, { 9625168, 70101485}, { 9435737, 69811425}, { 9287394, 69584273}, { 8757085, 68530695}, { 8673850, 68365333}, { 8445942, 67877601}, { 8187617, 67187177}, { 8139627, 67039434},
{ 7937861, 66234567}, { 7892565, 65909655}, { 7845288, 65439718}, { 7844011, 65310767}, { 7823103, 65136343}, { 7778117, 64761067}, { 7716333, 64313964}, { 7705694, 64124356}, { 7687717, 63803945}, { 7701643, 63790152}, { 7718011, 63773943},
{ 7758186, 63752036}, { 7729172, 63586572}, { 7697769, 63407488}, { 7779619, 63146399}, { 7790119, 63132497}, { 7857734, 63042993}, { 7899799, 63053366}, { 8115923, 63106666}, { 8464711, 63240292}, { 8677072, 63398904}, { 8767176, 63477143},
{ 8977927, 63660143}, { 9421383, 64100703}, { 9785048, 64413088}, { 9975436, 64589567}, { 10286420, 64877827}, { 11014721, 65410888}, { 11115862, 65482249}, { 11327524, 65631599}, { 11395991, 65675856}, { 11535890, 65766274}, { 12026448, 66109919},
{ 12502690, 66343355}, { 12786634, 66472769}, { 13164960, 66645193}, { 13207596, 66564001}, { 13256756, 66470394}, { 13640736, 65570500}, { 13683003, 65454507}, { 13718537, 65356988}, { 13735231, 65270567}, { 13747424, 65207437},
{ 13863686, 64629409}, { 13875328, 64496043}, { 13887975, 64351165}, { 13957488, 63607260}, { 13950883, 63386188}, { 13943973, 63154947}, { 13895952, 62476120}, { 13876483, 62262044}, { 13859838, 62079009}, { 13859584, 62074662},
{ 13859582, 62065658}, { 13859483, 61971042}, { 13862761, 55222623}, { 13815791, 55212684}, { 13617475, 55174296}, { 13379849, 55128299}, { 13200660, 55067043}, { 13117648, 55038667}, { 12798922, 54907256}, { 12743350, 54730557},
{ 12719703, 54655364}, { 12656225, 54324243}, { 12632418, 53676660}, { 12625539, 53489551}, { 12652785, 53052852}, { 12782795, 52820186}, { 12846930, 52705411}, { 13041220, 52491209}, { 13143647, 52409064}, { 13187810, 52373646},
{ 13354789, 52319639}, { 13381838, 52313108}, { 13407786, 52306845}, { 13609096, 52308186}, { 13798532, 52309451}, { 14794521, 52294618}, { 15549961, 52336594}, { 16050147, 52311338}, { 16209513, 52303295}, { 16312325, 52297439},
{ 16369590, 51869307}, { 16340473, 51576398}, { 16331091, 51482008}, { 16316170, 51377054}, { 16241360, 51186578}, { 16186688, 51047373}, { 16076915, 50725256}, { 16093629, 50461603}, { 16098435, 50385771}, { 16109774, 50333994},
{ 16208639, 50141731}, { 16271132, 50020206}, { 16284775, 49997056}, { 16295310, 49985147}, { 16360397, 49947770}, { 16432796, 49916484}, { 16999910, 49671395}, { 17079341, 49631019}, { 17221011, 49559013}, { 17356128, 49546264},
{ 17369996, 49528116}, { 17426993, 49502498}, { 17530282, 49456075}, { 17342293, 49148088}, { 17284381, 49008875}, { 17254026, 48935905}, { 17357436, 48625105}, { 17422365, 48429965}, { 17423796, 48426977}, { 17601162, 48056939},
{ 17599241, 47980228}, { 17595410, 47827198}, { 17579402, 47751708}, { 17538195, 47557388}, { 17400788, 46598168}, { 17023471, 46464319}, { 16973301, 46446494}, { 16812540, 46389386}, { 16673736, 46329440}, { 16319654, 46176525},
{ 15950663, 46003440}, { 15838028, 45939836}, { 15697899, 45836427}, { 15289766, 45502367}, { 15260072, 45476441}, { 14999104, 45248614}, { 14962927, 45210840}, { 14722491, 44959778}, { 14678301, 44921783}, { 14404868, 44686698},
{ 14020130, 44298671}, { 13905758, 44155324}, { 13566066, 43648328}, { 13163266, 43047144}, { 13102631, 42937239}, { 13070977, 42862998}, { 12945977, 42560557}, { 12902489, 42448510}, { 12696099, 41916758}, { 12684650, 41857975},
{ 12656516, 41713516}, { 12557005, 40938961}, { 12554067, 40837978}, { 12550435, 40713161}, { 12562692, 40535359}, { 12575839, 40344643}, { 12609216, 40034504}, { 12660395, 39915667}, { 12708691, 39803526}, { 12798899, 39599814},
{ 12938906, 39372986}, { 12995589, 39281154}, { 13232289, 39007147}, { 13498241, 38725717}, { 13591444, 38550048}, { 13628611, 38480001}, { 13631794, 38446522}, { 13586786, 38388985}, { 13507530, 38236091}, { 13096257, 38028857},
{ 12821362, 37838492}, { 12551686, 37651741}, { 12445887, 37503612}, { 12369283, 37396362}, { 12264258, 37242462}, { 12195026, 37044172}, { 12148552, 36863589}, { 12101329, 36680088}, { 12142095, 35348959}, { 12144651, 35291418},
{ 12162788, 34883134}, { 12163706, 34850506}, { 12168637, 34675334}, { 12163420, 34644423}, { 12134883, 34475307}, { 12106311, 33932082}, { 12095021, 33476333}, { 12094122, 33057779}, { 12092211, 32168031}, { 12100800, 31962352},
{ 12107580, 31800023}, { 12116077, 31640101}, { 12122543, 31518406}, { 12193613, 31111725}, { 12255946, 30755035}, { 12655685, 28642673}, { 12654000, 28322388}, { 12689137, 28120452}, { 12708722, 28007885}, { 12692342, 27740702},
{ 12770201, 27316837}, { 12810004, 27100162}, { 12822406, 26990057}, { 12840969, 26876333}, { 12930142, 26507364}, { 13006294, 26192274}, { 13140275, 25812749}, { 13171909, 25737294}, { 13213594, 25637871}, { 13513395, 24982223},
{ 13564918, 24904642}, { 13614340, 24830229}, { 13673478, 24765245}, { 13723561, 24710211}, { 13790283, 24595233}, { 13857122, 24480057}, { 14153860, 24116007}, { 14231993, 24020147}, { 14248273, 23981550}, { 14451243, 23786195},
{ 14602942, 23651634}, { 14684407, 23579375}, { 15221344, 23339532}, { 15255414, 23324310}, { 15480802, 23178412}, { 15646843, 23091400}, { 16018697, 22744059}, { 16456749, 22567685}, { 16708674, 22466255}, { 16837697, 22410158},
{ 17154392, 22190832}, { 17069931, 22106918}, { 17007737, 21985244}, { 16978925, 21928875}, { 17036320, 21826992}, { 17212750, 21670157}, { 17298093, 21594293}, { 17451145, 21457485}, { 17530883, 21256458}, { 17541075, 21230767},
{ 17549886, 21207629}, { 17244063, 20372250}, { 17209346, 20248411}, { 17092010, 20089995}, { 17023648, 19955801}, { 16984483, 19912896}, { 16834254, 19784836}, { 16625524, 19606905}, { 16620983, 19603024}, { 16616582, 19597241},
{ 16614255, 19589014}, { 16578856, 19463898}, { 16588025, 19439937}, { 16595650, 19420015}, { 16602627, 19365704}, { 16608518, 19319848}, { 16694764, 19210381}, { 16784442, 19096556}, { 16461235, 17851161}, { 16421291, 17669728},
{ 16359955, 17616552}, { 16304854, 17528237}, { 16266671, 17467038}, { 16001330, 17343372}, { 15927109, 17308781}, { 15828509, 17248124}, { 15766385, 17190011}, { 15678175, 17097940}, { 15629868, 17047518}, { 15678592, 16534350},
{ 15695434, 16356965}, { 15704034, 16303332}, { 15705308, 16269732}, { 15725443, 15743784}, { 15808595, 15332260}, { 15821377, 15312568}, { 15838901, 15285580}, { 15993537, 15201723}, { 16119571, 15175593}, { 16189683, 15163592},
{ 16237347, 15155438}, { 16508759, 15159065}, { 16375757, 14977910}, { 16282021, 14850635}, { 15510709, 13877187}, { 15342882, 13710959}, { 15237532, 13606608}, { 14831965, 13239743}, { 14581428, 13013122}, { 14293147, 12740902},
{ 14190984, 12660109}, { 13669460, 12247703}, { 12564414, 11331695}, { 12487187, 11271158}, { 12046686, 10925925}, { 11871179, 10835479}, { 11582487, 10686699}, { 11523291, 10654160}, { 11396348, 10324251}, { 11575096, 9791088},
{ 11656410, 9657529}, { 11694903, 9594301}, { 12154341, 8957487}, { 12327404, 8717611}, { 12920992, 7861977}, { 13163209, 7541046}, { 13299428, 7360558}, { 13534727, 7094968}, { 13607608, 7012705}, { 14344532, 6120949}, { 15087045, 5393680},
{ 15307430, 5177820}, { 15930737, 4553097}, { 16730116, 3841678}, { 17107544, 3505773}, { 17287251, 3346015}, { 17407773, 3251557}, { 17762201, 2970942}, { 18238970, 2593464}, { 18584923, 2367852}, { 18697829, 2294226}, { 18997703, 2084694},
{ 19253265, 1922140}, { 19413044, 1820512}, { 20082389, 1425058}, { 21018405, 914454}, { 21306702, 757182}, { 21909855, 426548}, { 22232009, 276063}, { 22432844, 180461}, { 22572399, 114027}, { 22900298, 67093}
};
out.holes.emplace_back(Slic3r::Points( {
{ 28812659, 51882256}, { 28813904, 51895244}, { 28807002, 51890550}, { 28850702, 52059657}, { 28856299, 52123368}, { 29045593, 52135332}, { 29004080, 52024610}, { 28932623, 51976002}, { 29332407, 51880142}, { 29334099, 51804647},
{ 29252306, 51781113}, { 29155613, 51753292}, { 28890648, 51728889}, { 28797131, 51720277}
} ));
return out;
}
static bool is_valid_orientation(const ExPolygon &p)
{
bool ret = p.contour.is_counter_clockwise();
for (auto &h : p.holes) ret = ret && h.is_clockwise();
return ret;
}
static bool is_efc_result_smaller(const ExPolygon &efc, const ExPolygon &orig)
{
double efc_area = efc.area();
return efc_area > 0. && efc_area < orig.area() && is_valid_orientation(efc);
}
SCENARIO("Elephant foot compensation", "[ElephantFoot]") {
GIVEN("Contour with hole") {
ExPolygon expoly = contour_with_hole();
WHEN("Compensated") {
// Elephant foot compensation shall not pinch off bits from this contour.
ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.2f);
#ifdef TESTS_EXPORT_SVGS
SVG::export_expolygons(debug_out_path("elephant_foot_compensation_with_hole.svg").c_str(),
{ { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } },
{ { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } });
#endif /* TESTS_EXPORT_SVGS */
THEN("area of the compensated polygon is smaller") {
REQUIRE(is_efc_result_smaller(expoly_compensated, expoly));
}
}
}
GIVEN("Tiny contour") {
ExPolygon expoly({ { 133382606, 94912473 }, { 134232493, 95001115 }, { 133783926, 95159440 }, { 133441897, 95180666 }, { 133408242, 95191984 }, { 133339012, 95166830 }, { 132991642, 95011087 }, { 133206549, 94908304 } });
WHEN("Compensated") {
ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.2f);
#ifdef TESTS_EXPORT_SVGS
SVG::export_expolygons(debug_out_path("elephant_foot_compensation_tiny.svg").c_str(),
{ { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } },
{ { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } });
#endif /* TESTS_EXPORT_SVGS */
THEN("Tiny contour is not compensated") {
REQUIRE(expoly_compensated == expoly);
}
}
}
GIVEN("Large box") {
ExPolygon expoly( { {50000000, 50000000 }, { 0, 50000000 }, { 0, 0 }, { 50000000, 0 } } );
WHEN("Compensated") {
ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.21f);
#ifdef TESTS_EXPORT_SVGS
SVG::export_expolygons(debug_out_path("elephant_foot_compensation_large_box.svg").c_str(),
{ { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } },
{ { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } });
#endif /* TESTS_EXPORT_SVGS */
THEN("area of the compensated polygon is smaller") {
REQUIRE(is_efc_result_smaller(expoly_compensated, expoly));
}
}
}
GIVEN("Thin ring (GH issue #2085)") {
ExPolygon expoly = thin_ring();
WHEN("Compensated") {
ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.25f);
#ifdef TESTS_EXPORT_SVGS
SVG::export_expolygons(debug_out_path("elephant_foot_compensation_thin_ring.svg").c_str(),
{ { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } },
{ { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } });
#endif /* TESTS_EXPORT_SVGS */
THEN("area of the compensated polygon is smaller") {
REQUIRE(is_efc_result_smaller(expoly_compensated, expoly));
}
}
}
#if 0
GIVEN("Varying inner offset") {
ExPolygon input = spirograph_gear_1mm().simplify(SCALED_EPSILON).front();
ExPolygon output;
std::vector<float> deltas(input.contour.points.size(), scale_(1.));
// mittered_offset_path_scaled_points is commented out somewhere above
output.contour.points = Slic3r::mittered_offset_path_scaled_points(input.contour.points, deltas, 2.);
#ifdef TESTS_EXPORT_SVGS
{
SVG svg(debug_out_path("elephant_foot_compensation_0.svg").c_str(), get_extents(output));
svg.draw(input, "blue");
svg.draw_outline(output, "black", coord_t(scale_(0.01)));
}
#endif /* TESTS_EXPORT_SVGS */
for (size_t i = 0; i <= deltas.size() / 2; ++ i)
deltas[i] = deltas[deltas.size() - i - 1] = scale_(1.) * double(i) / (0.5 * double(deltas.size()));
output.contour.points = Slic3r::mittered_offset_path_scaled_points(input.contour.points, deltas, 2.);
#ifdef TESTS_EXPORT_SVGS
{
SVG svg(debug_out_path("elephant_foot_compensation_varying.svg").c_str(), get_extents(output));
svg.draw(input, "blue");
svg.draw_outline(output, "black", coord_t(scale_(0.01)));
}
#endif /* TESTS_EXPORT_SVGS */
}
#endif
GIVEN("Rectangle with a narrow part sticking out") {
// Rectangle
ExPolygon expoly;
coord_t scaled_w = coord_t(scale_(10));
expoly.contour.points = { Vec2crd{ 0, 0 }, Vec2crd{ 0, scaled_w, }, Vec2crd{ scaled_w, scaled_w }, Vec2crd{ scaled_w, 0 } };
// Narrow part
ExPolygon expoly2;
coord_t scaled_h = coord_t(scale_(0.8));
expoly2.contour.points = { { scaled_w - coord_t(SCALED_EPSILON), scaled_w / 2 }, { scaled_w - coord_t(SCALED_EPSILON), scaled_w / 2 + scaled_h, },
{ 2 * scaled_w, scaled_w / 2 + scaled_h }, { 2 * scaled_w, scaled_w / 2 } };
// Rectangle with the narrow part.
expoly = union_ex({ expoly, expoly2 }).front();
WHEN("Partially compensated") {
ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f), 0.25f);
#ifdef TESTS_EXPORT_SVGS
SVG::export_expolygons(debug_out_path("elephant_foot_compensation_0.svg").c_str(),
{ { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } },
{ { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } });
#endif /* TESTS_EXPORT_SVGS */
THEN("area of the compensated polygon is smaller") {
REQUIRE(is_efc_result_smaller(expoly_compensated, expoly));
}
}
WHEN("Fully compensated") {
ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.35f, 0.2f, 0.4f), 0.17f);
#ifdef TESTS_EXPORT_SVGS
SVG::export_expolygons(debug_out_path("elephant_foot_compensation_1.svg").c_str(),
{ { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } },
{ { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } });
#endif /* TESTS_EXPORT_SVGS */
THEN("area of the compensated polygon is smaller") {
REQUIRE(is_efc_result_smaller(expoly_compensated, expoly));
}
}
}
GIVEN("Box with hole close to wall (GH issue #2998)") {
ExPolygon expoly = box_with_hole_close_to_wall();
WHEN("Compensated") {
ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.25f);
#ifdef TESTS_EXPORT_SVGS
SVG::export_expolygons(debug_out_path("elephant_foot_compensation_2.svg").c_str(),
{ { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } },
{ { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } });
#endif /* TESTS_EXPORT_SVGS */
THEN("area of the compensated polygon is smaller") {
REQUIRE(is_efc_result_smaller(expoly_compensated, expoly));
}
}
}
GIVEN("Spirograph wheel") {
// Rectangle
ExPolygon expoly = spirograph_gear_1mm();
WHEN("Partially compensated") {
ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f), 0.25f);
#ifdef TESTS_EXPORT_SVGS
SVG::export_expolygons(debug_out_path("elephant_foot_compensation_2.svg").c_str(),
{ { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } },
{ { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } });
#endif /* TESTS_EXPORT_SVGS */
THEN("area of the compensated polygon is smaller") {
REQUIRE(is_efc_result_smaller(expoly_compensated, expoly));
}
}
WHEN("Fully compensated") {
ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.35f, 0.2f, 0.4f), 0.17f);
#ifdef TESTS_EXPORT_SVGS
SVG::export_expolygons(debug_out_path("elephant_foot_compensation_3.svg").c_str(),
{ { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } },
{ { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } });
#endif /* TESTS_EXPORT_SVGS */
THEN("area of the compensated polygon is smaller") {
REQUIRE(is_efc_result_smaller(expoly_compensated, expoly));
}
}
WHEN("Brutally compensated") {
ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f), 0.6f);
#ifdef TESTS_EXPORT_SVGS
SVG::export_expolygons(debug_out_path("elephant_foot_compensation_4.svg").c_str(),
{ { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } },
{ { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } });
#endif /* TESTS_EXPORT_SVGS */
THEN("area of the compensated polygon is smaller") {
REQUIRE(is_efc_result_smaller(expoly_compensated, expoly));
}
}
}
GIVEN("Vase with fins") {
ExPolygon expoly = vase_with_fins();
WHEN("Compensated") {
ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.41f);
#ifdef TESTS_EXPORT_SVGS
SVG::export_expolygons(debug_out_path("elephant_foot_compensation_vase_with_fins.svg").c_str(),
{ { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } },
{ { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } });
#endif /* TESTS_EXPORT_SVGS */
THEN("area of the compensated polygon is smaller") {
REQUIRE(is_efc_result_smaller(expoly_compensated, expoly));
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
#include <catch2/catch.hpp>
#include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/ExPolygon.hpp"
using namespace Slic3r;
static inline bool points_close(const Point &p1, const Point &p2)
{
return (p1 - p2).cast<double>().norm() < SCALED_EPSILON;
}
static bool polygons_close_permuted(const Polygon &poly1, const Polygon &poly2, const std::vector<int> &permutation2)
{
if (poly1.size() != poly2.size() || poly1.size() != permutation2.size())
return false;
for (size_t i = 0; i < poly1.size(); ++ i)
if (poly1[i] != poly2[permutation2[i]])
return false;
return true;
}
SCENARIO("Basics", "[ExPolygon]") {
GIVEN("ccw_square") {
Polygon ccw_square{ { 100, 100 }, { 200, 100 }, { 200, 200 }, { 100, 200 } };
Polygon cw_hole_in_square{ { 140, 140 }, { 140, 160 }, { 160, 160 }, { 160, 140 } };
ExPolygon expolygon { ccw_square, cw_hole_in_square };
THEN("expolygon is valid") {
REQUIRE(expolygon.is_valid());
}
THEN("expolygon area") {
REQUIRE(expolygon.area() == Approx(100*100-20*20));
}
WHEN("Expolygon scaled") {
ExPolygon expolygon2 = expolygon;
expolygon2.scale(2.5);
REQUIRE(expolygon.contour.size() == expolygon2.contour.size());
REQUIRE(expolygon.holes.size() == 1);
REQUIRE(expolygon2.holes.size() == 1);
for (size_t i = 0; i < expolygon.contour.size(); ++ i)
REQUIRE(points_close(expolygon.contour[i] * 2.5, expolygon2.contour[i]));
for (size_t i = 0; i < expolygon.holes.front().size(); ++ i)
REQUIRE(points_close(expolygon.holes.front()[i] * 2.5, expolygon2.holes.front()[i]));
}
WHEN("Expolygon translated") {
ExPolygon expolygon2 = expolygon;
expolygon2.translate(10, -5);
REQUIRE(expolygon.contour.size() == expolygon2.contour.size());
REQUIRE(expolygon.holes.size() == 1);
REQUIRE(expolygon2.holes.size() == 1);
for (size_t i = 0; i < expolygon.contour.size(); ++ i)
REQUIRE(points_close(expolygon.contour[i] + Point(10, -5), expolygon2.contour[i]));
for (size_t i = 0; i < expolygon.holes.front().size(); ++ i)
REQUIRE(points_close(expolygon.holes.front()[i] + Point(10, -5), expolygon2.holes.front()[i]));
}
WHEN("Expolygon rotated around point") {
ExPolygon expolygon2 = expolygon;
expolygon2.rotate(M_PI / 2, Point(150, 150));
REQUIRE(expolygon.contour.size() == expolygon2.contour.size());
REQUIRE(expolygon.holes.size() == 1);
REQUIRE(expolygon2.holes.size() == 1);
REQUIRE(polygons_close_permuted(expolygon2.contour, expolygon.contour, { 1, 2, 3, 0}));
REQUIRE(polygons_close_permuted(expolygon2.holes.front(), expolygon.holes.front(), { 3, 0, 1, 2}));
}
}
}

View File

@@ -0,0 +1,790 @@
#include <catch2/catch.hpp>
#include "libslic3r/Point.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Polyline.hpp"
#include "libslic3r/Line.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/Geometry/Circle.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/ShortestPath.hpp"
//#include <random>
//#include "libnest2d/tools/benchmark.h"
#include "libslic3r/SVG.hpp"
#include "../libnest2d/printer_parts.hpp"
#include <unordered_set>
using namespace Slic3r;
TEST_CASE("Line::parallel_to", "[Geometry]"){
Line l{ { 100000, 0 }, { 0, 0 } };
Line l2{ { 200000, 0 }, { 0, 0 } };
REQUIRE(l.parallel_to(l));
REQUIRE(l.parallel_to(l2));
Line l3(l2);
l3.rotate(0.9 * EPSILON, { 0, 0 });
REQUIRE(l.parallel_to(l3));
Line l4(l2);
l4.rotate(1.1 * EPSILON, { 0, 0 });
REQUIRE(! l.parallel_to(l4));
// The angle epsilon is so low that vectors shorter than 100um rotated by epsilon radians are not rotated at all.
Line l5{ { 20000, 0 }, { 0, 0 } };
l5.rotate(1.1 * EPSILON, { 0, 0 });
REQUIRE(l.parallel_to(l5));
l.rotate(1., { 0, 0 });
Point offset{ 342876, 97636249 };
l.translate(offset);
l3.rotate(1., { 0, 0 });
l3.translate(offset);
l4.rotate(1., { 0, 0 });
l4.translate(offset);
REQUIRE(l.parallel_to(l3));
REQUIRE(!l.parallel_to(l4));
}
TEST_CASE("Line::perpendicular_to", "[Geometry]") {
Line l{ { 100000, 0 }, { 0, 0 } };
Line l2{ { 0, 200000 }, { 0, 0 } };
REQUIRE(! l.perpendicular_to(l));
REQUIRE(l.perpendicular_to(l2));
Line l3(l2);
l3.rotate(0.9 * EPSILON, { 0, 0 });
REQUIRE(l.perpendicular_to(l3));
Line l4(l2);
l4.rotate(1.1 * EPSILON, { 0, 0 });
REQUIRE(! l.perpendicular_to(l4));
// The angle epsilon is so low that vectors shorter than 100um rotated by epsilon radians are not rotated at all.
Line l5{ { 0, 20000 }, { 0, 0 } };
l5.rotate(1.1 * EPSILON, { 0, 0 });
REQUIRE(l.perpendicular_to(l5));
l.rotate(1., { 0, 0 });
Point offset{ 342876, 97636249 };
l.translate(offset);
l3.rotate(1., { 0, 0 });
l3.translate(offset);
l4.rotate(1., { 0, 0 });
l4.translate(offset);
REQUIRE(l.perpendicular_to(l3));
REQUIRE(! l.perpendicular_to(l4));
}
TEST_CASE("Polygon::contains works properly", "[Geometry]"){
// this test was failing on Windows (GH #1950)
Slic3r::Polygon polygon(Points({
Point(207802834,-57084522),
Point(196528149,-37556190),
Point(173626821,-25420928),
Point(171285751,-21366123),
Point(118673592,-21366123),
Point(116332562,-25420928),
Point(93431208,-37556191),
Point(82156517,-57084523),
Point(129714478,-84542120),
Point(160244873,-84542120)
}));
Point point(95706562, -57294774);
REQUIRE(polygon.contains(point));
}
SCENARIO("Intersections of line segments", "[Geometry]"){
GIVEN("Integer coordinates"){
Line line1(Point(5,15),Point(30,15));
Line line2(Point(10,20), Point(10,10));
THEN("The intersection is valid"){
Point point;
line1.intersection(line2,&point);
REQUIRE(Point(10,15) == point);
}
}
GIVEN("Scaled coordinates"){
Line line1(Point(73.6310778185108 / 0.00001, 371.74239268924 / 0.00001), Point(73.6310778185108 / 0.00001, 501.74239268924 / 0.00001));
Line line2(Point(75/0.00001, 437.9853/0.00001), Point(62.7484/0.00001, 440.4223/0.00001));
THEN("There is still an intersection"){
Point point;
REQUIRE(line1.intersection(line2,&point));
}
}
}
SCENARIO("polygon_is_convex works") {
GIVEN("A square of dimension 10") {
WHEN("Polygon is convex clockwise") {
Polygon cw_square { { {0, 0}, {0,10}, {10,10}, {10,0} } };
THEN("it is not convex") {
REQUIRE(! polygon_is_convex(cw_square));
}
}
WHEN("Polygon is convex counter-clockwise") {
Polygon ccw_square { { {0, 0}, {10,0}, {10,10}, {0,10} } };
THEN("it is convex") {
REQUIRE(polygon_is_convex(ccw_square));
}
}
}
GIVEN("A concave polygon") {
Polygon concave = { {0,0}, {10,0}, {10,10}, {0,10}, {0,6}, {4,6}, {4,4}, {0,4} };
THEN("It is not convex") {
REQUIRE(! polygon_is_convex(concave));
}
}
}
TEST_CASE("Creating a polyline generates the obvious lines", "[Geometry]"){
Slic3r::Polyline polyline;
polyline.points = Points({Point(0, 0), Point(10, 0), Point(20, 0)});
REQUIRE(polyline.lines().at(0).a == Point(0,0));
REQUIRE(polyline.lines().at(0).b == Point(10,0));
REQUIRE(polyline.lines().at(1).a == Point(10,0));
REQUIRE(polyline.lines().at(1).b == Point(20,0));
}
TEST_CASE("Splitting a Polygon generates a polyline correctly", "[Geometry]"){
Slic3r::Polygon polygon(Points({Point(0, 0), Point(10, 0), Point(5, 5)}));
Slic3r::Polyline split = polygon.split_at_index(1);
REQUIRE(split.points[0]==Point(10,0));
REQUIRE(split.points[1]==Point(5,5));
REQUIRE(split.points[2]==Point(0,0));
REQUIRE(split.points[3]==Point(10,0));
}
SCENARIO("BoundingBox", "[Geometry]") {
WHEN("Bounding boxes are scaled") {
BoundingBox bb(Points({Point(0, 1), Point(10, 2), Point(20, 2)}));
bb.scale(2);
REQUIRE(bb.min == Point(0,2));
REQUIRE(bb.max == Point(40,4));
}
WHEN("BoundingBox constructed from points") {
BoundingBox bb(Points{ {100,200}, {100, 200}, {500, -600} });
THEN("minimum is correct") {
REQUIRE(bb.min == Point{100,-600});
}
THEN("maximum is correct") {
REQUIRE(bb.max == Point{500,200});
}
}
WHEN("BoundingBox constructed from a single point") {
BoundingBox bb;
bb.merge({10, 10});
THEN("minimum equals to the only defined point") {
REQUIRE(bb.min == Point{10,10});
}
THEN("maximum equals to the only defined point") {
REQUIRE(bb.max == Point{10,10});
}
}
}
TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){
Slic3r::Polyline tmp = { Point(10,10), Point(20,10) };
Slic3r::Polygon area = offset(tmp,5).at(0);
REQUIRE(area.area() == Slic3r::Polygon(Points({Point(10,5),Point(20,5),Point(20,15),Point(10,15)})).area());
}
SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
Vec2d expected_center(-6, 0);
Vec2ds sample {Vec2d(6.0, 0), Vec2d(5.1961524, 3), Vec2d(3 ,5.1961524), Vec2d(0, 6.0), Vec2d(3, 5.1961524), Vec2d(-5.1961524, 3), Vec2d(-6.0, 0)};
std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Vec2d& a) { return a + expected_center;});
WHEN("Circle fit is called on the entire array") {
Vec2d result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample);
THEN("A center point of -6,0 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
WHEN("Circle fit is called on the first four points") {
Vec2d result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4);
THEN("A center point of -6,0 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
WHEN("Circle fit is called on the middle four points") {
Vec2d result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
THEN("A center point of -6,0 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
}
GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
Vec2d expected_center(-3, 9);
Vec2ds sample {Vec2d(6.0, 0), Vec2d(5.1961524, 3), Vec2d(3 ,5.1961524),
Vec2d(0, 6.0),
Vec2d(3, 5.1961524), Vec2d(-5.1961524, 3), Vec2d(-6.0, 0)};
std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Vec2d& a) { return a + expected_center;});
WHEN("Circle fit is called on the entire array") {
Vec2d result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample);
THEN("A center point of 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
WHEN("Circle fit is called on the first four points") {
Vec2d result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4);
THEN("A center point of 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
WHEN("Circle fit is called on the middle four points") {
Vec2d result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
THEN("A center point of 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
}
GIVEN("A vector of Points arranged in a half-circle with approximately the same distance R from some point") {
Point expected_center { Point::new_scale(-3, 9)};
Points sample {Point::new_scale(6.0, 0), Point::new_scale(5.1961524, 3), Point::new_scale(3 ,5.1961524),
Point::new_scale(0, 6.0),
Point::new_scale(3, 5.1961524), Point::new_scale(-5.1961524, 3), Point::new_scale(-6.0, 0)};
std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Point& a) { return a + expected_center;});
WHEN("Circle fit is called on the entire array") {
Point result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample);
THEN("A center point of scaled 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
WHEN("Circle fit is called on the first four points") {
Point result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4);
THEN("A center point of scaled 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
WHEN("Circle fit is called on the middle four points") {
Point result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
THEN("A center point of scaled 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
}
}
TEST_CASE("smallest_enclosing_circle_welzl", "[Geometry]") {
// Some random points in plane.
Points pts {
{ 89243, 4359 }, { 763465, 59687 }, { 3245, 734987 }, { 2459867, 987634 }, { 759866, 67843982 }, { 9754687, 9834658 }, { 87235089, 743984373 },
{ 65874456, 2987546 }, { 98234524, 657654873 }, { 786243598, 287934765 }, { 824356, 734265 }, { 82576449, 7864534 }, { 7826345, 3984765 }
};
const auto c = Slic3r::Geometry::smallest_enclosing_circle_welzl(pts);
// The radius returned is inflated by SCALED_EPSILON, thus all points should be inside.
bool all_inside = std::all_of(pts.begin(), pts.end(), [c](const Point &pt){ return c.contains(pt.cast<double>()); });
auto c2(c);
c2.radius -= SCALED_EPSILON * 2.1;
auto num_on_boundary = std::count_if(pts.begin(), pts.end(), [c2](const Point& pt) { return ! c2.contains(pt.cast<double>(), SCALED_EPSILON); });
REQUIRE(all_inside);
REQUIRE(num_on_boundary == 3);
}
SCENARIO("Path chaining", "[Geometry]") {
GIVEN("A path") {
Points points = { Point(26,26),Point(52,26),Point(0,26),Point(26,52),Point(26,0),Point(0,52),Point(52,52),Point(52,0) };
THEN("Chained with no diagonals (thus 26 units long)") {
std::vector<Points::size_type> indices = chain_points(points);
for (Points::size_type i = 0; i + 1 < indices.size(); ++ i) {
double dist = (points.at(indices.at(i)).cast<double>() - points.at(indices.at(i+1)).cast<double>()).norm();
REQUIRE(std::abs(dist-26) <= EPSILON);
}
}
}
GIVEN("Gyroid infill end points") {
Polylines polylines = {
{ {28122608, 3221037}, {27919139, 56036027} },
{ {33642863, 3400772}, {30875220, 56450360} },
{ {34579315, 3599827}, {35049758, 55971572} },
{ {26483070, 3374004}, {23971830, 55763598} },
{ {38931405, 4678879}, {38740053, 55077714} },
{ {20311895, 5015778}, {20079051, 54551952} },
{ {16463068, 6773342}, {18823514, 53992958} },
{ {44433771, 7424951}, {42629462, 53346059} },
{ {15697614, 7329492}, {15350896, 52089991} },
{ {48085792, 10147132}, {46435427, 50792118} },
{ {48828819, 10972330}, {49126582, 48368374} },
{ {9654526, 12656711}, {10264020, 47691584} },
{ {5726905, 18648632}, {8070762, 45082416} },
{ {54818187, 39579970}, {52974912, 43271272} },
{ {4464342, 37371742}, {5027890, 39106220} },
{ {54139746, 18417661}, {55177987, 38472580} },
{ {56527590, 32058461}, {56316456, 34067185} },
{ {3303988, 29215290}, {3569863, 32985633} },
{ {56255666, 25025857}, {56478310, 27144087} },
{ {4300034, 22805361}, {3667946, 25752601} },
{ {8266122, 14250611}, {6244813, 17751595} },
{ {12177955, 9886741}, {10703348, 11491900} }
};
Polylines chained = chain_polylines(polylines);
THEN("Chained taking the shortest path") {
double connection_length = 0.;
for (size_t i = 1; i < chained.size(); ++i) {
const Polyline &pl1 = chained[i - 1];
const Polyline &pl2 = chained[i];
connection_length += (pl2.first_point() - pl1.last_point()).cast<double>().norm();
}
REQUIRE(connection_length < 85206000.);
}
}
GIVEN("Loop pieces") {
Point a { 2185796, 19058485 };
Point b { 3957902, 18149382 };
Point c { 2912841, 18790564 };
Point d { 2831848, 18832390 };
Point e { 3179601, 18627769 };
Point f { 3137952, 18653370 };
Polylines polylines = { { a, b },
{ c, d },
{ e, f },
{ d, a },
{ f, c },
{ b, e } };
Polylines chained = chain_polylines(polylines, &a);
THEN("Connected without a gap") {
for (size_t i = 0; i < chained.size(); ++i) {
const Polyline &pl1 = (i == 0) ? chained.back() : chained[i - 1];
const Polyline &pl2 = chained[i];
REQUIRE(pl1.points.back() == pl2.points.front());
}
}
}
}
SCENARIO("Line distances", "[Geometry]"){
GIVEN("A line"){
Line line(Point(0, 0), Point(20, 0));
THEN("Points on the line segment have 0 distance"){
REQUIRE(line.distance_to(Point(0, 0)) == 0);
REQUIRE(line.distance_to(Point(20, 0)) == 0);
REQUIRE(line.distance_to(Point(10, 0)) == 0);
}
THEN("Points off the line have the appropriate distance"){
REQUIRE(line.distance_to(Point(10, 10)) == 10);
REQUIRE(line.distance_to(Point(50, 0)) == 30);
}
}
}
SCENARIO("Calculating angles", "[Geometry]")
{
GIVEN(("Vectors 30 degrees apart"))
{
std::vector<std::pair<Point, Point>> pts {
{ {1000, 0}, { 866, 500 } },
{ { 866, 500 }, { 500, 866 } },
{ { 500, 866 }, { 0, 1000 } },
{ { -500, 866 }, { -866, 500 } }
};
THEN("Angle detected is 30 degrees")
{
for (auto &p : pts)
REQUIRE(is_approx(angle(p.first, p.second), M_PI / 6.));
}
}
GIVEN(("Vectors 30 degrees apart"))
{
std::vector<std::pair<Point, Point>> pts {
{ { 866, 500 }, {1000, 0} },
{ { 500, 866 }, { 866, 500 } },
{ { 0, 1000 }, { 500, 866 } },
{ { -866, 500 }, { -500, 866 } }
};
THEN("Angle detected is -30 degrees")
{
for (auto &p : pts)
REQUIRE(is_approx(angle(p.first, p.second), - M_PI / 6.));
}
}
}
SCENARIO("Polygon convex/concave detection", "[Geometry]"){
static constexpr const double angle_threshold = M_PI / 3.;
GIVEN(("A Square with dimension 100")){
auto square = Slic3r::Polygon /*new_scale*/(Points({
Point(100,100),
Point(200,100),
Point(200,200),
Point(100,200)}));
THEN("It has 4 convex points counterclockwise"){
REQUIRE(square.concave_points(angle_threshold).size() == 0);
REQUIRE(square.convex_points(angle_threshold).size() == 4);
}
THEN("It has 4 concave points clockwise"){
square.make_clockwise();
REQUIRE(square.concave_points(angle_threshold).size() == 4);
REQUIRE(square.convex_points(angle_threshold).size() == 0);
}
}
GIVEN("A Square with an extra colinearvertex"){
auto square = Slic3r::Polygon /*new_scale*/(Points({
Point(150,100),
Point(200,100),
Point(200,200),
Point(100,200),
Point(100,100)}));
THEN("It has 4 convex points counterclockwise"){
REQUIRE(square.concave_points(angle_threshold).size() == 0);
REQUIRE(square.convex_points(angle_threshold).size() == 4);
}
}
GIVEN("A Square with an extra collinear vertex in different order"){
auto square = Slic3r::Polygon /*new_scale*/(Points({
Point(200,200),
Point(100,200),
Point(100,100),
Point(150,100),
Point(200,100)}));
THEN("It has 4 convex points counterclockwise"){
REQUIRE(square.concave_points(angle_threshold).size() == 0);
REQUIRE(square.convex_points(angle_threshold).size() == 4);
}
}
GIVEN("A triangle"){
auto triangle = Slic3r::Polygon(Points({
Point(16000170,26257364),
Point(714223,461012),
Point(31286371,461008)
}));
THEN("it has three convex vertices"){
REQUIRE(triangle.concave_points(angle_threshold).size() == 0);
REQUIRE(triangle.convex_points(angle_threshold).size() == 3);
}
}
GIVEN("A triangle with an extra collinear point"){
auto triangle = Slic3r::Polygon(Points({
Point(16000170,26257364),
Point(714223,461012),
Point(20000000,461012),
Point(31286371,461012)
}));
THEN("it has three convex vertices"){
REQUIRE(triangle.concave_points(angle_threshold).size() == 0);
REQUIRE(triangle.convex_points(angle_threshold).size() == 3);
}
}
GIVEN("A polygon with concave vertices with angles of specifically 4/3pi"){
// Two concave vertices of this polygon have angle = PI*4/3, so this test fails
// if epsilon is not used.
auto polygon = Slic3r::Polygon(Points({
Point(60246458,14802768),Point(64477191,12360001),
Point(63727343,11060995),Point(64086449,10853608),
Point(66393722,14850069),Point(66034704,15057334),
Point(65284646,13758387),Point(61053864,16200839),
Point(69200258,30310849),Point(62172547,42483120),
Point(61137680,41850279),Point(67799985,30310848),
Point(51399866,1905506),Point(38092663,1905506),
Point(38092663,692699),Point(52100125,692699)
}));
THEN("the correct number of points are detected"){
REQUIRE(polygon.concave_points(angle_threshold).size() == 6);
REQUIRE(polygon.convex_points(angle_threshold).size() == 10);
}
}
}
TEST_CASE("Triangle Simplification does not result in less than 3 points", "[Geometry]"){
auto triangle = Slic3r::Polygon(Points({
Point(16000170,26257364), Point(714223,461012), Point(31286371,461008)
}));
REQUIRE(triangle.simplify(250000).at(0).points.size() == 3);
}
SCENARIO("Ported from xs/t/14_geometry.t", "[Geometry]"){
GIVEN(("square")){
Slic3r::Points points { { 100, 100 }, {100, 200 }, { 200, 200 }, { 200, 100 }, { 150, 150 } };
Slic3r::Polygon hull = Slic3r::Geometry::convex_hull(points);
SECTION("convex hull returns the correct number of points") { REQUIRE(hull.points.size() == 4); }
}
SECTION("arrange returns expected number of positions") {
Pointfs positions;
Slic3r::Geometry::arrange(4, Vec2d(20, 20), 5, nullptr, positions);
REQUIRE(positions.size() == 4);
}
SECTION("directions_parallel") {
REQUIRE(Slic3r::Geometry::directions_parallel(0, 0, 0));
REQUIRE(Slic3r::Geometry::directions_parallel(0, M_PI, 0));
REQUIRE(Slic3r::Geometry::directions_parallel(0, 0, M_PI / 180));
REQUIRE(Slic3r::Geometry::directions_parallel(0, M_PI, M_PI / 180));
REQUIRE(! Slic3r::Geometry::directions_parallel(M_PI /2, M_PI, 0));
REQUIRE(! Slic3r::Geometry::directions_parallel(M_PI /2, PI, M_PI /180));
}
}
TEST_CASE("Convex polygon intersection on two disjoint squares", "[Geometry][Rotcalip]") {
Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
A.scale(1. / SCALING_FACTOR);
Polygon B = A;
B.translate(20 / SCALING_FACTOR, 0);
bool is_inters = Geometry::convex_polygons_intersect(A, B);
REQUIRE(is_inters == false);
}
TEST_CASE("Convex polygon intersection on two intersecting squares", "[Geometry][Rotcalip]") {
Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
A.scale(1. / SCALING_FACTOR);
Polygon B = A;
B.translate(5 / SCALING_FACTOR, 5 / SCALING_FACTOR);
bool is_inters = Geometry::convex_polygons_intersect(A, B);
REQUIRE(is_inters == true);
}
TEST_CASE("Convex polygon intersection on two squares touching one edge", "[Geometry][Rotcalip]") {
Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
A.scale(1. / SCALING_FACTOR);
Polygon B = A;
B.translate(10 / SCALING_FACTOR, 0);
bool is_inters = Geometry::convex_polygons_intersect(A, B);
REQUIRE(is_inters == false);
}
TEST_CASE("Convex polygon intersection on two squares touching one vertex", "[Geometry][Rotcalip]") {
Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
A.scale(1. / SCALING_FACTOR);
Polygon B = A;
B.translate(10 / SCALING_FACTOR, 10 / SCALING_FACTOR);
SVG svg{std::string("one_vertex_touch") + ".svg"};
svg.draw(A, "blue");
svg.draw(B, "green");
svg.Close();
bool is_inters = Geometry::convex_polygons_intersect(A, B);
REQUIRE(is_inters == false);
}
TEST_CASE("Convex polygon intersection on two overlapping squares", "[Geometry][Rotcalip]") {
Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
A.scale(1. / SCALING_FACTOR);
Polygon B = A;
bool is_inters = Geometry::convex_polygons_intersect(A, B);
REQUIRE(is_inters == true);
}
//// Only for benchmarking
//static Polygon gen_convex_poly(std::mt19937_64 &rg, size_t point_cnt)
//{
// std::uniform_int_distribution<coord_t> dist(0, 100);
// Polygon out;
// out.points.reserve(point_cnt);
// coord_t tr = dist(rg) * 2 / SCALING_FACTOR;
// for (size_t i = 0; i < point_cnt; ++i)
// out.points.emplace_back(tr + dist(rg) / SCALING_FACTOR,
// tr + dist(rg) / SCALING_FACTOR);
// return Geometry::convex_hull(out.points);
//}
//TEST_CASE("Convex polygon intersection test on random polygons", "[Geometry]") {
// constexpr size_t TEST_CNT = 1000;
// constexpr size_t POINT_CNT = 1000;
// auto seed = std::random_device{}();
//// unsigned long seed = 2525634386;
// std::mt19937_64 rg{seed};
// Benchmark bench;
// auto tests = reserve_vector<std::pair<Polygon, Polygon>>(TEST_CNT);
// auto results = reserve_vector<bool>(TEST_CNT);
// auto expects = reserve_vector<bool>(TEST_CNT);
// for (size_t i = 0; i < TEST_CNT; ++i) {
// tests.emplace_back(gen_convex_poly(rg, POINT_CNT), gen_convex_poly(rg, POINT_CNT));
// }
// bench.start();
// for (const auto &test : tests)
// results.emplace_back(Geometry::convex_polygons_intersect(test.first, test.second));
// bench.stop();
// std::cout << "Test time: " << bench.getElapsedSec() << std::endl;
// bench.start();
// for (const auto &test : tests)
// expects.emplace_back(!intersection(test.first, test.second).empty());
// bench.stop();
// std::cout << "Clipper time: " << bench.getElapsedSec() << std::endl;
// REQUIRE(results.size() == expects.size());
// auto seedstr = std::to_string(seed);
// for (size_t i = 0; i < results.size(); ++i) {
// // std::cout << expects[i] << " ";
// if (results[i] != expects[i]) {
// SVG svg{std::string("fail_seed") + seedstr + "_" + std::to_string(i) + ".svg"};
// svg.draw(tests[i].first, "blue");
// svg.draw(tests[i].second, "green");
// svg.Close();
// // std::cout << std::endl;
// }
// REQUIRE(results[i] == expects[i]);
// }
// std::cout << std::endl;
//}
struct Pair
{
size_t first, second;
bool operator==(const Pair &b) const { return first == b.first && second == b.second; }
};
template<> struct std::hash<Pair> {
size_t operator()(const Pair &c) const
{
return c.first * PRINTER_PART_POLYGONS.size() + c.second;
}
};
TEST_CASE("Convex polygon intersection test qidi polygons", "[Geometry][Rotcalip]") {
// Overlap of the same polygon should always be an intersection
for (size_t i = 0; i < PRINTER_PART_POLYGONS.size(); ++i) {
Polygon P = PRINTER_PART_POLYGONS[i];
P = Geometry::convex_hull(P.points);
bool res = Geometry::convex_polygons_intersect(P, P);
if (!res) {
SVG svg{std::string("fail_self") + std::to_string(i) + ".svg"};
svg.draw(P, "green");
svg.Close();
}
REQUIRE(res == true);
}
std::unordered_set<Pair> combos;
for (size_t i = 0; i < PRINTER_PART_POLYGONS.size(); ++i) {
for (size_t j = 0; j < PRINTER_PART_POLYGONS.size(); ++j) {
if (i != j) {
size_t a = std::min(i, j), b = std::max(i, j);
combos.insert(Pair{a, b});
}
}
}
// All disjoint
for (const auto &combo : combos) {
Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second];
A = Geometry::convex_hull(A.points);
B = Geometry::convex_hull(B.points);
auto bba = A.bounding_box();
auto bbb = B.bounding_box();
A.translate(-bba.center());
B.translate(-bbb.center());
B.translate(bba.size() + bbb.size());
bool res = Geometry::convex_polygons_intersect(A, B);
bool ref = !intersection(A, B).empty();
if (res != ref) {
SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"};
svg.draw(A, "blue");
svg.draw(B, "green");
svg.Close();
}
REQUIRE(res == ref);
}
// All intersecting
for (const auto &combo : combos) {
Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second];
A = Geometry::convex_hull(A.points);
B = Geometry::convex_hull(B.points);
auto bba = A.bounding_box();
auto bbb = B.bounding_box();
A.translate(-bba.center());
B.translate(-bbb.center());
bool res = Geometry::convex_polygons_intersect(A, B);
bool ref = !intersection(A, B).empty();
if (res != ref) {
SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"};
svg.draw(A, "blue");
svg.draw(B, "green");
svg.Close();
}
REQUIRE(res == ref);
}
}
TEST_CASE("Euler angles roundtrip", "[Geometry]") {
std::vector<Vec3d> euler_angles_vec = {{M_PI/2., -M_PI, 0.},
{M_PI, -M_PI, 0.},
{M_PI, -M_PI, 2*M_PI},
{0., 0., M_PI},
{M_PI, M_PI/2., 0.},
{0.2, 0.3, -0.5}};
// Also include all combinations of zero and +-pi/2:
for (double x : {0., M_PI/2., -M_PI/2.})
for (double y : {0., M_PI/2., -M_PI/2.})
for (double z : {0., M_PI/2., -M_PI/2.})
euler_angles_vec.emplace_back(x, y, z);
for (Vec3d& euler_angles : euler_angles_vec) {
Transform3d trafo1 = Geometry::rotation_transform(euler_angles);
euler_angles = Geometry::extract_rotation(trafo1);
Transform3d trafo2 = Geometry::rotation_transform(euler_angles);
REQUIRE(trafo1.isApprox(trafo2));
}
}

View File

@@ -0,0 +1,21 @@
#include <iostream>
#include <fstream>
#include <catch2/catch.hpp>
#include "libslic3r/SLA/Hollowing.hpp"
TEST_CASE("Hollow two overlapping spheres") {
using namespace Slic3r;
TriangleMesh sphere1 = make_sphere(10., 2 * PI / 20.), sphere2 = sphere1;
sphere1.translate(-5.f, 0.f, 0.f);
sphere2.translate( 5.f, 0.f, 0.f);
sphere1.merge(sphere2);
sla::hollow_mesh(sphere1, sla::HollowingConfig{}, sla::HollowingFlags::hfRemoveInsideTriangles);
sphere1.WriteOBJFile("twospheres.obj");
}

View File

@@ -0,0 +1,240 @@
#include <iostream>
#include <fstream>
#include <catch2/catch.hpp>
#include "libslic3r/TriangleMesh.hpp"
using namespace Slic3r;
TEST_CASE("Split empty mesh", "[its_split][its]") {
using namespace Slic3r;
indexed_triangle_set its;
std::vector<indexed_triangle_set> res = its_split(its);
REQUIRE(res.empty());
}
TEST_CASE("Split simple mesh consisting of one part", "[its_split][its]") {
using namespace Slic3r;
auto cube = its_make_cube(10., 10., 10.);
std::vector<indexed_triangle_set> res = its_split(cube);
REQUIRE(res.size() == 1);
REQUIRE(res.front().indices.size() == cube.indices.size());
REQUIRE(res.front().vertices.size() == cube.vertices.size());
}
void debug_write_obj(const std::vector<indexed_triangle_set> &res, const std::string &name)
{
#ifndef NDEBUG
size_t part_idx = 0;
for (auto &part_its : res) {
its_write_obj(part_its, (name + std::to_string(part_idx++) + ".obj").c_str());
}
#endif
}
TEST_CASE("Split two non-watertight mesh", "[its_split][its]") {
using namespace Slic3r;
auto cube1 = its_make_cube(10., 10., 10.);
cube1.indices.pop_back();
auto cube2 = cube1;
its_transform(cube1, identity3f().translate(Vec3f{-5.f, 0.f, 0.f}));
its_transform(cube2, identity3f().translate(Vec3f{5.f, 0.f, 0.f}));
its_merge(cube1, cube2);
std::vector<indexed_triangle_set> res = its_split(cube1);
REQUIRE(res.size() == 2);
REQUIRE(res[0].indices.size() == res[1].indices.size());
REQUIRE(res[0].indices.size() == cube2.indices.size());
REQUIRE(res[0].vertices.size() == res[1].vertices.size());
REQUIRE(res[0].vertices.size() == cube2.vertices.size());
debug_write_obj(res, "parts_non_watertight");
}
TEST_CASE("Split non-manifold mesh", "[its_split][its]") {
using namespace Slic3r;
auto cube = its_make_cube(10., 10., 10.), cube_low = cube;
its_transform(cube_low, identity3f().translate(Vec3f{10.f, 10.f, 10.f}));
its_merge(cube, cube_low);
its_merge_vertices(cube);
std::vector<indexed_triangle_set> res = its_split(cube);
REQUIRE(res.size() == 2);
REQUIRE(res[0].indices.size() == res[1].indices.size());
REQUIRE(res[0].indices.size() == cube_low.indices.size());
REQUIRE(res[0].vertices.size() == res[1].vertices.size());
REQUIRE(res[0].vertices.size() == cube_low.vertices.size());
debug_write_obj(res, "cubes_non_manifold");
}
TEST_CASE("Split two watertight meshes", "[its_split][its]") {
using namespace Slic3r;
auto sphere1 = its_make_sphere(10., 2 * PI / 200.), sphere2 = sphere1;
its_transform(sphere1, identity3f().translate(Vec3f{-5.f, 0.f, 0.f}));
its_transform(sphere2, identity3f().translate(Vec3f{5.f, 0.f, 0.f}));
its_merge(sphere1, sphere2);
std::vector<indexed_triangle_set> res = its_split(sphere1);
REQUIRE(res.size() == 2);
REQUIRE(res[0].indices.size() == res[1].indices.size());
REQUIRE(res[0].indices.size() == sphere2.indices.size());
REQUIRE(res[0].vertices.size() == res[1].vertices.size());
REQUIRE(res[0].vertices.size() == sphere2.vertices.size());
debug_write_obj(res, "parts_watertight");
}
#include <libslic3r/QuadricEdgeCollapse.hpp>
static float triangle_area(const Vec3f &v0, const Vec3f &v1, const Vec3f &v2)
{
Vec3f ab = v1 - v0;
Vec3f ac = v2 - v0;
return ab.cross(ac).norm() / 2.f;
}
static float triangle_area(const Vec3crd &triangle_inices, const std::vector<Vec3f> &vertices)
{
return triangle_area(vertices[triangle_inices[0]],
vertices[triangle_inices[1]],
vertices[triangle_inices[2]]);
}
#if 0
// clang complains about unused functions
static std::mt19937 create_random_generator() {
std::random_device rd;
std::mt19937 gen(rd());
return gen;
}
#endif
std::vector<Vec3f> its_sample_surface(const indexed_triangle_set &its,
double sample_per_mm2,
std::mt19937 random_generator) // = create_random_generator())
{
std::vector<Vec3f> samples;
std::uniform_real_distribution<float> rand01(0.f, 1.f);
for (const auto &triangle_indices : its.indices) {
float area = triangle_area(triangle_indices, its.vertices);
float countf;
float fractional = std::modf(area * sample_per_mm2, &countf);
int count = static_cast<int>(countf);
float generate = rand01(random_generator);
if (generate < fractional) ++count;
if (count == 0) continue;
const Vec3f &v0 = its.vertices[triangle_indices[0]];
const Vec3f &v1 = its.vertices[triangle_indices[1]];
const Vec3f &v2 = its.vertices[triangle_indices[2]];
for (int c = 0; c < count; c++) {
// barycentric coordinate
Vec3f b;
b[0] = rand01(random_generator);
b[1] = rand01(random_generator);
if ((b[0] + b[1]) > 1.f) {
b[0] = 1.f - b[0];
b[1] = 1.f - b[1];
}
b[2] = 1.f - b[0] - b[1];
Vec3f pos;
for (int i = 0; i < 3; i++) {
pos[i] = b[0] * v0[i] + b[1] * v1[i] + b[2] * v2[i];
}
samples.push_back(pos);
}
}
return samples;
}
#include "libslic3r/AABBTreeIndirect.hpp"
struct CompareConfig
{
float max_distance = 3.f;
float max_average_distance = 2.f;
};
bool is_similar(const indexed_triangle_set &from,
const indexed_triangle_set &to,
const CompareConfig &cfg)
{
// create ABBTree
auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
from.vertices, from.indices);
float sum_distance = 0.f;
float max_distance = 0.f;
auto collect_distances = [&](const Vec3f &surface_point) {
size_t hit_idx;
Vec3f hit_point;
float distance2 =
AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
from.vertices, from.indices, tree, surface_point, hit_idx, hit_point);
float distance = sqrt(distance2);
if (max_distance < distance) max_distance = distance;
sum_distance += distance;
};
for (const Vec3f &vertex : to.vertices) {
collect_distances(vertex);
}
for (const Vec3i &t : to.indices) {
Vec3f center(0,0,0);
for (size_t i = 0; i < 3; ++i) {
center += to.vertices[t[i]] / 3;
}
collect_distances(center);
}
size_t count = to.vertices.size() + to.indices.size();
float avg_distance = sum_distance / count;
if (avg_distance > cfg.max_average_distance ||
max_distance > cfg.max_distance)
return false;
return true;
}
#include "test_utils.hpp"
TEST_CASE("Simplify mesh by Quadric edge collapse to 5%", "[its]")
{
TriangleMesh mesh = load_model("frog_legs.obj");
double original_volume = its_volume(mesh.its);
uint32_t wanted_count = mesh.its.indices.size() * 0.05;
REQUIRE_FALSE(mesh.empty());
indexed_triangle_set its = mesh.its; // copy
float max_error = std::numeric_limits<float>::max();
its_quadric_edge_collapse(its, wanted_count, &max_error);
//its_write_obj(its, "frog_legs_qec.obj");
CHECK(its.indices.size() <= wanted_count);
double volume = its_volume(its);
CHECK(fabs(original_volume - volume) < 33.);
CompareConfig cfg;
cfg.max_average_distance = 0.043f;
cfg.max_distance = 0.32f;
CHECK(is_similar(mesh.its, its, cfg));
CHECK(is_similar(its, mesh.its, cfg));
}

View File

@@ -0,0 +1,35 @@
#include <catch2/catch.hpp>
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/JumpPointSearch.hpp"
using namespace Slic3r;
TEST_CASE("Test jump point search path finding", "[JumpPointSearch]")
{
Lines obstacles{};
obstacles.push_back(Line(Point::new_scale(0, 0), Point::new_scale(50, 50)));
obstacles.push_back(Line(Point::new_scale(0, 100), Point::new_scale(50, 50)));
obstacles.push_back(Line(Point::new_scale(0, 0), Point::new_scale(100, 0)));
obstacles.push_back(Line(Point::new_scale(0, 100), Point::new_scale(100, 100)));
obstacles.push_back(Line(Point::new_scale(25, -25), Point::new_scale(25, 125)));
JPSPathFinder jps;
jps.add_obstacles(obstacles);
Polyline path = jps.find_path(Point::new_scale(5, 50), Point::new_scale(100, 50));
path = jps.find_path(Point::new_scale(5, 50), Point::new_scale(150, 50));
path = jps.find_path(Point::new_scale(5, 50), Point::new_scale(25, 15));
path = jps.find_path(Point::new_scale(25, 25), Point::new_scale(125, 125));
// SECTION("Output is empty when source is also the destination") {
// bool found = astar::search_route(DummyTracer{}, 0, std::back_inserter(out));
// REQUIRE(out.empty());
// REQUIRE(found);
// }
// SECTION("Return false when there is no route to destination") {
// bool found = astar::search_route(DummyTracer{}, 1, std::back_inserter(out));
// REQUIRE(!found);
// REQUIRE(out.empty());
// }
}

View File

@@ -0,0 +1,137 @@
#include <catch2/catch.hpp>
#include "libslic3r/KDTreeIndirect.hpp"
#include "libslic3r/Execution/ExecutionSeq.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/PointGrid.hpp"
using namespace Slic3r;
//template<class G>
//struct Within { // Wrapper for the `within` predicate that counts calls.
// kdtree::Within<G> pred;
// Within(G box): pred{box} {}
// // Number of times the predicate was called
// mutable size_t call_count = 0;
// std::pair<bool, unsigned int> operator() (const Vec3f &p, size_t dim)
// {
// ++call_count;
// return pred(p, dim);
// }
//};
static double volume(const BoundingBox3Base<Vec3f> &box)
{
auto sz = box.size();
return sz.x() * sz.y() * sz.z();
}
TEST_CASE("Test kdtree query for a Box", "[KDTreeIndirect]")
{
auto vol = BoundingBox3Base<Vec3f>{{0.f, 0.f, 0.f}, {10.f, 10.f, 10.f}};
auto pgrid = point_grid(ex_seq, vol, Vec3f{0.1f, 0.1f, 0.1f});
REQUIRE(!pgrid.empty());
auto coordfn = [&pgrid] (size_t i, size_t D) { return pgrid.get(i)(int(D)); };
KDTreeIndirect<3, float, decltype(coordfn)> tree{coordfn, pgrid.point_count()};
std::vector<size_t> out;
auto qbox = BoundingBox3Base{Vec3f{0.f, 0.f, 0.f}, Vec3f{.5f, .5f, .5f}};
size_t call_count = 0;
out = find_nearby_points(tree, qbox.min, qbox.max, [&call_count](size_t) {
call_count++;
return true;
});
// Output shall be non-empty
REQUIRE(!out.empty());
std::sort(out.begin(), out.end());
// No duplicates allowed in the output
auto it = std::unique(out.begin(), out.end());
REQUIRE(it == out.end());
// Test if inside points are in the output and outside points are not.
bool succ = true;
for (size_t i = 0; i < pgrid.point_count(); ++i) {
auto foundit = std::find(out.begin(), out.end(), i);
bool contains = qbox.contains(pgrid.get(i));
succ = succ && contains ? foundit != out.end() : foundit == out.end();
if (!succ) {
std::cout << "invalid point: " << i << " " << pgrid.get(i).transpose()
<< std::endl;
break;
}
}
REQUIRE(succ);
// Test for the expected cost of the query.
double gridvolume = volume(vol);
double queryvolume = volume(qbox);
double volratio = (queryvolume / gridvolume);
REQUIRE(call_count < 3 * volratio * pgrid.point_count());
REQUIRE(call_count < pgrid.point_count());
}
//TEST_CASE("Test kdtree query for a Sphere", "[KDTreeIndirect]") {
// auto vol = BoundingBox3Base<Vec3f>{{0.f, 0.f, 0.f}, {10.f, 10.f, 10.f}};
// auto pgrid = point_grid(ex_seq, vol, Vec3f{0.1f, 0.1f, 0.1f});
// REQUIRE(!pgrid.empty());
// auto coordfn = [&pgrid] (size_t i, size_t D) { return pgrid.get(i)(int(D)); };
// kdtree::KDTreeIndirect<3, float, decltype(coordfn)> tree{coordfn, pgrid.point_count()};
// std::vector<size_t> out;
// auto querysphere = kdtree::Sphere{Vec3f{5.f, 5.f, 5.f}, 2.f};
// auto pred = Within(querysphere);
// kdtree::query(tree, pred, std::back_inserter(out));
// // Output shall be non-empty
// REQUIRE(!out.empty());
// std::sort(out.begin(), out.end());
// // No duplicates allowed in the output
// auto it = std::unique(out.begin(), out.end());
// REQUIRE(it == out.end());
// // Test if inside points are in the output and outside points are not.
// bool succ = true;
// for (size_t i = 0; i < pgrid.point_count(); ++i) {
// auto foundit = std::find(out.begin(), out.end(), i);
// bool contains = (querysphere.center - pgrid.get(i)).squaredNorm() < pred.pred.r2;
// succ = succ && contains ? foundit != out.end() : foundit == out.end();
// if (!succ) {
// std::cout << "invalid point: " << i << " " << pgrid.get(i).transpose()
// << std::endl;
// break;
// }
// }
// REQUIRE(succ);
// // Test for the expected cost of the query.
// double gridvolume = volume(vol);
// double queryvolume = volume(querysphere);
// double volratio = (queryvolume / gridvolume);
// REQUIRE(pred.call_count < 3 * volratio * pgrid.point_count());
// REQUIRE(pred.call_count < pgrid.point_count());
//}

View File

@@ -0,0 +1,376 @@
#define NOMINMAX
#include <catch2/catch.hpp>
#include <test_utils.hpp>
#include <fstream>
#include <libslic3r/MarchingSquares.hpp>
#include <libslic3r/SLA/RasterToPolygons.hpp>
#include <libslic3r/SLA/AGGRaster.hpp>
#include <libslic3r/MTUtils.hpp>
#include <libslic3r/SVG.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/TriangleMeshSlicer.hpp>
#include <libslic3r/TriangulateWall.hpp>
#include <libslic3r/Tesselate.hpp>
#include <libslic3r/SlicesToTriangleMesh.hpp>
using namespace Slic3r;
static double area(const sla::PixelDim &pxd)
{
return pxd.w_mm * pxd.h_mm;
}
static Slic3r::sla::RasterGrayscaleAA create_raster(
const sla::Resolution &res,
double disp_w = 100.,
double disp_h = 100.)
{
sla::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px};
auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)});
sla::RasterBase::Trafo trafo;
trafo.center_x = bb.center().x();
trafo.center_y = bb.center().y();
return sla::RasterGrayscaleAA{res, pixdim, trafo, agg::gamma_threshold(.5)};
}
static ExPolygon square(double a, Point center = {0, 0})
{
ExPolygon poly;
coord_t V = scaled(a / 2.);
poly.contour.points = {{-V, -V}, {V, -V}, {V, V}, {-V, V}};
poly.translate(center.x(), center.y());
return poly;
}
static ExPolygon square_with_hole(double a, Point center = {0, 0})
{
ExPolygon poly = square(a);
poly.holes.emplace_back();
coord_t V = scaled(a / 4.);
poly.holes.front().points = {{-V, V}, {V, V}, {V, -V}, {-V, -V}};
poly.translate(center.x(), center.y());
return poly;
}
static ExPolygons circle_with_hole(double r, Point center = {0, 0}) {
ExPolygon poly;
std::vector<double> pis = linspace_vector(0., 2 * PI, 100);
coord_t rs = scaled(r);
for (double phi : pis) {
poly.contour.points.emplace_back(rs * std::cos(phi), rs * std::sin(phi));
}
poly.holes.emplace_back(poly.contour);
poly.holes.front().reverse();
for (auto &p : poly.holes.front().points) p /= 2;
poly.translate(center.x(), center.y());
return {poly};
}
static const Vec2i W4x4 = {4, 4};
static const Vec2i W2x2 = {2, 2};
template<class Rst>
static void test_expolys(Rst && rst,
const ExPolygons & ref,
Vec2i window,
const std::string &name = "test")
{
for (const ExPolygon &expoly : ref) rst.draw(expoly);
std::fstream out(name + ".png", std::ios::out);
out << rst.encode(sla::PNGRasterEncoder{});
out.close();
ExPolygons extracted = sla::raster_to_polygons(rst, window);
SVG svg(name + ".svg");
svg.draw(extracted);
svg.draw(ref, "green");
svg.Close();
double max_rel_err = 0.1;
sla::PixelDim pxd = rst.pixel_dimensions();
double max_abs_err = area(pxd) * scaled(1.) * scaled(1.);
BoundingBox ref_bb;
for (auto &expoly : ref) ref_bb.merge(expoly.contour.bounding_box());
double max_displacement = 4. * (std::pow(pxd.h_mm, 2) + std::pow(pxd.w_mm, 2));
max_displacement *= scaled<double>(1.) * scaled(1.);
REQUIRE(extracted.size() == ref.size());
for (size_t i = 0; i < ref.size(); ++i) {
REQUIRE(extracted[i].contour.is_counter_clockwise());
REQUIRE(extracted[i].holes.size() == ref[i].holes.size());
for (auto &h : extracted[i].holes) REQUIRE(h.is_clockwise());
double refa = ref[i].area();
double abs_err = std::abs(extracted[i].area() - refa);
double rel_err = abs_err / refa;
REQUIRE((rel_err <= max_rel_err || abs_err <= max_abs_err));
BoundingBox bb;
for (auto &expoly : extracted) bb.merge(expoly.contour.bounding_box());
Point d = bb.center() - ref_bb.center();
REQUIRE(double(d.transpose() * d) <= max_displacement);
}
}
TEST_CASE("Empty raster should result in empty polygons", "[MarchingSquares]") {
sla::RasterGrayscaleAAGammaPower rst{{}, {}, {}};
ExPolygons extracted = sla::raster_to_polygons(rst);
REQUIRE(extracted.size() == 0);
}
TEST_CASE("Marching squares directions", "[MarchingSquares]") {
marchsq::Coord crd{1, 1};
REQUIRE(step(crd, marchsq::__impl::Dir::left).r == 1);
REQUIRE(step(crd, marchsq::__impl::Dir::left).c == 0);
REQUIRE(step(crd, marchsq::__impl::Dir::down).r == 2);
REQUIRE(step(crd, marchsq::__impl::Dir::down).c == 1);
REQUIRE(step(crd, marchsq::__impl::Dir::right).r == 1);
REQUIRE(step(crd, marchsq::__impl::Dir::right).c == 2);
REQUIRE(step(crd, marchsq::__impl::Dir::up).r == 0);
REQUIRE(step(crd, marchsq::__impl::Dir::up).c == 1);
}
TEST_CASE("Fully covered raster should result in a rectangle", "[MarchingSquares]") {
auto rst = create_raster({4, 4}, 4., 4.);
ExPolygon rect = square(4);
SECTION("Full accuracy") {
test_expolys(rst, {rect}, W2x2, "fully_covered_full_acc");
}
SECTION("Half accuracy") {
test_expolys(rst, {rect}, W4x4, "fully_covered_half_acc");
}
}
TEST_CASE("4x4 raster with one ring", "[MarchingSquares]") {
sla::PixelDim pixdim{1, 1};
// We need one additional row and column to detect edges
sla::RasterGrayscaleAA rst{{4, 4}, pixdim, {}, agg::gamma_threshold(.5)};
// Draw a triangle from individual pixels
rst.draw(square(1., {0500000, 0500000}));
rst.draw(square(1., {1500000, 0500000}));
rst.draw(square(1., {2500000, 0500000}));
rst.draw(square(1., {1500000, 1500000}));
rst.draw(square(1., {2500000, 1500000}));
rst.draw(square(1., {2500000, 2500000}));
std::fstream out("4x4.png", std::ios::out);
out << rst.encode(sla::PNGRasterEncoder{});
out.close();
ExPolygons extracted = sla::raster_to_polygons(rst);
SVG svg("4x4.svg");
svg.draw(extracted);
svg.Close();
REQUIRE(extracted.size() == 1);
}
TEST_CASE("4x4 raster with two rings", "[MarchingSquares]") {
sla::PixelDim pixdim{1, 1};
// We need one additional row and column to detect edges
sla::RasterGrayscaleAA rst{{5, 5}, pixdim, {}, agg::gamma_threshold(.5)};
SECTION("Ambiguous case with 'ac' square") {
// Draw a triangle from individual pixels
rst.draw(square(1., {3500000, 2500000}));
rst.draw(square(1., {3500000, 3500000}));
rst.draw(square(1., {2500000, 3500000}));
rst.draw(square(1., {2500000, 1500000}));
rst.draw(square(1., {1500000, 1500000}));
rst.draw(square(1., {1500000, 2500000}));
std::fstream out("4x4_ac.png", std::ios::out);
out << rst.encode(sla::PNGRasterEncoder{});
out.close();
ExPolygons extracted = sla::raster_to_polygons(rst);
SVG svg("4x4_ac.svg");
svg.draw(extracted);
svg.Close();
REQUIRE(extracted.size() == 2);
}
SECTION("Ambiguous case with 'bd' square") {
// Draw a triangle from individual pixels
rst.draw(square(1., {3500000, 1500000}));
rst.draw(square(1., {3500000, 2500000}));
rst.draw(square(1., {2500000, 1500000}));
rst.draw(square(1., {1500000, 2500000}));
rst.draw(square(1., {1500000, 3500000}));
rst.draw(square(1., {2500000, 3500000}));
std::fstream out("4x4_bd.png", std::ios::out);
out << rst.encode(sla::PNGRasterEncoder{});
out.close();
ExPolygons extracted = sla::raster_to_polygons(rst);
SVG svg("4x4_bd.svg");
svg.draw(extracted);
svg.Close();
REQUIRE(extracted.size() == 2);
}
}
TEST_CASE("Square with hole in the middle", "[MarchingSquares]") {
using namespace Slic3r;
ExPolygons inp = {square_with_hole(50.)};
SECTION("Proportional raster, 1x1 mm pixel size, full accuracy") {
test_expolys(create_raster({100, 100}, 100., 100.), inp, W2x2, "square_with_hole_proportional_1x1_mm_px_full");
}
SECTION("Proportional raster, 1x1 mm pixel size, half accuracy") {
test_expolys(create_raster({100, 100}, 100., 100.), inp, W4x4, "square_with_hole_proportional_1x1_mm_px_half");
}
SECTION("Landscape raster, 1x1 mm pixel size, full accuracy") {
test_expolys(create_raster({150, 100}, 150., 100.), inp, W2x2, "square_with_hole_landsc_1x1_mm_px_full");
}
SECTION("Landscape raster, 1x1 mm pixel size, half accuracy") {
test_expolys(create_raster({150, 100}, 150., 100.), inp, W4x4, "square_with_hole_landsc_1x1_mm_px_half");
}
SECTION("Portrait raster, 1x1 mm pixel size, full accuracy") {
test_expolys(create_raster({100, 150}, 100., 150.), inp, W2x2, "square_with_hole_portrait_1x1_mm_px_full");
}
SECTION("Portrait raster, 1x1 mm pixel size, half accuracy") {
test_expolys(create_raster({100, 150}, 100., 150.), inp, W4x4, "square_with_hole_portrait_1x1_mm_px_half");
}
SECTION("Proportional raster, 2x2 mm pixel size, full accuracy") {
test_expolys(create_raster({200, 200}, 100., 100.), inp, W2x2, "square_with_hole_proportional_2x2_mm_px_full");
}
SECTION("Proportional raster, 2x2 mm pixel size, half accuracy") {
test_expolys(create_raster({200, 200}, 100., 100.), inp, W4x4, "square_with_hole_proportional_2x2_mm_px_half");
}
SECTION("Proportional raster, 0.5x0.5 mm pixel size, full accuracy") {
test_expolys(create_raster({50, 50}, 100., 100.), inp, W2x2, "square_with_hole_proportional_0.5x0.5_mm_px_full");
}
SECTION("Proportional raster, 0.5x0.5 mm pixel size, half accuracy") {
test_expolys(create_raster({50, 50}, 100., 100.), inp, W4x4, "square_with_hole_proportional_0.5x0.5_mm_px_half");
}
}
TEST_CASE("Circle with hole in the middle", "[MarchingSquares]") {
using namespace Slic3r;
test_expolys(create_raster({1000, 1000}), circle_with_hole(25.), W2x2, "circle_with_hole");
}
static void recreate_object_from_rasters(const std::string &objname, float lh) {
TriangleMesh mesh = load_model(objname);
auto bb = mesh.bounding_box();
Vec3f tr = -bb.center().cast<float>();
mesh.translate(tr.x(), tr.y(), tr.z());
bb = mesh.bounding_box();
std::vector<ExPolygons> layers = slice_mesh_ex(mesh.its, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh));
sla::Resolution res{2560, 1440};
double disp_w = 120.96;
double disp_h = 68.04;
//#ifndef NDEBUG
// size_t cntr = 0;
//#endif
for (ExPolygons &layer : layers) {
auto rst = create_raster(res, disp_w, disp_h);
for (ExPolygon &island : layer) {
rst.draw(island);
}
//#ifndef NDEBUG
// std::fstream out(objname + std::to_string(cntr) + ".png", std::ios::out);
// out << rst.encode(sla::PNGRasterEncoder{});
// out.close();
//#endif
ExPolygons layer_ = sla::raster_to_polygons(rst);
// float delta = scaled(std::min(rst.pixel_dimensions().h_mm,
// rst.pixel_dimensions().w_mm)) / 2;
// layer_ = expolygons_simplify(layer_, delta);
//#ifndef NDEBUG
// SVG svg(objname + std::to_string(cntr) + ".svg", BoundingBox(Point{0, 0}, Point{scaled(disp_w), scaled(disp_h)}));
// svg.draw(layer_);
// svg.draw(layer, "green");
// svg.Close();
//#endif
double layera = 0., layera_ = 0.;
for (auto &p : layer) layera += p.area();
for (auto &p : layer_) layera_ += p.area();
//#ifndef NDEBUG
// std::cout << cntr++ << std::endl;
//#endif
double diff = std::abs(layera_ - layera);
REQUIRE((diff <= 0.1 * layera || diff < scaled<double>(1.) * scaled<double>(1.)));
layer = std::move(layer_);
}
indexed_triangle_set out = slices_to_mesh(layers, bb.min.z(), double(lh), double(lh));
its_write_obj(out, "out_from_rasters.obj");
}
TEST_CASE("Recreate object from rasters", "[SL1Import]") {
recreate_object_from_rasters("frog_legs.obj", 0.05f);
}

View File

@@ -0,0 +1,51 @@
#include <catch2/catch.hpp>
#include <test_utils.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/MeshBoolean.hpp>
using namespace Slic3r;
TEST_CASE("CGAL and TriangleMesh conversions", "[MeshBoolean]") {
TriangleMesh sphere = make_sphere(1.);
auto cgalmesh_ptr = MeshBoolean::cgal::triangle_mesh_to_cgal(sphere);
REQUIRE(cgalmesh_ptr);
REQUIRE(! MeshBoolean::cgal::does_self_intersect(*cgalmesh_ptr));
TriangleMesh M = MeshBoolean::cgal::cgal_to_triangle_mesh(*cgalmesh_ptr);
REQUIRE(M.its.vertices.size() == sphere.its.vertices.size());
REQUIRE(M.its.indices.size() == sphere.its.indices.size());
REQUIRE(M.volume() == Approx(sphere.volume()));
REQUIRE(! MeshBoolean::cgal::does_self_intersect(M));
}
Vec3d calc_normal(const Vec3i &triangle, const std::vector<Vec3f> &vertices)
{
Vec3d v0 = vertices[triangle[0]].cast<double>();
Vec3d v1 = vertices[triangle[1]].cast<double>();
Vec3d v2 = vertices[triangle[2]].cast<double>();
// n = triangle normal
Vec3d n = (v1 - v0).cross(v2 - v0);
n.normalize();
return n;
}
TEST_CASE("Add TriangleMeshes", "[MeshBoolean]")
{
TriangleMesh tm1 = make_sphere(1.6, 1.6);
size_t init_size = tm1.its.indices.size();
Vec3f move(5, -3, 7);
move.normalize();
tm1.translate(0.3 * move);
//its_write_obj(tm1.its, "tm1.obj");
TriangleMesh tm2 = make_cube(1., 1., 1.);
//its_write_obj(tm2.its, "tm2.obj");
MeshBoolean::cgal::plus(tm1, tm2);
//its_write_obj(tm1.its, "test_add.obj");
CHECK(tm1.its.indices.size() > init_size);
}

View File

@@ -0,0 +1,177 @@
#include <catch2/catch.hpp>
#include "libslic3r/Point.hpp"
#include "libslic3r/MutablePolygon.hpp"
using namespace Slic3r;
SCENARIO("Iterators", "[MutablePolygon]") {
GIVEN("Polygon with three points") {
Slic3r::MutablePolygon p({ { 0, 0 }, { 0, 1 }, { 1, 0 } });
WHEN("Iterating upwards") {
auto begin = p.begin();
auto end = p.end();
auto it = begin;
THEN("++ it is not equal to begin") {
REQUIRE(++ it != begin);
} THEN("++ it is not equal to end") {
REQUIRE(++ it != end);
} THEN("++ (++ it) is not equal to begin") {
REQUIRE(++ (++ it) != begin);
} THEN("++ (++ it) is equal to end") {
REQUIRE(++ (++ it) == end);
} THEN("++ (++ (++ it)) is equal to begin") {
REQUIRE(++ (++ (++ it)) == begin);
} THEN("++ (++ (++ it)) is not equal to end") {
REQUIRE(++ (++ (++ it)) != end);
}
}
WHEN("Iterating downwards") {
auto begin = p.begin();
auto end = p.end();
auto it = begin;
THEN("-- it is not equal to begin") {
REQUIRE(-- it != begin);
} THEN("-- it is equal to end") {
REQUIRE(-- it == end);
} THEN("-- (-- it) is not equal to begin") {
REQUIRE(-- (-- it) != begin);
} THEN("-- (-- it) is not equal to end") {
REQUIRE(-- (-- it) != end);
} THEN("-- (-- (-- it)) is equal to begin") {
REQUIRE(-- (-- (-- it)) == begin);
} THEN("-- (-- (-- it)) is not equal to end") {
REQUIRE(-- (-- (-- it)) != end);
}
}
WHEN("Deleting 1st point") {
auto it_2nd = p.begin().next();
auto it = p.begin().remove();
THEN("Size is 2") {
REQUIRE(p.size() == 2);
} THEN("p.begin().remove() == it_2nd") {
REQUIRE(it == it_2nd);
} THEN("it_2nd == new begin()") {
REQUIRE(it_2nd == p.begin());
}
}
WHEN("Deleting 2nd point") {
auto it_1st = p.begin();
auto it_2nd = it_1st.next();
auto it = it_2nd.remove();
THEN("Size is 2") {
REQUIRE(p.size() == 2);
REQUIRE(! p.empty());
} THEN("it_2nd.remove() == it_3rd") {
REQUIRE(it == it_2nd);
} THEN("it_1st == new begin()") {
REQUIRE(it_1st == p.begin());
}
}
WHEN("Deleting two points") {
p.begin().remove().remove();
THEN("Size is 1") {
REQUIRE(p.size() == 1);
} THEN("p.begin().next() == p.begin()") {
REQUIRE(p.begin().next() == p.begin());
} THEN("p.begin().prev() == p.begin()") {
REQUIRE(p.begin().prev() == p.begin());
}
}
WHEN("Deleting all points") {
auto it = p.begin().remove().remove().remove();
THEN("Size is 0") {
REQUIRE(p.size() == 0);
REQUIRE(p.empty());
} THEN("! p.begin().valid()") {
REQUIRE(!p.begin().valid());
} THEN("last iterator not valid") {
REQUIRE(! it.valid());
}
}
WHEN("Inserting a point at the beginning") {
p.insert(p.begin(), { 3, 4 });
THEN("Polygon content is ok") {
REQUIRE(p == MutablePolygon{ { 0, 0 }, { 0, 1 }, { 1, 0 }, { 3, 4 } });
}
}
WHEN("Inserting a point at the 2nd position") {
p.insert(++ p.begin(), { 3, 4 });
THEN("Polygon content is ok") {
REQUIRE(p == MutablePolygon{ { 0, 0 }, { 3, 4 }, { 0, 1 }, { 1, 0 } });
}
} WHEN("Inserting a point after a point was removed") {
size_t capacity = p.capacity();
THEN("Initial capacity is 3") {
REQUIRE(capacity == 3);
}
p.begin().remove();
THEN("After removal of the 1st point the capacity is still 3") {
REQUIRE(p.capacity() == 3);
}
THEN("After removal of the 1st point the content is ok") {
REQUIRE(p == MutablePolygon{ { 0, 1 }, { 1, 0 } });
}
p.insert(p.begin(), { 5, 6 });
THEN("After insertion at head position the polygon content is ok") {
REQUIRE(p == MutablePolygon{ { 0, 1 }, { 1, 0 }, { 5, 6 } });
} THEN("and the capacity is still 3") {
REQUIRE(p.capacity() == 3);
}
}
}
}
SCENARIO("Remove degenerate points from MutablePolygon", "[MutablePolygon]") {
GIVEN("Polygon with duplicate points"){
Slic3r::MutablePolygon p({
{ 0, 0 },
{ 0, 100 }, { 0, 100 }, { 0, 100 },
{ 0, 150 },
{ 0, 200 },
{ 200, 200 },
{ 180, 200 }, { 180, 200 },
{ 180, 20 },
{ 180, 0 },
});
WHEN("Duplicate points are removed") {
remove_duplicates(p);
THEN("Polygon content is ok") {
REQUIRE(p == Slic3r::MutablePolygon{ { 0, 0 }, { 0, 100 }, { 0, 150 }, { 0, 200 }, { 200, 200 }, { 180, 200 }, { 180, 20 }, { 180, 0 } });
}
}
}
}
SCENARIO("smooth_outward", "[MutablePolygon]") {
GIVEN("Convex polygon") {
MutablePolygon p{ { 0, 0 }, { scaled<coord_t>(10.), 0 }, { 0, scaled<coord_t>(10.) } };
WHEN("smooth_outward") {
MutablePolygon p2{ p };
smooth_outward(p2, scaled<double>(10.));
THEN("Polygon is unmodified") {
REQUIRE(p == p2);
}
}
}
GIVEN("Sharp tiny concave polygon (hole)") {
MutablePolygon p{ { 0, 0 }, { 0, scaled<coord_t>(5.) }, { scaled<coord_t>(10.), 0 } };
WHEN("smooth_outward") {
MutablePolygon p2{ p };
smooth_outward(p2, scaled<double>(10.));
THEN("Hole is closed") {
REQUIRE(p2.empty());
}
}
}
GIVEN("Two polygons") {
Polygons p{ { { 0, 0 }, { scaled<coord_t>(10.), 0 }, { 0, scaled<coord_t>(10.) } },
{ { 0, 0 }, { 0, scaled<coord_t>(5.) }, { scaled<coord_t>(10.), 0 } } };
WHEN("smooth_outward") {
p = smooth_outward(p, scaled<double>(10.));
THEN("CCW contour unmodified, CW contour removed.") {
REQUIRE(p == Polygons{ { { 0, 0 }, { scaled<coord_t>(10.), 0 }, { 0, scaled<coord_t>(10.) } } });
}
}
}
}

View File

@@ -0,0 +1,461 @@
#include <catch2/catch.hpp>
#include <queue>
#include "libslic3r/MutablePriorityQueue.hpp"
using namespace Slic3r;
// based on https://raw.githubusercontent.com/rollbear/prio_queue/master/self_test.cpp
// original source Copyright Björn Fahller 2015, Boost Software License, Version 1.0, http://www.boost.org/LICENSE_1_0.txt
TEST_CASE("Skip addressing", "[MutableSkipHeapPriorityQueue]") {
using skip_addressing = SkipHeapAddressing<8>;
SECTION("block root") {
REQUIRE(skip_addressing::is_block_root(1));
REQUIRE(skip_addressing::is_block_root(9));
REQUIRE(skip_addressing::is_block_root(17));
REQUIRE(skip_addressing::is_block_root(73));
REQUIRE(! skip_addressing::is_block_root(2));
REQUIRE(! skip_addressing::is_block_root(3));
REQUIRE(! skip_addressing::is_block_root(4));
REQUIRE(! skip_addressing::is_block_root(7));
REQUIRE(! skip_addressing::is_block_root(31));
}
SECTION("block leaf") {
REQUIRE(! skip_addressing::is_block_leaf(1));
REQUIRE(! skip_addressing::is_block_leaf(2));
REQUIRE(! skip_addressing::is_block_leaf(3));
REQUIRE(skip_addressing::is_block_leaf(4));
REQUIRE(skip_addressing::is_block_leaf(5));
REQUIRE(skip_addressing::is_block_leaf(6));
REQUIRE(skip_addressing::is_block_leaf(7));
REQUIRE(skip_addressing::is_block_leaf(28));
REQUIRE(skip_addressing::is_block_leaf(29));
REQUIRE(skip_addressing::is_block_leaf(30));
REQUIRE(! skip_addressing::is_block_leaf(257));
REQUIRE(skip_addressing::is_block_leaf(255));
}
SECTION("Obtaining child") {
REQUIRE(skip_addressing::child_of(1) == 2);
REQUIRE(skip_addressing::child_of(2) == 4);
REQUIRE(skip_addressing::child_of(3) == 6);
REQUIRE(skip_addressing::child_of(4) == 9);
REQUIRE(skip_addressing::child_of(31) == 249);
}
SECTION("Obtaining parent") {
REQUIRE(skip_addressing::parent_of(2) == 1);
REQUIRE(skip_addressing::parent_of(3) == 1);
REQUIRE(skip_addressing::parent_of(6) == 3);
REQUIRE(skip_addressing::parent_of(7) == 3);
REQUIRE(skip_addressing::parent_of(9) == 4);
REQUIRE(skip_addressing::parent_of(17) == 4);
REQUIRE(skip_addressing::parent_of(33) == 5);
REQUIRE(skip_addressing::parent_of(29) == 26);
REQUIRE(skip_addressing::parent_of(1097) == 140);
}
}
struct ValueIndexPair
{
int value;
size_t idx = 0;
};
template<size_t block_size = 16>
static auto make_test_priority_queue()
{
return make_miniheap_mutable_priority_queue<ValueIndexPair, block_size, false>(
[](ValueIndexPair &v, size_t idx){ v.idx = idx; },
[](ValueIndexPair &l, ValueIndexPair &r){ return l.value < r.value; });
}
TEST_CASE("Mutable priority queue - basic tests", "[MutableSkipHeapPriorityQueue]") {
SECTION("a default constructed queue is empty") {
auto q = make_test_priority_queue();
REQUIRE(q.empty());
REQUIRE(q.size() == 0);
}
SECTION("an empty queue is not empty when one element is inserted") {
auto q = make_test_priority_queue();
q.push({ 1 });
REQUIRE(!q.empty());
REQUIRE(q.size() == 1);
}
SECTION("a queue with one element has it on top") {
auto q = make_test_priority_queue();
q.push({ 8 });
REQUIRE(q.top().value == 8);
}
SECTION("a queue with one element becomes empty when popped") {
auto q = make_test_priority_queue();
q.push({ 9 });
q.pop();
REQUIRE(q.empty());
REQUIRE(q.size() == 0);
}
SECTION("insert sorted stays sorted") {
auto q = make_test_priority_queue();
for (auto i : { 1, 2, 3, 4, 5, 6, 7, 8 })
q.push({ i });
REQUIRE(q.top().value == 1);
q.pop();
REQUIRE(q.top().value == 2);
q.pop();
REQUIRE(q.top().value == 3);
q.pop();
REQUIRE(q.top().value == 4);
q.pop();
REQUIRE(q.top().value == 5);
q.pop();
REQUIRE(q.top().value == 6);
q.pop();
REQUIRE(q.top().value == 7);
q.pop();
REQUIRE(q.top().value == 8);
q.pop();
REQUIRE(q.empty());
}
SECTION("randomly inserted elements are popped sorted") {
auto q = make_test_priority_queue();
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dist(1, 100000);
int n[36000];
for (auto& i : n) {
i = dist(gen);
q.push({ i });
}
REQUIRE(!q.empty());
REQUIRE(q.size() == 36000);
std::sort(std::begin(n), std::end(n));
for (auto i : n) {
REQUIRE(q.top().value == i);
q.pop();
}
REQUIRE(q.empty());
}
}
TEST_CASE("Mutable priority queue - reshedule first", "[MutableSkipHeapPriorityQueue]") {
struct MyValue {
int value;
int *ptr;
size_t idx;
};
SECTION("reschedule top with highest prio leaves order unchanged") {
auto q = make_miniheap_mutable_priority_queue<MyValue, 4, false>(
[](MyValue& v, size_t idx) { v.idx = idx; },
[](MyValue& l, MyValue& r) { return l.value < r.value; });
// 0 1 2 3 4 5 6 7 8
int nums[] = { 32, 1, 88, 16, 9, 11, 3, 22, 23 };
for (auto &i : nums)
q.push({ i, &i, 0U });
REQUIRE(q.top().value == 1);
REQUIRE(q.top().ptr == &nums[1]);
REQUIRE(*q.top().ptr == 1);
// Update the top element.
q.top().value = 2;
q.update(1);
REQUIRE(q.top().value == 2);
REQUIRE(q.top().ptr == &nums[1]);
q.pop();
REQUIRE(q.top().value == 3);
REQUIRE(q.top().ptr == &nums[6]);
q.pop();
REQUIRE(q.top().value == 9);
REQUIRE(q.top().ptr == &nums[4]);
q.pop();
REQUIRE(q.top().value == 11);
REQUIRE(q.top().ptr == &nums[5]);
q.pop();
REQUIRE(q.top().value == 16);
REQUIRE(q.top().ptr == &nums[3]);
q.pop();
REQUIRE(q.top().value == 22);
REQUIRE(q.top().ptr == &nums[7]);
q.pop();
REQUIRE(q.top().value == 23);
REQUIRE(q.top().ptr == &nums[8]);
q.pop();
REQUIRE(q.top().value == 32);
REQUIRE(q.top().ptr == &nums[0]);
q.pop();
REQUIRE(q.top().value == 88);
REQUIRE(q.top().ptr == &nums[2]);
q.pop();
REQUIRE(q.empty());
}
SECTION("reschedule to mid range moves element to correct place") {
auto q = make_miniheap_mutable_priority_queue<MyValue, 4, false>(
[](MyValue& v, size_t idx) { v.idx = idx; },
[](MyValue& l, MyValue& r) { return l.value < r.value; });
// 0 1 2 3 4 5 6 7 8
int nums[] = { 32, 1, 88, 16, 9, 11, 3, 22, 23 };
for (auto& i : nums)
q.push({ i, &i, 0U });
REQUIRE(q.top().value == 1);
REQUIRE(q.top().ptr == &nums[1]);
REQUIRE(*q.top().ptr == 1);
// Update the top element.
q.top().value = 12;
q.update(1);
REQUIRE(q.top().value == 3);
REQUIRE(q.top().ptr == &nums[6]);
q.pop();
REQUIRE(q.top().value == 9);
REQUIRE(q.top().ptr == &nums[4]);
q.pop();
REQUIRE(q.top().value == 11);
REQUIRE(q.top().ptr == &nums[5]);
q.pop();
REQUIRE(q.top().value == 12);
REQUIRE(q.top().ptr == &nums[1]);
q.pop();
REQUIRE(q.top().value == 16);
REQUIRE(q.top().ptr == &nums[3]);
q.pop();
REQUIRE(q.top().value == 22);
REQUIRE(q.top().ptr == &nums[7]);
q.pop();
REQUIRE(q.top().value == 23);
REQUIRE(q.top().ptr == &nums[8]);
q.pop();
REQUIRE(q.top().value == 32);
REQUIRE(q.top().ptr == &nums[0]);
q.pop();
REQUIRE(q.top().value == 88);
REQUIRE(q.top().ptr == &nums[2]);
q.pop();
REQUIRE(q.empty());
}
SECTION("reschedule to last moves element to correct place", "heap")
{
auto q = make_miniheap_mutable_priority_queue<MyValue, 4, false>(
[](MyValue& v, size_t idx) { v.idx = idx; },
[](MyValue& l, MyValue& r) { return l.value < r.value; });
// 0 1 2 3 4 5 6 7 8
int nums[] = { 32, 1, 88, 16, 9, 11, 3, 22, 23 };
for (auto& i : nums)
q.push({ i, &i, 0U });
REQUIRE(q.top().value == 1);
REQUIRE(q.top().ptr == &nums[1]);
REQUIRE(*q.top().ptr == 1);
// Update the top element.
q.top().value = 89;
q.update(1);
REQUIRE(q.top().value == 3);
REQUIRE(q.top().ptr == &nums[6]);
q.pop();
REQUIRE(q.top().value == 9);
REQUIRE(q.top().ptr == &nums[4]);
q.pop();
REQUIRE(q.top().value == 11);
REQUIRE(q.top().ptr == &nums[5]);
q.pop();
REQUIRE(q.top().value == 16);
REQUIRE(q.top().ptr == &nums[3]);
q.pop();
REQUIRE(q.top().value == 22);
REQUIRE(q.top().ptr == &nums[7]);
q.pop();
REQUIRE(q.top().value == 23);
REQUIRE(q.top().ptr == &nums[8]);
q.pop();
REQUIRE(q.top().value == 32);
REQUIRE(q.top().ptr == &nums[0]);
q.pop();
REQUIRE(q.top().value == 88);
REQUIRE(q.top().ptr == &nums[2]);
q.pop();
REQUIRE(q.top().value == 89);
REQUIRE(q.top().ptr == &nums[1]);
q.pop();
REQUIRE(q.empty());
}
SECTION("reschedule top of 2 elements to last") {
auto q = make_test_priority_queue<8>();
q.push({ 1 });
q.push({ 2 });
REQUIRE(q.top().value == 1);
// Update the top element.
q.top().value = 3;
q.update(1);
REQUIRE(q.top().value == 2);
}
SECTION("reschedule top of 3 elements left to 2nd") {
auto q = make_test_priority_queue<8>();
q.push({ 1 });
q.push({ 2 });
q.push({ 4 });
REQUIRE(q.top().value == 1);
// Update the top element.
q.top().value = 3;
q.update(1);
REQUIRE(q.top().value == 2);
}
SECTION("reschedule top of 3 elements right to 2nd") {
auto q = make_test_priority_queue<8>();
q.push({ 1 });
q.push({ 4 });
q.push({ 2 });
REQUIRE(q.top().value == 1);
// Update the top element.
q.top().value = 3;
q.update(1);
REQUIRE(q.top().value == 2);
}
SECTION("reschedule top random gives same resultas pop/push") {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<unsigned> dist(1, 100000);
auto pq = make_test_priority_queue<8>();
std::priority_queue<int, std::vector<int>, std::greater<>> stdq;
for (size_t outer = 0; outer < 100; ++ outer) {
int num = gen();
pq.push({ num });
stdq.push({ num });
for (size_t inner = 0; inner < 100; ++ inner) {
int newval = gen();
// Update the top element.
pq.top().value = newval;
pq.update(1);
stdq.pop();
stdq.push({ newval });
auto n = pq.top().value;
auto sn = stdq.top();
REQUIRE(sn == n);
}
}
}
}
TEST_CASE("Mutable priority queue - first pop", "[MutableSkipHeapPriorityQueue]")
{
struct MyValue{
size_t id;
float val;
};
static constexpr const size_t count = 50000;
std::vector<size_t> idxs(count, {0});
auto q = make_miniheap_mutable_priority_queue<MyValue, 16, true>(
[&idxs](MyValue &v, size_t idx) { idxs[v.id] = idx; },
[](MyValue &l, MyValue &r) { return l.val < r.val; });
using QueueType = decltype(q);
THEN("Skip queue has 0th element unused, 1st element is the top of the queue.") {
CHECK(QueueType::address::is_padding(0));
CHECK(!QueueType::address::is_padding(1));
}
q.reserve(count);
for (size_t id = 0; id < count; ++ id)
q.push({ id, rand() / 100.f });
MyValue v = q.top(); // copy
THEN("Element at the top of the queue has a valid ID.") {
CHECK(v.id >= 0);
CHECK(v.id < count);
}
THEN("Element at the top of the queue has its position stored in idxs.") {
CHECK(idxs[v.id] == 1);
}
q.pop();
THEN("Element removed from the queue has its position in idxs reset to invalid.") {
CHECK(idxs[v.id] == q.invalid_id());
}
THEN("Element was removed from the queue, new top of the queue has its index set correctly.") {
CHECK(q.top().id >= 0);
CHECK(q.top().id < count);
CHECK(idxs[q.top().id] == 1);
}
}
TEST_CASE("Mutable priority queue complex", "[MutableSkipHeapPriorityQueue]")
{
struct MyValue {
size_t id;
float val;
};
size_t count = 5000;
std::vector<size_t> idxs(count, {0});
std::vector<bool> dels(count, false);
auto q = make_miniheap_mutable_priority_queue<MyValue, 16, true>(
[&](MyValue &v, size_t idx) { idxs[v.id] = idx; },
[](MyValue &l, MyValue &r) { return l.val < r.val; });
q.reserve(count);
auto rand_val = [&]()->float { return (rand() % 53) / 10.f; };
for (size_t id = 0; id < count; ++ id)
q.push({ id, rand_val() });
auto check = [&]()->bool{
for (size_t i = 0; i < idxs.size(); ++i) {
if (dels[i]) {
if (idxs[i] != q.invalid_id())
return false; // ERROR
} else {
size_t qid = idxs[i];
if (qid >= q.heap_size()) {
return false; // ERROR
}
MyValue &mv = q[qid];
if (mv.id != i) {
return false; // ERROR
}
}
}
return true;
};
CHECK(check()); // initial check
// Generate an element ID of an elmenet, which was not yet deleted, thus it is still valid.
auto get_valid_id = [&]()->int {
int id = 0;
do {
id = rand() % count;
} while (dels[id]);
return id;
};
// Remove first 100 elements from the queue of 5000 elements, cross-validate indices.
// Re-enter every 20th element back to the queue.
for (size_t i = 0; i < 100; i++) {
MyValue v = q.top(); // copy
q.pop();
dels[v.id] = true;
CHECK(check());
if (i % 20 == 0) {
v.val = rand_val();
q.push(v);
dels[v.id] = false;
CHECK(check());
continue;
}
// Remove some valid element from the queue.
int id = get_valid_id();
CHECK(idxs[id] != q.invalid_id());
q.remove(idxs[id]);
dels[id] = true;
CHECK(check());
// and change 5 random elements and reorder them in the queue.
for (size_t j = 0; j < 5; j++) {
int id = get_valid_id();
size_t qid = idxs[id];
MyValue &mv = q[qid];
mv.val = rand_val();
q.update(qid);
CHECK(check());
}
}
}

View File

@@ -0,0 +1,59 @@
#include <catch2/catch.hpp>
#include <test_utils.hpp>
#include <libslic3r/Optimize/BruteforceOptimizer.hpp>
#include <libslic3r/Optimize/NLoptOptimizer.hpp>
void check_opt_result(double score, double ref, double abs_err, double rel_err)
{
double abs_diff = std::abs(score - ref);
double rel_diff = std::abs(abs_diff / std::abs(ref));
bool abs_reached = abs_diff < abs_err;
bool rel_reached = rel_diff < rel_err;
bool precision_reached = abs_reached || rel_reached;
REQUIRE(precision_reached);
}
template<class Opt> void test_sin(Opt &&opt)
{
using namespace Slic3r::opt;
auto optfunc = [](const auto &in) {
auto [phi] = in;
return std::sin(phi);
};
auto init = initvals({PI});
auto optbounds = bounds({ {0., 2 * PI}});
Result result_min = opt.to_min().optimize(optfunc, init, optbounds);
Result result_max = opt.to_max().optimize(optfunc, init, optbounds);
check_opt_result(result_min.score, -1., 1e-2, 1e-4);
check_opt_result(result_max.score, 1., 1e-2, 1e-4);
}
template<class Opt> void test_sphere_func(Opt &&opt)
{
using namespace Slic3r::opt;
Result result = opt.to_min().optimize([](const auto &in) {
auto [x, y] = in;
return x * x + y * y + 1.;
}, initvals({.6, -0.2}), bounds({{-1., 1.}, {-1., 1.}}));
check_opt_result(result.score, 1., 1e-2, 1e-4);
}
TEST_CASE("Test brute force optimzer for basic 1D and 2D functions", "[Opt]") {
using namespace Slic3r::opt;
Optimizer<AlgBruteForce> opt;
test_sin(opt);
test_sphere_func(opt);
}

View File

@@ -0,0 +1,306 @@
#include <catch2/catch.hpp>
#include "libslic3r/PlaceholderParser.hpp"
#include "libslic3r/PrintConfig.hpp"
using namespace Slic3r;
SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
PlaceholderParser parser;
auto config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict( {
{ "printer_notes", " PRINTER_VENDOR_PRUSA3D PRINTER_MODEL_MK2 " },
{ "nozzle_diameter", "0.6;0.6;0.6;0.6" },
{ "temperature", "357;359;363;378" }
});
// To test the "first_layer_extrusion_width" over "first_layer_heigth".
// "first_layer_heigth" over "layer_height" is no more supported after first_layer_height was moved from PrintObjectConfig to PrintConfig.
// config.option<ConfigOptionFloatOrPercent>("first_layer_height")->value = 150.;
// config.option<ConfigOptionFloatOrPercent>("first_layer_height")->percent = true;
config.option<ConfigOptionFloatOrPercent>("first_layer_height")->value = 1.5 * config.opt_float("layer_height");
config.option<ConfigOptionFloatOrPercent>("first_layer_height")->percent = false;
// To let the PlaceholderParser throw when referencing first_layer_speed if it is set to percent, as the PlaceholderParser does not know
// a percent to what.
config.option<ConfigOptionFloatOrPercent>("first_layer_speed")->value = 50.;
config.option<ConfigOptionFloatOrPercent>("first_layer_speed")->percent = true;
ConfigOptionFloatsNullable *opt_filament_retract_length = config.option<ConfigOptionFloatsNullable>("filament_retract_length", true);
opt_filament_retract_length->values = { 5., ConfigOptionFloatsNullable::nil_value(), 3. };
parser.apply_config(config);
parser.set("foo", 0);
parser.set("bar", 2);
parser.set("num_extruders", 4);
parser.set("gcode_flavor", "marlin");
SECTION("nested config options (legacy syntax)") { REQUIRE(parser.process("[temperature_[foo]]") == "357"); }
SECTION("array reference") { REQUIRE(parser.process("{temperature[foo]}") == "357"); }
SECTION("whitespaces and newlines are maintained") { REQUIRE(parser.process("test [ temperature_ [foo] ] \n hu") == "test 357 \n hu"); }
SECTION("nullable is not null") { REQUIRE(parser.process("{is_nil(filament_retract_length[0])}") == "false"); }
SECTION("nullable is null") { REQUIRE(parser.process("{is_nil(filament_retract_length[1])}") == "true"); }
SECTION("nullable is not null 2") { REQUIRE(parser.process("{is_nil(filament_retract_length[2])}") == "false"); }
SECTION("multiple expressions") { REQUIRE(parser.process("{temperature[foo];temperature[foo]}") == "357357"); }
SECTION("multiple expressions with semicolons") { REQUIRE(parser.process("{temperature[foo];;;temperature[foo];}") == "357357"); }
SECTION("multiple expressions with semicolons 2") { REQUIRE(parser.process("{temperature[foo];;temperature[foo];}") == "357357"); }
SECTION("multiple expressions with semicolons 3") { REQUIRE(parser.process("{temperature[foo];;;temperature[foo];;}") == "357357"); }
SECTION("parsing string with escaped characters") { REQUIRE(parser.process("{\"hu\\nha\\\\\\\"ha\\\"\"}") == "hu\nha\\\"ha\""); }
WHEN("An UTF-8 character is used inside the code block") {
THEN("A std::runtime_error exception is thrown.") {
// full-width plus sign instead of plain +
REQUIRE_THROWS_AS(parser.process("{1\xEF\xBC\x8B 3}"), std::runtime_error);
}
}
WHEN("An UTF-8 character is used inside a string") {
THEN("UTF-8 sequence is processed correctly when quoted") {
// japanese "cool" or "stylish"
REQUIRE(parser.process("{1+\"\xE3\x81\x8B\xE3\x81\xA3\xE3\x81\x93\xE3\x81\x84\xE3\x81\x84\"+\" \"+3}") == "1\xE3\x81\x8B\xE3\x81\xA3\xE3\x81\x93\xE3\x81\x84\xE3\x81\x84 3");
}
}
WHEN("An UTF-8 character is used inside a string") {
THEN("UTF-8 sequence is processed correctly outside of code blocks") {
// japanese "cool" or "stylish"
REQUIRE(parser.process("{1+3}\xE3\x81\x8B\xE3\x81\xA3\xE3\x81\x93\xE3\x81\x84\xE3\x81\x84") == "4\xE3\x81\x8B\xE3\x81\xA3\xE3\x81\x93\xE3\x81\x84\xE3\x81\x84");
}
}
// Test the math expressions.
SECTION("math: 2*3") { REQUIRE(parser.process("{2*3}") == "6"); }
SECTION("math: 2*3/6") { REQUIRE(parser.process("{2*3/6}") == "1"); }
SECTION("math: 2*3/12") { REQUIRE(parser.process("{2*3/12}") == "0"); }
SECTION("math: 2.*3/12") { REQUIRE(std::stod(parser.process("{2.*3/12}")) == Approx(0.5)); }
SECTION("math: 10 % 2.5") { REQUIRE(std::stod(parser.process("{10%2.5}")) == Approx(0.)); }
SECTION("math: 11 % 2.5") { REQUIRE(std::stod(parser.process("{11%2.5}")) == Approx(1.)); }
SECTION("math: 2*(3-12)") { REQUIRE(parser.process("{2*(3-12)}") == "-18"); }
SECTION("math: 2*foo*(3-12)") { REQUIRE(parser.process("{2*foo*(3-12)}") == "0"); }
SECTION("math: 2*bar*(3-12)") { REQUIRE(parser.process("{2*bar*(3-12)}") == "-36"); }
SECTION("math: 2.5*bar*(3-12)") { REQUIRE(std::stod(parser.process("{2.5*bar*(3-12)}")) == Approx(-45)); }
SECTION("math: min(12, 14)") { REQUIRE(parser.process("{min(12, 14)}") == "12"); }
SECTION("math: max(12, 14)") { REQUIRE(parser.process("{max(12, 14)}") == "14"); }
SECTION("math: min(13.4, -1238.1)") { REQUIRE(std::stod(parser.process("{min(13.4, -1238.1)}")) == Approx(-1238.1)); }
SECTION("math: max(13.4, -1238.1)") { REQUIRE(std::stod(parser.process("{max(13.4, -1238.1)}")) == Approx(13.4)); }
SECTION("math: int(13.4)") { REQUIRE(parser.process("{int(13.4)}") == "13"); }
SECTION("math: int(-13.4)") { REQUIRE(parser.process("{int(-13.4)}") == "-13"); }
SECTION("math: round(13.4)") { REQUIRE(parser.process("{round(13.4)}") == "13"); }
SECTION("math: round(-13.4)") { REQUIRE(parser.process("{round(-13.4)}") == "-13"); }
SECTION("math: round(13.6)") { REQUIRE(parser.process("{round(13.6)}") == "14"); }
SECTION("math: round(-13.6)") { REQUIRE(parser.process("{round(-13.6)}") == "-14"); }
SECTION("math: digits(5, 15)") { REQUIRE(parser.process("{digits(5, 15)}") == " 5"); }
SECTION("math: digits(5., 15)") { REQUIRE(parser.process("{digits(5., 15)}") == " 5"); }
SECTION("math: zdigits(5, 15)") { REQUIRE(parser.process("{zdigits(5, 15)}") == "000000000000005"); }
SECTION("math: zdigits(5., 15)") { REQUIRE(parser.process("{zdigits(5., 15)}") == "000000000000005"); }
SECTION("math: digits(5, 15, 8)") { REQUIRE(parser.process("{digits(5, 15, 8)}") == " 5.00000000"); }
SECTION("math: digits(5., 15, 8)") { REQUIRE(parser.process("{digits(5, 15, 8)}") == " 5.00000000"); }
SECTION("math: zdigits(5, 15, 8)") { REQUIRE(parser.process("{zdigits(5, 15, 8)}") == "000005.00000000"); }
SECTION("math: zdigits(5., 15, 8)") { REQUIRE(parser.process("{zdigits(5, 15, 8)}") == "000005.00000000"); }
SECTION("math: digits(13.84375892476, 15, 8)") { REQUIRE(parser.process("{digits(13.84375892476, 15, 8)}") == " 13.84375892"); }
SECTION("math: zdigits(13.84375892476, 15, 8)") { REQUIRE(parser.process("{zdigits(13.84375892476, 15, 8)}") == "000013.84375892"); }
SECTION("math: ternary1") { REQUIRE(parser.process("{12 == 12 ? 1 - 3 : 2 * 2 * unknown_symbol}") == "-2"); }
SECTION("math: ternary2") { REQUIRE(parser.process("{12 == 21/2 ? 1 - 1 - unknown_symbol : 2 * 2}") == "4"); }
SECTION("math: ternary3") { REQUIRE(parser.process("{12 == 13 ? 1 - 1 * unknown_symbol : 2 * 2}") == "4"); }
SECTION("math: ternary4") { REQUIRE(parser.process("{12 == 2 * 6 ? 1 - 1 : 2 * unknown_symbol}") == "0"); }
SECTION("math: ternary nested") { REQUIRE(parser.process("{12 == 2 * 6 ? 3 - 1 != 2 ? does_not_exist : 0 * 0 - 0 / 1 + 12345 : bull ? 3 - cokoo : 2 * unknown_symbol}") == "12345"); }
SECTION("math: interpolate_table(13.84375892476, (0, 0), (20, 20))") { REQUIRE(std::stod(parser.process("{interpolate_table(13.84375892476, (0, 0), (20, 20))}")) == Approx(13.84375892476)); }
SECTION("math: interpolate_table(13, (0, 0), (20, 20), (30, 20))") { REQUIRE(std::stod(parser.process("{interpolate_table(13, (0, 0), (20, 20), (30, 20))}")) == Approx(13.)); }
SECTION("math: interpolate_table(25, (0, 0), (20, 20), (30, 20))") { REQUIRE(std::stod(parser.process("{interpolate_table(25, (0, 0), (20, 20), (30, 20))}")) == Approx(20.)); }
// Test the "coFloatOrPercent" and "xxx_extrusion_width" substitutions.
// first_layer_extrusion_width ratio_over first_layer_heigth.
SECTION("perimeter_extrusion_width") { REQUIRE(std::stod(parser.process("{perimeter_extrusion_width}")) == Approx(0.67500001192092896)); }
SECTION("first_layer_extrusion_width") { REQUIRE(std::stod(parser.process("{first_layer_extrusion_width}")) == Approx(0.9)); }
SECTION("support_material_xy_spacing") { REQUIRE(std::stod(parser.process("{support_material_xy_spacing}")) == Approx(0.3375)); }
// external_perimeter_speed over perimeter_speed
SECTION("external_perimeter_speed") { REQUIRE(std::stod(parser.process("{external_perimeter_speed}")) == Approx(30.)); }
// infill_overlap over perimeter_extrusion_width
SECTION("infill_overlap") { REQUIRE(std::stod(parser.process("{infill_overlap}")) == Approx(0.16875)); }
// If first_layer_speed is set to percent, then it is applied over respective extrusion types by overriding their respective speeds.
// The PlaceholderParser has no way to know which extrusion type the caller has in mind, therefore it throws.
SECTION("first_layer_speed") { REQUIRE_THROWS(parser.process("{first_layer_speed}")); }
// Test the boolean expression parser.
auto boolean_expression = [&parser](const std::string& templ) { return parser.evaluate_boolean_expression(templ, parser.config()); };
SECTION("boolean expression parser: 12 == 12") { REQUIRE(boolean_expression("12 == 12")); }
SECTION("boolean expression parser: 12 != 12") { REQUIRE(! boolean_expression("12 != 12")); }
SECTION("boolean expression parser: regex matches") { REQUIRE(boolean_expression("\"has some PATTERN embedded\" =~ /.*PATTERN.*/")); }
SECTION("boolean expression parser: regex does not match") { REQUIRE(! boolean_expression("\"has some PATTERN embedded\" =~ /.*PTRN.*/")); }
SECTION("boolean expression parser: accessing variables, equal") { REQUIRE(boolean_expression("foo + 2 == bar")); }
SECTION("boolean expression parser: accessing variables, not equal") { REQUIRE(! boolean_expression("foo + 3 == bar")); }
SECTION("boolean expression parser: (12 == 12) and (13 != 14)") { REQUIRE(boolean_expression("(12 == 12) and (13 != 14)")); }
SECTION("boolean expression parser: (12 == 12) && (13 != 14)") { REQUIRE(boolean_expression("(12 == 12) && (13 != 14)")); }
SECTION("boolean expression parser: (12 == 12) or (13 == 14)") { REQUIRE(boolean_expression("(12 == 12) or (13 == 14)")); }
SECTION("boolean expression parser: (12 == 12) || (13 == 14)") { REQUIRE(boolean_expression("(12 == 12) || (13 == 14)")); }
SECTION("boolean expression parser: (12 == 12) and not (13 == 14)") { REQUIRE(boolean_expression("(12 == 12) and not (13 == 14)")); }
SECTION("boolean expression parser: ternary true") { REQUIRE(boolean_expression("(12 == 12) ? (1 - 1 == 0) : (2 * 2 == 3 * unknown_symbol)")); }
SECTION("boolean expression parser: ternary false") { REQUIRE(! boolean_expression("(12 == 21/2) ? (1 - 1 == 0 - unknown_symbol) : (2 * 2 == 3)")); }
SECTION("boolean expression parser: ternary false 2") { REQUIRE(boolean_expression("(12 == 13) ? (1 - 1 == 3 * unknown_symbol) : (2 * 2 == 4)")); }
SECTION("boolean expression parser: ternary true 2") { REQUIRE(! boolean_expression("(12 == 2 * 6) ? (1 - 1 == 3) : (2 * 2 == 4 * unknown_symbol)")); }
SECTION("boolean expression parser: lower than - false") { REQUIRE(! boolean_expression("12 < 3")); }
SECTION("boolean expression parser: lower than - true") { REQUIRE(boolean_expression("12 < 22")); }
SECTION("boolean expression parser: greater than - true") { REQUIRE(boolean_expression("12 > 3")); }
SECTION("boolean expression parser: greater than - false") { REQUIRE(! boolean_expression("12 > 22")); }
SECTION("boolean expression parser: lower than or equal- false") { REQUIRE(! boolean_expression("12 <= 3")); }
SECTION("boolean expression parser: lower than or equal - true") { REQUIRE(boolean_expression("12 <= 22")); }
SECTION("boolean expression parser: greater than or equal - true") { REQUIRE(boolean_expression("12 >= 3")); }
SECTION("boolean expression parser: greater than or equal - false") { REQUIRE(! boolean_expression("12 >= 22")); }
SECTION("boolean expression parser: lower than or equal (same values) - true") { REQUIRE(boolean_expression("12 <= 12")); }
SECTION("boolean expression parser: greater than or equal (same values) - true") { REQUIRE(boolean_expression("12 >= 12")); }
SECTION("boolean expression parser: one_of(\"a\", \"a\", \"b\", \"c\")") { REQUIRE(boolean_expression("one_of(\"a\", \"a\", \"b\", \"c\")")); }
SECTION("boolean expression parser: one_of(\"b\", \"a\", \"b\", \"c\")") { REQUIRE(boolean_expression("one_of(\"b\", \"a\", \"b\", \"c\")")); }
SECTION("boolean expression parser: one_of(\"c\", \"a\", \"b\", \"c\")") { REQUIRE(boolean_expression("one_of(\"c\", \"a\", \"b\", \"c\")")); }
SECTION("boolean expression parser: one_of(\"d\", \"a\", \"b\", \"c\")") { REQUIRE(! boolean_expression("one_of(\"d\", \"a\", \"b\", \"c\")")); }
SECTION("boolean expression parser: one_of(\"a\")") { REQUIRE(! boolean_expression("one_of(\"a\")")); }
SECTION("boolean expression parser: one_of(\"a\", \"a\")") { REQUIRE(boolean_expression("one_of(\"a\", \"a\")")); }
SECTION("boolean expression parser: one_of(\"b\", \"a\")") { REQUIRE(! boolean_expression("one_of(\"b\", \"a\")")); }
SECTION("boolean expression parser: one_of(\"abcdef\", /.*c.*/)") { REQUIRE(boolean_expression("one_of(\"abcdef\", /.*c.*/)")); }
SECTION("boolean expression parser: one_of(\"abcdef\", /.*f.*/, /.*c.*/)") { REQUIRE(boolean_expression("one_of(\"abcdef\", /.*f.*/, /.*c.*/)")); }
SECTION("boolean expression parser: one_of(\"abcdef\", ~\".*f.*\", ~\".*c.*\")") { REQUIRE(boolean_expression("one_of(\"abcdef\", ~\".*f.*\", ~\".*c.*\")")); }
SECTION("boolean expression parser: one_of(\"ghij\", /.*f.*/, /.*c.*/)") { REQUIRE(! boolean_expression("one_of(\"ghij\", /.*f.*/, /.*c.*/)")); }
SECTION("boolean expression parser: one_of(\"ghij\", ~\".*f.*\", ~\".*c.*\")") { REQUIRE(! boolean_expression("one_of(\"ghij\", ~\".*f.*\", ~\".*c.*\")")); }
SECTION("complex expression") { REQUIRE(boolean_expression("printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1")); }
SECTION("complex expression2") { REQUIRE(boolean_expression("printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.6 and num_extruders>1)")); }
SECTION("complex expression3") { REQUIRE(! boolean_expression("printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.3 and num_extruders>1)")); }
SECTION("enum expression") { REQUIRE(boolean_expression("gcode_flavor == \"marlin\"")); }
SECTION("write to a scalar variable") {
DynamicConfig config_outputs;
config_outputs.set_key_value("writable_string", new ConfigOptionString());
parser.process("{writable_string = \"Written\"}", 0, nullptr, &config_outputs, nullptr);
REQUIRE(parser.process("{writable_string}", 0, nullptr, &config_outputs, nullptr) == "Written");
}
SECTION("write to a vector variable") {
DynamicConfig config_outputs;
config_outputs.set_key_value("writable_floats", new ConfigOptionFloats({ 0., 0., 0. }));
parser.process("{writable_floats[1] = 33}", 0, nullptr, &config_outputs, nullptr);
REQUIRE(config_outputs.opt_float("writable_floats", 1) == Approx(33.));
}
}
SCENARIO("Placeholder parser variables", "[PlaceholderParser]") {
PlaceholderParser parser;
auto config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({
{ "filament_notes", "testnotes" },
{ "enable_dynamic_fan_speeds", "1" },
{ "nozzle_diameter", "0.6;0.6;0.6;0.6" },
{ "temperature", "357;359;363;378" }
});
PlaceholderParser::ContextData context_with_global_dict;
context_with_global_dict.global_config = std::make_unique<DynamicConfig>();
SECTION("create an int local variable") { REQUIRE(parser.process("{local myint = 33+2}{myint}", 0, nullptr, nullptr, nullptr) == "35"); }
SECTION("create a string local variable") { REQUIRE(parser.process("{local mystr = \"mine\" + \"only\" + \"mine\"}{mystr}", 0, nullptr, nullptr, nullptr) == "mineonlymine"); }
SECTION("create a bool local variable") { REQUIRE(parser.process("{local mybool = 1 + 1 == 2}{mybool}", 0, nullptr, nullptr, nullptr) == "true"); }
SECTION("create an int global variable") { REQUIRE(parser.process("{global myint = 33+2}{myint}", 0, nullptr, nullptr, &context_with_global_dict) == "35"); }
SECTION("create a string global variable") { REQUIRE(parser.process("{global mystr = \"mine\" + \"only\" + \"mine\"}{mystr}", 0, nullptr, nullptr, &context_with_global_dict) == "mineonlymine"); }
SECTION("create a bool global variable") { REQUIRE(parser.process("{global mybool = 1 + 1 == 2}{mybool}", 0, nullptr, nullptr, &context_with_global_dict) == "true"); }
SECTION("create an int local variable and overwrite it") { REQUIRE(parser.process("{local myint = 33+2}{myint = 12}{myint}", 0, nullptr, nullptr, nullptr) == "12"); }
SECTION("create a string local variable and overwrite it") { REQUIRE(parser.process("{local mystr = \"mine\" + \"only\" + \"mine\"}{mystr = \"yours\"}{mystr}", 0, nullptr, nullptr, nullptr) == "yours"); }
SECTION("create a bool local variable and overwrite it") { REQUIRE(parser.process("{local mybool = 1 + 1 == 2}{mybool = false}{mybool}", 0, nullptr, nullptr, nullptr) == "false"); }
SECTION("create an int global variable and overwrite it") { REQUIRE(parser.process("{global myint = 33+2}{myint = 12}{myint}", 0, nullptr, nullptr, &context_with_global_dict) == "12"); }
SECTION("create a string global variable and overwrite it") { REQUIRE(parser.process("{global mystr = \"mine\" + \"only\" + \"mine\"}{mystr = \"yours\"}{mystr}", 0, nullptr, nullptr, &context_with_global_dict) == "yours"); }
SECTION("create a bool global variable and overwrite it") { REQUIRE(parser.process("{global mybool = 1 + 1 == 2}{mybool = false}{mybool}", 0, nullptr, nullptr, &context_with_global_dict) == "false"); }
SECTION("create an int local variable and redefine it") { REQUIRE(parser.process("{local myint = 33+2}{local myint = 12}{myint}", 0, nullptr, nullptr, nullptr) == "12"); }
SECTION("create a string local variable and redefine it") { REQUIRE(parser.process("{local mystr = \"mine\" + \"only\" + \"mine\"}{local mystr = \"yours\"}{mystr}", 0, nullptr, nullptr, nullptr) == "yours"); }
SECTION("create a bool local variable and redefine it") { REQUIRE(parser.process("{local mybool = 1 + 1 == 2}{local mybool = false}{mybool}", 0, nullptr, nullptr, nullptr) == "false"); }
SECTION("create an int global variable and redefine it") { REQUIRE(parser.process("{global myint = 33+2}{global myint = 12}{myint}", 0, nullptr, nullptr, &context_with_global_dict) == "12"); }
SECTION("create a string global variable and redefine it") { REQUIRE(parser.process("{global mystr = \"mine\" + \"only\" + \"mine\"}{global mystr = \"yours\"}{mystr}", 0, nullptr, nullptr, &context_with_global_dict) == "yours"); }
SECTION("create a bool global variable and redefine it") { REQUIRE(parser.process("{global mybool = 1 + 1 == 2}{global mybool = false}{mybool}", 0, nullptr, nullptr, &context_with_global_dict) == "false"); }
SECTION("create an ints local variable with repeat()") { REQUIRE(parser.process("{local myint = repeat(2*3, 4*6)}{myint[5]}", 0, nullptr, nullptr, nullptr) == "24"); }
SECTION("create a strings local variable with repeat()") { REQUIRE(parser.process("{local mystr = repeat(2*3, \"mine\" + \"only\" + \"mine\")}{mystr[5]}", 0, nullptr, nullptr, nullptr) == "mineonlymine"); }
SECTION("create a bools local variable with repeat()") { REQUIRE(parser.process("{local mybool = repeat(5, 1 + 1 == 2)}{mybool[4]}", 0, nullptr, nullptr, nullptr) == "true"); }
SECTION("create an ints global variable with repeat()") { REQUIRE(parser.process("{global myint = repeat(2*3, 4*6)}{myint[5]}", 0, nullptr, nullptr, &context_with_global_dict) == "24"); }
SECTION("create a strings global variable with repeat()") { REQUIRE(parser.process("{global mystr = repeat(2*3, \"mine\" + \"only\" + \"mine\")}{mystr[5]}", 0, nullptr, nullptr, &context_with_global_dict) == "mineonlymine"); }
SECTION("create a bools global variable with repeat()") { REQUIRE(parser.process("{global mybool = repeat(5, 1 + 1 == 2)}{mybool[4]}", 0, nullptr, nullptr, &context_with_global_dict) == "true"); }
SECTION("create an ints local variable with initializer list") { REQUIRE(parser.process("{local myint = (2*3, 4*6, 5*5)}{myint[1]}", 0, nullptr, nullptr, nullptr) == "24"); }
SECTION("create a strings local variable with initializer list") { REQUIRE(parser.process("{local mystr = (2*3, \"mine\" + \"only\" + \"mine\", 8)}{mystr[1]}", 0, nullptr, nullptr, nullptr) == "mineonlymine"); }
SECTION("create a bools local variable with initializer list") { REQUIRE(parser.process("{local mybool = (3*3 == 8, 1 + 1 == 2)}{mybool[1]}", 0, nullptr, nullptr, nullptr) == "true"); }
SECTION("create an ints global variable with initializer list") { REQUIRE(parser.process("{global myint = (2*3, 4*6, 5*5)}{myint[1]}", 0, nullptr, nullptr, &context_with_global_dict) == "24"); }
SECTION("create a strings global variable with initializer list") { REQUIRE(parser.process("{global mystr = (2*3, \"mine\" + \"only\" + \"mine\", 8)}{mystr[1]}", 0, nullptr, nullptr, &context_with_global_dict) == "mineonlymine"); }
SECTION("create a bools global variable with initializer list") { REQUIRE(parser.process("{global mybool = (2*3 == 8, 1 + 1 == 2, 5*5 != 33)}{mybool[1]}", 0, nullptr, nullptr, &context_with_global_dict) == "true"); }
SECTION("create an ints local variable by a copy") { REQUIRE(parser.process("{local myint = temperature}{myint[0]}", 0, &config, nullptr, nullptr) == "357"); }
SECTION("create a strings local variable by a copy") { REQUIRE(parser.process("{local mystr = filament_notes}{mystr[0]}", 0, &config, nullptr, nullptr) == "testnotes"); }
SECTION("create a bools local variable by a copy") { REQUIRE(parser.process("{local mybool = enable_dynamic_fan_speeds}{mybool[0]}", 0, &config, nullptr, nullptr) == "true"); }
SECTION("create an ints global variable by a copy") { REQUIRE(parser.process("{global myint = temperature}{myint[0]}", 0, &config, nullptr, &context_with_global_dict) == "357"); }
SECTION("create a strings global variable by a copy") { REQUIRE(parser.process("{global mystr = filament_notes}{mystr[0]}", 0, &config, nullptr, &context_with_global_dict) == "testnotes"); }
SECTION("create a bools global variable by a copy") { REQUIRE(parser.process("{global mybool = enable_dynamic_fan_speeds}{mybool[0]}", 0, &config, nullptr, &context_with_global_dict) == "true"); }
SECTION("create an ints local variable by a copy and overwrite it") {
REQUIRE(parser.process("{local myint = temperature}{myint = repeat(2*3, 4*6)}{myint[5]}", 0, &config, nullptr, nullptr) == "24");
REQUIRE(parser.process("{local myint = temperature}{myint = (2*3, 4*6)}{myint[1]}", 0, &config, nullptr, nullptr) == "24");
REQUIRE(parser.process("{local myint = temperature}{myint = (1)}{myint = temperature}{myint[0]}", 0, &config, nullptr, nullptr) == "357");
}
SECTION("create a strings local variable by a copy and overwrite it") {
REQUIRE(parser.process("{local mystr = filament_notes}{mystr = repeat(2*3, \"mine\" + \"only\" + \"mine\")}{mystr[5]}", 0, &config, nullptr, nullptr) == "mineonlymine");
REQUIRE(parser.process("{local mystr = filament_notes}{mystr = (2*3, \"mine\" + \"only\" + \"mine\")}{mystr[1]}", 0, &config, nullptr, nullptr) == "mineonlymine");
REQUIRE(parser.process("{local mystr = filament_notes}{mystr = (2*3, \"mine\" + \"only\" + \"mine\")}{mystr = filament_notes}{mystr[0]}", 0, &config, nullptr, nullptr) == "testnotes");
}
SECTION("create a bools local variable by a copy and overwrite it") {
REQUIRE(parser.process("{local mybool = enable_dynamic_fan_speeds}{mybool = repeat(2*3, true)}{mybool[5]}", 0, &config, nullptr, nullptr) == "true");
REQUIRE(parser.process("{local mybool = enable_dynamic_fan_speeds}{mybool = (false, true)}{mybool[1]}", 0, &config, nullptr, nullptr) == "true");
REQUIRE(parser.process("{local mybool = enable_dynamic_fan_speeds}{mybool = (false, false)}{mybool = enable_dynamic_fan_speeds}{mybool[0]}", 0, &config, nullptr, nullptr) == "true");
}
SECTION("size() of a non-empty vector returns the right size") { REQUIRE(parser.process("{local myint = (0, 1, 2, 3)}{size(myint)}", 0, nullptr, nullptr, nullptr) == "4"); }
SECTION("size() of a an empty vector returns the right size") { REQUIRE(parser.process("{local myint = (0);myint=();size(myint)}", 0, nullptr, nullptr, nullptr) == "0"); }
SECTION("empty() of a non-empty vector returns false") { REQUIRE(parser.process("{local myint = (0, 1, 2, 3)}{empty(myint)}", 0, nullptr, nullptr, nullptr) == "false"); }
SECTION("empty() of a an empty vector returns true") { REQUIRE(parser.process("{local myint = (0);myint=();empty(myint)}", 0, nullptr, nullptr, nullptr) == "true"); }
SECTION("nested if with new variables") {
std::string script =
"{if 1 == 1}{local myints = (5, 4, 3, 2, 1)}{else}{local myfloats = (1., 2., 3., 4., 5., 6., 7.)}{endif}"
"{myints[1]},{size(myints)}";
REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "4,5");
}
SECTION("nested if with new variables 2") {
std::string script =
"{if 1 == 0}{local myints = (5, 4, 3, 2, 1)}{else}{local myfloats = (1., 2., 3., 4., 5., 6., 7.)}{endif}"
"{size(myfloats)}";
REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "7");
}
SECTION("nested if with new variables 2, mixing }{ with ;") {
std::string script =
"{if 1 == 0 then local myints = (5, 4, 3, 2, 1);else;local myfloats = (1., 2., 3., 4., 5., 6., 7.);endif}"
"{size(myfloats)}";
REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "7");
}
SECTION("nested if with new variables, two level") {
std::string script =
"{if 1 == 1}{if 2 == 3}{nejaka / haluz}{else}{local myints = (6, 5, 4, 3, 2, 1)}{endif}{else}{if zase * haluz}{else}{local myfloats = (1., 2., 3., 4., 5., 6., 7.)}{endif}{endif}"
"{size(myints)}";
REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "6");
}
SECTION("if with empty block and ;") {
std::string script =
"{if false then else;local myfloats = (1., 2., 3., 4., 5., 6., 7.);endif}"
"{size(myfloats)}";
REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "7");
}
SECTION("nested if with new variables, two level, mixing }{ with ;") {
std::string script =
"{if 1 == 1 then if 2 == 3}nejaka / haluz{else local myints = (6, 5, 4, 3, 2, 1) endif else if zase * haluz then else local myfloats = (1., 2., 3., 4., 5., 6., 7.) endif endif}"
"{size(myints)}";
REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "6");
}
SECTION("nested if with new variables, two level, mixing }{ with ; 2") {
std::string script =
"{if 1 == 1 then if 2 == 3 then nejaka / haluz else}{local myints = (6, 5, 4, 3, 2, 1)}{endif else if zase * haluz then else local myfloats = (1., 2., 3., 4., 5., 6., 7.) endif endif}"
"{size(myints)}";
REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "6");
}
SECTION("nested if with new variables, two level, mixing }{ with ; 3") {
std::string script =
"{if 1 == 1 then if 2 == 3 then nejaka / haluz else}{local myints = (6, 5, 4, 3, 2, 1)}{endif else}{if zase * haluz}{else local myfloats = (1., 2., 3., 4., 5., 6., 7.) endif}{endif}"
"{size(myints)}";
REQUIRE(parser.process(script, 0, nullptr, nullptr, nullptr) == "6");
}
SECTION("if else completely empty") { REQUIRE(parser.process("{if false then elsif false then else endif}", 0, nullptr, nullptr, nullptr) == ""); }
}

View File

@@ -0,0 +1,55 @@
#define NOMINMAX
#include <catch2/catch.hpp>
#include <numeric>
#include "libslic3r/PNGReadWrite.hpp"
#include "libslic3r/SLA/AGGRaster.hpp"
#include "libslic3r/BoundingBox.hpp"
using namespace Slic3r;
static sla::RasterGrayscaleAA create_raster(const sla::Resolution &res)
{
sla::PixelDim pixdim{1., 1.};
auto bb = BoundingBox({0, 0}, {scaled(1.), scaled(1.)});
sla::RasterBase::Trafo trafo;
trafo.center_x = bb.center().x();
trafo.center_y = bb.center().y();
return sla::RasterGrayscaleAA{res, pixdim, trafo, agg::gamma_threshold(.5)};
}
TEST_CASE("PNG read", "[PNG]") {
auto rst = create_raster({100, 100});
size_t rstsum = 0;
for (size_t r = 0; r < rst.resolution().height_px; ++r)
for (size_t c = 0; c < rst.resolution().width_px; ++c)
rstsum += rst.read_pixel(c, r);
SECTION("Correct png buffer should be recognized as such.") {
auto enc_rst = rst.encode(sla::PNGRasterEncoder{});
REQUIRE(Slic3r::png::is_png({enc_rst.data(), enc_rst.size()}));
}
SECTION("Fake png buffer should be recognized as such.") {
std::vector<uint8_t> fake(10, '\0');
REQUIRE(!Slic3r::png::is_png({fake.data(), fake.size()}));
}
SECTION("Decoded PNG buffer resolution should match the original") {
auto enc_rst = rst.encode(sla::PNGRasterEncoder{});
png::ImageGreyscale img;
png::decode_png({enc_rst.data(), enc_rst.size()}, img);
REQUIRE(img.rows == rst.resolution().height_px);
REQUIRE(img.cols == rst.resolution().width_px);
size_t sum = std::accumulate(img.buf.begin(), img.buf.end(), size_t(0));
REQUIRE(sum == rstsum);
}
}

View File

@@ -0,0 +1,230 @@
#include <catch2/catch.hpp>
#include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp"
using namespace Slic3r;
SCENARIO("Converted Perl tests", "[Polygon]") {
GIVEN("ccw_square") {
Polygon ccw_square{ { 100, 100 }, { 200, 100 }, { 200, 200 }, { 100, 200 } };
Polygon cw_square(ccw_square);
cw_square.reverse();
THEN("ccw_square is valid") {
REQUIRE(ccw_square.is_valid());
}
THEN("cw_square is valid") {
REQUIRE(cw_square.is_valid());
}
THEN("ccw_square.area") {
REQUIRE(ccw_square.area() == 100 * 100);
}
THEN("cw_square.area") {
REQUIRE(cw_square.area() == - 100 * 100);
}
THEN("ccw_square.centroid") {
REQUIRE(ccw_square.centroid() == Point { 150, 150 });
}
THEN("cw_square.centroid") {
REQUIRE(cw_square.centroid() == Point { 150, 150 });
}
THEN("ccw_square.contains_point(150, 150)") {
REQUIRE(ccw_square.contains({ 150, 150 }));
}
THEN("cw_square.contains_point(150, 150)") {
REQUIRE(cw_square.contains({ 150, 150 }));
}
THEN("conversion to lines") {
REQUIRE(ccw_square.lines() == Lines{
{ { 100, 100 }, { 200, 100 } },
{ { 200, 100 }, { 200, 200 } },
{ { 200, 200 }, { 100, 200 } },
{ { 100, 200 }, { 100, 100 } } });
}
THEN("split_at_first_point") {
REQUIRE(ccw_square.split_at_first_point() == Polyline { ccw_square[0], ccw_square[1], ccw_square[2], ccw_square[3], ccw_square[0] });
}
THEN("split_at_index(2)") {
REQUIRE(ccw_square.split_at_index(2) == Polyline { ccw_square[2], ccw_square[3], ccw_square[0], ccw_square[1], ccw_square[2] });
}
THEN("split_at_vertex(ccw_square[2])") {
REQUIRE(ccw_square.split_at_vertex(ccw_square[2]) == Polyline { ccw_square[2], ccw_square[3], ccw_square[0], ccw_square[1], ccw_square[2] });
}
THEN("is_counter_clockwise") {
REQUIRE(ccw_square.is_counter_clockwise());
}
THEN("! is_counter_clockwise") {
REQUIRE(! cw_square.is_counter_clockwise());
}
THEN("make_counter_clockwise") {
cw_square.make_counter_clockwise();
REQUIRE(cw_square.is_counter_clockwise());
}
THEN("make_counter_clockwise^2") {
cw_square.make_counter_clockwise();
cw_square.make_counter_clockwise();
REQUIRE(cw_square.is_counter_clockwise());
}
THEN("first_point") {
REQUIRE(&ccw_square.first_point() == &ccw_square.points.front());
}
}
GIVEN("Triangulating hexagon") {
Polygon hexagon{ { 100, 0 } };
for (size_t i = 1; i < 6; ++ i) {
Point p = hexagon.points.front();
p.rotate(PI / 3 * i);
hexagon.points.emplace_back(p);
}
Polygons triangles;
hexagon.triangulate_convex(&triangles);
THEN("right number of triangles") {
REQUIRE(triangles.size() == 4);
}
THEN("all triangles are ccw") {
auto it = std::find_if(triangles.begin(), triangles.end(), [](const Polygon &tri) { return tri.is_clockwise(); });
REQUIRE(it == triangles.end());
}
}
GIVEN("General triangle") {
Polygon polygon { { 50000000, 100000000 }, { 300000000, 102000000 }, { 50000000, 104000000 } };
Line line { { 175992032, 102000000 }, { 47983964, 102000000 } };
Point intersection;
bool has_intersection = polygon.intersection(line, &intersection);
THEN("Intersection with line") {
REQUIRE(has_intersection);
REQUIRE(intersection == Point { 50000000, 102000000 });
}
}
}
TEST_CASE("Centroid of Trapezoid must be inside", "[Polygon][Utils]")
{
Slic3r::Polygon trapezoid {
{ 4702134, 1124765853 },
{ -4702134, 1124765853 },
{ -9404268, 1049531706 },
{ 9404268, 1049531706 },
};
Point centroid = trapezoid.centroid();
CHECK(trapezoid.contains(centroid));
}
// This test currently only covers remove_collinear_points.
// All remaining tests are to be ported from xs/t/06_polygon.t
Slic3r::Points collinear_circle({
Slic3r::Point::new_scale(0, 0), // 3 collinear points at beginning
Slic3r::Point::new_scale(10, 0),
Slic3r::Point::new_scale(20, 0),
Slic3r::Point::new_scale(30, 10),
Slic3r::Point::new_scale(40, 20), // 2 collinear points
Slic3r::Point::new_scale(40, 30),
Slic3r::Point::new_scale(30, 40), // 3 collinear points
Slic3r::Point::new_scale(20, 40),
Slic3r::Point::new_scale(10, 40),
Slic3r::Point::new_scale(-10, 20),
Slic3r::Point::new_scale(-20, 10),
Slic3r::Point::new_scale(-20, 0), // 3 collinear points at end
Slic3r::Point::new_scale(-10, 0),
Slic3r::Point::new_scale(-5, 0)
});
SCENARIO("Remove collinear points from Polygon", "[Polygon]") {
GIVEN("Polygon with collinear points"){
Slic3r::Polygon p(collinear_circle);
WHEN("collinear points are removed") {
remove_collinear(p);
THEN("Leading collinear points are removed") {
REQUIRE(p.points.front() == Slic3r::Point::new_scale(20, 0));
}
THEN("Trailing collinear points are removed") {
REQUIRE(p.points.back() == Slic3r::Point::new_scale(-20, 0));
}
THEN("Number of remaining points is correct") {
REQUIRE(p.points.size() == 7);
}
}
}
}
SCENARIO("Simplify polygon", "[Polygon]")
{
GIVEN("gear") {
auto gear = Polygon::new_scale({
{144.9694,317.1543}, {145.4181,301.5633}, {146.3466,296.921}, {131.8436,294.1643}, {131.7467,294.1464},
{121.7238,291.5082}, {117.1631,290.2776}, {107.9198,308.2068}, {100.1735,304.5101}, {104.9896,290.3672},
{106.6511,286.2133}, {93.453,279.2327}, {81.0065,271.4171}, {67.7886,286.5055}, {60.7927,280.1127},
{69.3928,268.2566}, {72.7271,264.9224}, {61.8152,253.9959}, {52.2273,242.8494}, {47.5799,245.7224},
{34.6577,252.6559}, {30.3369,245.2236}, {42.1712,236.3251}, {46.1122,233.9605}, {43.2099,228.4876},
{35.0862,211.5672}, {33.1441,207.0856}, {13.3923,212.1895}, {10.6572,203.3273}, {6.0707,204.8561},
{7.2775,204.4259}, {29.6713,196.3631}, {25.9815,172.1277}, {25.4589,167.2745}, {19.8337,167.0129},
{5.0625,166.3346}, {5.0625,156.9425}, {5.3701,156.9282}, {21.8636,156.1628}, {25.3713,156.4613},
{25.4243,155.9976}, {29.3432,155.8157}, {30.3838,149.3549}, {26.3596,147.8137}, {27.1085,141.2604},
{29.8466,126.8337}, {24.5841,124.9201}, {10.6664,119.8989}, {13.4454,110.9264}, {33.1886,116.0691},
{38.817,103.1819}, {45.8311,89.8133}, {30.4286,76.81}, {35.7686,70.0812}, {48.0879,77.6873},
{51.564,81.1635}, {61.9006,69.1791}, {72.3019,58.7916}, {60.5509,42.5416}, {68.3369,37.1532},
{77.9524,48.1338}, {80.405,52.2215}, {92.5632,44.5992}, {93.0123,44.3223}, {106.3561,37.2056},
{100.8631,17.4679}, {108.759,14.3778}, {107.3148,11.1283}, {117.0002,32.8627}, {140.9109,27.3974},
{145.7004,26.4994}, {145.1346,6.1011}, {154.502,5.4063}, {156.9398,25.6501}, {171.0557,26.2017},
{181.3139,27.323}, {186.2377,27.8532}, {191.6031,8.5474}, {200.6724,11.2756}, {197.2362,30.2334},
{220.0789,39.1906}, {224.3261,41.031}, {236.3506,24.4291}, {243.6897,28.6723}, {234.2956,46.7747},
{245.6562,55.1643}, {257.2523,65.0901}, {261.4374,61.5679}, {273.1709,52.8031}, {278.555,59.5164},
{268.4334,69.8001}, {264.1615,72.3633}, {268.2763,77.9442}, {278.8488,93.5305}, {281.4596,97.6332},
{286.4487,95.5191}, {300.2821,90.5903}, {303.4456,98.5849}, {286.4523,107.7253}, {293.7063,131.1779},
{294.9748,135.8787}, {314.918,133.8172}, {315.6941,143.2589}, {300.9234,146.1746}, {296.6419,147.0309},
{297.1839,161.7052}, {296.6136,176.3942}, {302.1147,177.4857}, {316.603,180.3608}, {317.1658,176.7341},
{315.215,189.6589}, {315.1749,189.6548}, {294.9411,187.5222}, {291.13,201.7233}, {286.2615,215.5916},
{291.1944,218.2545}, {303.9158,225.1271}, {299.2384,233.3694}, {285.7165,227.6001}, {281.7091,225.1956},
{273.8981,237.6457}, {268.3486,245.2248}, {267.4538,246.4414}, {264.8496,250.0221}, {268.6392,253.896},
{278.5017,265.2131}, {272.721,271.4403}, {257.2776,258.3579}, {234.4345,276.5687}, {242.6222,294.8315},
{234.9061,298.5798}, {227.0321,286.2841}, {225.2505,281.8301}, {211.5387,287.8187}, {202.3025,291.0935},
{197.307,292.831}, {199.808,313.1906}, {191.5298,315.0787}, {187.3082,299.8172}, {186.4201,295.3766},
{180.595,296.0487}, {161.7854,297.4248}, {156.8058,297.6214}, {154.3395,317.8592}
});
WHEN("simplified") {
size_t num_points = gear.size();
Polygons simplified = gear.simplify(1000.);
THEN("gear simplified to a single polygon") {
REQUIRE(simplified.size() == 1);
}
THEN("gear was reduced using Douglas-Peucker") {
//note printf "original points: %d\nnew points: %d", $num_points, scalar(@{$simplified->[0]});
REQUIRE(simplified.front().size() < num_points);
}
}
}
}
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/ExPolygonsIndex.hpp"
TEST_CASE("Indexing expolygons", "[ExPolygon]")
{
ExPolygons expolys{
ExPolygon{Polygon{{0, 0}, {10, 0}, {0, 5}}, Polygon{{4, 3}, {6, 3}, {5, 2}}},
ExPolygon{Polygon{{100, 0}, {110, 0}, {100, 5}}, Polygon{{104, 3}, {106, 3}, {105, 2}}}
};
Points points = to_points(expolys);
Lines lines = to_lines(expolys);
Linesf linesf = to_linesf(expolys);
ExPolygonsIndices ids(expolys);
REQUIRE(points.size() == lines.size());
REQUIRE(points.size() == linesf.size());
REQUIRE(points.size() == ids.get_count());
for (size_t i = 0; i < ids.get_count(); i++) {
ExPolygonsIndex id = ids.cvt(i);
const ExPolygon &expoly = expolys[id.expolygons_index];
const Polygon &poly = id.is_contour() ? expoly.contour : expoly.holes[id.hole_index()];
const Points &pts = poly.points;
const Point &p = pts[id.point_index];
CHECK(points[i] == p);
CHECK(lines[i].a == p);
CHECK(linesf[i].a.cast<int>() == p);
CHECK(ids.cvt(id) == i);
const Point &p_b = ids.is_last_point(id) ? pts.front() : pts[id.point_index + 1];
CHECK(lines[i].b == p_b);
CHECK(linesf[i].b.cast<int>() == p_b);
}
}

View File

@@ -0,0 +1,28 @@
#include <catch2/catch.hpp>
#include "libslic3r/Point.hpp"
#include "libslic3r/Polyline.hpp"
using namespace Slic3r;
SCENARIO("Simplify polyline", "[Polyline]")
{
GIVEN("polyline 1") {
auto polyline = Polyline{ {0,0},{1,0},{2,0},{2,1},{2,2},{1,2},{0,2},{0,1},{0,0} };
WHEN("simplified with Douglas-Peucker") {
polyline.simplify(1.);
THEN("simplified correctly") {
REQUIRE(polyline == Polyline{ {0,0}, {2,0}, {2,2}, {0,2}, {0,0} });
}
}
}
GIVEN("polyline 2") {
auto polyline = Polyline{ {0,0}, {50,50}, {100,0}, {125,-25}, {150,50} };
WHEN("simplified with Douglas-Peucker") {
polyline.simplify(25.);
THEN("not simplified") {
REQUIRE(polyline == Polyline{ {0,0}, {50,50}, {125,-25}, {150,50} });
}
}
}
}

View File

@@ -0,0 +1,304 @@
#include <catch2/catch.hpp>
#include <test_utils.hpp>
#include <libslic3r/QuadricEdgeCollapse.hpp>
#include <libslic3r/TriangleMesh.hpp> // its - indexed_triangle_set
#include "libslic3r/AABBTreeIndirect.hpp" // is similar
using namespace Slic3r;
namespace Private {
struct Similarity
{
float max_distance = 0.f;
float average_distance = 0.f;
Similarity() = default;
Similarity(float max_distance, float average_distance)
: max_distance(max_distance), average_distance(average_distance)
{}
};
// border for our algorithm with frog_leg model and decimation to 5%
Similarity frog_leg_5(0.32f, 0.043f);
Similarity get_similarity(const indexed_triangle_set &from,
const indexed_triangle_set &to)
{
// create ABBTree
auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
from.vertices, from.indices);
float sum_distance = 0.f;
float max_distance = 0.f;
auto collect_distances = [&](const Vec3f &surface_point) {
size_t hit_idx;
Vec3f hit_point;
float distance2 =
AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
from.vertices, from.indices, tree, surface_point, hit_idx,
hit_point);
float distance = sqrt(distance2);
if (max_distance < distance) max_distance = distance;
sum_distance += distance;
};
for (const Vec3f &vertex : to.vertices) { collect_distances(vertex); }
for (const Vec3i &t : to.indices) {
Vec3f center(0, 0, 0);
for (size_t i = 0; i < 3; ++i) { center += to.vertices[t[i]] / 3; }
collect_distances(center);
}
size_t count = to.vertices.size() + to.indices.size();
float average_distance = sum_distance / count;
std::cout << "max_distance = " << max_distance << ", average_distance = " << average_distance << std::endl;
return Similarity(max_distance, average_distance);
}
void is_better_similarity(const indexed_triangle_set &its_first,
const indexed_triangle_set &its_second,
const Similarity & compare)
{
Similarity s1 = get_similarity(its_first, its_second);
Similarity s2 = get_similarity(its_second, its_first);
CHECK(s1.average_distance < compare.average_distance);
CHECK(s1.max_distance < compare.max_distance);
CHECK(s2.average_distance < compare.average_distance);
CHECK(s2.max_distance < compare.max_distance);
}
void is_worse_similarity(const indexed_triangle_set &its_first,
const indexed_triangle_set &its_second,
const Similarity & compare)
{
Similarity s1 = get_similarity(its_first, its_second);
Similarity s2 = get_similarity(its_second, its_first);
if (s1.max_distance < compare.max_distance &&
s2.max_distance < compare.max_distance)
CHECK(false);
}
bool exist_triangle_with_twice_vertices(const std::vector<stl_triangle_vertex_indices> &indices)
{
for (const auto &face : indices)
if (face[0] == face[1] || face[0] == face[2] || face[1] == face[2])
return true;
return false;
}
} // namespace Private
TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]")
{
indexed_triangle_set its;
its.vertices = {Vec3f(-1.f, 0.f, 0.f), Vec3f(0.f, 1.f, 0.f),
Vec3f(1.f, 0.f, 0.f), Vec3f(0.f, 0.f, 1.f),
// vertex to be removed
Vec3f(0.9f, .1f, -.1f)};
its.indices = {Vec3i(1, 0, 3), Vec3i(2, 1, 3), Vec3i(0, 2, 3),
Vec3i(0, 1, 4), Vec3i(1, 2, 4), Vec3i(2, 0, 4)};
// edge to remove is between vertices 2 and 4 on trinagles 4 and 5
indexed_triangle_set its_ = its; // copy
// its_write_obj(its, "tetrhedron_in.obj");
uint32_t wanted_count = its.indices.size() - 1;
its_quadric_edge_collapse(its, wanted_count);
// its_write_obj(its, "tetrhedron_out.obj");
CHECK(its.indices.size() == 4);
CHECK(its.vertices.size() == 4);
for (size_t i = 0; i < 3; i++) {
CHECK(its.indices[i] == its_.indices[i]);
}
for (size_t i = 0; i < 4; i++) {
if (i == 2) continue;
CHECK(its.vertices[i] == its_.vertices[i]);
}
const Vec3f &v = its.vertices[2]; // new vertex
const Vec3f &v2 = its_.vertices[2]; // moved vertex
const Vec3f &v4 = its_.vertices[4]; // removed vertex
for (size_t i = 0; i < 3; i++) {
bool is_between = (v[i] < v4[i] && v[i] > v2[i]) ||
(v[i] > v4[i] && v[i] < v2[i]);
CHECK(is_between);
}
Private::Similarity max_similarity(0.75f, 0.014f);
Private::is_better_similarity(its, its_, max_similarity);
}
static bool is_equal(const std::vector<stl_vertex> &v1,
const std::vector<stl_vertex> &v2,
float epsilon = std::numeric_limits<float>::epsilon())
{
// is same count?
if (v1.size() != v2.size()) return false;
// check all v1 vertices
for (const auto &v1_ : v1) {
auto is_equal = [&v1_, epsilon](const auto &v2_) {
for (size_t i = 0; i < 3; i++)
if (fabs(v1_[i] - v2_[i]) > epsilon)
return false;
return true;
};
// is v1 vertex in v2 vertices?
if(std::find_if(v2.begin(), v2.end(), is_equal) == v2.end()) return false;
}
return true;
}
TEST_CASE("Reduce to one triangle by Quadric Edge Collapse", "[its]")
{
// !!! Not work (no manifold - open edges{0-1, 1-2, 2-4, 4-5, 5-3, 3-0}):
/////////////image////
// * 5 //
// |\ //
// | \ //
// 3 *--* 4 //
// | /|\ //
// |/ | \ //
// 0 *--*--* 2 //
// 1 //
//////////////////////
// all triangles are on a plane therefore quadric is zero and
// when reduce edge between vertices 3 and 4 new vertex lay on vertex 3 not 4 !!!
indexed_triangle_set its;
its.vertices = {Vec3f(0.f, 0.f, 0.f), Vec3f(1.f, 0.f, 0.f),
Vec3f(2.f, 0.f, 0.f), Vec3f(0.f, 1.f, 0.f),
Vec3f(1.f, 1.f, 0.f), Vec3f(0.f, 2.f, 0.f)};
its.indices = {Vec3i(0, 1, 4), Vec3i(1, 2, 4), Vec3i(0, 4, 3),
Vec3i(3, 4, 5)};
std::vector<stl_vertex> triangle_vertices = {its.vertices[0],
its.vertices[2],
its.vertices[5]};
uint32_t wanted_count = 1;
its_quadric_edge_collapse(its, wanted_count);
// result should be one triangle made of vertices 0, 2, 5
// NOT WORK
//CHECK(its.indices.size() == wanted_count);
//// check all triangle vertices
//CHECK(is_equal(its.vertices, triangle_vertices));
}
TEST_CASE("Reduce to one tetrahedron by Quadric Edge Collapse", "[its]")
{
// Extend previous test to tetrahedron to make it manifold
indexed_triangle_set its;
its.vertices = {
Vec3f(0.f, 0.f, 0.f), Vec3f(1.f, 0.f, 0.f), Vec3f(2.f, 0.f, 0.f),
Vec3f(0.f, 1.f, 0.f), Vec3f(1.f, 1.f, 0.f),
Vec3f(0.f, 2.f, 0.f)
// tetrahedron extetion
, Vec3f(0.f, 0.f, -2.f)
};
std::vector<stl_vertex> tetrahedron_vertices = {its.vertices[0],
its.vertices[2],
its.vertices[5],
// tetrahedron extetion
its.vertices[6]};
its.indices = {Vec3i(0, 1, 4), Vec3i(1, 2, 4), Vec3i(0, 4, 3), Vec3i(3, 4, 5),
// tetrahedron extetion
Vec3i(4, 2, 6), Vec3i(5, 4, 6), Vec3i(3, 5, 6), Vec3i(0, 3, 6), Vec3i(1, 0, 6), Vec3i(2, 1, 6)
};
uint32_t wanted_count = 4;
//its_write_obj(its, "tetrhedron_in.obj");
its_quadric_edge_collapse(its, wanted_count);
//its_write_obj(its, "tetrhedron_out.obj");
// result should be tetrahedron
CHECK(its.indices.size() == wanted_count);
// check all tetrahedron vertices
CHECK(is_equal(its.vertices, tetrahedron_vertices));
}
TEST_CASE("Simplify frog_legs.obj to 5% by Quadric edge collapse", "[its][quadric_edge_collapse]")
{
TriangleMesh mesh = load_model("frog_legs.obj");
double original_volume = its_volume(mesh.its);
uint32_t wanted_count = mesh.its.indices.size() * 0.05;
REQUIRE_FALSE(mesh.empty());
indexed_triangle_set its = mesh.its; // copy
float max_error = std::numeric_limits<float>::max();
its_quadric_edge_collapse(its, wanted_count, &max_error);
// its_write_obj(its, "frog_legs_qec.obj");
CHECK(its.indices.size() <= wanted_count);
double volume = its_volume(its);
CHECK(fabs(original_volume - volume) < 33.);
Private::is_better_similarity(mesh.its, its, Private::frog_leg_5);
}
#include <libigl/igl/qslim.h>
TEST_CASE("Simplify frog_legs.obj to 5% by IGL/qslim", "[]")
{
std::string obj_filename = "frog_legs.obj";
TriangleMesh mesh = load_model(obj_filename);
REQUIRE_FALSE(mesh.empty());
indexed_triangle_set &its = mesh.its;
//double original_volume = its_volume(its);
uint32_t wanted_count = its.indices.size() * 0.05;
Eigen::MatrixXd V(its.vertices.size(), 3);
Eigen::MatrixXi F(its.indices.size(), 3);
for (size_t j = 0; j < its.vertices.size(); ++j) {
Vec3d vd = its.vertices[j].cast<double>();
for (int i = 0; i < 3; ++i) V(j, i) = vd(i);
}
for (size_t j = 0; j < its.indices.size(); ++j) {
const auto &f = its.indices[j];
for (int i = 0; i < 3; ++i) F(j, i) = f(i);
}
size_t max_m = wanted_count;
Eigen::MatrixXd U;
Eigen::MatrixXi G;
Eigen::VectorXi J, I;
CHECK(igl::qslim(V, F, max_m, U, G, J, I));
// convert to its
indexed_triangle_set its_out;
its_out.vertices.reserve(U.size()/3);
its_out.indices.reserve(G.size()/3);
size_t U_size = U.size() / 3;
for (size_t i = 0; i < U_size; i++)
its_out.vertices.emplace_back(U(i, 0), U(i, 1), U(i, 2));
size_t G_size = G.size() / 3;
for (size_t i = 0; i < G_size; i++)
its_out.indices.emplace_back(G(i, 0), G(i, 1), G(i, 2));
// check if algorithm is still worse than our
Private::is_worse_similarity(its_out, its, Private::frog_leg_5);
// its_out, its --> avg_distance: 0.0351217, max_distance 0.364316
// its, its_out --> avg_distance: 0.0412358, max_distance 0.238913
}
TEST_CASE("Simplify trouble case", "[its]")
{
TriangleMesh tm = load_model("simplification.obj");
REQUIRE_FALSE(tm.empty());
float max_error = std::numeric_limits<float>::max();
uint32_t wanted_count = 0;
its_quadric_edge_collapse(tm.its, wanted_count, &max_error);
CHECK(!Private::exist_triangle_with_twice_vertices(tm.its.indices));
}
TEST_CASE("Simplified cube should not be empty.", "[its]")
{
auto its = its_make_cube(1, 2, 3);
float max_error = std::numeric_limits<float>::max();
uint32_t wanted_count = 0;
its_quadric_edge_collapse(its, wanted_count, &max_error);
CHECK(!its.indices.empty());
}

View File

@@ -0,0 +1,284 @@
#include <catch2/catch.hpp>
#include <libslic3r/libslic3r.h>
#include <libslic3r/Algorithm/RegionExpansion.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/Polygon.hpp>
#include <libslic3r/SVG.cpp>
using namespace Slic3r;
//#define DEBUG_TEMP_DIR "d:\\temp\\"
SCENARIO("Region expansion basics", "[RegionExpansion]") {
static constexpr const coord_t ten = scaled<coord_t>(10.);
GIVEN("two touching squares") {
Polygon square1{ { 1 * ten, 1 * ten }, { 2 * ten, 1 * ten }, { 2 * ten, 2 * ten }, { 1 * ten, 2 * ten } };
Polygon square2{ { 2 * ten, 1 * ten }, { 3 * ten, 1 * ten }, { 3 * ten, 2 * ten }, { 2 * ten, 2 * ten } };
Polygon square3{ { 1 * ten, 2 * ten }, { 2 * ten, 2 * ten }, { 2 * ten, 3 * ten }, { 1 * ten, 3 * ten } };
static constexpr const float expansion = scaled<float>(1.);
auto test_expansion = [](const Polygon &src, const Polygon &boundary) {
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{src} }, { ExPolygon{boundary} },
expansion,
scaled<float>(0.3), // expansion step
5); // max num steps
THEN("Single anchor is produced") {
REQUIRE(expanded.size() == 1);
}
THEN("The area of the anchor is 10mm2") {
REQUIRE(area(expanded.front()) == Approx(expansion * ten));
}
};
WHEN("second square expanded into the first square (to left)") {
test_expansion(square2, square1);
}
WHEN("first square expanded into the second square (to right)") {
test_expansion(square1, square2);
}
WHEN("third square expanded into the first square (down)") {
test_expansion(square3, square1);
}
WHEN("first square expanded into the third square (up)") {
test_expansion(square1, square3);
}
}
GIVEN("simple bridge") {
Polygon square1{ { 1 * ten, 1 * ten }, { 2 * ten, 1 * ten }, { 2 * ten, 2 * ten }, { 1 * ten, 2 * ten } };
Polygon square2{ { 2 * ten, 1 * ten }, { 3 * ten, 1 * ten }, { 3 * ten, 2 * ten }, { 2 * ten, 2 * ten } };
Polygon square3{ { 3 * ten, 1 * ten }, { 4 * ten, 1 * ten }, { 4 * ten, 2 * ten }, { 3 * ten, 2 * ten } };
WHEN("expanded") {
static constexpr const float expansion = scaled<float>(1.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{square2} }, { ExPolygon{square1}, ExPolygon{square3} },
expansion,
scaled<float>(0.3), // expansion step
5); // max num steps
THEN("Two anchors are produced") {
REQUIRE(expanded.size() == 1);
REQUIRE(expanded.front().size() == 2);
}
THEN("The area of each anchor is 10mm2") {
REQUIRE(area(expanded.front().front()) == Approx(expansion * ten));
REQUIRE(area(expanded.front().back()) == Approx(expansion * ten));
}
}
WHEN("fully expanded") {
static constexpr const float expansion = scaled<float>(10.1);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{square2} }, { ExPolygon{square1}, ExPolygon{square3} },
expansion,
scaled<float>(2.3), // expansion step
5); // max num steps
THEN("Two anchors are produced") {
REQUIRE(expanded.size() == 1);
REQUIRE(expanded.front().size() == 2);
}
THEN("The area of each anchor is 100mm2") {
REQUIRE(area(expanded.front().front()) == Approx(sqr<double>(ten)));
REQUIRE(area(expanded.front().back()) == Approx(sqr<double>(ten)));
}
}
}
GIVEN("two bridges") {
Polygon left_support { { 1 * ten, 1 * ten }, { 2 * ten, 1 * ten }, { 2 * ten, 4 * ten }, { 1 * ten, 4 * ten } };
Polygon right_support { { 3 * ten, 1 * ten }, { 4 * ten, 1 * ten }, { 4 * ten, 4 * ten }, { 3 * ten, 4 * ten } };
Polygon bottom_bridge { { 2 * ten, 1 * ten }, { 3 * ten, 1 * ten }, { 3 * ten, 2 * ten }, { 2 * ten, 2 * ten } };
Polygon top_bridge { { 2 * ten, 3 * ten }, { 3 * ten, 3 * ten }, { 3 * ten, 4 * ten }, { 2 * ten, 4 * ten } };
WHEN("expanded") {
static constexpr const float expansion = scaled<float>(1.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{bottom_bridge}, ExPolygon{top_bridge} }, { ExPolygon{left_support}, ExPolygon{right_support} },
expansion,
scaled<float>(0.3), // expansion step
5); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "two_bridges-out.svg",
{ { { { ExPolygon{left_support}, ExPolygon{right_support} } }, { "supports", "orange", 0.5f } },
{ { { ExPolygon{bottom_bridge}, ExPolygon{top_bridge} } }, { "bridges", "blue", 0.5f } },
{ { union_ex(union_(expanded.front(), expanded.back())) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("Two anchors are produced for each bridge") {
REQUIRE(expanded.size() == 2);
REQUIRE(expanded.front().size() == 2);
REQUIRE(expanded.back().size() == 2);
}
THEN("The area of each anchor is 10mm2") {
double a = expansion * ten + M_PI * sqr(expansion) / 4;
double eps = sqr(scaled<double>(0.1));
REQUIRE(is_approx(area(expanded.front().front()), a, eps));
REQUIRE(is_approx(area(expanded.front().back()), a, eps));
REQUIRE(is_approx(area(expanded.back().front()), a, eps));
REQUIRE(is_approx(area(expanded.back().back()), a, eps));
}
}
}
GIVEN("rectangle with rhombic cut-out") {
double diag = 1 * ten * sqrt(2.) / 4.;
Polygon square_with_rhombic_cutout{ { 0, 0 }, { 1 * ten, 0 }, { ten / 2, ten / 2 }, { 1 * ten, 1 * ten }, { 0, 1 * ten } };
Polygon rhombic { { ten / 2, ten / 2 }, { 3 * ten / 4, ten / 4 }, { 1 * ten, ten / 2 }, { 3 * ten / 4, 3 * ten / 4 } };
WHEN("expanded") {
static constexpr const float expansion = scaled<float>(1.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{rhombic} }, { ExPolygon{square_with_rhombic_cutout} },
expansion,
scaled<float>(0.1), // expansion step
11); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "rectangle_with_rhombic_cut-out.svg",
{ { { { ExPolygon{square_with_rhombic_cutout} } }, { "square_with_rhombic_cutout", "orange", 0.5f } },
{ { { ExPolygon{rhombic} } }, { "rhombic", "blue", 0.5f } },
{ { union_ex(expanded.front()) }, { "bridges", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("Single anchor is produced") {
REQUIRE(expanded.size() == 1);
}
THEN("The area of anchor is correct") {
double area_calculated = area(expanded.front());
double area_expected = 2. * diag * expansion + M_PI * sqr(expansion) * 0.75;
REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled<double>(0.2))));
}
}
WHEN("extra expanded") {
static constexpr const float expansion = scaled<float>(2.5);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{rhombic} }, { ExPolygon{square_with_rhombic_cutout} },
expansion,
scaled<float>(0.25), // expansion step
11); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "rectangle_with_rhombic_cut-out2.svg",
{ { { { ExPolygon{square_with_rhombic_cutout} } }, { "square_with_rhombic_cutout", "orange", 0.5f } },
{ { { ExPolygon{rhombic} } }, { "rhombic", "blue", 0.5f } },
{ { union_ex(expanded.front()) }, { "bridges", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("Single anchor is produced") {
REQUIRE(expanded.size() == 1);
}
THEN("The area of anchor is correct") {
double area_calculated = area(expanded.front());
double area_expected = 2. * diag * expansion + M_PI * sqr(expansion) * 0.75;
REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled<double>(0.3))));
}
}
}
GIVEN("square with two holes") {
Polygon outer{ { 0, 0 }, { 3 * ten, 0 }, { 3 * ten, 5 * ten }, { 0, 5 * ten } };
Polygon hole1{ { 1 * ten, 1 * ten }, { 1 * ten, 2 * ten }, { 2 * ten, 2 * ten }, { 2 * ten, 1 * ten } };
Polygon hole2{ { 1 * ten, 3 * ten }, { 1 * ten, 4 * ten }, { 2 * ten, 4 * ten }, { 2 * ten, 3 * ten } };
ExPolygon boundary(outer);
boundary.holes = { hole1, hole2 };
Polygon anchor{ { -1 * ten, coord_t(1.5 * ten) }, { 0 * ten, coord_t(1.5 * ten) }, { 0, coord_t(3.5 * ten) }, { -1 * ten, coord_t(3.5 * ten) } };
WHEN("expanded") {
static constexpr const float expansion = scaled<float>(5.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary },
expansion,
scaled<float>(0.4), // expansion step
15); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-out.svg",
{ { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } },
{ { { boundary } }, { "boundary", "blue", 0.5f } },
{ { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("The anchor expands into a single region") {
REQUIRE(expanded.size() == 1);
REQUIRE(expanded.front().size() == 1);
}
THEN("The area of anchor is correct") {
double area_calculated = area(expanded.front());
double area_expected = double(expansion) * 2. * double(ten) + M_PI * sqr(expansion) * 0.5;
REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled<double>(0.45))));
}
}
WHEN("expanded even more") {
static constexpr const float expansion = scaled<float>(25.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary },
expansion,
scaled<float>(2.), // expansion step
15); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-expanded2-out.svg",
{ { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } },
{ { { boundary } }, { "boundary", "blue", 0.5f } },
{ { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("The anchor expands into a single region") {
REQUIRE(expanded.size() == 1);
REQUIRE(expanded.front().size() == 1);
}
}
WHEN("expanded yet even more") {
static constexpr const float expansion = scaled<float>(28.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary },
expansion,
scaled<float>(2.), // expansion step
20); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-expanded3-out.svg",
{ { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } },
{ { { boundary } }, { "boundary", "blue", 0.5f } },
{ { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("The anchor expands into a single region with two holes") {
REQUIRE(expanded.size() == 1);
REQUIRE(expanded.front().size() == 3);
}
}
WHEN("expanded fully") {
static constexpr const float expansion = scaled<float>(35.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary },
expansion,
scaled<float>(2.), // expansion step
25); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-expanded_fully-out.svg",
{ { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } },
{ { { boundary } }, { "boundary", "blue", 0.5f } },
{ { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("The anchor expands into a single region with two holes, fully covering the boundary") {
REQUIRE(expanded.size() == 1);
REQUIRE(expanded.front().size() == 3);
REQUIRE(area(expanded.front()) == Approx(area(boundary)));
}
}
}
GIVEN("square with hole, hole edge anchored") {
Polygon outer{ { -1 * ten, -1 * ten }, { 2 * ten, -1 * ten }, { 2 * ten, 2 * ten }, { -1 * ten, 2 * ten } };
Polygon hole { { 0, ten }, { ten, ten }, { ten, 0 }, { 0, 0 } };
Polygon anchor{ { 0, 0 }, { ten, 0 }, { ten, ten }, { 0, ten } };
ExPolygon boundary(outer);
boundary.holes = { hole };
WHEN("expanded") {
static constexpr const float expansion = scaled<float>(5.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary },
expansion,
scaled<float>(0.4), // expansion step
15); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_hole_anchored-out.svg",
{ { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } },
{ { { boundary } }, { "boundary", "blue", 0.5f } },
{ { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("The anchor expands into a single region with a hole") {
REQUIRE(expanded.size() == 1);
REQUIRE(expanded.front().size() == 2);
}
THEN("The area of anchor is correct") {
double area_calculated = area(expanded.front());
double area_expected = double(expansion) * 4. * double(ten) + M_PI * sqr(expansion);
REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled<double>(0.6))));
}
}
}
}

View File

@@ -0,0 +1,57 @@
#include <catch2/catch.hpp>
#include "libslic3r/Model.hpp"
#include "libslic3r/Format/STL.hpp"
using namespace Slic3r;
static inline std::string stl_path(const char* path)
{
return std::string(TEST_DATA_DIR) + "/test_stl/" + path;
}
SCENARIO("Reading an STL file", "[stl]") {
GIVEN("umlauts in the path of a binary STL file, Czech characters in the file name") {
WHEN("STL file is read") {
Slic3r::Model model;
THEN("load should succeed") {
REQUIRE(Slic3r::load_stl(stl_path("Geräte/20mmbox-čřšřěá.stl").c_str(), &model));
REQUIRE(is_approx(model.objects.front()->volumes.front()->mesh().size(), Vec3d(20, 20, 20)));
}
}
}
GIVEN("in ASCII format") {
WHEN("line endings LF") {
Slic3r::Model model;
THEN("load should succeed") {
REQUIRE(Slic3r::load_stl(stl_path("ASCII/20mmbox-LF.stl").c_str(), &model));
REQUIRE(is_approx(model.objects.front()->volumes.front()->mesh().size(), Vec3d(20, 20, 20)));
}
}
WHEN("line endings CRLF") {
Slic3r::Model model;
THEN("load should succeed") {
REQUIRE(Slic3r::load_stl(stl_path("ASCII/20mmbox-CRLF.stl").c_str(), &model));
REQUIRE(is_approx(model.objects.front()->volumes.front()->mesh().size(), Vec3d(20, 20, 20)));
}
}
#if 0
// ASCII STLs ending with just carriage returns are not supported. These were used by the old Macs, while the Unix based MacOS uses LFs as any other Unix.
WHEN("line endings CR") {
Slic3r::Model model;
THEN("load should succeed") {
REQUIRE(Slic3r::load_stl(stl_path("ASCII/20mmbox-CR.stl").c_str(), &model));
REQUIRE(is_approx(model.objects.front()->volumes.front()->mesh().size(), Vec3d(20, 20, 20)));
}
}
#endif
WHEN("nonstandard STL file (text after ending tags, invalid normals, for example infinities)") {
Slic3r::Model model;
THEN("load should succeed") {
REQUIRE(Slic3r::load_stl(stl_path("ASCII/20mmbox-nonstandard.stl").c_str(), &model));
REQUIRE(is_approx(model.objects.front()->volumes.front()->mesh().size(), Vec3d(20, 20, 20)));
}
}
}
}

View File

@@ -0,0 +1,122 @@
#include <catch2/catch.hpp>
#include <test_utils.hpp>
#include <libslic3r/SurfaceMesh.hpp>
using namespace Slic3r;
// Generate a broken cube mesh. Face 8 is inverted, face 11 is missing.
indexed_triangle_set its_make_cube_broken(double xd, double yd, double zd)
{
auto x = float(xd), y = float(yd), z = float(zd);
return {
{ {0, 1, 2}, {0, 2, 3}, {4, 5, 6}, {4, 6, 7},
{0, 4, 7}, {0, 7, 1}, {1, 7, 6}, {1, 6, 2},
{2, 5, 6}, {2, 5, 3}, {4, 0, 3} /*missing face*/ },
{ {x, y, 0}, {x, 0, 0}, {0, 0, 0}, {0, y, 0},
{x, y, z}, {0, y, z}, {0, 0, z}, {x, 0, z} }
};
}
TEST_CASE("SurfaceMesh on a cube", "[SurfaceMesh]") {
indexed_triangle_set cube = its_make_cube(1., 1., 1.);
SurfaceMesh sm(cube);
const Halfedge_index hi_first = sm.halfedge(Face_index(0));
Halfedge_index hi = hi_first;
REQUIRE(! hi_first.is_invalid());
SECTION("next / prev halfedge") {
hi = sm.next(hi);
REQUIRE(hi != hi_first);
hi = sm.next(hi);
hi = sm.next(hi);
REQUIRE(hi == hi_first);
hi = sm.prev(hi);
REQUIRE(hi != hi_first);
hi = sm.prev(hi);
hi = sm.prev(hi);
REQUIRE(hi == hi_first);
}
SECTION("next_around_target") {
// Check that we get to the same halfedge after applying next_around_target
// four times.
const Vertex_index target_vert = sm.target(hi_first);
for (int i=0; i<4;++i) {
hi = sm.next_around_target(hi);
REQUIRE((hi == hi_first) == (i == 3));
REQUIRE(sm.is_same_vertex(sm.target(hi), target_vert));
REQUIRE(! sm.is_border(hi));
}
}
SECTION("iterate around target and source") {
hi = sm.next_around_target(hi);
hi = sm.prev_around_target(hi);
hi = sm.prev_around_source(hi);
hi = sm.next_around_source(hi);
REQUIRE(hi == hi_first);
}
SECTION("opposite") {
const Vertex_index target = sm.target(hi);
const Vertex_index source = sm.source(hi);
hi = sm.opposite(hi);
REQUIRE(sm.is_same_vertex(target, sm.source(hi)));
REQUIRE(sm.is_same_vertex(source, sm.target(hi)));
hi = sm.opposite(hi);
REQUIRE(hi == hi_first);
}
SECTION("halfedges walk") {
for (int i=0; i<4; ++i) {
hi = sm.next(hi);
hi = sm.opposite(hi);
}
REQUIRE(hi == hi_first);
}
SECTION("point accessor") {
Halfedge_index hi = sm.halfedge(Face_index(0));
hi = sm.opposite(hi);
hi = sm.prev(hi);
hi = sm.opposite(hi);
REQUIRE(hi.face() == Face_index(6));
REQUIRE(sm.point(sm.target(hi)).isApprox(cube.vertices[7]));
}
}
TEST_CASE("SurfaceMesh on a broken cube", "[SurfaceMesh]") {
indexed_triangle_set cube = its_make_cube_broken(1., 1., 1.);
SurfaceMesh sm(cube);
SECTION("Check inverted face") {
Halfedge_index hi = sm.halfedge(Face_index(8));
for (int i=0; i<3; ++i) {
REQUIRE(! hi.is_invalid());
REQUIRE(sm.is_border(hi));
}
REQUIRE(hi == sm.halfedge(Face_index(8)));
hi = sm.opposite(hi);
REQUIRE(hi.is_invalid());
}
SECTION("missing face") {
Halfedge_index hi = sm.halfedge(Face_index(0));
for (int i=0; i<3; ++i)
hi = sm.next_around_source(hi);
hi = sm.next(hi);
REQUIRE(sm.is_border(hi));
REQUIRE(! hi.is_invalid());
hi = sm.opposite(hi);
REQUIRE(hi.is_invalid());
}
}

View File

@@ -0,0 +1,49 @@
#include <catch2/catch.hpp>
#include "libslic3r/Time.hpp"
#include <sstream>
#include <iomanip>
#include <locale>
using namespace Slic3r;
static void test_time_fmt(Slic3r::Utils::TimeFormat fmt) {
using namespace Slic3r::Utils;
time_t t = get_current_time_utc();
std::string tstr = time2str(t, TimeZone::local, fmt);
time_t parsedtime = str2time(tstr, TimeZone::local, fmt);
REQUIRE(t == parsedtime);
tstr = time2str(t, TimeZone::utc, fmt);
parsedtime = str2time(tstr, TimeZone::utc, fmt);
REQUIRE(t == parsedtime);
parsedtime = str2time("not valid string", TimeZone::local, fmt);
REQUIRE(parsedtime == time_t(-1));
parsedtime = str2time("not valid string", TimeZone::utc, fmt);
REQUIRE(parsedtime == time_t(-1));
}
TEST_CASE("ISO8601Z", "[Timeutils]") {
test_time_fmt(Slic3r::Utils::TimeFormat::iso8601Z);
std::string mydate = "20190710T085000Z";
time_t t = Slic3r::Utils::parse_iso_utc_timestamp(mydate);
std::string date = Slic3r::Utils::iso_utc_timestamp(t);
REQUIRE(date == mydate);
}
TEST_CASE("Slic3r_UTC_Time_Format", "[Timeutils]") {
using namespace Slic3r::Utils;
test_time_fmt(TimeFormat::gcode);
std::string mydate = "2019-07-10 at 08:50:00 UTC";
time_t t = Slic3r::Utils::str2time(mydate, TimeZone::utc, TimeFormat::gcode);
std::string date = Slic3r::Utils::utc_timestamp(t);
REQUIRE(date == mydate);
}

View File

@@ -0,0 +1,127 @@
#include <catch2/catch.hpp>
#include <libslic3r/Triangulation.hpp>
#include <libslic3r/SVG.hpp> // only debug visualization
using namespace Slic3r;
namespace Private{
void store_trinagulation(const ExPolygons &shape,
const std::vector<Vec3i> &triangles,
const char* file_name = "C:/data/temp/triangulation.svg",
double scale = 1e5)
{
BoundingBox bb;
for (const auto &expoly : shape) bb.merge(expoly.contour.points);
bb.scale(scale);
SVG svg_vis(file_name, bb);
svg_vis.draw(shape, "gray", .7f);
Points pts = to_points(shape);
svg_vis.draw(pts, "black", 4 * scale);
for (const Vec3i &t : triangles) {
Slic3r::Polygon triangle({pts[t[0]], pts[t[1]], pts[t[2]]});
triangle.scale(scale);
svg_vis.draw(triangle, "green");
}
// prevent visualization in test
CHECK(false);
}
} // namespace
TEST_CASE("Triangulate rectangle with restriction on edge", "[Triangulation]")
{
// 0 1 2 3
Points points = {Point(1, 1), Point(2, 1), Point(2, 2), Point(1, 2)};
Triangulation::HalfEdges edges1 = {{1, 3}};
std::vector<Vec3i> indices1 = Triangulation::triangulate(points, edges1);
auto check = [](int i1, int i2, Vec3i t) -> bool {
return true;
return (t[0] == i1 || t[1] == i1 || t[2] == i1) &&
(t[0] == i2 || t[1] == i2 || t[2] == i2);
};
REQUIRE(indices1.size() == 2);
int i1 = edges1.begin()->first, i2 = edges1.begin()->second;
CHECK(check(i1, i2, indices1[0]));
CHECK(check(i1, i2, indices1[1]));
Triangulation::HalfEdges edges2 = {{0, 2}};
std::vector<Vec3i> indices2 = Triangulation::triangulate(points, edges2);
REQUIRE(indices2.size() == 2);
i1 = edges2.begin()->first;
i2 = edges2.begin()->second;
CHECK(check(i1, i2, indices2[0]));
CHECK(check(i1, i2, indices2[1]));
}
TEST_CASE("Triangulation polygon", "[triangulation]")
{
Points points = {Point(416, 346), Point(445, 362), Point(463, 389),
Point(469, 427), Point(445, 491)};
Polygon polygon(points);
Polygons polygons({polygon});
ExPolygon expolygon(points);
ExPolygons expolygons({expolygon});
std::vector<Vec3i> tp = Triangulation::triangulate(polygon);
std::vector<Vec3i> tps = Triangulation::triangulate(polygons);
std::vector<Vec3i> tep = Triangulation::triangulate(expolygon);
std::vector<Vec3i> teps = Triangulation::triangulate(expolygons);
//Private::store_trinagulation(expolygons, teps);
CHECK(tp.size() == tps.size());
CHECK(tep.size() == teps.size());
CHECK(tp.size() == tep.size());
CHECK(tp.size() == 3);
}
TEST_CASE("Triangulation M shape polygon", "[triangulation]")
{
// 0 1 2 3 4
Polygon shape_M = {Point(0, 0), Point(2, 0), Point(2, 2), Point(1, 1), Point(0, 2)};
std::vector<Vec3i> triangles = Triangulation::triangulate(shape_M);
// Check outer triangle is not contain
std::set<int> outer_triangle = {2, 3, 4};
bool is_in = false;
for (const Vec3i &t : triangles) {
for (size_t i = 0; i < 3; i++) {
int index = t[i];
if (outer_triangle.find(index) == outer_triangle.end()) {
is_in = false;
break;
} else {
is_in = true;
}
}
if (is_in) break;
}
//Private::store_trinagulation({ExPolygon(shape_M)}, triangles);
CHECK(triangles.size() == 3);
CHECK(!is_in);
}
// same point in triangulation are not Supported
TEST_CASE("Triangulation 2 polygons with same point", "[triangulation]")
{
Slic3r::Polygon polygon1 = {
Point(416, 346), Point(445, 362),
Point(463, 389), Point(469, 427) /* This point */,
Point(445, 491)
};
Slic3r::Polygon polygon2 = {
Point(495, 488), Point(469, 427) /* This point */,
Point(495, 364)
};
ExPolygons shape2d = {ExPolygon(polygon1), ExPolygon(polygon2)};
std::vector<Vec3i> shape_triangles = Triangulation::triangulate(shape2d);
//Private::store_trinagulation(shape2d, shape_triangles);
CHECK(shape_triangles.size() == 4);
}

View File

@@ -0,0 +1,35 @@
#include <catch2/catch.hpp>
#include "libslic3r/libslic3r.h"
SCENARIO("Test fast_round_up()") {
using namespace Slic3r;
THEN("fast_round_up<int>(1.5) is 2") {
REQUIRE(fast_round_up<int>(1.5) == 2);
}
THEN("fast_round_up<int>(1.499999999999999) is 1") {
REQUIRE(fast_round_up<int>(1.499999999999999) == 1);
}
THEN("fast_round_up<int>(0.5) is 1") {
REQUIRE(fast_round_up<int>(0.5) == 1);
}
THEN("fast_round_up<int>(0.49999999999999994) is 0") {
REQUIRE(fast_round_up<int>(0.49999999999999994) == 0);
}
THEN("fast_round_up<int>(-0.5) is 0") {
REQUIRE(fast_round_up<int>(-0.5) == 0);
}
THEN("fast_round_up<int>(-0.51) is -1") {
REQUIRE(fast_round_up<int>(-0.51) == -1);
}
THEN("fast_round_up<int>(-0.51) is -1") {
REQUIRE(fast_round_up<int>(-0.51) == -1);
}
THEN("fast_round_up<int>(-1.5) is -1") {
REQUIRE(fast_round_up<int>(-1.5) == -1);
}
THEN("fast_round_up<int>(-1.51) is -2") {
REQUIRE(fast_round_up<int>(-1.51) == -2);
}
}

File diff suppressed because it is too large Load Diff