Fix tests on all platforms
Try to link tests on Mac. Fix inaccurate pad brim size fix build on mac (attempt 2) Fixes for support tree faults and race conditions in release mode. Fix crashing test executable on gcc 4.9 fix warning on msvc
This commit is contained in:
parent
be7428d66e
commit
8ca7e56d0f
12 changed files with 381 additions and 319 deletions
|
@ -7,7 +7,10 @@ set(TEST_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data)
|
|||
file(TO_NATIVE_PATH "${TEST_DATA_DIR}" TEST_DATA_DIR)
|
||||
|
||||
add_library(test_common INTERFACE)
|
||||
target_compile_definitions(test_common INTERFACE TEST_DATA_DIR="${TEST_DATA_DIR}")
|
||||
target_compile_definitions(test_common INTERFACE TEST_DATA_DIR=R"\(${TEST_DATA_DIR}\)")
|
||||
target_link_libraries(test_common INTERFACE GTest::GTest GTest::Main)
|
||||
if (APPLE)
|
||||
target_link_libraries(test_common INTERFACE "-liconv -framework IOKit" "-framework CoreFoundation" -lc++)
|
||||
endif()
|
||||
|
||||
add_subdirectory(sla_print)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <random>
|
||||
|
||||
// Debug
|
||||
#include <fstream>
|
||||
|
@ -18,10 +20,10 @@
|
|||
|
||||
#include "libslic3r/SVG.hpp"
|
||||
|
||||
#if defined(WIN32) || defined(_WIN32)
|
||||
#define PATH_SEPARATOR "\\"
|
||||
#else
|
||||
#define PATH_SEPARATOR "/"
|
||||
#if defined(WIN32) || defined(_WIN32)
|
||||
#define PATH_SEPARATOR R"(\)"
|
||||
#else
|
||||
#define PATH_SEPARATOR R"(/)"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
@ -30,7 +32,7 @@ using namespace Slic3r;
|
|||
TriangleMesh load_model(const std::string &obj_filename)
|
||||
{
|
||||
TriangleMesh mesh;
|
||||
auto fpath = std::string(TEST_DATA_DIR PATH_SEPARATOR) + obj_filename;
|
||||
auto fpath = TEST_DATA_DIR PATH_SEPARATOR + obj_filename;
|
||||
load_obj(fpath.c_str(), &mesh);
|
||||
return mesh;
|
||||
}
|
||||
|
@ -46,21 +48,21 @@ void check_validity(const TriangleMesh &input_mesh,
|
|||
ASSUME_NO_REPAIR)
|
||||
{
|
||||
TriangleMesh mesh{input_mesh};
|
||||
|
||||
|
||||
if (flags & ASSUME_NO_EMPTY) {
|
||||
ASSERT_FALSE(mesh.empty());
|
||||
} else if (mesh.empty())
|
||||
} else if (mesh.empty())
|
||||
return; // If it can be empty and it is, there is nothing left to do.
|
||||
|
||||
|
||||
ASSERT_TRUE(stl_validate(&mesh.stl));
|
||||
|
||||
|
||||
bool do_update_shared_vertices = false;
|
||||
mesh.repair(do_update_shared_vertices);
|
||||
|
||||
|
||||
if (flags & ASSUME_NO_REPAIR) {
|
||||
ASSERT_FALSE(mesh.needed_repair());
|
||||
}
|
||||
|
||||
|
||||
if (flags & ASSUME_MANIFOLD) {
|
||||
mesh.require_shared_vertices();
|
||||
if (!mesh.is_manifold()) mesh.WriteOBJFile("non_manifold.obj");
|
||||
|
@ -80,21 +82,21 @@ void test_pad(const std::string & obj_filename,
|
|||
PadByproducts & out)
|
||||
{
|
||||
ASSERT_TRUE(padcfg.validate().empty());
|
||||
|
||||
|
||||
TriangleMesh mesh = load_model(obj_filename);
|
||||
|
||||
|
||||
ASSERT_FALSE(mesh.empty());
|
||||
|
||||
|
||||
// Create pad skeleton only from the model
|
||||
Slic3r::sla::pad_blueprint(mesh, out.model_contours);
|
||||
|
||||
|
||||
ASSERT_FALSE(out.model_contours.empty());
|
||||
|
||||
|
||||
// Create the pad geometry for the model contours only
|
||||
Slic3r::sla::create_pad({}, out.model_contours, out.mesh, padcfg);
|
||||
|
||||
|
||||
check_validity(out.mesh);
|
||||
|
||||
|
||||
auto bb = out.mesh.bounding_box();
|
||||
ASSERT_DOUBLE_EQ(bb.max.z() - bb.min.z(),
|
||||
padcfg.full_height());
|
||||
|
@ -122,19 +124,25 @@ void check_support_tree_integrity(const sla::SupportTreeBuilder &stree,
|
|||
double gnd = stree.ground_level;
|
||||
double H1 = cfg.max_solo_pillar_height_mm;
|
||||
double H2 = cfg.max_dual_pillar_height_mm;
|
||||
|
||||
for (const sla::Head &head : stree.heads()) {
|
||||
ASSERT_TRUE(!head.is_valid() ||
|
||||
head.pillar_id != sla::ID_UNSET ||
|
||||
head.bridge_id != sla::ID_UNSET);
|
||||
}
|
||||
|
||||
for (const sla::Pillar &pillar : stree.pillars()) {
|
||||
if (std::abs(pillar.endpoint().z() - gnd) < EPSILON) {
|
||||
double h = pillar.height;
|
||||
|
||||
|
||||
if (h > H1) ASSERT_GE(pillar.links, 1);
|
||||
else if(h > H2) { ASSERT_GE(pillar.links, 2); }
|
||||
else if(h > H2) { ASSERT_GE(pillar.links, 2); }
|
||||
}
|
||||
|
||||
|
||||
ASSERT_LE(pillar.links, cfg.pillar_cascade_neighbors);
|
||||
ASSERT_LE(pillar.bridges, cfg.max_bridges_on_pillar);
|
||||
}
|
||||
|
||||
|
||||
double max_bridgelen = 0.;
|
||||
auto chck_bridge = [&cfg](const sla::Bridge &bridge, double &max_brlen) {
|
||||
Vec3d n = bridge.endp - bridge.startp;
|
||||
|
@ -144,15 +152,15 @@ void check_support_tree_integrity(const sla::SupportTreeBuilder &stree,
|
|||
double z = n.z();
|
||||
double polar = std::acos(z / d);
|
||||
double slope = -polar + PI / 2.;
|
||||
ASSERT_TRUE(slope >= cfg.bridge_slope || slope <= -cfg.bridge_slope);
|
||||
ASSERT_GE(std::abs(slope), cfg.bridge_slope - EPSILON);
|
||||
};
|
||||
|
||||
|
||||
for (auto &bridge : stree.bridges()) chck_bridge(bridge, max_bridgelen);
|
||||
ASSERT_LE(max_bridgelen, cfg.max_bridge_length_mm);
|
||||
|
||||
|
||||
max_bridgelen = 0;
|
||||
for (auto &bridge : stree.crossbridges()) chck_bridge(bridge, max_bridgelen);
|
||||
|
||||
|
||||
double md = cfg.max_pillar_link_distance_mm / std::cos(-cfg.bridge_slope);
|
||||
ASSERT_LE(max_bridgelen, md);
|
||||
}
|
||||
|
@ -163,9 +171,9 @@ void test_supports(const std::string & obj_filename,
|
|||
{
|
||||
using namespace Slic3r;
|
||||
TriangleMesh mesh = load_model(obj_filename);
|
||||
|
||||
|
||||
ASSERT_FALSE(mesh.empty());
|
||||
|
||||
|
||||
TriangleMeshSlicer slicer{&mesh};
|
||||
|
||||
auto bb = mesh.bounding_box();
|
||||
|
@ -176,50 +184,50 @@ void test_supports(const std::string & obj_filename,
|
|||
|
||||
out.slicegrid = grid(float(gnd), float(zmax), layer_h);
|
||||
slicer.slice(out.slicegrid , CLOSING_RADIUS, &out.model_slices, []{});
|
||||
|
||||
|
||||
// Create the special index-triangle mesh with spatial indexing which
|
||||
// is the input of the support point and support mesh generators
|
||||
sla::EigenMesh3D emesh{mesh};
|
||||
|
||||
|
||||
// Create the support point generator
|
||||
sla::SLAAutoSupports::Config autogencfg;
|
||||
autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm);
|
||||
sla::SLAAutoSupports point_gen{emesh, out.model_slices, out.slicegrid,
|
||||
autogencfg, [] {}, [](int) {}};
|
||||
|
||||
|
||||
// Get the calculated support points.
|
||||
std::vector<sla::SupportPoint> support_points = point_gen.output();
|
||||
|
||||
|
||||
int validityflags = ASSUME_NO_REPAIR;
|
||||
|
||||
|
||||
// If there is no elevation, support points shall be removed from the
|
||||
// bottom of the object.
|
||||
if (supportcfg.object_elevation_mm < EPSILON) {
|
||||
sla::remove_bottom_points(support_points, zmin,
|
||||
supportcfg.base_height_mm);
|
||||
supportcfg.base_height_mm);
|
||||
} else {
|
||||
// Should be support points at least on the bottom of the model
|
||||
ASSERT_FALSE(support_points.empty());
|
||||
|
||||
|
||||
// Also the support mesh should not be empty.
|
||||
validityflags |= ASSUME_NO_EMPTY;
|
||||
}
|
||||
|
||||
|
||||
// Generate the actual support tree
|
||||
sla::SupportTreeBuilder treebuilder;
|
||||
treebuilder.build(sla::SupportableMesh{emesh, support_points, supportcfg});
|
||||
|
||||
|
||||
check_support_tree_integrity(treebuilder, supportcfg);
|
||||
|
||||
|
||||
const TriangleMesh &output_mesh = treebuilder.retrieve_mesh();
|
||||
|
||||
|
||||
check_validity(output_mesh, validityflags);
|
||||
|
||||
|
||||
// Quick check if the dimensions and placement of supports are correct
|
||||
auto obb = output_mesh.bounding_box();
|
||||
ASSERT_DOUBLE_EQ(obb.min.z(), zmin - supportcfg.object_elevation_mm);
|
||||
ASSERT_LE(obb.max.z(), zmax);
|
||||
|
||||
|
||||
// Move out the support tree into the byproducts, we can examine it further
|
||||
// in various tests.
|
||||
out.supporttree = std::move(treebuilder);
|
||||
|
@ -237,31 +245,31 @@ void test_support_model_collision(
|
|||
const sla::SupportConfig &input_supportcfg = {})
|
||||
{
|
||||
SupportByproducts byproducts;
|
||||
|
||||
|
||||
sla::SupportConfig supportcfg = input_supportcfg;
|
||||
|
||||
|
||||
// Set head penetration to a small negative value which should ensure that
|
||||
// the supports will not touch the model body.
|
||||
supportcfg.head_penetration_mm = -0.1;
|
||||
|
||||
|
||||
test_supports(obj_filename, supportcfg, byproducts);
|
||||
|
||||
|
||||
// Slice the support mesh given the slice grid of the model.
|
||||
std::vector<ExPolygons> support_slices =
|
||||
byproducts.supporttree.slice(byproducts.slicegrid, CLOSING_RADIUS);
|
||||
|
||||
|
||||
// The slices originate from the same slice grid so the numbers must match
|
||||
ASSERT_EQ(support_slices.size(), byproducts.model_slices.size());
|
||||
|
||||
|
||||
bool notouch = true;
|
||||
for (size_t n = 0; notouch && n < support_slices.size(); ++n) {
|
||||
const ExPolygons &sup_slice = support_slices[n];
|
||||
const ExPolygons &mod_slice = byproducts.model_slices[n];
|
||||
|
||||
|
||||
Polygons intersections = intersection(sup_slice, mod_slice);
|
||||
notouch = notouch && intersections.empty();
|
||||
}
|
||||
|
||||
|
||||
ASSERT_TRUE(notouch);
|
||||
}
|
||||
|
||||
|
@ -283,25 +291,52 @@ const char *const SUPPORT_TEST_MODELS[] = {
|
|||
|
||||
} // namespace
|
||||
|
||||
// Test pair hash for 'nums' random number pairs.
|
||||
template <class I, class II> void test_pairhash()
|
||||
{
|
||||
std::map<II, std::pair<I, I> > ints;
|
||||
for (I i = 0; i < 1000; ++i)
|
||||
for (I j = 0; j < 1000; ++j) {
|
||||
if (j != i) {
|
||||
II hash_ij = sla::pairhash<I, II>(i, j);
|
||||
II hash_ji = sla::pairhash<I, II>(j, i);
|
||||
ASSERT_EQ(hash_ij, hash_ji);
|
||||
|
||||
auto it = ints.find(hash_ij);
|
||||
|
||||
if (it != ints.end()) {
|
||||
ASSERT_TRUE(
|
||||
(it->second.first == i && it->second.second == j) ||
|
||||
(it->second.first == j && it->second.second == i));
|
||||
} else ints[hash_ij] = std::make_pair(i, j);
|
||||
}
|
||||
}
|
||||
const constexpr size_t nums = 1000;
|
||||
I A[nums] = {0}, B[nums] = {0};
|
||||
std::unordered_set<I> CH;
|
||||
std::unordered_map<II, std::pair<I, I>> ints;
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
|
||||
const I Ibits = int(sizeof(I) * CHAR_BIT);
|
||||
const II IIbits = int(sizeof(II) * CHAR_BIT);
|
||||
const int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits;
|
||||
|
||||
const I Imax = I(std::pow(2., bits) - 1);
|
||||
std::uniform_int_distribution<I> dis(0, Imax);
|
||||
|
||||
for (size_t i = 0; i < nums;) {
|
||||
I a = dis(gen);
|
||||
if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; }
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < nums;) {
|
||||
I b = dis(gen);
|
||||
if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; }
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < nums; ++i) {
|
||||
I a = A[i], b = B[i];
|
||||
|
||||
ASSERT_TRUE(a != b);
|
||||
|
||||
II hash_ab = sla::pairhash<I, II>(a, b);
|
||||
II hash_ba = sla::pairhash<I, II>(b, a);
|
||||
ASSERT_EQ(hash_ab, hash_ba);
|
||||
|
||||
auto it = ints.find(hash_ab);
|
||||
|
||||
if (it != ints.end()) {
|
||||
ASSERT_TRUE(
|
||||
(it->second.first == a && it->second.second == b) ||
|
||||
(it->second.first == b && it->second.second == a));
|
||||
} else
|
||||
ints[hash_ab] = std::make_pair(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SLASupportGeneration, PillarPairHashShouldBeUnique) {
|
||||
|
@ -312,61 +347,61 @@ TEST(SLASupportGeneration, PillarPairHashShouldBeUnique) {
|
|||
|
||||
TEST(SLASupportGeneration, FlatPadGeometryIsValid) {
|
||||
sla::PadConfig padcfg;
|
||||
|
||||
|
||||
// Disable wings
|
||||
padcfg.wall_height_mm = .0;
|
||||
|
||||
|
||||
for (auto &fname : BELOW_PAD_TEST_OBJECTS) test_pad(fname, padcfg);
|
||||
}
|
||||
|
||||
TEST(SLASupportGeneration, WingedPadGeometryIsValid) {
|
||||
sla::PadConfig padcfg;
|
||||
|
||||
|
||||
// Add some wings to the pad to test the cavity
|
||||
padcfg.wall_height_mm = 1.;
|
||||
|
||||
|
||||
for (auto &fname : BELOW_PAD_TEST_OBJECTS) test_pad(fname, padcfg);
|
||||
}
|
||||
|
||||
TEST(SLASupportGeneration, FlatPadAroundObjectIsValid) {
|
||||
sla::PadConfig padcfg;
|
||||
|
||||
|
||||
// Add some wings to the pad to test the cavity
|
||||
padcfg.wall_height_mm = 0.;
|
||||
// padcfg.embed_object.stick_stride_mm = 0.;
|
||||
padcfg.embed_object.enabled = true;
|
||||
padcfg.embed_object.everywhere = true;
|
||||
|
||||
|
||||
for (auto &fname : AROUND_PAD_TEST_OBJECTS) test_pad(fname, padcfg);
|
||||
}
|
||||
|
||||
TEST(SLASupportGeneration, WingedPadAroundObjectIsValid) {
|
||||
sla::PadConfig padcfg;
|
||||
|
||||
|
||||
// Add some wings to the pad to test the cavity
|
||||
padcfg.wall_height_mm = 1.;
|
||||
padcfg.embed_object.enabled = true;
|
||||
padcfg.embed_object.everywhere = true;
|
||||
|
||||
|
||||
for (auto &fname : AROUND_PAD_TEST_OBJECTS) test_pad(fname, padcfg);
|
||||
}
|
||||
|
||||
TEST(SLASupportGeneration, ElevatedSupportGeometryIsValid) {
|
||||
sla::SupportConfig supportcfg;
|
||||
supportcfg.object_elevation_mm = 5.;
|
||||
|
||||
|
||||
for (auto fname : SUPPORT_TEST_MODELS) test_supports(fname);
|
||||
}
|
||||
|
||||
TEST(SLASupportGeneration, FloorSupportGeometryIsValid) {
|
||||
sla::SupportConfig supportcfg;
|
||||
supportcfg.object_elevation_mm = 0;
|
||||
|
||||
|
||||
for (auto &fname: SUPPORT_TEST_MODELS) test_supports(fname, supportcfg);
|
||||
}
|
||||
|
||||
TEST(SLASupportGeneration, SupportsDoNotPierceModel) {
|
||||
|
||||
|
||||
sla::SupportConfig supportcfg;
|
||||
|
||||
for (auto fname : SUPPORT_TEST_MODELS)
|
||||
|
@ -382,7 +417,7 @@ TEST(SLARasterOutput, InitializedRasterShouldBeNONEmpty) {
|
|||
// Default Prusa SL1 display parameters
|
||||
sla::Raster::Resolution res{2560, 1440};
|
||||
sla::Raster::PixelDim pixdim{120. / res.width_px, 68. / res.height_px};
|
||||
|
||||
|
||||
sla::Raster raster;
|
||||
raster.reset(res, pixdim);
|
||||
ASSERT_FALSE(raster.empty());
|
||||
|
@ -404,54 +439,54 @@ static void check_raster_transformations(sla::Raster::Orientation o,
|
|||
double disp_w = 120., disp_h = 68.;
|
||||
sla::Raster::Resolution res{2560, 1440};
|
||||
sla::Raster::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px};
|
||||
|
||||
|
||||
auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)});
|
||||
sla::Raster::Trafo trafo{o, mirroring};
|
||||
trafo.origin_x = bb.center().x();
|
||||
trafo.origin_y = bb.center().y();
|
||||
|
||||
|
||||
sla::Raster raster{res, pixdim, trafo};
|
||||
|
||||
|
||||
// create box of size 32x32 pixels (not 1x1 to avoid antialiasing errors)
|
||||
coord_t pw = 32 * coord_t(std::ceil(scaled<double>(pixdim.w_mm)));
|
||||
coord_t ph = 32 * coord_t(std::ceil(scaled<double>(pixdim.h_mm)));
|
||||
ExPolygon box;
|
||||
box.contour.points = {{-pw, -ph}, {pw, -ph}, {pw, ph}, {-pw, ph}};
|
||||
|
||||
|
||||
double tr_x = scaled<double>(20.), tr_y = tr_x;
|
||||
|
||||
|
||||
box.translate(tr_x, tr_y);
|
||||
ExPolygon expected_box = box;
|
||||
|
||||
|
||||
// Now calculate the position of the translated box according to output
|
||||
// trafo.
|
||||
if (o == sla::Raster::Orientation::roPortrait) expected_box.rotate(PI / 2.);
|
||||
|
||||
|
||||
if (mirroring[X])
|
||||
for (auto &p : expected_box.contour.points) p.x() = -p.x();
|
||||
|
||||
|
||||
if (mirroring[Y])
|
||||
for (auto &p : expected_box.contour.points) p.y() = -p.y();
|
||||
|
||||
|
||||
raster.draw(box);
|
||||
|
||||
|
||||
Point expected_coords = expected_box.contour.bounding_box().center();
|
||||
double rx = unscaled(expected_coords.x() + bb.center().x()) / pixdim.w_mm;
|
||||
double ry = unscaled(expected_coords.y() + bb.center().y()) / pixdim.h_mm;
|
||||
auto w = size_t(std::floor(rx));
|
||||
auto h = res.height_px - size_t(std::floor(ry));
|
||||
|
||||
|
||||
ASSERT_TRUE(w < res.width_px && h < res.height_px);
|
||||
|
||||
|
||||
auto px = raster.read_pixel(w, h);
|
||||
|
||||
|
||||
if (px != FullWhite) {
|
||||
sla::PNGImage img;
|
||||
std::fstream outf("out.png", std::ios::out);
|
||||
|
||||
outf << img.serialize(raster);
|
||||
|
||||
outf << img.serialize(raster);
|
||||
}
|
||||
|
||||
|
||||
ASSERT_EQ(px, FullWhite);
|
||||
}
|
||||
|
||||
|
@ -472,7 +507,7 @@ static ExPolygon square_with_hole(double v)
|
|||
{
|
||||
ExPolygon poly;
|
||||
coord_t V = scaled(v / 2.);
|
||||
|
||||
|
||||
poly.contour.points = {{-V, -V}, {V, -V}, {V, V}, {-V, V}};
|
||||
poly.holes.emplace_back();
|
||||
V = V / 2;
|
||||
|
@ -488,16 +523,16 @@ static double pixel_area(TPixel px, const sla::Raster::PixelDim &pxdim)
|
|||
static double raster_white_area(const sla::Raster &raster)
|
||||
{
|
||||
if (raster.empty()) return std::nan("");
|
||||
|
||||
|
||||
auto res = raster.resolution();
|
||||
double a = 0;
|
||||
|
||||
|
||||
for (size_t x = 0; x < res.width_px; ++x)
|
||||
for (size_t y = 0; y < res.height_px; ++y) {
|
||||
auto px = raster.read_pixel(x, y);
|
||||
a += pixel_area(px, raster.pixel_dimensions());
|
||||
}
|
||||
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
|
@ -505,15 +540,15 @@ static double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd)
|
|||
{
|
||||
auto lines = p.lines();
|
||||
double pix_err = pixel_area(FullWhite, pd) / 2.;
|
||||
|
||||
|
||||
// Worst case is when a line is parallel to the shorter axis of one pixel,
|
||||
// when the line will be composed of the max number of pixels
|
||||
double pix_l = std::min(pd.h_mm, pd.w_mm);
|
||||
|
||||
|
||||
double error = 0.;
|
||||
for (auto &l : lines)
|
||||
error += (unscaled(l.length()) / pix_l) * pix_err;
|
||||
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
@ -524,26 +559,26 @@ TEST(SLARasterOutput, RasterizedPolygonAreaShouldMatch) {
|
|||
|
||||
sla::Raster raster{res, pixdim};
|
||||
auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)});
|
||||
|
||||
|
||||
ExPolygon poly = square_with_hole(10.);
|
||||
poly.translate(bb.center().x(), bb.center().y());
|
||||
raster.draw(poly);
|
||||
|
||||
|
||||
double a = poly.area() / (scaled<double>(1.) * scaled(1.));
|
||||
double ra = raster_white_area(raster);
|
||||
double diff = std::abs(a - ra);
|
||||
|
||||
|
||||
ASSERT_LE(diff, predict_error(poly, pixdim));
|
||||
|
||||
|
||||
raster.clear();
|
||||
poly = square_with_hole(60.);
|
||||
poly.translate(bb.center().x(), bb.center().y());
|
||||
raster.draw(poly);
|
||||
|
||||
|
||||
a = poly.area() / (scaled<double>(1.) * scaled(1.));
|
||||
ra = raster_white_area(raster);
|
||||
diff = std::abs(a - ra);
|
||||
|
||||
|
||||
ASSERT_LE(diff, predict_error(poly, pixdim));
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue