WIP "ensure verticall wall thickness" rework:

1) New region expansion code to propagate wave from a boundary
   of a region inside of it.
2) get_extents() extended with a template attribute to work with
   zero area data sets.
3) ClipperZUtils.hpp for handling Clipper operation with Z coordinate
   (for source contour identification)
This commit is contained in:
Vojtech Bubnik 2022-12-20 09:09:10 +01:00
parent d3734aa5ae
commit 11c0e567a6
19 changed files with 964 additions and 85 deletions

View file

@ -1,7 +1,7 @@
#include <catch2/catch.hpp>
#include "test_data.hpp"
#include "clipper/clipper_z.hpp"
#include "libslic3r/ClipperZUtils.hpp"
#include "libslic3r/clipper.hpp"
using namespace Slic3r;
@ -132,3 +132,72 @@ SCENARIO("Clipper Z", "[ClipperZ]")
REQUIRE(pt.z() == 1);
}
SCENARIO("Intersection with multiple polylines", "[ClipperZ]")
{
// 1000x1000 CCQ square
ClipperLib_Z::Path clip { { 0, 0, 1 }, { 1000, 0, 1 }, { 1000, 1000, 1 }, { 0, 1000, 1 } };
// Two lines interseting inside the square above, crossing the bottom edge of the square.
ClipperLib_Z::Path line1 { { +100, -100, 2 }, { +900, +900, 2 } };
ClipperLib_Z::Path line2 { { +100, +900, 3 }, { +900, -100, 3 } };
ClipperLib_Z::Clipper clipper;
ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections;
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
clipper.ZFillFunction(visitor.clipper_callback());
clipper.AddPath(line1, ClipperLib_Z::ptSubject, false);
clipper.AddPath(line2, ClipperLib_Z::ptSubject, false);
clipper.AddPath(clip, ClipperLib_Z::ptClip, true);
ClipperLib_Z::PolyTree polytree;
ClipperLib_Z::Paths paths;
clipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(polytree, paths);
REQUIRE(paths.size() == 2);
THEN("First output polyline is a trimmed 2nd line") {
// Intermediate point (intersection) was removed)
REQUIRE(paths.front().size() == 2);
REQUIRE(paths.front().front().z() == 3);
REQUIRE(paths.front().back().z() < 0);
REQUIRE(intersections[- paths.front().back().z() - 1] == std::pair<coord_t, coord_t>(1, 3));
}
THEN("Second output polyline is a trimmed 1st line") {
// Intermediate point (intersection) was removed)
REQUIRE(paths[1].size() == 2);
REQUIRE(paths[1].front().z() < 0);
REQUIRE(paths[1].back().z() == 2);
REQUIRE(intersections[- paths[1].front().z() - 1] == std::pair<coord_t, coord_t>(1, 2));
}
}
SCENARIO("Interseting a closed loop as an open polyline", "[ClipperZ]")
{
// 1000x1000 CCQ square
ClipperLib_Z::Path clip{ { 0, 0, 1 }, { 1000, 0, 1 }, { 1000, 1000, 1 }, { 0, 1000, 1 } };
// Two lines interseting inside the square above, crossing the bottom edge of the square.
ClipperLib_Z::Path rect{ { 500, 500, 2}, { 500, 1500, 2 }, { 1500, 1500, 2}, { 500, 1500, 2}, { 500, 500, 2 } };
ClipperLib_Z::Clipper clipper;
clipper.AddPath(rect, ClipperLib_Z::ptSubject, false);
clipper.AddPath(clip, ClipperLib_Z::ptClip, true);
ClipperLib_Z::PolyTree polytree;
ClipperLib_Z::Paths paths;
ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections;
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
clipper.ZFillFunction(visitor.clipper_callback());
clipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(std::move(polytree), paths);
THEN("Open polyline is clipped into two pieces") {
REQUIRE(paths.size() == 2);
REQUIRE(paths.front().size() == 2);
REQUIRE(paths.back().size() == 2);
REQUIRE(paths.front().front().z() == 2);
REQUIRE(paths.back().back().z() == 2);
REQUIRE(paths.front().front().x() == paths.back().back().x());
REQUIRE(paths.front().front().y() == paths.back().back().y());
}
}

View file

@ -23,6 +23,7 @@ add_executable(${_TEST_NAME}_tests
test_stl.cpp
test_meshboolean.cpp
test_marchingsquares.cpp
test_region_expansion.cpp
test_timeutils.cpp
test_utils.cpp
test_voronoi.cpp

View file

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