#include #include #include #include "printer_parts.h" #include #include std::vector& prusaParts() { static std::vector ret; if(ret.empty()) { ret.reserve(PRINTER_PART_POLYGONS.size()); for(auto& inp : PRINTER_PART_POLYGONS) ret.emplace_back(inp); } return ret; } TEST(BasicFunctionality, Angles) { using namespace libnest2d; Degrees deg(180); Radians rad(deg); Degrees deg2(rad); ASSERT_DOUBLE_EQ(rad, Pi); ASSERT_DOUBLE_EQ(deg, 180); ASSERT_DOUBLE_EQ(deg2, 180); ASSERT_DOUBLE_EQ(rad, (Radians) deg); ASSERT_DOUBLE_EQ( (Degrees) rad, deg); ASSERT_TRUE(rad == deg); Segment seg = {{0, 0}, {12, -10}}; ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 270 && Degrees(seg.angleToXaxis()) < 360); seg = {{0, 0}, {12, 10}}; ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 0 && Degrees(seg.angleToXaxis()) < 90); seg = {{0, 0}, {-12, 10}}; ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 90 && Degrees(seg.angleToXaxis()) < 180); seg = {{0, 0}, {-12, -10}}; ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 180 && Degrees(seg.angleToXaxis()) < 270); seg = {{0, 0}, {1, 0}}; ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 0); seg = {{0, 0}, {0, 1}}; ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 90); seg = {{0, 0}, {-1, 0}}; ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 180); seg = {{0, 0}, {0, -1}}; ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 270); } // Simple test, does not use gmock TEST(BasicFunctionality, creationAndDestruction) { using namespace libnest2d; Item sh = { {0, 0}, {1, 0}, {1, 1}, {0, 1} }; ASSERT_EQ(sh.vertexCount(), 4u); Item sh2 ({ {0, 0}, {1, 0}, {1, 1}, {0, 1} }); ASSERT_EQ(sh2.vertexCount(), 4u); // copy Item sh3 = sh2; ASSERT_EQ(sh3.vertexCount(), 4u); sh2 = {}; ASSERT_EQ(sh2.vertexCount(), 0u); ASSERT_EQ(sh3.vertexCount(), 4u); } TEST(GeometryAlgorithms, Distance) { using namespace libnest2d; Point p1 = {0, 0}; Point p2 = {10, 0}; Point p3 = {10, 10}; ASSERT_DOUBLE_EQ(PointLike::distance(p1, p2), 10); ASSERT_DOUBLE_EQ(PointLike::distance(p1, p3), sqrt(200)); Segment seg(p1, p3); ASSERT_DOUBLE_EQ(PointLike::distance(p2, seg), 7.0710678118654755); auto result = PointLike::horizontalDistance(p2, seg); auto check = [](Coord val, Coord expected) { if(std::is_floating_point::value) ASSERT_DOUBLE_EQ(static_cast(val), static_cast(expected)); else ASSERT_EQ(val, expected); }; ASSERT_TRUE(result.second); check(result.first, 10); result = PointLike::verticalDistance(p2, seg); ASSERT_TRUE(result.second); check(result.first, -10); result = PointLike::verticalDistance(Point{10, 20}, seg); ASSERT_TRUE(result.second); check(result.first, 10); Point p4 = {80, 0}; Segment seg2 = { {0, 0}, {0, 40} }; result = PointLike::horizontalDistance(p4, seg2); ASSERT_TRUE(result.second); check(result.first, 80); result = PointLike::verticalDistance(p4, seg2); // Point should not be related to the segment ASSERT_FALSE(result.second); } TEST(GeometryAlgorithms, Area) { using namespace libnest2d; Rectangle rect(10, 10); ASSERT_EQ(rect.area(), 100); Rectangle rect2 = {100, 100}; ASSERT_EQ(rect2.area(), 10000); Item item = { {61, 97}, {70, 151}, {176, 151}, {189, 138}, {189, 59}, {70, 59}, {61, 77}, {61, 97} }; ASSERT_TRUE(ShapeLike::area(item.transformedShape()) > 0 ); } TEST(GeometryAlgorithms, IsPointInsidePolygon) { using namespace libnest2d; Rectangle rect(10, 10); Point p = {1, 1}; ASSERT_TRUE(rect.isPointInside(p)); p = {11, 11}; ASSERT_FALSE(rect.isPointInside(p)); p = {11, 12}; ASSERT_FALSE(rect.isPointInside(p)); p = {3, 3}; ASSERT_TRUE(rect.isPointInside(p)); } //TEST(GeometryAlgorithms, Intersections) { // using namespace binpack2d; // Rectangle rect(70, 30); // rect.translate({80, 60}); // Rectangle rect2(80, 60); // rect2.translate({80, 0}); //// ASSERT_FALSE(Item::intersects(rect, rect2)); // Segment s1({0, 0}, {10, 10}); // Segment s2({1, 1}, {11, 11}); // ASSERT_FALSE(ShapeLike::intersects(s1, s1)); // ASSERT_FALSE(ShapeLike::intersects(s1, s2)); //} // Simple test, does not use gmock TEST(GeometryAlgorithms, LeftAndDownPolygon) { using namespace libnest2d; using namespace libnest2d; Box bin(100, 100); BottomLeftPlacer placer(bin); Item item = {{70, 75}, {88, 60}, {65, 50}, {60, 30}, {80, 20}, {42, 20}, {35, 35}, {35, 55}, {40, 75}, {70, 75}}; Item leftControl = { {40, 75}, {35, 55}, {35, 35}, {42, 20}, {0, 20}, {0, 75}, {40, 75}}; Item downControl = {{88, 60}, {88, 0}, {35, 0}, {35, 35}, {42, 20}, {80, 20}, {60, 30}, {65, 50}, {88, 60}}; Item leftp(placer.leftPoly(item)); ASSERT_TRUE(ShapeLike::isValid(leftp.rawShape()).first); ASSERT_EQ(leftp.vertexCount(), leftControl.vertexCount()); for(size_t i = 0; i < leftControl.vertexCount(); i++) { ASSERT_EQ(getX(leftp.vertex(i)), getX(leftControl.vertex(i))); ASSERT_EQ(getY(leftp.vertex(i)), getY(leftControl.vertex(i))); } Item downp(placer.downPoly(item)); ASSERT_TRUE(ShapeLike::isValid(downp.rawShape()).first); ASSERT_EQ(downp.vertexCount(), downControl.vertexCount()); for(size_t i = 0; i < downControl.vertexCount(); i++) { ASSERT_EQ(getX(downp.vertex(i)), getX(downControl.vertex(i))); ASSERT_EQ(getY(downp.vertex(i)), getY(downControl.vertex(i))); } } // Simple test, does not use gmock TEST(GeometryAlgorithms, ArrangeRectanglesTight) { using namespace libnest2d; std::vector rects = { {80, 80}, {60, 90}, {70, 30}, {80, 60}, {60, 60}, {60, 40}, {40, 40}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {20, 20} }; Arranger arrange(Box(210, 250)); auto groups = arrange(rects.begin(), rects.end()); ASSERT_EQ(groups.size(), 1u); ASSERT_EQ(groups[0].size(), rects.size()); // check for no intersections, no containment: for(auto result : groups) { bool valid = true; for(Item& r1 : result) { for(Item& r2 : result) { if(&r1 != &r2 ) { valid = !Item::intersects(r1, r2) || Item::touches(r1, r2); valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); ASSERT_TRUE(valid); } } } } } TEST(GeometryAlgorithms, ArrangeRectanglesLoose) { using namespace libnest2d; // std::vector rects = { {40, 40}, {10, 10}, {20, 20} }; std::vector rects = { {80, 80}, {60, 90}, {70, 30}, {80, 60}, {60, 60}, {60, 40}, {40, 40}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {10, 10}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}, {20, 20} }; Coord min_obj_distance = 5; Arranger arrange(Box(210, 250), min_obj_distance); auto groups = arrange(rects.begin(), rects.end()); ASSERT_EQ(groups.size(), 1u); ASSERT_EQ(groups[0].size(), rects.size()); // check for no intersections, no containment: auto result = groups[0]; bool valid = true; for(Item& r1 : result) { for(Item& r2 : result) { if(&r1 != &r2 ) { valid = !Item::intersects(r1, r2); valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); ASSERT_TRUE(valid); } } } } namespace { using namespace libnest2d; template void exportSVG(std::vector>& result, const Bin& bin, int idx = 0) { std::string loc = "out"; static std::string svg_header = R"raw( )raw"; int i = idx; auto r = result; // for(auto r : result) { std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out); if(out.is_open()) { out << svg_header; Item rbin( Rectangle(bin.width(), bin.height()) ); for(unsigned i = 0; i < rbin.vertexCount(); i++) { auto v = rbin.vertex(i); setY(v, -getY(v)/SCALE + 500 ); setX(v, getX(v)/SCALE); rbin.setVertex(i, v); } out << ShapeLike::serialize(rbin.rawShape()) << std::endl; for(Item& sh : r) { Item tsh(sh.transformedShape()); for(unsigned i = 0; i < tsh.vertexCount(); i++) { auto v = tsh.vertex(i); setY(v, -getY(v)/SCALE + 500); setX(v, getX(v)/SCALE); tsh.setVertex(i, v); } out << ShapeLike::serialize(tsh.rawShape()) << std::endl; } out << "\n" << std::endl; } out.close(); // i++; // } } } TEST(GeometryAlgorithms, BottomLeftStressTest) { using namespace libnest2d; auto& input = prusaParts(); Box bin(210, 250); BottomLeftPlacer placer(bin); auto it = input.begin(); auto next = it; int i = 0; while(it != input.end() && ++next != input.end()) { placer.pack(*it); placer.pack(*next); auto result = placer.getItems(); bool valid = true; if(result.size() == 2) { Item& r1 = result[0]; Item& r2 = result[1]; valid = !Item::intersects(r1, r2) || Item::touches(r1, r2); valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); if(!valid) { std::cout << "error index: " << i << std::endl; exportSVG(result, bin, i); } // ASSERT_TRUE(valid); } else { std::cout << "something went terribly wrong!" << std::endl; } placer.clearItems(); it++; i++; } } namespace { struct ItemPair { Item orbiter; Item stationary; }; std::vector nfp_testdata = { { { {80, 50}, {100, 70}, {120, 50}, {80, 50} }, { {10, 10}, {10, 40}, {40, 40}, {40, 10}, {10, 10} } }, { { {80, 50}, {60, 70}, {80, 90}, {120, 90}, {140, 70}, {120, 50}, {80, 50} }, { {10, 10}, {10, 40}, {40, 40}, {40, 10}, {10, 10} } }, { { {40, 10}, {30, 10}, {20, 20}, {20, 30}, {30, 40}, {40, 40}, {50, 30}, {50, 20}, {40, 10} }, { {80, 0}, {80, 30}, {110, 30}, {110, 0}, {80, 0} } }, { { {117, 107}, {118, 109}, {120, 112}, {122, 113}, {128, 113}, {130, 112}, {132, 109}, {133, 107}, {133, 103}, {132, 101}, {130, 98}, {128, 97}, {122, 97}, {120, 98}, {118, 101}, {117, 103}, {117, 107} }, { {102, 116}, {111, 126}, {114, 126}, {144, 106}, {148, 100}, {148, 85}, {147, 84}, {102, 84}, {102, 116}, } }, { { {99, 122}, {108, 140}, {110, 142}, {139, 142}, {151, 122}, {151, 102}, {142, 70}, {139, 68}, {111, 68}, {108, 70}, {99, 102}, {99, 122}, }, { {107, 124}, {128, 125}, {133, 125}, {136, 124}, {140, 121}, {142, 119}, {143, 116}, {143, 109}, {141, 93}, {139, 89}, {136, 86}, {134, 85}, {108, 85}, {107, 86}, {107, 124}, } }, { { {91, 100}, {94, 144}, {117, 153}, {118, 153}, {159, 112}, {159, 110}, {156, 66}, {133, 57}, {132, 57}, {91, 98}, {91, 100}, }, { {101, 90}, {103, 98}, {107, 113}, {114, 125}, {115, 126}, {135, 126}, {136, 125}, {144, 114}, {149, 90}, {149, 89}, {148, 87}, {145, 84}, {105, 84}, {102, 87}, {101, 89}, {101, 90}, } } }; } TEST(GeometryAlgorithms, nfpConvexConvex) { using namespace libnest2d; const Coord SCALE = 1000000; Box bin(210*SCALE, 250*SCALE); int testcase = 0; auto& exportfun = exportSVG<1, Box>; auto onetest = [&](Item& orbiter, Item& stationary){ testcase++; orbiter.translate({210*SCALE, 0}); auto&& nfp = Nfp::noFitPolygon(stationary.rawShape(), orbiter.transformedShape()); auto v = ShapeLike::isValid(nfp); if(!v.first) { std::cout << v.second << std::endl; } ASSERT_TRUE(v.first); Item infp(nfp); int i = 0; auto rorbiter = orbiter.transformedShape(); auto vo = Nfp::referenceVertex(rorbiter); ASSERT_TRUE(stationary.isInside(infp)); for(auto v : infp) { auto dx = getX(v) - getX(vo); auto dy = getY(v) - getY(vo); Item tmp = orbiter; tmp.translate({dx, dy}); bool notinside = !tmp.isInside(stationary); bool notintersecting = !Item::intersects(tmp, stationary) || Item::touches(tmp, stationary); if(!(notinside && notintersecting)) { std::vector> inp = { std::ref(stationary), std::ref(tmp), std::ref(infp) }; exportfun(inp, bin, testcase*i++); } ASSERT_TRUE(notintersecting); ASSERT_TRUE(notinside); } }; for(auto& td : nfp_testdata) { auto orbiter = td.orbiter; auto stationary = td.stationary; onetest(orbiter, stationary); } for(auto& td : nfp_testdata) { auto orbiter = td.stationary; auto stationary = td.orbiter; onetest(orbiter, stationary); } } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }