Add the full source of BambuStudio

using version 1.0.10
This commit is contained in:
lane.wei 2022-07-15 23:37:19 +08:00 committed by Lane.Wei
parent 30bcadab3e
commit 1555904bef
3771 changed files with 1251328 additions and 0 deletions

View file

@ -0,0 +1,40 @@
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_clipper_offset.cpp
test_clipper_utils.cpp
test_config.cpp
test_elephant_foot_compensation.cpp
test_geometry.cpp
test_placeholder_parser.cpp
test_polygon.cpp
test_mutable_polygon.cpp
test_mutable_priority_queue.cpp
test_stl.cpp
test_meshboolean.cpp
test_marchingsquares.cpp
test_timeutils.cpp
test_voronoi.cpp
test_optimizers.cpp
test_png_io.cpp
test_timeutils.cpp
test_indexed_triangle_set.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)
bambuslicer_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,41 @@
#include <catch_main.hpp>
#include "libslic3r/Utils.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);
}
}
}

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/Prusa.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/prusa.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/Prusa.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,60 @@
#include <catch2/catch.hpp>
#include <test_utils.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/AABBTreeIndirect.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.));
}

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,301 @@
#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 }, { 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, { 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, { 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()));
}
}
}
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());
}
}
}
}
template<e_ordering o = e_ordering::OFF, class P, class Tree>
double polytree_area(const Tree &tree, std::vector<P> *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,239 @@
#include <catch2/catch.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]") {
GIVEN("A config generated from default options") {
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_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);
}
}
}
}
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,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));
}
}
}
}

View file

@ -0,0 +1,710 @@
#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(std::vector<Point>({
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 = std::vector<Point>({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(std::vector<Point>({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));
}
TEST_CASE("Bounding boxes are scaled appropriately", "[Geometry]"){
BoundingBox bb(std::vector<Point>({Point(0, 1), Point(10, 2), Point(20, 2)}));
bb.scale(2);
REQUIRE(bb.min == Point(0,2));
REQUIRE(bb.max == Point(40,4));
}
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(std::vector<Point>({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") {
std::vector<Point> 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("Polygon convex/concave detection", "[Geometry]"){
GIVEN(("A Square with dimension 100")){
auto square = Slic3r::Polygon /*new_scale*/(std::vector<Point>({
Point(100,100),
Point(200,100),
Point(200,200),
Point(100,200)}));
THEN("It has 4 convex points counterclockwise"){
REQUIRE(square.concave_points(PI*4/3).size() == 0);
REQUIRE(square.convex_points(PI*2/3).size() == 4);
}
THEN("It has 4 concave points clockwise"){
square.make_clockwise();
REQUIRE(square.concave_points(PI*4/3).size() == 4);
REQUIRE(square.convex_points(PI*2/3).size() == 0);
}
}
GIVEN("A Square with an extra colinearvertex"){
auto square = Slic3r::Polygon /*new_scale*/(std::vector<Point>({
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(PI*4/3).size() == 0);
REQUIRE(square.convex_points(PI*2/3).size() == 4);
}
}
GIVEN("A Square with an extra collinear vertex in different order"){
auto square = Slic3r::Polygon /*new_scale*/(std::vector<Point>({
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(PI*4/3).size() == 0);
REQUIRE(square.convex_points(PI*2/3).size() == 4);
}
}
GIVEN("A triangle"){
auto triangle = Slic3r::Polygon(std::vector<Point>({
Point(16000170,26257364),
Point(714223,461012),
Point(31286371,461008)
}));
THEN("it has three convex vertices"){
REQUIRE(triangle.concave_points(PI*4/3).size() == 0);
REQUIRE(triangle.convex_points(PI*2/3).size() == 3);
}
}
GIVEN("A triangle with an extra collinear point"){
auto triangle = Slic3r::Polygon(std::vector<Point>({
Point(16000170,26257364),
Point(714223,461012),
Point(20000000,461012),
Point(31286371,461012)
}));
THEN("it has three convex vertices"){
REQUIRE(triangle.concave_points(PI*4/3).size() == 0);
REQUIRE(triangle.convex_points(PI*2/3).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(std::vector<Point>({
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(PI*4/3).size() == 6);
REQUIRE(polygon.convex_points(PI*2/3).size() == 10);
}
}
}
TEST_CASE("Triangle Simplification does not result in less than 3 points", "[Geometry]"){
auto triangle = Slic3r::Polygon(std::vector<Point>({
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 prusa 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);
}
}

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,311 @@
#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;
}
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);
}
CompareConfig cfg;
cfg.max_average_distance = 0.014f;
cfg.max_distance = 0.75f;
CHECK(is_similar(its, its_, cfg));
CHECK(is_similar(its_, its, cfg));
}
#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));
}
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;
}
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(!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,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::RasterBase::PixelDim &pxd)
{
return pxd.w_mm * pxd.h_mm;
}
static Slic3r::sla::RasterGrayscaleAA create_raster(
const sla::RasterBase::Resolution &res,
double disp_w = 100.,
double disp_h = 100.)
{
sla::RasterBase::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::RasterBase::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::RasterBase::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::RasterBase::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::RasterBase::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,25 @@
#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));
}

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,442 @@
#include <catch2/catch.hpp>
#include <queue>
#include "libslic3r/MutablePriorityQueue.hpp"
// 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{
int id;
float val;
};
size_t count = 50000;
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);
for (size_t id = 0; id < count; id++) {
MyValue mv;
mv.id = id;
mv.val = rand();
q.push(mv);
}
MyValue it = q.top(); // copy
q.pop();
bool valid = (it.id != 0) && (idxs[0] < 3 * count);
CHECK(valid);
}
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; };
size_t ord = 0;
for (size_t id = 0; id < count; id++) {
MyValue mv;
mv.id = ord++;
mv.val = rand_val();
q.push(mv);
}
auto check = [&]()->bool{
for (size_t i = 0; i < idxs.size(); ++i) {
if (dels[i]) continue;
size_t qid = idxs[i];
if (qid > 3*count) {
return false;
}
MyValue &mv = q[qid];
if (mv.id != i) {
return false; // ERROR
}
}
return true;
};
CHECK(check()); // initial check
auto get_valid_id = [&]()->int {
int id = 0;
do {
id = rand() % count;
} while (dels[id]);
return id;
};
for (size_t i = 0; i < 100; i++) {
MyValue it = q.top(); // copy
q.pop();
dels[it.id] = true;
CHECK(check());
if (i % 20 == 0) {
it.val = rand_val();
q.push(it);
dels[it.id] = false;
CHECK(check());
continue;
}
int id = get_valid_id();
q.remove(idxs[id]);
dels[id] = true;
CHECK(check());
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,113 @@
#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;
parser.apply_config(config);
parser.set("foo", 0);
parser.set("bar", 2);
parser.set("num_extruders", 4);
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"); }
// 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"); }
// 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)")); }
SECTION("boolean expression parser: ternary false") { REQUIRE(! boolean_expression("(12 == 21/2) ? (1 - 1 == 0) : (2 * 2 == 3)")); }
SECTION("boolean expression parser: ternary false 2") { REQUIRE(boolean_expression("(12 == 13) ? (1 - 1 == 3) : (2 * 2 == 4)")); }
SECTION("boolean expression parser: ternary true 2") { REQUIRE(! boolean_expression("(12 == 2 * 6) ? (1 - 1 == 3) : (2 * 2 == 4)")); }
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("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)")); }
}

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::RasterBase::Resolution &res)
{
sla::RasterBase::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,150 @@
#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);
}
}
}
}

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,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);
}

File diff suppressed because it is too large Load diff