mirror of
https://github.com/QIDITECH/QIDISlicer.git
synced 2026-02-02 17:08:42 +03:00
PRUSA 2.7.0
This commit is contained in:
@@ -6,6 +6,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_aabbindirect.cpp
|
||||
test_kdtreeindirect.cpp
|
||||
test_arachne.cpp
|
||||
test_arc_welder.cpp
|
||||
test_clipper_offset.cpp
|
||||
test_clipper_utils.cpp
|
||||
test_color.cpp
|
||||
@@ -38,8 +39,10 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_astar.cpp
|
||||
test_anyptr.cpp
|
||||
test_jump_point_search.cpp
|
||||
../data/qidiparts.cpp
|
||||
../data/qidiparts.hpp
|
||||
test_support_spots_generator.cpp
|
||||
../data/prusaparts.cpp
|
||||
../data/prusaparts.hpp
|
||||
test_static_map.cpp
|
||||
)
|
||||
|
||||
if (TARGET OpenVDB::openvdb)
|
||||
|
||||
@@ -743,4 +743,23 @@ TEST_CASE("Arachne - #10034 - Degenerated Voronoi diagram - That wasn't fixed by
|
||||
#ifdef ARACHNE_DEBUG_OUT
|
||||
export_perimeters_to_svg(debug_out_path("arachne-degenerated-diagram-10034-rotation-not-works.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
|
||||
#endif
|
||||
}
|
||||
TEST_CASE("Arachne - SPE-1837 - No perimeters generated", "[ArachneNoPerimetersGeneratedSPE1837]") {
|
||||
Polygon poly_0 = {
|
||||
Point( 10000000, 10000000),
|
||||
Point(-10000000, 10000000),
|
||||
Point(-10000000, -10000000),
|
||||
Point( 10000000, -10000000)
|
||||
};
|
||||
|
||||
Polygons polygons = {poly_0};
|
||||
coord_t ext_perimeter_spacing = 300000;
|
||||
coord_t perimeter_spacing = 700000;
|
||||
coord_t inset_count = 1;
|
||||
|
||||
Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults());
|
||||
wall_tool_paths.generate();
|
||||
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
|
||||
|
||||
REQUIRE(!perimeters.empty());
|
||||
}
|
||||
511
tests/libslic3r/test_arc_welder.cpp
Normal file
511
tests/libslic3r/test_arc_welder.cpp
Normal file
@@ -0,0 +1,511 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <test_utils.hpp>
|
||||
|
||||
#include <random>
|
||||
|
||||
#include <libslic3r/Geometry/ArcWelder.hpp>
|
||||
#include <libslic3r/Geometry/Circle.hpp>
|
||||
#include <libslic3r/SVG.hpp>
|
||||
#include <libslic3r/libslic3r.h>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("arc basics", "[ArcWelder]") {
|
||||
using namespace Slic3r::Geometry;
|
||||
|
||||
WHEN("arc from { 2000.f, 1000.f } to { 1000.f, 2000.f }") {
|
||||
Vec2f p1{ 2000.f, 1000.f };
|
||||
Vec2f p2{ 1000.f, 2000.f };
|
||||
float r{ 1000.f };
|
||||
const double s = 1000.f / sqrt(2.);
|
||||
THEN("90 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, true);
|
||||
Vec2f m = ArcWelder::arc_middle_point(p1, p2, r, true);
|
||||
REQUIRE(is_approx(c, Vec2f{ 1000.f, 1000.f }));
|
||||
REQUIRE(ArcWelder::arc_angle(p1, p2, r) == Approx(0.5 * M_PI));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * 0.5 * M_PI).epsilon(0.001));
|
||||
REQUIRE(is_approx(m, Vec2f{ 1000.f + s, 1000.f + s }, 0.001f));
|
||||
}
|
||||
THEN("90 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, false);
|
||||
Vec2f m = ArcWelder::arc_middle_point(p1, p2, r, false);
|
||||
REQUIRE(is_approx(c, Vec2f{ 2000.f, 2000.f }));
|
||||
REQUIRE(is_approx(m, Vec2f{ 2000.f - s, 2000.f - s }, 0.001f));
|
||||
}
|
||||
THEN("270 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, true);
|
||||
Vec2f m = ArcWelder::arc_middle_point(p1, p2, - r, true);
|
||||
REQUIRE(is_approx(c, Vec2f{ 2000.f, 2000.f }));
|
||||
REQUIRE(ArcWelder::arc_angle(p1, p2, - r) == Approx(1.5 * M_PI));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * 1.5 * M_PI).epsilon(0.001));
|
||||
REQUIRE(is_approx(m, Vec2f{ 2000.f + s, 2000.f + s }, 0.001f));
|
||||
}
|
||||
THEN("270 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, false);
|
||||
Vec2f m = ArcWelder::arc_middle_point(p1, p2, - r, false);
|
||||
REQUIRE(is_approx(c, Vec2f{ 1000.f, 1000.f }));
|
||||
REQUIRE(is_approx(m, Vec2f{ 1000.f - s, 1000.f - s }, 0.001f));
|
||||
}
|
||||
}
|
||||
WHEN("arc from { 1707.11f, 1707.11f } to { 1000.f, 2000.f }") {
|
||||
Vec2f p1{ 1707.11f, 1707.11f };
|
||||
Vec2f p2{ 1000.f, 2000.f };
|
||||
float r{ 1000.f };
|
||||
Vec2f center1 = Vec2f{ 1000.f, 1000.f };
|
||||
// Center on the other side of the CCW arch.
|
||||
Vec2f center2 = center1 + 2. * (0.5 * (p1 + p2) - center1);
|
||||
THEN("45 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, true);
|
||||
REQUIRE(is_approx(c, center1, 1.f));
|
||||
REQUIRE(ArcWelder::arc_angle(p1, p2, r) == Approx(0.25 * M_PI));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * 0.25 * M_PI).epsilon(0.001));
|
||||
}
|
||||
THEN("45 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, false);
|
||||
REQUIRE(is_approx(c, center2, 1.f));
|
||||
}
|
||||
THEN("315 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, true);
|
||||
REQUIRE(is_approx(c, center2, 1.f));
|
||||
REQUIRE(ArcWelder::arc_angle(p1, p2, - r) == Approx((2. - 0.25) * M_PI));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * (2. - 0.25) * M_PI).epsilon(0.001));
|
||||
}
|
||||
THEN("315 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, false);
|
||||
REQUIRE(is_approx(c, center1, 1.f));
|
||||
}
|
||||
}
|
||||
WHEN("arc from { 1866.f, 1500.f } to { 1000.f, 2000.f }") {
|
||||
Vec2f p1{ 1866.f, 1500.f };
|
||||
Vec2f p2{ 1000.f, 2000.f };
|
||||
float r{ 1000.f };
|
||||
Vec2f center1 = Vec2f{ 1000.f, 1000.f };
|
||||
// Center on the other side of the CCW arch.
|
||||
Vec2f center2 = center1 + 2. * (0.5 * (p1 + p2) - center1);
|
||||
THEN("60 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, true);
|
||||
REQUIRE(is_approx(c, center1, 1.f));
|
||||
REQUIRE(is_approx(ArcWelder::arc_angle(p1, p2, r), float(M_PI / 3.), 0.001f));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * M_PI / 3.).epsilon(0.001));
|
||||
}
|
||||
THEN("60 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, false);
|
||||
REQUIRE(is_approx(c, center2, 1.f));
|
||||
}
|
||||
THEN("300 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, true);
|
||||
REQUIRE(is_approx(c, center2, 1.f));
|
||||
REQUIRE(is_approx(ArcWelder::arc_angle(p1, p2, - r), float((2. - 1./3.) * M_PI), 0.001f));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * (2. - 1. / 3.) * M_PI).epsilon(0.001));
|
||||
}
|
||||
THEN("300 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, false);
|
||||
REQUIRE(is_approx(c, center1, 1.f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("arc discretization", "[ArcWelder]") {
|
||||
using namespace Slic3r::Geometry;
|
||||
WHEN("arc from { 2, 1 } to { 1, 2 }") {
|
||||
const Point p1 = Point::new_scale(2., 1.);
|
||||
const Point p2 = Point::new_scale(1., 2.);
|
||||
const Point center = Point::new_scale(1., 1.);
|
||||
const float radius = scaled<float>(1.);
|
||||
const float resolution = scaled<float>(0.002);
|
||||
auto test = [center, resolution, radius](const Point &p1, const Point &p2, const float r, const bool ccw) {
|
||||
Vec2f c = ArcWelder::arc_center(p1.cast<float>(), p2.cast<float>(), r, ccw);
|
||||
REQUIRE((p1.cast<float>() - c).norm() == Approx(radius));
|
||||
REQUIRE((c - center.cast<float>()).norm() == Approx(0.));
|
||||
Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution);
|
||||
REQUIRE(pts.size() >= 2);
|
||||
REQUIRE(pts.front() == p1);
|
||||
REQUIRE(pts.back() == p2);
|
||||
for (const Point &p : pts)
|
||||
REQUIRE(std::abs((p.cast<double>() - c.cast<double>()).norm() - double(radius)) < double(resolution + SCALED_EPSILON));
|
||||
};
|
||||
THEN("90 degrees arc, CCW") {
|
||||
test(p1, p2, radius, true);
|
||||
}
|
||||
THEN("270 degrees arc, CCW") {
|
||||
test(p2, p1, - radius, true);
|
||||
}
|
||||
THEN("90 degrees arc, CW") {
|
||||
test(p2, p1, radius, false);
|
||||
}
|
||||
THEN("270 degrees arc, CW") {
|
||||
test(p1, p2, - radius, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_arc_fit_variance(const Point &p1, const Point &p2, const float r, const float r_fit, const bool ccw, const Points::const_iterator begin, const Points::const_iterator end)
|
||||
{
|
||||
using namespace Slic3r::Geometry;
|
||||
double variance = ArcWelder::arc_fit_variance(p1, p2, r, ccw, begin, end);
|
||||
double variance_fit = ArcWelder::arc_fit_variance(p1, p2, r_fit, ccw, begin, end);
|
||||
REQUIRE(variance_fit < variance);
|
||||
};
|
||||
|
||||
void test_arc_fit_max_deviation(const Point &p1, const Point &p2, const float r, const float r_fit, const bool ccw, const Points::const_iterator begin, const Points::const_iterator end)
|
||||
{
|
||||
using namespace Slic3r::Geometry;
|
||||
double max_deviation = ArcWelder::arc_fit_max_deviation(p1, p2, r, ccw, begin, end);
|
||||
double max_deviation_fit = ArcWelder::arc_fit_max_deviation(p1, p2, r_fit, ccw, begin, end);
|
||||
REQUIRE(std::abs(max_deviation_fit) < std::abs(max_deviation));
|
||||
};
|
||||
|
||||
void test_arc_fit(const Point &p1, const Point &p2, const float r, const float r_fit, const bool ccw, const Points::const_iterator begin, const Points::const_iterator end)
|
||||
{
|
||||
test_arc_fit_variance(p1, p2, r, r_fit, ccw, begin, end);
|
||||
test_arc_fit_max_deviation(p1, p2, r, r_fit, ccw, begin, end);
|
||||
};
|
||||
|
||||
TEST_CASE("arc fitting", "[ArcWelder]") {
|
||||
using namespace Slic3r::Geometry;
|
||||
|
||||
WHEN("arc from { 2, 1 } to { 1, 2 }") {
|
||||
const Point p1 = Point::new_scale(2., 1.);
|
||||
const Point p2 = Point::new_scale(1., 2.);
|
||||
const Point center = Point::new_scale(1., 1.);
|
||||
const float radius = scaled<float>(1.);
|
||||
const float resolution = scaled<float>(0.002);
|
||||
auto test = [center, resolution](const Point &p1, const Point &p2, const float r, const bool ccw) {
|
||||
Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution);
|
||||
ArcWelder::Path path = ArcWelder::fit_path(pts, resolution + SCALED_EPSILON, ArcWelder::default_scaled_resolution);
|
||||
REQUIRE(path.size() == 2);
|
||||
REQUIRE(path.front().point == p1);
|
||||
REQUIRE(path.front().radius == 0.f);
|
||||
REQUIRE(path.back().point == p2);
|
||||
REQUIRE(path.back().ccw() == ccw);
|
||||
test_arc_fit(p1, p2, r, path.back().radius, true, pts.begin(), pts.end());
|
||||
};
|
||||
THEN("90 degrees arc, CCW is fitted") {
|
||||
test(p1, p2, radius, true);
|
||||
}
|
||||
THEN("270 degrees arc, CCW is fitted") {
|
||||
test(p2, p1, - radius, true);
|
||||
}
|
||||
THEN("90 degrees arc, CW is fitted") {
|
||||
test(p2, p1, radius, false);
|
||||
}
|
||||
THEN("270 degrees arc, CW is fitted") {
|
||||
test(p1, p2, - radius, false);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("arc from { 2, 1 } to { 1, 2 }, another arc from { 2, 1 } to { 0, 2 }, tangentially connected") {
|
||||
const Point p1 = Point::new_scale(2., 1.);
|
||||
const Point p2 = Point::new_scale(1., 2.);
|
||||
const Point p3 = Point::new_scale(0., 3.);
|
||||
const Point center1 = Point::new_scale(1., 1.);
|
||||
const Point center2 = Point::new_scale(1., 3.);
|
||||
const float radius = scaled<float>(1.);
|
||||
const float resolution = scaled<float>(0.002);
|
||||
auto test = [center1, center2, resolution](const Point &p1, const Point &p2, const Point &p3, const float r, const bool ccw) {
|
||||
Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution);
|
||||
size_t num_pts1 = pts.size();
|
||||
{
|
||||
Points pts2 = ArcWelder::arc_discretize(p2, p3, - r, ! ccw, resolution);
|
||||
REQUIRE(pts.back() == pts2.front());
|
||||
pts.insert(pts.end(), std::next(pts2.begin()), pts2.end());
|
||||
}
|
||||
ArcWelder::Path path = ArcWelder::fit_path(pts, resolution + SCALED_EPSILON, ArcWelder::default_scaled_resolution);
|
||||
REQUIRE(path.size() == 3);
|
||||
REQUIRE(path.front().point == p1);
|
||||
REQUIRE(path.front().radius == 0.f);
|
||||
REQUIRE(path[1].point == p2);
|
||||
REQUIRE(path[1].ccw() == ccw);
|
||||
REQUIRE(path.back().point == p3);
|
||||
REQUIRE(path.back().ccw() == ! ccw);
|
||||
test_arc_fit(p1, p2, r, path[1].radius, ccw, pts.begin(), pts.begin() + num_pts1);
|
||||
test_arc_fit(p2, p3, - r, path.back().radius, ! ccw, pts.begin() + num_pts1 - 1, pts.end());
|
||||
};
|
||||
THEN("90 degrees arches, CCW are fitted") {
|
||||
test(p1, p2, p3, radius, true);
|
||||
}
|
||||
THEN("270 degrees arc, CCW is fitted") {
|
||||
test(p3, p2, p1, -radius, true);
|
||||
}
|
||||
THEN("90 degrees arc, CW is fitted") {
|
||||
test(p3, p2, p1, radius, false);
|
||||
}
|
||||
THEN("270 degrees arc, CW is fitted") {
|
||||
test(p1, p2, p3, -radius, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("least squares arc fitting, interpolating end points", "[ArcWelder]") {
|
||||
using namespace Slic3r::Geometry;
|
||||
|
||||
// Generate bunch of random arches.
|
||||
const coord_t max_coordinate = scaled<coord_t>(sqrt(250. - 1.));
|
||||
static constexpr const double min_radius = scaled<double>(0.01);
|
||||
static constexpr const double max_radius = scaled<double>(250.);
|
||||
// static constexpr const double deviation = scaled<double>(0.5);
|
||||
static constexpr const double deviation = scaled<double>(0.1);
|
||||
// Seeded with a fixed seed, to be repeatable.
|
||||
std::mt19937 rng(867092346);
|
||||
std::uniform_int_distribution<int32_t> coord_sampler(0, int32_t(max_coordinate));
|
||||
std::uniform_real_distribution<double> angle_sampler(0.001, 2. * M_PI - 0.001);
|
||||
std::uniform_real_distribution<double> radius_sampler(min_radius, max_radius);
|
||||
std::uniform_int_distribution<int> num_samples_sampler(1, 100);
|
||||
auto test_arc_fitting = [&rng, &coord_sampler, &num_samples_sampler, &angle_sampler, &radius_sampler]() {
|
||||
auto sample_point = [&rng, &coord_sampler]() {
|
||||
return Vec2d(coord_sampler(rng), coord_sampler(rng));
|
||||
};
|
||||
// Start and end point of the arc:
|
||||
Vec2d center_pos = sample_point();
|
||||
double angle0 = angle_sampler(rng);
|
||||
double angle = angle_sampler(rng);
|
||||
double radius = radius_sampler(rng);
|
||||
Vec2d v1 = Eigen::Rotation2D(angle0) * Vec2d(1., 0.);
|
||||
Vec2d v2 = Eigen::Rotation2D(angle0 + angle) * Vec2d(1., 0.);
|
||||
Vec2d start_pos = center_pos + radius * v1;
|
||||
Vec2d end_pos = center_pos + radius * v2;
|
||||
std::vector<Vec2d> samples;
|
||||
size_t num_samples = num_samples_sampler(rng);
|
||||
for (size_t i = 0; i < num_samples; ++ i) {
|
||||
double sample_r = sqrt(std::uniform_real_distribution<double>(sqr(std::max(0., radius - deviation)), sqr(radius + deviation))(rng));
|
||||
double sample_a = std::uniform_real_distribution<double>(0., angle)(rng);
|
||||
Vec2d pt = center_pos + Eigen::Rotation2D(angle0 + sample_a) * Vec2d(sample_r, 0.);
|
||||
samples.emplace_back(pt);
|
||||
assert((pt - center_pos).norm() > radius - deviation - SCALED_EPSILON);
|
||||
assert((pt - center_pos).norm() < radius + deviation + SCALED_EPSILON);
|
||||
}
|
||||
// Vec2d new_center = ArcWelder::arc_fit_center_algebraic_ls(start_pos, end_pos, center_pos, samples.begin(), samples.end());
|
||||
THEN("Center is fitted correctly") {
|
||||
std::optional<Vec2d> new_center_opt = ArcWelder::arc_fit_center_gauss_newton_ls(start_pos, end_pos, center_pos, samples.begin(), samples.end(), 10);
|
||||
REQUIRE(new_center_opt);
|
||||
if (new_center_opt) {
|
||||
Vec2d new_center = *new_center_opt;
|
||||
double new_radius = (new_center - start_pos).norm();
|
||||
double total_deviation = 0;
|
||||
double new_total_deviation = 0;
|
||||
for (const Vec2d &s : samples) {
|
||||
total_deviation += sqr((s - center_pos).norm() - radius);
|
||||
new_total_deviation += sqr((s - new_center).norm() - radius);
|
||||
}
|
||||
total_deviation /= double(num_samples);
|
||||
new_total_deviation /= double(num_samples);
|
||||
REQUIRE(new_total_deviation <= total_deviation);
|
||||
|
||||
#if 0
|
||||
double dist = (center_pos - new_center).norm();
|
||||
printf("Radius: %lf, Angle: %lf deg, Samples: %d, Dist: %lf\n", unscaled<double>(radius), 180. * angle / M_PI, int(num_samples), unscaled<double>(dist));
|
||||
// REQUIRE(is_approx(center_pos, new_center, deviation));
|
||||
if (dist > scaled<double>(1.)) {
|
||||
static int irun = 0;
|
||||
char path[2048];
|
||||
sprintf(path, "d:\\temp\\debug\\circle-fit-%d.svg", irun++);
|
||||
Eigen::AlignedBox<double, 2> bbox(start_pos, end_pos);
|
||||
for (const Vec2d& sample : samples)
|
||||
bbox.extend(sample);
|
||||
bbox.extend(center_pos);
|
||||
Slic3r::SVG svg(path, BoundingBox(bbox.min().cast<coord_t>(), bbox.max().cast<coord_t>()).inflated(bbox.sizes().maxCoeff() * 0.05));
|
||||
Points arc_src;
|
||||
for (size_t i = 0; i <= 1000; ++i)
|
||||
arc_src.emplace_back((center_pos + Eigen::Rotation2D(angle0 + double(i) * angle / 1000.) * Vec2d(radius, 0.)).cast<coord_t>());
|
||||
svg.draw(Polyline(arc_src));
|
||||
Points arc_new;
|
||||
double new_arc_angle = ArcWelder::arc_angle(start_pos, end_pos, (new_center - start_pos).norm());
|
||||
for (size_t i = 0; i <= 1000; ++i)
|
||||
arc_new.emplace_back((new_center + Eigen::Rotation2D(double(i) * new_arc_angle / 1000.) * (start_pos - new_center)).cast<coord_t>());
|
||||
svg.draw(Polyline(arc_new), "magenta");
|
||||
svg.draw(Point(start_pos.cast<coord_t>()), "blue");
|
||||
svg.draw(Point(end_pos.cast<coord_t>()), "blue");
|
||||
svg.draw(Point(center_pos.cast<coord_t>()), "blue");
|
||||
for (const Vec2d &sample : samples)
|
||||
svg.draw(Point(sample.cast<coord_t>()), "red");
|
||||
svg.draw(Point(new_center.cast<coord_t>()), "magenta");
|
||||
}
|
||||
if (!is_approx(center_pos, new_center, scaled<double>(5.))) {
|
||||
printf("Failed\n");
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
printf("Fitting failed\n");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WHEN("Generating a random arc and randomized arc samples") {
|
||||
for (size_t i = 0; i < 1000; ++ i)
|
||||
test_arc_fitting();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("arc wedge test", "[ArcWelder]") {
|
||||
using namespace Slic3r::Geometry;
|
||||
|
||||
WHEN("test point inside wedge, arc from { 2, 1 } to { 1, 2 }") {
|
||||
const int64_t s = 1000000;
|
||||
const Vec2i64 p1{ 2 * s, s };
|
||||
const Vec2i64 p2{ s, 2 * s };
|
||||
const Vec2i64 center{ s, s };
|
||||
const int64_t radius{ s };
|
||||
auto test = [center](
|
||||
// Arc data
|
||||
const Vec2i64 &p1, const Vec2i64 &p2, const int64_t r, const bool ccw,
|
||||
// Test data
|
||||
const Vec2i64 &ptest, const bool ptest_inside) {
|
||||
const Vec2d c = ArcWelder::arc_center(p1.cast<double>(), p2.cast<double>(), double(r), ccw);
|
||||
REQUIRE(is_approx(c, center.cast<double>()));
|
||||
REQUIRE(ArcWelder::inside_arc_wedge(p1, p2, center, r > 0, ccw, ptest) == ptest_inside);
|
||||
REQUIRE(ArcWelder::inside_arc_wedge(p1.cast<double>(), p2.cast<double>(), double(r), ccw, ptest.cast<double>()) == ptest_inside);
|
||||
};
|
||||
auto test_quadrants = [center, test](
|
||||
// Arc data
|
||||
const Vec2i64 &p1, const Vec2i64 &p2, const int64_t r, const bool ccw,
|
||||
// Test data
|
||||
const Vec2i64 &ptest1, const bool ptest_inside1,
|
||||
const Vec2i64 &ptest2, const bool ptest_inside2,
|
||||
const Vec2i64 &ptest3, const bool ptest_inside3,
|
||||
const Vec2i64 &ptest4, const bool ptest_inside4) {
|
||||
test(p1, p2, r, ccw, ptest1 + center, ptest_inside1);
|
||||
test(p1, p2, r, ccw, ptest2 + center, ptest_inside2);
|
||||
test(p1, p2, r, ccw, ptest3 + center, ptest_inside3);
|
||||
test(p1, p2, r, ccw, ptest4 + center, ptest_inside4);
|
||||
};
|
||||
THEN("90 degrees arc, CCW") {
|
||||
test_quadrants(p1, p2, radius, true,
|
||||
Vec2i64{ s, s }, true,
|
||||
Vec2i64{ s, - s }, false,
|
||||
Vec2i64{ - s, s }, false,
|
||||
Vec2i64{ - s, - s }, false);
|
||||
}
|
||||
THEN("270 degrees arc, CCW") {
|
||||
test_quadrants(p2, p1, -radius, true,
|
||||
Vec2i64{ s, s }, false,
|
||||
Vec2i64{ s, - s }, true,
|
||||
Vec2i64{ - s, s }, true,
|
||||
Vec2i64{ - s, - s }, true);
|
||||
}
|
||||
THEN("90 degrees arc, CW") {
|
||||
test_quadrants(p2, p1, radius, false,
|
||||
Vec2i64{ s, s }, true,
|
||||
Vec2i64{ s, - s }, false,
|
||||
Vec2i64{ - s, s }, false,
|
||||
Vec2i64{ - s, - s }, false);
|
||||
}
|
||||
THEN("270 degrees arc, CW") {
|
||||
test_quadrants(p1, p2, -radius, false,
|
||||
Vec2i64{ s, s }, false,
|
||||
Vec2i64{ s, - s }, true,
|
||||
Vec2i64{ - s, s }, true,
|
||||
Vec2i64{ - s, - s }, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// For quantization
|
||||
//#include <libslic3r/GCode/GCodeWriter.hpp>
|
||||
|
||||
TEST_CASE("arc quantization", "[ArcWelder]") {
|
||||
using namespace Slic3r::Geometry;
|
||||
|
||||
WHEN("generating a bunch of random arches") {
|
||||
static constexpr const size_t len = 100000;
|
||||
static constexpr const coord_t max_coordinate = scaled<coord_t>(250.);
|
||||
static constexpr const float max_radius = scaled<float>(250.);
|
||||
ArcWelder::Segments path;
|
||||
path.reserve(len + 1);
|
||||
// Seeded with a fixed seed, to be repeatable.
|
||||
std::mt19937 rng(987432690);
|
||||
// Generate bunch of random arches.
|
||||
std::uniform_int_distribution<int32_t> coord_sampler(0, int32_t(max_coordinate));
|
||||
std::uniform_real_distribution<float> radius_sampler(- max_radius, max_radius);
|
||||
std::uniform_int_distribution<int> orientation_sampler(0, 1);
|
||||
path.push_back({ Point{coord_sampler(rng), coord_sampler(rng)}, 0, ArcWelder::Orientation::CCW });
|
||||
for (size_t i = 0; i < len; ++ i) {
|
||||
ArcWelder::Segment seg { Point{coord_sampler(rng), coord_sampler(rng)}, radius_sampler(rng), orientation_sampler(rng) ? ArcWelder::Orientation::CCW : ArcWelder::Orientation::CW };
|
||||
while ((seg.point.cast<double>() - path.back().point.cast<double>()).norm() > 2. * std::abs(seg.radius))
|
||||
seg = { Point{coord_sampler(rng), coord_sampler(rng)}, radius_sampler(rng), orientation_sampler(rng) ? ArcWelder::Orientation::CCW : ArcWelder::Orientation::CW };
|
||||
path.push_back(seg);
|
||||
}
|
||||
// Run the test, quantize coordinates and radius, find the maximum error of quantization comparing the two arches.
|
||||
struct ArcEval {
|
||||
double error;
|
||||
double radius;
|
||||
double angle;
|
||||
};
|
||||
std::vector<ArcEval> center_errors_R;
|
||||
center_errors_R.reserve(len);
|
||||
std::vector<double> center_errors_R_exact;
|
||||
center_errors_R_exact.reserve(len);
|
||||
std::vector<double> center_errors_IJ;
|
||||
center_errors_IJ.reserve(len);
|
||||
for (size_t i = 0; i < len; ++ i) {
|
||||
// Source arc:
|
||||
const Vec2d start_point = unscaled<double>(path[i].point);
|
||||
const Vec2d end_point = unscaled<double>(path[i + 1].point);
|
||||
const double radius = unscaled<double>(path[i + 1].radius);
|
||||
const bool ccw = path[i + 1].ccw();
|
||||
const Vec2d center = ArcWelder::arc_center(start_point, end_point, radius, ccw);
|
||||
{
|
||||
const double d1 = (start_point - center).norm();
|
||||
const double d2 = (end_point - center).norm();
|
||||
const double dx = (end_point - start_point).norm();
|
||||
assert(std::abs(d1 - std::abs(radius)) < EPSILON);
|
||||
assert(std::abs(d2 - std::abs(radius)) < EPSILON);
|
||||
}
|
||||
// Quantized arc:
|
||||
const Vec2d start_point_quantized { GCodeFormatter::quantize_xyzf(start_point.x()), GCodeFormatter::quantize_xyzf(start_point.y()) };
|
||||
const Vec2d end_point_quantized { GCodeFormatter::quantize_xyzf(end_point .x()), GCodeFormatter::quantize_xyzf(end_point .y()) };
|
||||
const double radius_quantized { GCodeFormatter::quantize_xyzf(radius) };
|
||||
const Vec2d center_quantized { GCodeFormatter::quantize_xyzf(center .x()), GCodeFormatter::quantize_xyzf(center .y()) };
|
||||
// Evaulate maximum error for the quantized arc given by the end points and radius.
|
||||
const Vec2d center_from_quantized = ArcWelder::arc_center(start_point_quantized, end_point_quantized, radius, ccw);
|
||||
const Vec2d center_reconstructed = ArcWelder::arc_center(start_point_quantized, end_point_quantized, radius_quantized, ccw);
|
||||
#if 0
|
||||
center_errors_R.push_back({
|
||||
std::abs((center_reconstructed - center).norm()),
|
||||
radius,
|
||||
ArcWelder::arc_angle(start_point, end_point, radius) * 180. / M_PI
|
||||
});
|
||||
if (center_errors_R.back().error > 0.15)
|
||||
printf("Fuj\n");
|
||||
#endif
|
||||
center_errors_R_exact.emplace_back(std::abs((center_from_quantized - center).norm()));
|
||||
if (center_errors_R_exact.back() > 0.15)
|
||||
printf("Fuj\n");
|
||||
center_errors_IJ.emplace_back(std::abs((center_quantized - center).norm()));
|
||||
if (center_errors_IJ.back() > 0.15)
|
||||
printf("Fuj\n");
|
||||
|
||||
// Adjust center of the arc to the quantized end points.
|
||||
Vec2d third_point = ArcWelder::arc_middle_point(start_point, end_point, radius, ccw);
|
||||
double third_point_radius = (third_point - center).norm();
|
||||
assert(std::abs(third_point_radius - std::abs(radius)) < EPSILON);
|
||||
std::optional<Vec2d> center_adjusted = try_circle_center(start_point_quantized, end_point_quantized, third_point, EPSILON);
|
||||
//assert(center_adjusted);
|
||||
if (center_adjusted) {
|
||||
const double radius_adjusted = (center_adjusted.value() - start_point_quantized).norm() * (radius > 0 ? 1. : -1.);
|
||||
const double radius_adjusted_quantized = GCodeFormatter::quantize_xyzf(radius_adjusted);
|
||||
// Evaulate maximum error for the quantized arc given by the end points and radius.
|
||||
const Vec2f center_reconstructed = ArcWelder::arc_center(start_point_quantized.cast<float>(), end_point_quantized.cast<float>(), float(radius_adjusted_quantized), ccw);
|
||||
double rtest = std::abs(radius_adjusted_quantized);
|
||||
double d1 = std::abs((center_reconstructed - start_point.cast<float>()).norm() - rtest);
|
||||
double d2 = std::abs((center_reconstructed - end_point.cast<float>()).norm() - rtest);
|
||||
double d3 = std::abs((center_reconstructed - third_point.cast<float>()).norm() - rtest);
|
||||
double d = std::max(d1, std::max(d2, d3));
|
||||
center_errors_R.push_back({
|
||||
d,
|
||||
radius,
|
||||
ArcWelder::arc_angle(start_point, end_point, radius) * 180. / M_PI
|
||||
});
|
||||
} else {
|
||||
printf("Adjusted circle is collinear.\n");
|
||||
}
|
||||
}
|
||||
std::sort(center_errors_R.begin(), center_errors_R.end(), [](auto l, auto r) { return l.error > r.error; });
|
||||
std::sort(center_errors_R_exact.begin(), center_errors_R_exact.end(), [](auto l, auto r) { return l > r; });
|
||||
std::sort(center_errors_IJ.begin(), center_errors_IJ.end(), [](auto l, auto r) { return l > r; });
|
||||
printf("Maximum error R: %lf\n", center_errors_R.back().error);
|
||||
printf("Maximum error R exact: %lf\n", center_errors_R_exact.back());
|
||||
printf("Maximum error IJ: %lf\n", center_errors_IJ.back());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -23,7 +23,8 @@ TEST_CASE("Cut character from surface", "[]")
|
||||
|
||||
Transform3d tr = Transform3d::Identity();
|
||||
tr.translate(Vec3d(0., 0., -z_depth));
|
||||
tr.scale(Emboss::SHAPE_SCALE);
|
||||
double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE
|
||||
tr.scale(text_shape_scale);
|
||||
Emboss::OrthoProject cut_projection(tr, Vec3d(0., 0., z_depth));
|
||||
|
||||
auto object = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5);
|
||||
@@ -158,7 +159,7 @@ TEST_CASE("CutSurface in 3mf", "[Emboss]")
|
||||
|
||||
FontProp fp = tc.style.prop;
|
||||
ExPolygons shapes = Emboss::text2shapes(ff, tc.text.c_str(), fp);
|
||||
double shape_scale = Emboss::get_shape_scale(fp, *ff.font_file);
|
||||
double shape_scale = Emboss::get_text_shape_scale(fp, *ff.font_file);
|
||||
|
||||
Emboss::OrthoProject projection = create_projection_for_cut(
|
||||
cut_projection_tr, shape_scale, get_extents(shapes), z_range);
|
||||
|
||||
@@ -195,15 +195,17 @@ TEST_CASE("Visualize glyph from font", "[Emboss]")
|
||||
#endif // VISUALIZE
|
||||
|
||||
#include "test_utils.hpp"
|
||||
#include "nanosvg/nanosvg.h" // load SVG file
|
||||
#include "libslic3r/NSVGUtils.hpp"
|
||||
#include <nanosvg/nanosvg.h> // load SVG file
|
||||
#include <libslic3r/NSVGUtils.hpp>
|
||||
#include <libslic3r/IntersectionPoints.hpp>
|
||||
ExPolygons heal_and_check(const Polygons &polygons)
|
||||
{
|
||||
Pointfs intersections_prev = intersection_points(polygons);
|
||||
IntersectionsLines intersections_prev = get_intersections(polygons);
|
||||
Points polygons_points = to_points(polygons);
|
||||
Points duplicits_prev = collect_duplicates(polygons_points);
|
||||
|
||||
ExPolygons shape = Emboss::heal_shape(polygons);
|
||||
auto [shape, success] = Emboss::heal_polygons(polygons);
|
||||
CHECK(success);
|
||||
|
||||
// Is default shape for unhealabled shape?
|
||||
bool is_default_shape =
|
||||
@@ -213,7 +215,7 @@ ExPolygons heal_and_check(const Polygons &polygons)
|
||||
shape.front().holes.front().points.size() == 4 ;
|
||||
CHECK(!is_default_shape);
|
||||
|
||||
Pointfs intersections = intersection_points(shape);
|
||||
IntersectionsLines intersections = get_intersections(shape);
|
||||
Points shape_points = to_points(shape);
|
||||
Points duplicits = collect_duplicates(shape_points);
|
||||
//{
|
||||
@@ -244,7 +246,9 @@ void scale(Polygons &polygons, double multiplicator) {
|
||||
Polygons load_polygons(const std::string &svg_file) {
|
||||
std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + svg_file;
|
||||
NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f);
|
||||
Polygons polygons = NSVGUtils::to_polygons(image);
|
||||
NSVGLineParams param{1000};
|
||||
param.scale = 10.;
|
||||
Polygons polygons = to_polygons(*image, param);
|
||||
nsvgDelete(image);
|
||||
return polygons;
|
||||
}
|
||||
@@ -258,7 +262,8 @@ TEST_CASE("Heal of 'i' in ALIENATO.TTF", "[Emboss]")
|
||||
auto a = heal_and_check(polygons);
|
||||
|
||||
Polygons scaled_shape = polygons; // copy
|
||||
scale(scaled_shape, 1 / Emboss::SHAPE_SCALE);
|
||||
double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE
|
||||
scale(scaled_shape, 1 / text_shape_scale);
|
||||
auto b = heal_and_check(scaled_shape);
|
||||
|
||||
// different scale
|
||||
@@ -282,12 +287,37 @@ TEST_CASE("Heal of 'm' in Allura_Script.ttf", "[Emboss]")
|
||||
auto a = heal_and_check(polygons);
|
||||
}
|
||||
|
||||
#include "libslic3r/NSVGUtils.hpp"
|
||||
TEST_CASE("Heal of svg contour overlap", "[Emboss]") {
|
||||
|
||||
std::string svg_file = "contour_neighbor.svg";
|
||||
auto image = nsvgParseFromFile(TEST_DATA_DIR PATH_SEPARATOR + svg_file, "mm");
|
||||
NSVGLineParams param(1e10);
|
||||
ExPolygonsWithIds shapes = create_shape_with_ids(*image, param);
|
||||
Polygons polygons;
|
||||
for (ExPolygonsWithId &shape : shapes)
|
||||
polygons.push_back(shape.expoly.front().contour);
|
||||
auto a = heal_and_check(polygons);
|
||||
}
|
||||
|
||||
// Input contour is extracted from case above "contour_neighbor.svg" with trouble shooted scale
|
||||
TEST_CASE("Heal of overlaping contour", "[Emboss]"){
|
||||
// Extracted from svg:
|
||||
Points contour{{2228926, 1543620}, {745002, 2065101}, {745002, 2065094}, {744990, 2065094}, {684487, 1466338},
|
||||
{510999, 908378}, {236555, 403250}, {-126813, -37014}, {-567074, -400382}, {-1072201, -674822},
|
||||
{-567074, -400378}, {-126813, -37010}, {236555, 403250}, {510999, 908382}, {684487, 1466346},
|
||||
{744990, 2065105}, {-2219648, 2073234}, {-2228926, -908814}, {-1646879, -2073235}};
|
||||
ExPolygons shapes = {ExPolygon{contour}};
|
||||
CHECK(Emboss::heal_expolygons(shapes));
|
||||
}
|
||||
TEST_CASE("Heal of points close to line", "[Emboss]")
|
||||
{
|
||||
std::string file_name = "points_close_to_line.svg";
|
||||
std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + file_name;
|
||||
NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f);
|
||||
Polygons polygons = NSVGUtils::to_polygons(image);
|
||||
NSVGLineParams param{1000};
|
||||
param.scale = 1.;
|
||||
Polygons polygons = to_polygons(*image, param);
|
||||
nsvgDelete(image);
|
||||
REQUIRE(polygons.size() == 1);
|
||||
Polygon polygon = polygons.front();
|
||||
@@ -309,16 +339,19 @@ The other kids at school nicknamed him Ix,\n\
|
||||
which in the language of Betelgeuse Five translates as\t\n\
|
||||
\"boy who is not able satisfactorily to explain what a Hrung is,\n\
|
||||
nor why it should choose to collapse on Betelgeuse Seven\".";
|
||||
float line_height = 10.f, depth = 2.f;
|
||||
float line_height = 10.f;
|
||||
|
||||
auto font = Emboss::create_font_file(font_path.c_str());
|
||||
REQUIRE(font != nullptr);
|
||||
|
||||
Emboss::FontFileWithCache ffwc(std::move(font));
|
||||
FontProp fp{line_height, depth};
|
||||
ExPolygons shapes = Emboss::text2shapes(ffwc, text.c_str(), fp);
|
||||
FontProp fp{line_height};
|
||||
|
||||
auto was_canceled = []() { return false; };
|
||||
ExPolygons shapes = Emboss::text2shapes(ffwc, text.c_str(), fp, was_canceled);
|
||||
REQUIRE(!shapes.empty());
|
||||
|
||||
float depth = 2.f;
|
||||
Emboss::ProjectZ projection(depth);
|
||||
indexed_triangle_set its = Emboss::polygons2model(shapes, projection);
|
||||
CHECK(!its.indices.empty());
|
||||
@@ -468,7 +501,8 @@ TEST_CASE("Cut surface", "[]")
|
||||
|
||||
Transform3d tr = Transform3d::Identity();
|
||||
tr.translate(Vec3d(0., 0., -z_depth));
|
||||
tr.scale(Emboss::SHAPE_SCALE);
|
||||
double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE
|
||||
tr.scale(text_shape_scale);
|
||||
Emboss::OrthoProject cut_projection(tr, Vec3d(0., 0., z_depth));
|
||||
|
||||
auto object = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5);
|
||||
@@ -491,7 +525,7 @@ TEST_CASE("Cut surface", "[]")
|
||||
#include <sstream>
|
||||
#include <cereal/cereal.hpp>
|
||||
#include <cereal/archives/binary.hpp>
|
||||
TEST_CASE("UndoRedo serialization", "[Emboss]")
|
||||
TEST_CASE("UndoRedo TextConfiguration serialization", "[Emboss]")
|
||||
{
|
||||
TextConfiguration tc;
|
||||
tc.text = "Dovede-li se člověk zasmát sám sobě, nevyjde ze smíchu po celý život.";
|
||||
@@ -500,11 +534,12 @@ TEST_CASE("UndoRedo serialization", "[Emboss]")
|
||||
es.path = "Simply the best";
|
||||
es.type = EmbossStyle::Type::file_path;
|
||||
FontProp &fp = es.prop;
|
||||
fp.angle = 100.;
|
||||
fp.distance = 10.;
|
||||
fp.char_gap = 1;
|
||||
fp.use_surface = true;
|
||||
tc.fix_3mf_tr = Transform3d::Identity();
|
||||
fp.char_gap = 3;
|
||||
fp.line_gap = 7;
|
||||
fp.boldness = 2.3f;
|
||||
fp.skew = 4.5f;
|
||||
fp.collection_number = 13;
|
||||
fp.size_in_mm= 6.7f;
|
||||
|
||||
std::stringstream ss; // any stream can be used
|
||||
{
|
||||
@@ -520,7 +555,45 @@ TEST_CASE("UndoRedo serialization", "[Emboss]")
|
||||
}
|
||||
CHECK(tc.style == tc_loaded.style);
|
||||
CHECK(tc.text == tc_loaded.text);
|
||||
CHECK(tc.fix_3mf_tr.has_value() == tc_loaded.fix_3mf_tr.has_value());
|
||||
}
|
||||
|
||||
#include "libslic3r/EmbossShape.hpp"
|
||||
TEST_CASE("UndoRedo EmbossShape serialization", "[Emboss]")
|
||||
{
|
||||
EmbossShape emboss;
|
||||
emboss.shapes_with_ids = {{0, {{{0, 0}, {10, 0}, {10, 10}, {0, 10}}, {{5, 5}, {6, 5}, {6, 6}, {5, 6}}}}};
|
||||
emboss.scale = 2.;
|
||||
emboss.projection.depth = 5.;
|
||||
emboss.projection.use_surface = true;
|
||||
emboss.fix_3mf_tr = Transform3d::Identity();
|
||||
emboss.svg_file = EmbossShape::SvgFile{};
|
||||
emboss.svg_file->path = "Everything starts somewhere, though many physicists disagree.\
|
||||
But people have always been dimly aware of the problem with the start of things.\
|
||||
They wonder how the snowplough driver gets to work,\
|
||||
or how the makers of dictionaries look up the spelling of words.";
|
||||
emboss.svg_file->path_in_3mf = "Všechno někde začíná, i když mnoho fyziků nesouhlasí.\
|
||||
Ale lidé si vždy jen matně uvědomovali problém se začátkem věcí.\
|
||||
Zajímalo je, jak se řidič sněžného pluhu dostane do práce\
|
||||
nebo jak tvůrci slovníků vyhledávají pravopis slov.";
|
||||
emboss.svg_file->file_data = std::make_unique<std::string>("cite: Terry Pratchett");
|
||||
|
||||
std::stringstream ss; // any stream can be used
|
||||
{
|
||||
cereal::BinaryOutputArchive oarchive(ss); // Create an output archive
|
||||
oarchive(emboss);
|
||||
} // archive goes out of scope, ensuring all contents are flushed
|
||||
|
||||
EmbossShape emboss_loaded;
|
||||
{
|
||||
cereal::BinaryInputArchive iarchive(ss); // Create an input archive
|
||||
iarchive(emboss_loaded);
|
||||
}
|
||||
CHECK(emboss.shapes_with_ids.front().expoly == emboss_loaded.shapes_with_ids.front().expoly);
|
||||
CHECK(emboss.scale == emboss_loaded.scale);
|
||||
CHECK(emboss.projection.depth == emboss_loaded.projection.depth);
|
||||
CHECK(emboss.projection.use_surface == emboss_loaded.projection.use_surface);
|
||||
CHECK(emboss.svg_file->path == emboss_loaded.svg_file->path);
|
||||
CHECK(emboss.svg_file->path_in_3mf == emboss_loaded.svg_file->path_in_3mf);
|
||||
}
|
||||
|
||||
|
||||
@@ -780,7 +853,7 @@ using MyMesh = Slic3r::MeshBoolean::cgal2::CGALMesh;
|
||||
|
||||
// Second Idea
|
||||
// Store original its inside of text configuration[optional]
|
||||
// Cause problem with next editation of object -> cut, simplify, Netfabb, Hollow, ...(transform original vertices)
|
||||
// Cause problem with next editation of object -> cut, simplify, repair by WinSDK, Hollow, ...(transform original vertices)
|
||||
TEST_CASE("Emboss extrude cut", "[Emboss-Cut]")
|
||||
{
|
||||
std::string font_path = get_font_filepath();
|
||||
|
||||
@@ -65,3 +65,99 @@ SCENARIO("Basics", "[ExPolygon]") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#include <sstream>
|
||||
#include <cereal/cereal.hpp>
|
||||
#include <cereal/archives/binary.hpp>
|
||||
#include "libslic3r/ExPolygonSerialize.hpp"
|
||||
TEST_CASE("Serialization of expolygons", "[ExPolygon, Cereal, serialization]")
|
||||
{
|
||||
ExPolygons expolys{{
|
||||
// expolygon 1 - without holes
|
||||
{{0,0}, {10,0}, {10,10}, {0,10}}, // contour
|
||||
// expolygon 2 - with rect 1px hole
|
||||
{{{0,0}, {10,0}, {10,10}, {0,10}},
|
||||
{{5, 5}, {6, 5}, {6, 6}, {5, 6}}}
|
||||
}};
|
||||
|
||||
std::stringstream ss; // any stream can be used
|
||||
{
|
||||
cereal::BinaryOutputArchive oarchive(ss); // Create an output archive
|
||||
oarchive(expolys);
|
||||
} // archive goes out of scope, ensuring all contents are flushed
|
||||
|
||||
std::string data = ss.str();
|
||||
CHECK(!data.empty());
|
||||
|
||||
ExPolygons expolys_loaded;
|
||||
{
|
||||
cereal::BinaryInputArchive iarchive(ss); // Create an input archive
|
||||
iarchive(expolys_loaded);
|
||||
}
|
||||
|
||||
CHECK(expolys == expolys_loaded);
|
||||
}
|
||||
|
||||
#include <cereal/archives/json.hpp>
|
||||
#include <regex>
|
||||
// It is used to serialize expolygons into 3mf.
|
||||
TEST_CASE("Serialization of expolygons to string", "[ExPolygon, Cereal, serialization]")
|
||||
{
|
||||
ExPolygons expolys{{
|
||||
// expolygon 1 - without holes
|
||||
{{0,0}, {10,0}, {10,10}, {0,10}}, // contour
|
||||
// expolygon 2 - with rect 1px hole
|
||||
{{{0,0}, {10,0}, {10,10}, {0,10}},
|
||||
{{5, 5}, {6, 5}, {6, 6}, {5, 6}}}
|
||||
}};
|
||||
|
||||
std::stringstream ss_out; // any stream can be used
|
||||
{
|
||||
cereal::JSONOutputArchive oarchive(ss_out); // Create an output archive
|
||||
oarchive(expolys);
|
||||
} // archive goes out of scope, ensuring all contents are flushed
|
||||
|
||||
//Simplify text representation of expolygons
|
||||
std::string data = ss_out.str();
|
||||
// Data contain this JSON string
|
||||
//{
|
||||
// "value0": [
|
||||
// {
|
||||
// "value0": {
|
||||
// "value0":
|
||||
// [{"value0": 0, "value1": 0}, {"value0": 10, "value1": 0}, {"value0": 10, "value1": 10}, {"value0": 0, "value1": 10}]
|
||||
// },
|
||||
// "value1": []
|
||||
// },
|
||||
// {
|
||||
// "value0": {
|
||||
// "value0":
|
||||
// [{"value0": 0, "value1": 0}, {"value0": 10, "value1": 0}, {"value0": 10, "value1": 10}, {"value0": 0, "value1": 10}]
|
||||
// },
|
||||
// "value1": [{
|
||||
// "value0":
|
||||
// [{"value0": 5, "value1": 5}, {"value0": 6, "value1": 5}, {"value0": 6, "value1": 6}, {"value0": 5, "value1": 6}]
|
||||
// }]
|
||||
// }
|
||||
// ]
|
||||
//}
|
||||
|
||||
// Change JSON named object to JSON arrays(without name)
|
||||
|
||||
// RegEx for wihitespace = "[ \t\r\n\v\f]"
|
||||
std::regex r("\"value[0-9]+\":|[ \t\r\n\v\f]");
|
||||
std::string data_short = std::regex_replace(data, r , "");
|
||||
std::replace(data_short.begin(), data_short.end(), '{', '[');
|
||||
std::replace(data_short.begin(), data_short.end(), '}', ']');
|
||||
CHECK(!data_short.empty());
|
||||
// Cereal acceptable string
|
||||
// [[[[[[0,0],[10,0],[10,10],[0,10]]],[]],[[[[0,0],[10,0],[10,10],[0,10]]],[[[[5,5],[6,5],[6,6],[5,6]]]]]]]
|
||||
std::stringstream ss_in(data_short);
|
||||
ExPolygons expolys_loaded;
|
||||
{
|
||||
cereal::JSONInputArchive iarchive(ss_in); // Create an input archive
|
||||
iarchive(expolys_loaded);
|
||||
}
|
||||
|
||||
CHECK(expolys == expolys_loaded);
|
||||
}
|
||||
@@ -84,16 +84,16 @@ TEST_CASE("Line::perpendicular_to", "[Geometry]") {
|
||||
TEST_CASE("Polygon::contains works properly", "[Geometry]"){
|
||||
// this test was failing on Windows (GH #1950)
|
||||
Slic3r::Polygon polygon(Points({
|
||||
Point(207802834,-57084522),
|
||||
Point(196528149,-37556190),
|
||||
Point(173626821,-25420928),
|
||||
Point(171285751,-21366123),
|
||||
Point(118673592,-21366123),
|
||||
Point(116332562,-25420928),
|
||||
Point(93431208,-37556191),
|
||||
Point(82156517,-57084523),
|
||||
Point(129714478,-84542120),
|
||||
Point(160244873,-84542120)
|
||||
{207802834,-57084522},
|
||||
{196528149,-37556190},
|
||||
{173626821,-25420928},
|
||||
{171285751,-21366123},
|
||||
{118673592,-21366123},
|
||||
{116332562,-25420928},
|
||||
{93431208,-37556191},
|
||||
{82156517,-57084523},
|
||||
{129714478,-84542120},
|
||||
{160244873,-84542120}
|
||||
}));
|
||||
Point point(95706562, -57294774);
|
||||
REQUIRE(polygon.contains(point));
|
||||
@@ -196,6 +196,40 @@ TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){
|
||||
REQUIRE(area.area() == Slic3r::Polygon(Points({Point(10,5),Point(20,5),Point(20,15),Point(10,15)})).area());
|
||||
}
|
||||
|
||||
SCENARIO("Circle Fit, 3 points", "[Geometry]") {
|
||||
WHEN("Three points make a circle") {
|
||||
double s1 = scaled<double>(1.);
|
||||
THEN("circle_center(): A center point { 0, 0 } is returned") {
|
||||
Vec2d center = Geometry::circle_center(Vec2d{ s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ -s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(is_approx(center, Vec2d(0, 0)));
|
||||
}
|
||||
THEN("circle_center(): A center point { 0, 0 } is returned for points in reverse") {
|
||||
Vec2d center = Geometry::circle_center(Vec2d{ -s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(is_approx(center, Vec2d(0, 0)));
|
||||
}
|
||||
THEN("try_circle_center(): A center point { 0, 0 } is returned") {
|
||||
std::optional<Vec2d> center = Geometry::try_circle_center(Vec2d{ s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ -s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(center);
|
||||
REQUIRE(is_approx(*center, Vec2d(0, 0)));
|
||||
}
|
||||
THEN("try_circle_center(): A center point { 0, 0 } is returned for points in reverse") {
|
||||
std::optional<Vec2d> center = Geometry::try_circle_center(Vec2d{ -s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(center);
|
||||
REQUIRE(is_approx(*center, Vec2d(0, 0)));
|
||||
}
|
||||
}
|
||||
WHEN("Three points are collinear") {
|
||||
double s1 = scaled<double>(1.);
|
||||
THEN("circle_center(): A center point { 2, 0 } is returned") {
|
||||
Vec2d center = Geometry::circle_center(Vec2d{ s1, 0. }, Vec2d{ 2. * s1, 0. }, Vec2d{ 3. * s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(is_approx(center, Vec2d(2. * s1, 0)));
|
||||
}
|
||||
THEN("try_circle_center(): Fails for collinear points") {
|
||||
std::optional<Vec2d> center = Geometry::try_circle_center(Vec2d{ s1, 0. }, Vec2d{ 2. * s1, 0. }, Vec2d{ 3. * s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(! center);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
@@ -288,6 +322,53 @@ SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Circle Fit, least squares by decomposition or by solving normal equation", "[Geometry]") {
|
||||
auto test_circle_fit = [](const Geometry::Circled &circle, const Vec2d ¢er, const double radius) {
|
||||
THEN("A center point matches.") {
|
||||
REQUIRE(is_approx(circle.center, center));
|
||||
}
|
||||
THEN("Radius matches") {
|
||||
REQUIRE(is_approx(circle.radius, radius));
|
||||
}
|
||||
};
|
||||
|
||||
GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
|
||||
const Vec2d expected_center(-6., 0.);
|
||||
const double expected_radius = 6.;
|
||||
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, least squares SVD") {
|
||||
test_circle_fit(Geometry::circle_linear_least_squares_svd(sample), expected_center, expected_radius);
|
||||
}
|
||||
WHEN("Circle fit is called on the first four points, least squares SVD") {
|
||||
test_circle_fit(Geometry::circle_linear_least_squares_svd(Vec2ds(sample.cbegin(), sample.cbegin() + 4)), expected_center, expected_radius);
|
||||
}
|
||||
WHEN("Circle fit is called on the middle four points, least squares SVD") {
|
||||
test_circle_fit(Geometry::circle_linear_least_squares_svd(Vec2ds(sample.cbegin() + 2, sample.cbegin() + 6)), expected_center, expected_radius);
|
||||
}
|
||||
|
||||
WHEN("Circle fit is called on the entire array, least squares QR decomposition") {
|
||||
test_circle_fit(Geometry::circle_linear_least_squares_qr(sample), expected_center, expected_radius);
|
||||
}
|
||||
WHEN("Circle fit is called on the first four points, least squares QR decomposition") {
|
||||
test_circle_fit(Geometry::circle_linear_least_squares_qr(Vec2ds(sample.cbegin(), sample.cbegin() + 4)), expected_center, expected_radius);
|
||||
}
|
||||
WHEN("Circle fit is called on the middle four points, least squares QR decomposition") {
|
||||
test_circle_fit(Geometry::circle_linear_least_squares_qr(Vec2ds(sample.cbegin() + 2, sample.cbegin() + 6)), expected_center, expected_radius);
|
||||
}
|
||||
|
||||
WHEN("Circle fit is called on the entire array, least squares by normal equations") {
|
||||
test_circle_fit(Geometry::circle_linear_least_squares_normal(sample), expected_center, expected_radius);
|
||||
}
|
||||
WHEN("Circle fit is called on the first four points, least squares by normal equations") {
|
||||
test_circle_fit(Geometry::circle_linear_least_squares_normal(Vec2ds(sample.cbegin(), sample.cbegin() + 4)), expected_center, expected_radius);
|
||||
}
|
||||
WHEN("Circle fit is called on the middle four points, least squares by normal equations") {
|
||||
test_circle_fit(Geometry::circle_linear_least_squares_normal(Vec2ds(sample.cbegin() + 2, sample.cbegin() + 6)), expected_center, expected_radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
TEST_CASE("smallest_enclosing_circle_welzl", "[Geometry]") {
|
||||
// Some random points in plane.
|
||||
Points pts {
|
||||
@@ -310,6 +391,7 @@ SCENARIO("Path chaining", "[Geometry]") {
|
||||
GIVEN("A path") {
|
||||
Points points = { Point(26,26),Point(52,26),Point(0,26),Point(26,52),Point(26,0),Point(0,52),Point(52,52),Point(52,0) };
|
||||
THEN("Chained with no diagonals (thus 26 units long)") {
|
||||
// if chain_points() works correctly, these points should be joined with no diagonal paths
|
||||
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();
|
||||
|
||||
@@ -5,6 +5,24 @@
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
SCENARIO("Simplify polyne, template", "[Polyline]")
|
||||
{
|
||||
Points polyline{ {0,0}, {1000,0}, {2000,0}, {2000,1000}, {2000,2000}, {1000,2000}, {0,2000}, {0,1000}, {0,0} };
|
||||
WHEN("simplified with Douglas-Peucker with back inserter") {
|
||||
Points out;
|
||||
douglas_peucker<int64_t>(polyline.begin(), polyline.end(), std::back_inserter(out), 10, [](const Point &p) { return p; });
|
||||
THEN("simplified correctly") {
|
||||
REQUIRE(out == Points{ {0,0}, {2000,0}, {2000,2000}, {0,2000}, {0,0} });
|
||||
}
|
||||
}
|
||||
WHEN("simplified with Douglas-Peucker in place") {
|
||||
Points out{ polyline };
|
||||
out.erase(douglas_peucker<int64_t>(out.begin(), out.end(), out.begin(), 10, [](const Point &p) { return p; }), out.end());
|
||||
THEN("simplified correctly") {
|
||||
REQUIRE(out == Points{ {0,0}, {2000,0}, {2000,2000}, {0,2000}, {0,0} });
|
||||
}
|
||||
}
|
||||
}
|
||||
SCENARIO("Simplify polyline", "[Polyline]")
|
||||
{
|
||||
GIVEN("polyline 1") {
|
||||
|
||||
98
tests/libslic3r/test_static_map.cpp
Normal file
98
tests/libslic3r/test_static_map.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <string_view>
|
||||
|
||||
#include "libslic3r/StaticMap.hpp"
|
||||
|
||||
TEST_CASE("Empty static map should be possible to create and should be empty", "[StaticMap]")
|
||||
{
|
||||
using namespace Slic3r;
|
||||
|
||||
static const constexpr StaticSet EmptySet;
|
||||
|
||||
static const constexpr auto EmptyMap = make_staticmap<int, int>();
|
||||
|
||||
constexpr bool is_map_empty = EmptyMap.empty();
|
||||
constexpr bool is_set_empty = EmptySet.empty();
|
||||
|
||||
REQUIRE(is_map_empty);
|
||||
REQUIRE(is_set_empty);
|
||||
}
|
||||
|
||||
TEST_CASE("StaticSet should derive it's type from the initializer", "[StaticMap]") {
|
||||
using namespace Slic3r;
|
||||
static const constexpr StaticSet iOneSet = { 1 };
|
||||
static constexpr size_t iOneSetSize = iOneSet.size();
|
||||
|
||||
REQUIRE(iOneSetSize == 1);
|
||||
|
||||
static const constexpr StaticSet iManySet = { 1, 3, 5, 80, 40 };
|
||||
static constexpr size_t iManySetSize = iManySet.size();
|
||||
|
||||
REQUIRE(iManySetSize == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("StaticMap should derive it's type using make_staticmap", "[StaticMap]") {
|
||||
using namespace Slic3r;
|
||||
static const constexpr auto ciOneMap = make_staticmap<char, int>({
|
||||
{'a', 1},
|
||||
});
|
||||
|
||||
static constexpr size_t ciOneMapSize = ciOneMap.size();
|
||||
static constexpr bool ciOneMapValid = query(ciOneMap, 'a').value_or(0) == 1;
|
||||
|
||||
REQUIRE(ciOneMapSize == 1);
|
||||
REQUIRE(ciOneMapValid);
|
||||
|
||||
static const constexpr auto ciManyMap = make_staticmap<char, int>({
|
||||
{'a', 1}, {'b', 2}, {'A', 10}
|
||||
});
|
||||
|
||||
static constexpr size_t ciManyMapSize = ciManyMap.size();
|
||||
static constexpr bool ciManyMapValid =
|
||||
query(ciManyMap, 'a').value_or(0) == 1 &&
|
||||
query(ciManyMap, 'b').value_or(0) == 2 &&
|
||||
query(ciManyMap, 'A').value_or(0) == 10 &&
|
||||
!contains(ciManyMap, 'B') &&
|
||||
!query(ciManyMap, 'c').has_value();
|
||||
|
||||
REQUIRE(ciManyMapSize == 3);
|
||||
REQUIRE(ciManyMapValid);
|
||||
|
||||
for (auto &[k, v] : ciManyMap) {
|
||||
auto val = query(ciManyMap, k);
|
||||
REQUIRE(val.has_value());
|
||||
REQUIRE(*val == v);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("StaticSet should be able to find contained values", "[StaticMap]")
|
||||
{
|
||||
using namespace Slic3r;
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
auto cmp = [](const char *a, const char *b) constexpr {
|
||||
return std::string_view{a} < std::string_view{b};
|
||||
};
|
||||
|
||||
static constexpr StaticSet CStrSet = {cmp, "One", "Two", "Three"};
|
||||
static constexpr StaticSet StringSet = {"One"sv, "Two"sv, "Three"sv};
|
||||
|
||||
static constexpr bool CStrSetValid = query(CStrSet, "One").has_value() &&
|
||||
contains(CStrSet, "Two") &&
|
||||
contains(CStrSet, "Three") &&
|
||||
!contains(CStrSet, "one") &&
|
||||
!contains(CStrSet, "two") &&
|
||||
!contains(CStrSet, "three");
|
||||
|
||||
static constexpr bool StringSetValid = contains(StringSet, "One"sv) &&
|
||||
contains(StringSet, "Two"sv) &&
|
||||
contains(StringSet, "Three"sv) &&
|
||||
!contains(StringSet, "one"sv) &&
|
||||
!contains(StringSet, "two"sv) &&
|
||||
!contains(StringSet, "three"sv);
|
||||
|
||||
REQUIRE(CStrSetValid);
|
||||
REQUIRE(StringSetValid);
|
||||
REQUIRE(CStrSet.size() == 3);
|
||||
REQUIRE(StringSet.size() == 3);
|
||||
}
|
||||
163
tests/libslic3r/test_support_spots_generator.cpp
Normal file
163
tests/libslic3r/test_support_spots_generator.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/SupportSpotsGenerator.hpp>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace SupportSpotsGenerator;
|
||||
|
||||
|
||||
TEST_CASE("Numerical integral calculation compared with exact solution.", "[SupportSpotsGenerator]") {
|
||||
const float width = 10;
|
||||
const float height = 20;
|
||||
const Polygon polygon = {
|
||||
scaled(Vec2f{-width / 2, -height / 2}),
|
||||
scaled(Vec2f{width / 2, -height / 2}),
|
||||
scaled(Vec2f{width / 2, height / 2}),
|
||||
scaled(Vec2f{-width / 2, height / 2})
|
||||
};
|
||||
|
||||
const Integrals integrals{{polygon}};
|
||||
CHECK(integrals.area == Approx(width * height));
|
||||
CHECK(integrals.x_i.x() == Approx(0));
|
||||
CHECK(integrals.x_i.y() == Approx(0));
|
||||
CHECK(integrals.x_i_squared.x() == Approx(std::pow(width, 3) * height / 12));
|
||||
CHECK(integrals.x_i_squared.y() == Approx(width * std::pow(height, 3) / 12));
|
||||
}
|
||||
|
||||
TEST_CASE("Moment values and ratio check.", "[SupportSpotsGenerator]") {
|
||||
const float width = 40;
|
||||
const float height = 2;
|
||||
|
||||
// Moments are calculated at centroid.
|
||||
// Polygon centroid must not be (0, 0).
|
||||
const Polygon polygon = {
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{width, 0}),
|
||||
scaled(Vec2f{width, height}),
|
||||
scaled(Vec2f{0, height})
|
||||
};
|
||||
|
||||
const Integrals integrals{{polygon}};
|
||||
|
||||
const Vec2f x_axis{1, 0};
|
||||
const float x_axis_moment = compute_second_moment(integrals, x_axis);
|
||||
|
||||
const Vec2f y_axis{0, 1};
|
||||
const float y_axis_moment = compute_second_moment(integrals, y_axis);
|
||||
|
||||
const float moment_ratio = std::pow(width / height, 2);
|
||||
|
||||
// Ensure the object transaltion has no effect.
|
||||
CHECK(x_axis_moment == Approx(width * std::pow(height, 3) / 12));
|
||||
CHECK(y_axis_moment == Approx(std::pow(width, 3) * height / 12));
|
||||
// If the object is "wide" the y axis moments should be large compared to x axis moment.
|
||||
CHECK(y_axis_moment / x_axis_moment == Approx(moment_ratio));
|
||||
}
|
||||
|
||||
TEST_CASE("Moments calculation for rotated axis.", "[SupportSpotsGenerator]") {
|
||||
|
||||
Polygon polygon = {
|
||||
scaled(Vec2f{6.362284076172198, 138.9674202217155}),
|
||||
scaled(Vec2f{97.48779843751677, 106.08136606617076}),
|
||||
scaled(Vec2f{135.75221821532384, 66.84428834668765}),
|
||||
scaled(Vec2f{191.5308049852741, 45.77905628725614}),
|
||||
scaled(Vec2f{182.7525148049201, 74.01799041087513}),
|
||||
scaled(Vec2f{296.83210979283473, 196.80022572637228}),
|
||||
scaled(Vec2f{215.16434429179148, 187.45715418834143}),
|
||||
scaled(Vec2f{64.64574271229334, 284.293883209721}),
|
||||
scaled(Vec2f{110.76507036894843, 174.35633141113783}),
|
||||
scaled(Vec2f{77.56229640885199, 189.33057746591336})
|
||||
};
|
||||
|
||||
Integrals integrals{{polygon}};
|
||||
|
||||
// Meassured counterclockwise from (1, 0)
|
||||
const float angle = 1.432f;
|
||||
Vec2f axis{std::cos(angle), std::sin(angle)};
|
||||
|
||||
float moment_calculated_then_rotated = compute_second_moment(
|
||||
integrals,
|
||||
axis
|
||||
);
|
||||
|
||||
// We want to rotate the object clockwise by angle to align the axis with (1, 0)
|
||||
// Method .rotate is counterclockwise for positive angle
|
||||
polygon.rotate(-angle);
|
||||
|
||||
Integrals integrals_rotated{{polygon}};
|
||||
float moment_rotated_polygon = compute_second_moment(
|
||||
integrals_rotated,
|
||||
Vec2f{1, 0}
|
||||
);
|
||||
|
||||
// Up to 0.1% accuracy
|
||||
CHECK_THAT(moment_calculated_then_rotated, Catch::Matchers::WithinRel(moment_rotated_polygon, 0.001f));
|
||||
}
|
||||
|
||||
struct ObjectPartFixture {
|
||||
const Polyline polyline{
|
||||
Point{scaled(Vec2f{0, 0})},
|
||||
Point{scaled(Vec2f{1, 0})},
|
||||
};
|
||||
const float width = 0.1f;
|
||||
bool connected_to_bed = true;
|
||||
coordf_t print_head_z = 0.2;
|
||||
coordf_t layer_height = 0.2;
|
||||
ExtrusionAttributes attributes;
|
||||
ExtrusionEntityCollection collection;
|
||||
std::vector<const ExtrusionEntityCollection*> extrusions{};
|
||||
Polygon expected_polygon{
|
||||
Point{scaled(Vec2f{0, -width / 2})},
|
||||
Point{scaled(Vec2f{1, -width / 2})},
|
||||
Point{scaled(Vec2f{1, width / 2})},
|
||||
Point{scaled(Vec2f{0, width / 2})}
|
||||
};
|
||||
|
||||
ObjectPartFixture() {
|
||||
attributes.width = width;
|
||||
const ExtrusionPath path{polyline, attributes};
|
||||
collection.append(path);
|
||||
extrusions.push_back(&collection);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(ObjectPartFixture, "Constructing ObjectPart using extrusion collections", "[SupportSpotsGenerator]") {
|
||||
ObjectPart part{
|
||||
extrusions,
|
||||
connected_to_bed,
|
||||
print_head_z,
|
||||
layer_height,
|
||||
std::nullopt
|
||||
};
|
||||
|
||||
Integrals expected{{expected_polygon}};
|
||||
|
||||
CHECK(part.connected_to_bed == true);
|
||||
Vec3f volume_centroid{part.volume_centroid_accumulator / part.volume};
|
||||
CHECK(volume_centroid.x() == Approx(0.5));
|
||||
CHECK(volume_centroid.y() == Approx(0));
|
||||
CHECK(volume_centroid.z() == Approx(layer_height / 2));
|
||||
CHECK(part.sticking_area == Approx(expected.area));
|
||||
CHECK(part.sticking_centroid_accumulator.x() == Approx(expected.x_i.x()));
|
||||
CHECK(part.sticking_centroid_accumulator.y() == Approx(expected.x_i.y()));
|
||||
CHECK(part.sticking_second_moment_of_area_accumulator.x() == Approx(expected.x_i_squared.x()));
|
||||
CHECK(part.sticking_second_moment_of_area_accumulator.y() == Approx(expected.x_i_squared.y()));
|
||||
CHECK(part.sticking_second_moment_of_area_covariance_accumulator == Approx(expected.xy).margin(1e-6));
|
||||
CHECK(part.volume == Approx(layer_height * width));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(ObjectPartFixture, "Constructing ObjectPart with brim", "[SupportSpotsGenerator]") {
|
||||
float brim_width = 1;
|
||||
Polygons brim = get_brim(ExPolygon{expected_polygon}, BrimType::btOuterOnly, brim_width);
|
||||
|
||||
ObjectPart part{
|
||||
extrusions,
|
||||
connected_to_bed,
|
||||
print_head_z,
|
||||
layer_height,
|
||||
brim
|
||||
};
|
||||
|
||||
CHECK(part.sticking_area == Approx((1 + 2*brim_width) * (width + 2*brim_width)));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user