mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-06-04 19:12:42 +00:00
Working arrange_objects with DJD selection heuristic and a bottom-left placement strategy.
This commit is contained in:
48
xs/src/libnest2d/tests/CMakeLists.txt
Normal file
48
xs/src/libnest2d/tests/CMakeLists.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
|
||||
# Try to find existing GTest installation
|
||||
find_package(GTest QUIET)
|
||||
|
||||
if(NOT GTEST_FOUND)
|
||||
# Go and download google test framework, integrate it with the build
|
||||
set(GTEST_LIBRARIES gtest gmock)
|
||||
|
||||
if (CMAKE_VERSION VERSION_LESS 3.2)
|
||||
set(UPDATE_DISCONNECTED_IF_AVAILABLE "")
|
||||
else()
|
||||
set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1")
|
||||
endif()
|
||||
|
||||
include(DownloadProject)
|
||||
download_project(PROJ googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG release-1.8.0
|
||||
${UPDATE_DISCONNECTED_IF_AVAILABLE}
|
||||
)
|
||||
|
||||
# Prevent GoogleTest from overriding our compiler/linker options
|
||||
# when building with Visual Studio
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
|
||||
add_subdirectory(${googletest_SOURCE_DIR}
|
||||
${googletest_BINARY_DIR}
|
||||
)
|
||||
|
||||
else()
|
||||
include_directories(${GTEST_INCLUDE_DIRS} )
|
||||
endif()
|
||||
|
||||
include_directories(BEFORE ${LIBNEST2D_HEADERS})
|
||||
add_executable(bp2d_tests test.cpp printer_parts.h printer_parts.cpp)
|
||||
target_link_libraries(bp2d_tests libnest2d
|
||||
${GTEST_LIBRARIES}
|
||||
)
|
||||
|
||||
if(DEFINED LIBNEST2D_TEST_LIBRARIES)
|
||||
target_link_libraries(bp2d_tests ${LIBNEST2D_TEST_LIBRARIES})
|
||||
endif()
|
||||
|
||||
add_test(gtests bp2d_tests)
|
||||
|
||||
add_executable(main EXCLUDE_FROM_ALL main.cpp printer_parts.cpp printer_parts.h)
|
||||
target_link_libraries(main libnest2d)
|
||||
target_include_directories(main PUBLIC ${CMAKE_SOURCE_DIR})
|
||||
58
xs/src/libnest2d/tests/benchmark.h
Normal file
58
xs/src/libnest2d/tests/benchmark.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) Tamás Mészáros
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
#ifndef INCLUDE_BENCHMARK_H_
|
||||
#define INCLUDE_BENCHMARK_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <ratio>
|
||||
|
||||
/**
|
||||
* A class for doing benchmarks.
|
||||
*/
|
||||
class Benchmark {
|
||||
typedef std::chrono::high_resolution_clock Clock;
|
||||
typedef Clock::duration Duration;
|
||||
typedef Clock::time_point TimePoint;
|
||||
|
||||
TimePoint t1, t2;
|
||||
Duration d;
|
||||
|
||||
inline double to_sec(Duration d) {
|
||||
return d.count() * double(Duration::period::num) / Duration::period::den;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Measure time from the moment of this call.
|
||||
*/
|
||||
void start() { t1 = Clock::now(); }
|
||||
|
||||
/**
|
||||
* Measure time to the moment of this call.
|
||||
*/
|
||||
void stop() { t2 = Clock::now(); }
|
||||
|
||||
/**
|
||||
* Get the time elapsed between a start() end a stop() call.
|
||||
* @return Returns the elapsed time in seconds.
|
||||
*/
|
||||
double getElapsedSec() { d = t2 - t1; return to_sec(d); }
|
||||
};
|
||||
|
||||
|
||||
#endif /* INCLUDE_BENCHMARK_H_ */
|
||||
260
xs/src/libnest2d/tests/main.cpp
Normal file
260
xs/src/libnest2d/tests/main.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include <libnest2d.h>
|
||||
#include <libnest2d/geometries_io.hpp>
|
||||
|
||||
#include "printer_parts.h"
|
||||
#include "benchmark.h"
|
||||
|
||||
namespace {
|
||||
using namespace libnest2d;
|
||||
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
|
||||
//using PackGroup = std::vector<ItemGroup>;
|
||||
|
||||
template<int SCALE, class Bin >
|
||||
void exportSVG(PackGroup& result, const Bin& bin) {
|
||||
|
||||
std::string loc = "out";
|
||||
|
||||
static std::string svg_header =
|
||||
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg height="500" width="500" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
)raw";
|
||||
|
||||
int i = 0;
|
||||
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<Formats::SVG>(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<Formats::SVG>(tsh.rawShape()) << std::endl;
|
||||
}
|
||||
out << "\n</svg>" << std::endl;
|
||||
}
|
||||
out.close();
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
template< int SCALE, class Bin>
|
||||
void exportSVG(ItemGroup& result, const Bin& bin, int idx) {
|
||||
|
||||
std::string loc = "out";
|
||||
|
||||
static std::string svg_header =
|
||||
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg height="500" width="500" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
)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<Formats::SVG>(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<Formats::SVG>(tsh.rawShape()) << std::endl;
|
||||
}
|
||||
out << "\n</svg>" << std::endl;
|
||||
}
|
||||
out.close();
|
||||
|
||||
// i++;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void findDegenerateCase() {
|
||||
using namespace libnest2d;
|
||||
|
||||
auto input = PRINTER_PART_POLYGONS;
|
||||
|
||||
auto scaler = [](Item& item) {
|
||||
for(unsigned i = 0; i < item.vertexCount(); i++) {
|
||||
auto v = item.vertex(i);
|
||||
setX(v, 100*getX(v)); setY(v, 100*getY(v));
|
||||
item.setVertex(i, v);
|
||||
}
|
||||
};
|
||||
|
||||
auto cmp = [](const Item& t1, const Item& t2) {
|
||||
return t1.area() > t2.area();
|
||||
};
|
||||
|
||||
std::for_each(input.begin(), input.end(), scaler);
|
||||
|
||||
std::sort(input.begin(), input.end(), cmp);
|
||||
|
||||
Box bin(210*100, 250*100);
|
||||
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<100>(result, bin, i);
|
||||
}
|
||||
} else {
|
||||
std::cout << "something went terribly wrong!" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
placer.clearItems();
|
||||
it++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void arrangeRectangles() {
|
||||
using namespace libnest2d;
|
||||
|
||||
|
||||
// std::vector<Rectangle> input = {
|
||||
// {80, 80},
|
||||
// {110, 10},
|
||||
// {200, 5},
|
||||
// {80, 30},
|
||||
// {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},
|
||||
// {80, 80},
|
||||
// {110, 10},
|
||||
// {200, 5},
|
||||
// {80, 30},
|
||||
// {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}
|
||||
// };
|
||||
|
||||
auto input = PRINTER_PART_POLYGONS;
|
||||
|
||||
const int SCALE = 1000000;
|
||||
// const int SCALE = 1;
|
||||
|
||||
Box bin(210*SCALE, 250*SCALE);
|
||||
|
||||
auto scaler = [&SCALE, &bin](Item& item) {
|
||||
// double max_area = 0;
|
||||
for(unsigned i = 0; i < item.vertexCount(); i++) {
|
||||
auto v = item.vertex(i);
|
||||
setX(v, SCALE*getX(v)); setY(v, SCALE*getY(v));
|
||||
item.setVertex(i, v);
|
||||
// double area = item.area();
|
||||
// if(max_area < area) {
|
||||
// max_area = area;
|
||||
// bin = item.boundingBox();
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
Coord min_obj_distance = 2*SCALE;
|
||||
|
||||
std::for_each(input.begin(), input.end(), scaler);
|
||||
|
||||
Arranger<BottomLeftPlacer, DJDHeuristic> arrange(bin, min_obj_distance);
|
||||
|
||||
Benchmark bench;
|
||||
|
||||
bench.start();
|
||||
auto result = arrange(input.begin(),
|
||||
input.end());
|
||||
|
||||
bench.stop();
|
||||
|
||||
std::cout << bench.getElapsedSec() << std::endl;
|
||||
|
||||
for(auto& it : input) {
|
||||
auto ret = ShapeLike::isValid(it.transformedShape());
|
||||
std::cout << ret.second << std::endl;
|
||||
}
|
||||
|
||||
exportSVG<SCALE>(result, bin);
|
||||
|
||||
}
|
||||
|
||||
int main(void /*int argc, char **argv*/) {
|
||||
arrangeRectangles();
|
||||
// findDegenerateCase();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
339
xs/src/libnest2d/tests/printer_parts.cpp
Normal file
339
xs/src/libnest2d/tests/printer_parts.cpp
Normal file
@@ -0,0 +1,339 @@
|
||||
#include "printer_parts.h"
|
||||
|
||||
const std::vector<libnest2d::Item> PRINTER_PART_POLYGONS = {
|
||||
{
|
||||
{120, 114},
|
||||
{130, 114},
|
||||
{130, 103},
|
||||
{128, 96},
|
||||
{122, 96},
|
||||
{120, 103},
|
||||
{120, 114}
|
||||
},
|
||||
{
|
||||
{61, 97},
|
||||
{70, 151},
|
||||
{176, 151},
|
||||
{189, 138},
|
||||
{189, 59},
|
||||
{70, 59},
|
||||
{61, 77},
|
||||
{61, 97}
|
||||
},
|
||||
{
|
||||
{72, 147},
|
||||
{94, 151},
|
||||
{178, 151},
|
||||
{178, 59},
|
||||
{72, 59},
|
||||
{72, 147}
|
||||
},
|
||||
{
|
||||
{121, 119},
|
||||
{123, 119},
|
||||
{129, 109},
|
||||
{129, 107},
|
||||
{128, 100},
|
||||
{127, 98},
|
||||
{123, 91},
|
||||
{121, 91},
|
||||
{121, 119},
|
||||
},
|
||||
{
|
||||
{93, 104},
|
||||
{100, 146},
|
||||
{107, 152},
|
||||
{136, 152},
|
||||
{142, 146},
|
||||
{157, 68},
|
||||
{157, 61},
|
||||
{154, 58},
|
||||
{104, 58},
|
||||
{93, 101},
|
||||
{93, 104},
|
||||
},
|
||||
{
|
||||
{90, 91},
|
||||
{114, 130},
|
||||
{158, 130},
|
||||
{163, 126},
|
||||
{163, 123},
|
||||
{152, 80},
|
||||
{116, 80},
|
||||
{90, 81},
|
||||
{87, 86},
|
||||
{90, 91},
|
||||
},
|
||||
{
|
||||
{111, 114},
|
||||
{114, 122},
|
||||
{139, 122},
|
||||
{139, 88},
|
||||
{114, 88},
|
||||
{111, 97},
|
||||
{111, 114},
|
||||
},
|
||||
{
|
||||
{120, 107},
|
||||
{125, 110},
|
||||
{130, 110},
|
||||
{130, 100},
|
||||
{120, 100},
|
||||
{120, 107},
|
||||
},
|
||||
{
|
||||
{113, 123},
|
||||
{137, 123},
|
||||
{137, 87},
|
||||
{113, 87},
|
||||
{113, 123},
|
||||
},
|
||||
{
|
||||
{107, 104},
|
||||
{110, 127},
|
||||
{114, 131},
|
||||
{136, 131},
|
||||
{140, 127},
|
||||
{143, 104},
|
||||
{143, 79},
|
||||
{107, 79},
|
||||
{107, 104},
|
||||
},
|
||||
{
|
||||
{48, 135},
|
||||
{50, 138},
|
||||
{52, 140},
|
||||
{198, 140},
|
||||
{202, 135},
|
||||
{202, 72},
|
||||
{200, 70},
|
||||
{50, 70},
|
||||
{48, 72},
|
||||
{48, 135},
|
||||
},
|
||||
{
|
||||
{115, 104},
|
||||
{116, 106},
|
||||
{123, 119},
|
||||
{127, 119},
|
||||
{134, 106},
|
||||
{135, 104},
|
||||
{135, 98},
|
||||
{134, 96},
|
||||
{132, 93},
|
||||
{128, 91},
|
||||
{122, 91},
|
||||
{118, 93},
|
||||
{116, 96},
|
||||
{115, 98},
|
||||
{115, 104},
|
||||
},
|
||||
{
|
||||
{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},
|
||||
},
|
||||
{
|
||||
{93, 116},
|
||||
{94, 118},
|
||||
{141, 121},
|
||||
{151, 121},
|
||||
{156, 118},
|
||||
{157, 116},
|
||||
{157, 91},
|
||||
{156, 89},
|
||||
{94, 89},
|
||||
{93, 91},
|
||||
{93, 116},
|
||||
},
|
||||
{
|
||||
{89, 60},
|
||||
{91, 66},
|
||||
{134, 185},
|
||||
{139, 198},
|
||||
{140, 200},
|
||||
{141, 201},
|
||||
{159, 201},
|
||||
{161, 199},
|
||||
{161, 195},
|
||||
{157, 179},
|
||||
{114, 26},
|
||||
{110, 12},
|
||||
{108, 10},
|
||||
{106, 9},
|
||||
{92, 9},
|
||||
{89, 50},
|
||||
{89, 60},
|
||||
},
|
||||
{
|
||||
{99, 130},
|
||||
{101, 133},
|
||||
{118, 150},
|
||||
{142, 150},
|
||||
{145, 148},
|
||||
{151, 142},
|
||||
{151, 80},
|
||||
{142, 62},
|
||||
{139, 60},
|
||||
{111, 60},
|
||||
{108, 62},
|
||||
{102, 80},
|
||||
{99, 95},
|
||||
{99, 130},
|
||||
},
|
||||
{
|
||||
{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},
|
||||
},
|
||||
{
|
||||
{107, 146},
|
||||
{124, 146},
|
||||
{141, 96},
|
||||
{143, 79},
|
||||
{143, 73},
|
||||
{142, 70},
|
||||
{140, 68},
|
||||
{136, 65},
|
||||
{134, 64},
|
||||
{127, 64},
|
||||
{107, 65},
|
||||
{107, 146},
|
||||
},
|
||||
{
|
||||
{113, 118},
|
||||
{115, 120},
|
||||
{129, 129},
|
||||
{137, 129},
|
||||
{137, 81},
|
||||
{129, 81},
|
||||
{115, 90},
|
||||
{113, 92},
|
||||
{113, 118},
|
||||
},
|
||||
{
|
||||
{112, 122},
|
||||
{138, 122},
|
||||
{138, 88},
|
||||
{112, 88},
|
||||
{112, 122},
|
||||
},
|
||||
{
|
||||
{102, 116},
|
||||
{111, 126},
|
||||
{114, 126},
|
||||
{144, 106},
|
||||
{148, 100},
|
||||
{148, 85},
|
||||
{147, 84},
|
||||
{102, 84},
|
||||
{102, 116},
|
||||
},
|
||||
{
|
||||
{112, 110},
|
||||
{121, 112},
|
||||
{129, 112},
|
||||
{138, 110},
|
||||
{138, 106},
|
||||
{134, 98},
|
||||
{117, 98},
|
||||
{114, 102},
|
||||
{112, 106},
|
||||
{112, 110},
|
||||
},
|
||||
{
|
||||
{100, 156},
|
||||
{102, 158},
|
||||
{104, 159},
|
||||
{143, 159},
|
||||
{150, 152},
|
||||
{150, 58},
|
||||
{143, 51},
|
||||
{104, 51},
|
||||
{102, 52},
|
||||
{100, 54},
|
||||
{100, 156}
|
||||
},
|
||||
{
|
||||
{106, 151},
|
||||
{108, 151},
|
||||
{139, 139},
|
||||
{144, 134},
|
||||
{144, 76},
|
||||
{139, 71},
|
||||
{108, 59},
|
||||
{106, 59},
|
||||
{106, 151}
|
||||
},
|
||||
{
|
||||
{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}
|
||||
}
|
||||
};
|
||||
9
xs/src/libnest2d/tests/printer_parts.h
Normal file
9
xs/src/libnest2d/tests/printer_parts.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#ifndef PRINTER_PARTS_H
|
||||
#define PRINTER_PARTS_H
|
||||
|
||||
#include <vector>
|
||||
#include <libnest2d.h>
|
||||
|
||||
extern const std::vector<libnest2d::Item> PRINTER_PART_POLYGONS;
|
||||
|
||||
#endif // PRINTER_PARTS_H
|
||||
474
xs/src/libnest2d/tests/test.cpp
Normal file
474
xs/src/libnest2d/tests/test.cpp
Normal file
@@ -0,0 +1,474 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include <fstream>
|
||||
|
||||
#include <libnest2d.h>
|
||||
#include "printer_parts.h"
|
||||
#include <libnest2d/geometries_io.hpp>
|
||||
#include <libnest2d/geometries_nfp.hpp>
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
// 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(), 4);
|
||||
|
||||
Item sh2 ({ {0, 0}, {1, 0}, {1, 1}, {0, 1} });
|
||||
|
||||
ASSERT_EQ(sh2.vertexCount(), 4);
|
||||
|
||||
// copy
|
||||
Item sh3 = sh2;
|
||||
|
||||
ASSERT_EQ(sh3.vertexCount(), 4);
|
||||
|
||||
sh2 = {};
|
||||
|
||||
ASSERT_EQ(sh2.vertexCount(), 0);
|
||||
ASSERT_EQ(sh3.vertexCount(), 4);
|
||||
|
||||
}
|
||||
|
||||
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<Coord>::value)
|
||||
ASSERT_DOUBLE_EQ(static_cast<double>(val), 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);
|
||||
|
||||
}
|
||||
|
||||
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<Rectangle> 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<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250));
|
||||
|
||||
auto groups = arrange(rects.begin(), rects.end());
|
||||
|
||||
ASSERT_EQ(groups.size(), 1);
|
||||
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<Rectangle> rects = { {40, 40}, {10, 10}, {20, 20} };
|
||||
std::vector<Rectangle> 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<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250),
|
||||
min_obj_distance);
|
||||
|
||||
auto groups = arrange(rects.begin(), rects.end());
|
||||
|
||||
ASSERT_EQ(groups.size(), 1);
|
||||
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<unsigned long SCALE = 1, class Bin>
|
||||
void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin, int idx = 0) {
|
||||
|
||||
|
||||
std::string loc = "out";
|
||||
|
||||
static std::string svg_header =
|
||||
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg height="500" width="500" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
)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<Formats::SVG>(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<Formats::SVG>(tsh.rawShape()) << std::endl;
|
||||
}
|
||||
out << "\n</svg>" << std::endl;
|
||||
}
|
||||
out.close();
|
||||
|
||||
// i++;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
TEST(GeometryAlgorithms, BottomLeftStressTest) {
|
||||
using namespace libnest2d;
|
||||
|
||||
auto input = PRINTER_PART_POLYGONS;
|
||||
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(GeometryAlgorithms, nfpConvexConvex) {
|
||||
using namespace libnest2d;
|
||||
|
||||
const unsigned long SCALE = 1;
|
||||
|
||||
Box bin(210*SCALE, 250*SCALE);
|
||||
|
||||
Item stationary = {
|
||||
{120, 114},
|
||||
{130, 114},
|
||||
{130, 103},
|
||||
{128, 96},
|
||||
{122, 96},
|
||||
{120, 103},
|
||||
{120, 114}
|
||||
};
|
||||
|
||||
Item orbiter = {
|
||||
{72, 147},
|
||||
{94, 151},
|
||||
{178, 151},
|
||||
{178, 59},
|
||||
{72, 59},
|
||||
{72, 147}
|
||||
};
|
||||
|
||||
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 = *(ShapeLike::begin(rorbiter));
|
||||
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);
|
||||
|
||||
if(!(notinside && notintersecting)) {
|
||||
std::vector<std::reference_wrapper<Item>> inp = {
|
||||
std::ref(stationary), std::ref(tmp), std::ref(infp)
|
||||
};
|
||||
|
||||
exportSVG<SCALE>(inp, bin, i++);
|
||||
}
|
||||
|
||||
//ASSERT_TRUE(notintersecting);
|
||||
ASSERT_TRUE(notinside);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Reference in New Issue
Block a user