diff --git a/xs/src/libnest2d/CMakeLists.txt b/xs/src/libnest2d/CMakeLists.txt index ac9530a63..7977066c1 100644 --- a/xs/src/libnest2d/CMakeLists.txt +++ b/xs/src/libnest2d/CMakeLists.txt @@ -35,6 +35,7 @@ set(LIBNEST2D_SRCFILES libnest2d/geometry_traits.hpp libnest2d/geometries_io.hpp libnest2d/common.hpp + libnest2d/optimizer.hpp libnest2d/placers/placer_boilerplate.hpp libnest2d/placers/bottomleftplacer.hpp libnest2d/placers/nfpplacer.hpp @@ -43,6 +44,10 @@ set(LIBNEST2D_SRCFILES libnest2d/selections/filler.hpp libnest2d/selections/firstfit.hpp libnest2d/selections/djd_heuristic.hpp + libnest2d/optimizers/simplex.hpp + libnest2d/optimizers/subplex.hpp + libnest2d/optimizers/genetic.hpp + libnest2d/optimizers/nlopt_boilerplate.hpp ) if((NOT LIBNEST2D_GEOMETRIES_TARGET) OR (LIBNEST2D_GEOMETRIES_TARGET STREQUAL "")) @@ -68,17 +73,24 @@ else() message(STATUS "Libnest2D backend is: ${LIBNEST2D_GEOMETRIES_TARGET}") endif() -message(STATUS "clipper lib is: ${LIBNEST2D_GEOMETRIES_TARGET}") +message(STATUS "clipper lib is: ${LIBNEST2D_GEOMETRIES_TARGET}") + +find_package(NLopt 1.4) +if(NOT NLopt_FOUND) + message(STATUS "NLopt not found so downloading and automatic build is performed...") + include(DownloadNLopt) +endif() +find_package(Threads REQUIRED) add_library(libnest2d_static STATIC ${LIBNEST2D_SRCFILES} ) -target_link_libraries(libnest2d_static PRIVATE ${LIBNEST2D_GEOMETRIES_TARGET}) -target_include_directories(libnest2d_static PUBLIC ${CMAKE_SOURCE_DIR}) +target_link_libraries(libnest2d_static PRIVATE ${LIBNEST2D_GEOMETRIES_TARGET} ${NLopt_LIBS}) +target_include_directories(libnest2d_static PUBLIC ${CMAKE_SOURCE_DIR} ${NLopt_INCLUDE_DIR}) set_target_properties(libnest2d_static PROPERTIES PREFIX "") if(LIBNEST2D_BUILD_SHARED_LIB) add_library(libnest2d SHARED ${LIBNEST2D_SRCFILES} ) - target_link_libraries(libnest2d PRIVATE ${LIBNEST2D_GEOMETRIES_TARGET}) - target_include_directories(libnest2d PUBLIC ${CMAKE_SOURCE_DIR}) + target_link_libraries(libnest2d PRIVATE ${LIBNEST2D_GEOMETRIES_TARGET} ${NLopt_LIBS}) + target_include_directories(libnest2d PUBLIC ${CMAKE_SOURCE_DIR} ${NLopt_INCLUDE_DIR}) set_target_properties(libnest2d PROPERTIES PREFIX "") endif() @@ -98,6 +110,6 @@ if(LIBNEST2D_BUILD_EXAMPLES) tests/svgtools.hpp tests/printer_parts.cpp tests/printer_parts.h) - target_link_libraries(example libnest2d_static) + target_link_libraries(example libnest2d_static Threads::Threads) target_include_directories(example PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) endif() diff --git a/xs/src/libnest2d/README.md b/xs/src/libnest2d/README.md index f2182d649..3508801a8 100644 --- a/xs/src/libnest2d/README.md +++ b/xs/src/libnest2d/README.md @@ -1,29 +1,26 @@ # Introduction Libnest2D is a library and framework for the 2D bin packaging problem. -Inspired from the [SVGNest](svgnest.com) Javascript library the project is is +Inspired from the [SVGNest](svgnest.com) Javascript library the project is built from scratch in C++11. The library is written with a policy that it should be usable out of the box with a very simple interface but has to be customizable -to the very core as well. This has led to a design where the algorithms are -defined in a header only fashion with template only geometry types. These -geometries can have custom or already existing implementation to avoid copying -or having unnecessary dependencies. +to the very core as well. The algorithms are defined in a header only fashion +with templated geometry types. These geometries can have custom or already +existing implementation to avoid copying or having unnecessary dependencies. -A default backend is provided if a user just wants to use the library out of the -box without implementing the interface of these geometry types. The default -backend is built on top of boost geometry and the -[polyclipping](http://www.angusj.com/delphi/clipper.php) library and implies the -dependency on these packages as well as the compilation of the backend (although -I may find a solution in the future to make the backend header only as well). +A default backend is provided if the user of the library just wants to use it +out of the box without additional integration. The default backend is reasonably +fast and robust, being built on top of boost geometry and the +[polyclipping](http://www.angusj.com/delphi/clipper.php) library. Usage of +this default backend implies the dependency on these packages as well as the +compilation of the backend itself (The default backend is not yet header only). -This software is currently under heavy construction and lacks a throughout -documentation and some essential algorithms as well. At this point a fairly -untested version of the DJD selection heuristic is working with a bottom-left -placing strategy which may produce usable arrangements in most cases. +This software is currently under construction and lacks a throughout +documentation and some essential algorithms as well. At this stage it works well +for rectangles and convex closed polygons without considering holes and +concavities. -The no-fit polygon based placement strategy will be implemented in the very near -future which should produce high quality results for convex and non convex -polygons with holes as well. +Holes and non-convex polygons will be usable in the near future as well. # References - [SVGNest](https://github.com/Jack000/SVGnest) diff --git a/xs/src/libnest2d/cmake_modules/DownloadNLopt.cmake b/xs/src/libnest2d/cmake_modules/DownloadNLopt.cmake new file mode 100644 index 000000000..814213b38 --- /dev/null +++ b/xs/src/libnest2d/cmake_modules/DownloadNLopt.cmake @@ -0,0 +1,31 @@ +include(DownloadProject) + +if (CMAKE_VERSION VERSION_LESS 3.2) + set(UPDATE_DISCONNECTED_IF_AVAILABLE "") +else() + set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") +endif() + +# set(NLopt_DIR ${CMAKE_BINARY_DIR}/nlopt) +include(DownloadProject) +download_project( PROJ nlopt + GIT_REPOSITORY https://github.com/stevengj/nlopt.git + GIT_TAG 1fcbcbf2fe8e34234e016cc43a6c41d3e8453e1f #master #nlopt-2.4.2 + # CMAKE_CACHE_ARGS -DBUILD_SHARED_LIBS:BOOL=OFF -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${NLopt_DIR} + ${UPDATE_DISCONNECTED_IF_AVAILABLE} +) + +set(SHARED_LIBS_STATE BUILD_SHARED_LIBS) +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(NLOPT_PYTHON OFF CACHE BOOL "" FORCE) +set(NLOPT_OCTAVE OFF CACHE BOOL "" FORCE) +set(NLOPT_MATLAB OFF CACHE BOOL "" FORCE) +set(NLOPT_GUILE OFF CACHE BOOL "" FORCE) +set(NLOPT_SWIG OFF CACHE BOOL "" FORCE) +set(NLOPT_LINK_PYTHON OFF CACHE BOOL "" FORCE) + +add_subdirectory(${nlopt_SOURCE_DIR} ${nlopt_BINARY_DIR}) + +set(NLopt_LIBS nlopt) +set(NLopt_INCLUDE_DIR ${nlopt_BINARY_DIR}) +set(SHARED_LIBS_STATE ${SHARED_STATE}) \ No newline at end of file diff --git a/xs/src/libnest2d/cmake_modules/FindNLopt.cmake b/xs/src/libnest2d/cmake_modules/FindNLopt.cmake new file mode 100644 index 000000000..4b93be7b6 --- /dev/null +++ b/xs/src/libnest2d/cmake_modules/FindNLopt.cmake @@ -0,0 +1,125 @@ +#/////////////////////////////////////////////////////////////////////////// +#//------------------------------------------------------------------------- +#// +#// Description: +#// cmake module for finding NLopt installation +#// NLopt installation location is defined by environment variable $NLOPT +#// +#// following variables are defined: +#// NLopt_DIR - NLopt installation directory +#// NLopt_INCLUDE_DIR - NLopt header directory +#// NLopt_LIBRARY_DIR - NLopt library directory +#// NLopt_LIBS - NLopt library files +#// +#// Example usage: +#// find_package(NLopt 1.4 REQUIRED) +#// +#// +#//------------------------------------------------------------------------- + + +set(NLopt_FOUND FALSE) +set(NLopt_ERROR_REASON "") +set(NLopt_DEFINITIONS "") +set(NLopt_LIBS) + + +set(NLopt_DIR $ENV{NLOPT}) +if(NOT NLopt_DIR) + + set(NLopt_FOUND TRUE) + + set(_NLopt_LIB_NAMES "nlopt") + find_library(NLopt_LIBS + NAMES ${_NLopt_LIB_NAMES}) + if(NOT NLopt_LIBS) + set(NLopt_FOUND FALSE) + set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Cannot find NLopt library '${_NLopt_LIB_NAMES}'.") + else() + get_filename_component(NLopt_DIR ${NLopt_LIBS} PATH) + endif() + unset(_NLopt_LIB_NAMES) + + set(_NLopt_HEADER_FILE_NAME "nlopt.hpp") + find_file(_NLopt_HEADER_FILE + NAMES ${_NLopt_HEADER_FILE_NAME}) + if(NOT _NLopt_HEADER_FILE) + set(NLopt_FOUND FALSE) + set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Cannot find NLopt header file '${_NLopt_HEADER_FILE_NAME}'.") + endif() + unset(_NLopt_HEADER_FILE_NAME) + unset(_NLopt_HEADER_FILE) + + if(NOT NLopt_FOUND) + set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} NLopt not found in system directories (and environment variable NLOPT is not set).") + else() + get_filename_component(NLopt_INCLUDE_DIR ${_NLopt_HEADER_FILE} DIRECTORY ) + endif() + + + +else() + + set(NLopt_FOUND TRUE) + + set(NLopt_INCLUDE_DIR "${NLopt_DIR}/include") + if(NOT EXISTS "${NLopt_INCLUDE_DIR}") + set(NLopt_FOUND FALSE) + set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Directory '${NLopt_INCLUDE_DIR}' does not exist.") + endif() + + set(NLopt_LIBRARY_DIR "${NLopt_DIR}/lib") + if(NOT EXISTS "${NLopt_LIBRARY_DIR}") + set(NLopt_FOUND FALSE) + set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Directory '${NLopt_LIBRARY_DIR}' does not exist.") + endif() + + set(_NLopt_LIB_NAMES "nlopt_cxx") + find_library(NLopt_LIBS + NAMES ${_NLopt_LIB_NAMES} + PATHS ${NLopt_LIBRARY_DIR} + NO_DEFAULT_PATH) + if(NOT NLopt_LIBS) + set(NLopt_FOUND FALSE) + set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Cannot find NLopt library '${_NLopt_LIB_NAMES}' in '${NLopt_LIBRARY_DIR}'.") + endif() + unset(_NLopt_LIB_NAMES) + + set(_NLopt_HEADER_FILE_NAME "nlopt.hpp") + find_file(_NLopt_HEADER_FILE + NAMES ${_NLopt_HEADER_FILE_NAME} + PATHS ${NLopt_INCLUDE_DIR} + NO_DEFAULT_PATH) + if(NOT _NLopt_HEADER_FILE) + set(NLopt_FOUND FALSE) + set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Cannot find NLopt header file '${_NLopt_HEADER_FILE_NAME}' in '${NLopt_INCLUDE_DIR}'.") + endif() + unset(_NLopt_HEADER_FILE_NAME) + unset(_NLopt_HEADER_FILE) + +endif() + + +# make variables changeable +mark_as_advanced( + NLopt_INCLUDE_DIR + NLopt_LIBRARY_DIR + NLopt_LIBS + NLopt_DEFINITIONS + ) + + +# report result +if(NLopt_FOUND) + message(STATUS "Found NLopt in '${NLopt_DIR}'.") + message(STATUS "Using NLopt include directory '${NLopt_INCLUDE_DIR}'.") + message(STATUS "Using NLopt library '${NLopt_LIBS}'.") +else() + if(NLopt_FIND_REQUIRED) + message(FATAL_ERROR "Unable to find requested NLopt installation:${NLopt_ERROR_REASON}") + else() + if(NOT NLopt_FIND_QUIETLY) + message(STATUS "NLopt was not found:${NLopt_ERROR_REASON}") + endif() + endif() +endif() \ No newline at end of file diff --git a/xs/src/libnest2d/libnest2d/boost_alg.hpp b/xs/src/libnest2d/libnest2d/boost_alg.hpp index fb43c2125..8cd126f68 100644 --- a/xs/src/libnest2d/libnest2d/boost_alg.hpp +++ b/xs/src/libnest2d/libnest2d/boost_alg.hpp @@ -5,6 +5,9 @@ #include #endif +#ifdef __clang__ +#undef _MSC_EXTENSIONS +#endif #include // this should be removed to not confuse the compiler @@ -152,7 +155,7 @@ template<> struct indexed_access { return bp2d::getX(seg.first()); } static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) { - bp2d::setX(seg.first(), coord); + auto p = seg.first(); bp2d::setX(p, coord); seg.first(p); } }; @@ -161,7 +164,7 @@ template<> struct indexed_access { return bp2d::getY(seg.first()); } static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) { - bp2d::setY(seg.first(), coord); + auto p = seg.first(); bp2d::setY(p, coord); seg.first(p); } }; @@ -170,7 +173,7 @@ template<> struct indexed_access { return bp2d::getX(seg.second()); } static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) { - bp2d::setX(seg.second(), coord); + auto p = seg.second(); bp2d::setX(p, coord); seg.second(p); } }; @@ -179,7 +182,7 @@ template<> struct indexed_access { return bp2d::getY(seg.second()); } static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) { - bp2d::setY(seg.second(), coord); + auto p = seg.second(); bp2d::setY(p, coord); seg.second(p); } }; @@ -325,20 +328,23 @@ inline bool ShapeLike::intersects(const PathImpl& sh1, // Tell libnest2d how to make string out of a ClipperPolygon object template<> inline bool ShapeLike::intersects(const PolygonImpl& sh1, - const PolygonImpl& sh2) { + const PolygonImpl& sh2) +{ return boost::geometry::intersects(sh1, sh2); } // Tell libnest2d how to make string out of a ClipperPolygon object template<> inline bool ShapeLike::intersects(const bp2d::Segment& s1, - const bp2d::Segment& s2) { + const bp2d::Segment& s2) +{ return boost::geometry::intersects(s1, s2); } #ifndef DISABLE_BOOST_AREA template<> -inline double ShapeLike::area(const PolygonImpl& shape) { +inline double ShapeLike::area(const PolygonImpl& shape) +{ return boost::geometry::area(shape); } #endif @@ -364,16 +370,25 @@ inline bool ShapeLike::touches( const PolygonImpl& sh1, return boost::geometry::touches(sh1, sh2); } +template<> +inline bool ShapeLike::touches( const PointImpl& point, + const PolygonImpl& shape) +{ + return boost::geometry::touches(point, shape); +} + #ifndef DISABLE_BOOST_BOUNDING_BOX template<> -inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh) { +inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh) +{ bp2d::Box b; boost::geometry::envelope(sh, b); return b; } template<> -inline bp2d::Box ShapeLike::boundingBox(const bp2d::Shapes& shapes) { +inline bp2d::Box ShapeLike::boundingBox(const bp2d::Shapes& shapes) +{ bp2d::Box b; boost::geometry::envelope(shapes, b); return b; @@ -398,17 +413,19 @@ inline PolygonImpl ShapeLike::convexHull(const bp2d::Shapes& shapes) } #endif +#ifndef DISABLE_BOOST_ROTATE template<> inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) { namespace trans = boost::geometry::strategy::transform; PolygonImpl cpy = sh; - trans::rotate_transformer rotate(rads); + boost::geometry::transform(cpy, sh, rotate); } +#endif #ifndef DISABLE_BOOST_TRANSLATE template<> diff --git a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.cpp b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.cpp index 6bd7bf99f..746edf1f4 100644 --- a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.cpp +++ b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.cpp @@ -1,15 +1,35 @@ #include "clipper_backend.hpp" +#include namespace libnest2d { namespace { + +class SpinLock { + std::atomic_flag& lck_; +public: + + inline SpinLock(std::atomic_flag& flg): lck_(flg) {} + + inline void lock() { + while(lck_.test_and_set(std::memory_order_acquire)) {} + } + + inline void unlock() { lck_.clear(std::memory_order_release); } +}; + class HoleCache { friend struct libnest2d::ShapeLike; std::unordered_map< const PolygonImpl*, ClipperLib::Paths> map; ClipperLib::Paths& _getHoles(const PolygonImpl* p) { + static std::atomic_flag flg = ATOMIC_FLAG_INIT; + SpinLock lock(flg); + + lock.lock(); ClipperLib::Paths& paths = map[p]; + lock.unlock(); if(paths.size() != p->Childs.size()) { paths.reserve(p->Childs.size()); diff --git a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp index 6621d7085..ef9a2b622 100644 --- a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp +++ b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp @@ -103,7 +103,7 @@ inline TCoord& PointLike::y(PointImpl& p) } template<> -inline void ShapeLike::reserve(PolygonImpl& sh, unsigned long vertex_capacity) +inline void ShapeLike::reserve(PolygonImpl& sh, size_t vertex_capacity) { return sh.Contour.reserve(vertex_capacity); } @@ -164,6 +164,95 @@ inline void ShapeLike::offset(PolygonImpl& sh, TCoord distance) { } +// TODO : make it convex hull right and faster than boost + +//#define DISABLE_BOOST_CONVEX_HULL +//inline TCoord cross( const PointImpl &O, +// const PointImpl &A, +// const PointImpl &B) +//{ +// return (A.X - O.X) * (B.Y - O.Y) - (A.Y - O.Y) * (B.X - O.X); +//} + +//template<> +//inline PolygonImpl ShapeLike::convexHull(const PolygonImpl& sh) +//{ +// auto& P = sh.Contour; +// PolygonImpl ret; + +// size_t n = P.size(), k = 0; +// if (n <= 3) return ret; + +// auto& H = ret.Contour; +// H.resize(2*n); +// std::vector indices(P.size(), 0); +// std::iota(indices.begin(), indices.end(), 0); + +// // Sort points lexicographically +// std::sort(indices.begin(), indices.end(), [&P](unsigned i1, unsigned i2){ +// auto& p1 = P[i1], &p2 = P[i2]; +// return p1.X < p2.X || (p1.X == p2.X && p1.Y < p2.Y); +// }); + +// // Build lower hull +// for (size_t i = 0; i < n; ++i) { +// while (k >= 2 && cross(H[k-2], H[k-1], P[i]) <= 0) k--; +// H[k++] = P[i]; +// } + +// // Build upper hull +// for (size_t i = n-1, t = k+1; i > 0; --i) { +// while (k >= t && cross(H[k-2], H[k-1], P[i-1]) <= 0) k--; +// H[k++] = P[i-1]; +// } + +// H.resize(k-1); +// return ret; +//} + +//template<> +//inline PolygonImpl ShapeLike::convexHull( +// const ShapeLike::Shapes& shapes) +//{ +// PathImpl P; +// PolygonImpl ret; + +// size_t n = 0, k = 0; +// for(auto& sh : shapes) { n += sh.Contour.size(); } + +// P.reserve(n); +// for(auto& sh : shapes) { P.insert(P.end(), +// sh.Contour.begin(), sh.Contour.end()); } + +// if (n <= 3) { ret.Contour = P; return ret; } + +// auto& H = ret.Contour; +// H.resize(2*n); +// std::vector indices(P.size(), 0); +// std::iota(indices.begin(), indices.end(), 0); + +// // Sort points lexicographically +// std::sort(indices.begin(), indices.end(), [&P](unsigned i1, unsigned i2){ +// auto& p1 = P[i1], &p2 = P[i2]; +// return p1.X < p2.X || (p1.X == p2.X && p1.Y < p2.Y); +// }); + +// // Build lower hull +// for (size_t i = 0; i < n; ++i) { +// while (k >= 2 && cross(H[k-2], H[k-1], P[i]) <= 0) k--; +// H[k++] = P[i]; +// } + +// // Build upper hull +// for (size_t i = n-1, t = k+1; i > 0; --i) { +// while (k >= t && cross(H[k-2], H[k-1], P[i-1]) <= 0) k--; +// H[k++] = P[i-1]; +// } + +// H.resize(k-1); +// return ret; +//} + template<> inline PolygonImpl& Nfp::minkowskiAdd(PolygonImpl& sh, const PolygonImpl& other) @@ -263,8 +352,30 @@ inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs) for(auto& hole : sh.Childs) for(auto& p : hole->Contour) { p += offs; } } -#define DISABLE_BOOST_NFP_MERGE +#define DISABLE_BOOST_ROTATE +template<> +inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) +{ + using Coord = TCoord; + auto cosa = rads.cos();//std::cos(rads); + auto sina = rads.sin(); //std::sin(rads); + + for(auto& p : sh.Contour) { + p = { + static_cast(p.X * cosa - p.Y * sina), + static_cast(p.X * sina + p.Y * cosa) + }; + } + for(auto& hole : sh.Childs) for(auto& p : hole->Contour) { + p = { + static_cast(p.X * cosa - p.Y * sina), + static_cast(p.X * sina + p.Y * cosa) + }; + } +} + +#define DISABLE_BOOST_NFP_MERGE template<> inline Nfp::Shapes Nfp::merge(const Nfp::Shapes& shapes, const PolygonImpl& sh) diff --git a/xs/src/libnest2d/libnest2d/common.hpp b/xs/src/libnest2d/libnest2d/common.hpp index 6e6174352..9de75415b 100644 --- a/xs/src/libnest2d/libnest2d/common.hpp +++ b/xs/src/libnest2d/libnest2d/common.hpp @@ -1,6 +1,15 @@ #ifndef LIBNEST2D_CONFIG_HPP #define LIBNEST2D_CONFIG_HPP +#ifndef NDEBUG +#include +#endif + +#include +#include +#include +#include + #if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L #define BP2D_NOEXCEPT #define BP2D_CONSTEXPR @@ -9,12 +18,50 @@ #define BP2D_CONSTEXPR constexpr #endif -#include -#include -#include + +/* + * Debugging output dout and derr definition + */ +//#ifndef NDEBUG +//# define dout std::cout +//# define derr std::cerr +//#else +//# define dout 0 && std::cout +//# define derr 0 && std::cerr +//#endif namespace libnest2d { +struct DOut { +#ifndef NDEBUG + std::ostream& out = std::cout; +#endif +}; + +struct DErr { +#ifndef NDEBUG + std::ostream& out = std::cerr; +#endif +}; + +template +inline DOut&& operator<<( DOut&& out, T&& d) { +#ifndef NDEBUG + out.out << d; +#endif + return std::move(out); +} + +template +inline DErr&& operator<<( DErr&& out, T&& d) { +#ifndef NDEBUG + out.out << d; +#endif + return std::move(out); +} +inline DOut dout() { return DOut(); } +inline DErr derr() { return DErr(); } + template< class T > struct remove_cvref { using type = typename std::remove_cv< @@ -24,9 +71,58 @@ struct remove_cvref { template< class T > using remove_cvref_t = typename remove_cvref::type; +template< class T > +using remove_ref_t = typename std::remove_reference::type; + template using enable_if_t = typename std::enable_if::type; +template +struct invoke_result { + using type = typename std::result_of::type; +}; + +template +using invoke_result_t = typename invoke_result::type; + +/* ************************************************************************** */ +/* C++14 std::index_sequence implementation: */ +/* ************************************************************************** */ + +/** + * \brief C++11 conformant implementation of the index_sequence type from C++14 + */ +template struct index_sequence { + using value_type = size_t; + BP2D_CONSTEXPR value_type size() const { return sizeof...(Ints); } +}; + +// A Help structure to generate the integer list +template struct genSeq; + +// Recursive template to generate the list +template struct genSeq { + // Type will contain a genSeq with Nseq appended by one element + using Type = typename genSeq< I - 1, I - 1, Nseq...>::Type; +}; + +// Terminating recursion +template struct genSeq<0, Nseq...> { + // If I is zero, Type will contain index_sequence with the fuly generated + // integer list. + using Type = index_sequence; +}; + +/// Helper alias to make an index sequence from 0 to N +template using make_index_sequence = typename genSeq::Type; + +/// Helper alias to make an index sequence for a parameter pack +template +using index_sequence_for = make_index_sequence; + + +/* ************************************************************************** */ + /** * A useful little tool for triggering static_assert error messages e.g. when * a mandatory template specialization (implementation) is missing. @@ -35,12 +131,14 @@ using enable_if_t = typename std::enable_if::type; */ template struct always_false { enum { value = false }; }; -const auto BP2D_CONSTEXPR Pi = 3.141592653589793238463; // 2*std::acos(0); +const double BP2D_CONSTEXPR Pi = 3.141592653589793238463; // 2*std::acos(0); +const double BP2D_CONSTEXPR Pi_2 = 2*Pi; /** * @brief Only for the Radian and Degrees classes to behave as doubles. */ class Double { +protected: double val_; public: Double(): val_(double{}) { } @@ -56,12 +154,29 @@ class Degrees; * @brief Data type representing radians. It supports conversion to degrees. */ class Radians: public Double { + mutable double sin_ = std::nan(""), cos_ = std::nan(""); public: Radians(double rads = Double() ): Double(rads) {} inline Radians(const Degrees& degs); inline operator Degrees(); inline double toDegrees(); + + inline double sin() const { + if(std::isnan(sin_)) { + cos_ = std::cos(val_); + sin_ = std::sin(val_); + } + return sin_; + } + + inline double cos() const { + if(std::isnan(cos_)) { + cos_ = std::cos(val_); + sin_ = std::sin(val_); + } + return cos_; + } }; /** diff --git a/xs/src/libnest2d/libnest2d/geometries_nfp.hpp b/xs/src/libnest2d/libnest2d/geometries_nfp.hpp index 9f8bd6031..f3672d4fe 100644 --- a/xs/src/libnest2d/libnest2d/geometries_nfp.hpp +++ b/xs/src/libnest2d/libnest2d/geometries_nfp.hpp @@ -75,12 +75,12 @@ static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) { RawShape rsh; // Final nfp placeholder std::vector edgelist; - size_t cap = ShapeLike::contourVertexCount(sh) + + auto cap = ShapeLike::contourVertexCount(sh) + ShapeLike::contourVertexCount(other); // Reserve the needed memory edgelist.reserve(cap); - ShapeLike::reserve(rsh, cap); + ShapeLike::reserve(rsh, static_cast(cap)); { // place all edges from sh into edgelist auto first = ShapeLike::cbegin(sh); @@ -208,9 +208,10 @@ static inline bool _vsort(const TPoint& v1, const TPoint& v2) { using Coord = TCoord>; - auto diff = getY(v1) - getY(v2); + Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2); + auto diff = y1 - y2; if(std::abs(diff) <= std::numeric_limits::epsilon()) - return getX(v1) < getX(v2); + return x1 < x2; return diff < 0; } diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index b1353b457..f4cfe31af 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -20,17 +20,17 @@ template using TCoord = typename CoordType>::Type; /// Getting the type of point structure used by a shape. -template struct PointType { using Type = void; }; +template struct PointType { /*using Type = void;*/ }; /// TPoint as shorthand for `typename PointType::Type`. template using TPoint = typename PointType>::Type; /// Getting the VertexIterator type of a shape class. -template struct VertexIteratorType { using Type = void; }; +template struct VertexIteratorType { /*using Type = void;*/ }; /// Getting the const vertex iterator for a shape class. -template struct VertexConstIteratorType { using Type = void; }; +template struct VertexConstIteratorType {/* using Type = void;*/ }; /** * TVertexIterator as shorthand for @@ -86,23 +86,47 @@ public: inline RawPoint center() const BP2D_NOEXCEPT; }; +/** + * \brief An abstraction of a directed line segment with two points. + */ template class _Segment: PointPair { using PointPair::p1; using PointPair::p2; + mutable Radians angletox_ = std::nan(""); public: inline _Segment() {} + inline _Segment(const RawPoint& p, const RawPoint& pp): PointPair({p, pp}) {} + /** + * @brief Get the first point. + * @return Returns the starting point. + */ inline const RawPoint& first() const BP2D_NOEXCEPT { return p1; } + + /** + * @brief The end point. + * @return Returns the end point of the segment. + */ inline const RawPoint& second() const BP2D_NOEXCEPT { return p2; } - inline RawPoint& first() BP2D_NOEXCEPT { return p1; } - inline RawPoint& second() BP2D_NOEXCEPT { return p2; } + inline void first(const RawPoint& p) BP2D_NOEXCEPT + { + angletox_ = std::nan(""); p1 = p; + } + inline void second(const RawPoint& p) BP2D_NOEXCEPT { + angletox_ = std::nan(""); p2 = p; + } + + /// Returns the angle measured to the X (horizontal) axis. inline Radians angleToXaxis() const; + + /// The length of the segment in the measure of the coordinate system. + inline double length(); }; // This struct serves as a namespace. The only difference is that is can be @@ -204,13 +228,14 @@ struct PointLike { }; template -TCoord _Box::width() const BP2D_NOEXCEPT { +TCoord _Box::width() const BP2D_NOEXCEPT +{ return PointLike::x(maxCorner()) - PointLike::x(minCorner()); } - template -TCoord _Box::height() const BP2D_NOEXCEPT { +TCoord _Box::height() const BP2D_NOEXCEPT +{ return PointLike::y(maxCorner()) - PointLike::y(minCorner()); } @@ -221,33 +246,37 @@ template TCoord getY(const RawPoint& p) { return PointLike::y(p); } template -void setX(RawPoint& p, const TCoord& val) { +void setX(RawPoint& p, const TCoord& val) +{ PointLike::x(p) = val; } template -void setY(RawPoint& p, const TCoord& val) { +void setY(RawPoint& p, const TCoord& val) +{ PointLike::y(p) = val; } template inline Radians _Segment::angleToXaxis() const { - static const double Pi_2 = 2*Pi; - TCoord dx = getX(second()) - getX(first()); - TCoord dy = getY(second()) - getY(first()); + if(std::isnan(angletox_)) { + TCoord dx = getX(second()) - getX(first()); + TCoord dy = getY(second()) - getY(first()); - double a = std::atan2(dy, dx); -// if(dx == 0 && dy >= 0) return Pi/2; -// if(dx == 0 && dy < 0) return 3*Pi/2; -// if(dy == 0 && dx >= 0) return 0; -// if(dy == 0 && dx < 0) return Pi; + double a = std::atan2(dy, dx); + auto s = std::signbit(a); -// double ddx = static_cast(dx); - auto s = std::signbit(a); -// double a = std::atan(ddx/dy); - if(s) a += Pi_2; - return a; + if(s) a += Pi_2; + angletox_ = a; + } + return angletox_; +} + +template +inline double _Segment::length() +{ + return PointLike::distance(first(), second()); } template @@ -319,7 +348,7 @@ struct ShapeLike { // Optional, does nothing by default template - static void reserve(RawShape& /*sh*/, unsigned long /*vertex_capacity*/) {} + static void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {} template static void addVertex(RawShape& sh, Args...args) @@ -415,6 +444,15 @@ struct ShapeLike { return false; } + template + static bool touches( const TPoint& /*point*/, + const RawShape& /*shape*/) + { + static_assert(always_false::value, + "ShapeLike::touches(point, shape) unimplemented!"); + return false; + } + template static _Box> boundingBox(const RawShape& /*sh*/) { diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index 76df1829b..aa36bd1bf 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -9,6 +9,9 @@ #include #include "geometry_traits.hpp" +//#include "optimizers/subplex.hpp" +//#include "optimizers/simplex.hpp" +#include "optimizers/genetic.hpp" namespace libnest2d { @@ -48,6 +51,7 @@ class _Item { mutable bool area_cache_valid_ = false; mutable RawShape offset_cache_; mutable bool offset_cache_valid_ = false; + public: /// The type of the shape which was handed over as the template argument. @@ -188,7 +192,7 @@ public: } /// The number of the outer ring vertices. - inline unsigned long vertexCount() const { + inline size_t vertexCount() const { return ShapeLike::contourVertexCount(sh_); } @@ -495,6 +499,8 @@ public: /// Clear the packed items so a new session can be started. inline void clearItems() { impl_.clearItems(); } + inline double filledArea() const { return impl_.filledArea(); } + #ifndef NDEBUG inline auto getDebugItems() -> decltype(impl_.debug_items_)& { @@ -629,7 +635,7 @@ template class Arranger { using TSel = SelectionStrategyLike; TSel selector_; - + bool use_min_bb_rotation_ = false; public: using Item = typename PlacementStrategy::Item; using ItemRef = std::reference_wrapper; @@ -713,9 +719,9 @@ public: } /// Set a progress indicatior function object for the selector. - inline void progressIndicator(ProgressFunction func) + inline Arranger& progressIndicator(ProgressFunction func) { - selector_.progressIndicator(func); + selector_.progressIndicator(func); return *this; } inline PackGroup lastResult() { @@ -727,6 +733,10 @@ public: return ret; } + inline Arranger& useMinimumBoundigBoxRotation(bool s = true) { + use_min_bb_rotation_ = s; return *this; + } + private: template solver(stopcr); + + auto orig_rot = item.rotation(); + + auto result = solver.optimize_min([&item, &orig_rot](Radians rot){ + item.rotation(orig_rot + rot); + auto bb = item.boundingBox(); + return std::sqrt(bb.height()*bb.width()); + }, opt::initvals(Radians(0)), opt::bound(-Pi/2, Pi/2)); + + item.rotation(orig_rot); + + return std::get<0>(result.optimum); + } + template inline void __arrange(TIter from, TIter to) { if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { item.addOffset(static_cast(std::ceil(min_obj_distance_/2.0))); }); + if(use_min_bb_rotation_) + std::for_each(from, to, [this](Item& item){ + Radians rot = findBestRotation(item); + item.rotate(rot); + }); + selector_.template packItems( from, to, bin_, pconfig_); - if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { + if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) { item.removeOffset(); }); diff --git a/xs/src/libnest2d/libnest2d/optimizer.hpp b/xs/src/libnest2d/libnest2d/optimizer.hpp new file mode 100644 index 000000000..52e67f7d5 --- /dev/null +++ b/xs/src/libnest2d/libnest2d/optimizer.hpp @@ -0,0 +1,419 @@ +#ifndef OPTIMIZER_HPP +#define OPTIMIZER_HPP + +#include +#include +#include +#include "common.hpp" + +namespace libnest2d { namespace opt { + +using std::forward; +using std::tuple; +using std::get; +using std::tuple_element; + +/// A Type trait for upper and lower limit of a numeric type. +template +struct limits { + inline static T min() { return std::numeric_limits::min(); } + inline static T max() { return std::numeric_limits::max(); } +}; + +template +struct limits::has_infinity, void>> { + inline static T min() { return -std::numeric_limits::infinity(); } + inline static T max() { return std::numeric_limits::infinity(); } +}; + +/// An interval of possible input values for optimization +template +class Bound { + T min_; + T max_; +public: + Bound(const T& min = limits::min(), + const T& max = limits::max()): min_(min), max_(max) {} + inline const T min() const BP2D_NOEXCEPT { return min_; } + inline const T max() const BP2D_NOEXCEPT { return max_; } +}; + +/** + * Helper function to make a Bound object with its type deduced automatically. + */ +template +inline Bound bound(const T& min, const T& max) { return Bound(min, max); } + +/** + * This is the type of an input tuple for the object function. It holds the + * values and their type in each dimension. + */ +template using Input = tuple; + +template +inline tuple initvals(Args...args) { return std::make_tuple(args...); } + +/** + * @brief Helper class to be able to loop over a parameter pack's elements. + */ +class metaloop { +// The implementation is based on partial struct template specializations. +// Basically we need a template type that is callable and takes an integer +// non-type template parameter which can be used to implement recursive calls. +// +// C++11 will not allow the usage of a plain template function that is why we +// use struct with overloaded call operator. At the same time C++11 prohibits +// partial template specialization with a non type parameter such as int. We +// need to wrap that in a type (see metaloop::Int). + +/* + * A helper alias to create integer values wrapped as a type. It is nessecary + * because a non type template parameter (such as int) would be prohibited in + * a partial specialization. Also for the same reason we have to use a class + * _Metaloop instead of a simple function as a functor. A function cannot be + * partially specialized in a way that is neccesary for this trick. + */ +template using Int = std::integral_constant; + +/* + * Helper class to implement in-place functors. + * + * We want to be able to use inline functors like a lambda to keep the code + * as clear as possible. + */ +template class MapFn { + Fn&& fn_; +public: + + // It takes the real functor that can be specified in-place but only + // with C++14 because the second parameter's type will depend on the + // type of the parameter pack element that is processed. In C++14 we can + // specify this second parameter type as auto in the lamda parameter list. + inline MapFn(Fn&& fn): fn_(forward(fn)) {} + + template void operator ()(T&& pack_element) { + // We provide the index as the first parameter and the pack (or tuple) + // element as the second parameter to the functor. + fn_(N, forward(pack_element)); + } +}; + +/* + * Implementation of the template loop trick. + * We create a mechanism for looping over a parameter pack in compile time. + * \tparam Idx is the loop index which will be decremented at each recursion. + * \tparam Args The parameter pack that will be processed. + * + */ +template +class _MetaLoop {}; + +// Implementation for the first element of Args... +template +class _MetaLoop, Args...> { +public: + + const static BP2D_CONSTEXPR int N = 0; + const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1; + + template + void run( Tup&& valtup, Fn&& fn) { + MapFn {forward(fn)} (get(valtup)); + } +}; + +// Implementation for the N-th element of Args... +template +class _MetaLoop, Args...> { +public: + + const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1; + + template + void run(Tup&& valtup, Fn&& fn) { + MapFn {forward(fn)} (std::get(valtup)); + + // Recursive call to process the next element of Args + _MetaLoop, Args...> ().run(forward(valtup), + forward(fn)); + } +}; + +/* + * Instantiation: We must instantiate the template with the last index because + * the generalized version calls the decremented instantiations recursively. + * Once the instantiation with the first index is called, the terminating + * version of run is called which does not call itself anymore. + * + * If you are utterly annoyed, at least you have learned a super crazy + * functional metaprogramming pattern. + */ +template +using MetaLoop = _MetaLoop, Args...>; + +public: + +/** + * \brief The final usable function template. + * + * This is similar to what varags was on C but in compile time C++11. + * You can call: + * apply(, ); + * For example: + * + * struct mapfunc { + * template void operator()(int N, T&& element) { + * std::cout << "The value of the parameter "<< N <<": " + * << element << std::endl; + * } + * }; + * + * apply(mapfunc(), 'a', 10, 151.545); + * + * C++14: + * apply([](int N, auto&& element){ + * std::cout << "The value of the parameter "<< N <<": " + * << element << std::endl; + * }, 'a', 10, 151.545); + * + * This yields the output: + * The value of the parameter 0: a + * The value of the parameter 1: 10 + * The value of the parameter 2: 151.545 + * + * As an addition, the function can be called with a tuple as the second + * parameter holding the arguments instead of a parameter pack. + * + */ +template +inline static void apply(Fn&& fn, Args&&...args) { + MetaLoop().run(tuple(forward(args)...), + forward(fn)); +} + +/// The version of apply with a tuple rvalue reference. +template +inline static void apply(Fn&& fn, tuple&& tup) { + MetaLoop().run(std::move(tup), forward(fn)); +} + +/// The version of apply with a tuple lvalue reference. +template +inline static void apply(Fn&& fn, tuple& tup) { + MetaLoop().run(tup, forward(fn)); +} + +/// The version of apply with a tuple const reference. +template +inline static void apply(Fn&& fn, const tuple& tup) { + MetaLoop().run(tup, forward(fn)); +} + +/** + * Call a function with its arguments encapsualted in a tuple. + */ +template +inline static auto +callFunWithTuple(Fn&& fn, Tup&& tup, index_sequence) -> + decltype(fn(std::get(tup)...)) +{ + return fn(std::get(tup)...); +} + +}; + +/** + * @brief Specific optimization methods for which a default optimizer + * implementation can be instantiated. + */ +enum class Method { + L_SIMPLEX, + L_SUBPLEX, + G_GENETIC, + //... +}; + +/** + * @brief Info about result of an optimization. These codes are exactly the same + * as the nlopt codes for convinience. + */ +enum ResultCodes { + FAILURE = -1, /* generic failure code */ + INVALID_ARGS = -2, + OUT_OF_MEMORY = -3, + ROUNDOFF_LIMITED = -4, + FORCED_STOP = -5, + SUCCESS = 1, /* generic success code */ + STOPVAL_REACHED = 2, + FTOL_REACHED = 3, + XTOL_REACHED = 4, + MAXEVAL_REACHED = 5, + MAXTIME_REACHED = 6 +}; + +/** + * \brief A type to hold the complete result of the optimization. + */ +template +struct Result { + ResultCodes resultcode; + std::tuple optimum; + double score; +}; + +/** + * @brief The stop limit can be specified as the absolute error or as the + * relative error, just like in nlopt. + */ +enum class StopLimitType { + ABSOLUTE, + RELATIVE +}; + +/** + * @brief A type for specifying the stop criteria. + */ +struct StopCriteria { + + /// Relative or absolute termination error + StopLimitType type = StopLimitType::RELATIVE; + + /// The error value that is interpredted depending on the type property. + double stoplimit = 0.0001; + + unsigned max_iterations = 0; +}; + +/** + * \brief The Optimizer base class with CRTP pattern. + */ +template +class Optimizer { +protected: + enum class OptDir{ + MIN, + MAX + } dir_; + + StopCriteria stopcr_; + +public: + + inline explicit Optimizer(const StopCriteria& scr = {}): stopcr_(scr) {} + + /** + * \brief Optimize for minimum value of the provided objectfunction. + * \param objectfunction The function that will be searched for the minimum + * return value. + * \param initvals A tuple with the initial values for the search + * \param bounds A parameter pack with the bounds for each dimension. + * \return Returns a Result structure. + * An example call would be: + * auto result = opt.optimize_min( + * [](std::tuple x) // object function + * { + * return std::pow(std::get<0>(x), 2); + * }, + * std::make_tuple(-0.5), // initial value + * {-1.0, 1.0} // search space bounds + * ); + */ + template + inline Result optimize_min(Func&& objectfunction, + Input initvals, + Bound... bounds) + { + dir_ = OptDir::MIN; + return static_cast(this)->template optimize( + forward(objectfunction), initvals, bounds... ); + } + + template + inline Result optimize_min(Func&& objectfunction, + Input initvals) + { + dir_ = OptDir::MIN; + return static_cast(this)->template optimize( + objectfunction, initvals, Bound()... ); + } + + template + inline Result optimize_min(Func&& objectfunction) + { + dir_ = OptDir::MIN; + return static_cast(this)->template optimize( + objectfunction, + Input(), + Bound()... ); + } + + /// Same as optimize_min but optimizes for maximum function value. + template + inline Result optimize_max(Func&& objectfunction, + Input initvals, + Bound... bounds) + { + dir_ = OptDir::MAX; + return static_cast(this)->template optimize( + objectfunction, initvals, bounds... ); + } + + template + inline Result optimize_max(Func&& objectfunction, + Input initvals) + { + dir_ = OptDir::MAX; + return static_cast(this)->template optimize( + objectfunction, initvals, Bound()... ); + } + + template + inline Result optimize_max(Func&& objectfunction) + { + dir_ = OptDir::MAX; + return static_cast(this)->template optimize( + objectfunction, + Input(), + Bound()... ); + } + +}; + +// Just to be able to instantiate an unimplemented method and generate compile +// error. +template +class DummyOptimizer : public Optimizer> { + friend class Optimizer>; + +public: + DummyOptimizer() { + static_assert(always_false::value, "Optimizer unimplemented!"); + } + + template + Result optimize(Func&& func, + std::tuple initvals, + Bound... args) + { + return Result(); + } +}; + +// Specializing this struct will tell what kind of optimizer to generate for +// a given method +template struct OptimizerSubclass { using Type = DummyOptimizer<>; }; + +/// Optimizer type based on the method provided in parameter m. +template using TOptimizer = typename OptimizerSubclass::Type; + +/// Global optimizer with an explicitly specified local method. +template +inline TOptimizer GlobalOptimizer(Method, const StopCriteria& scr = {}) +{ // Need to be specialized in order to do anything useful. + return TOptimizer(scr); +} + +} +} + +#endif // OPTIMIZER_HPP diff --git a/xs/src/libnest2d/libnest2d/optimizers/genetic.hpp b/xs/src/libnest2d/libnest2d/optimizers/genetic.hpp new file mode 100644 index 000000000..276854a12 --- /dev/null +++ b/xs/src/libnest2d/libnest2d/optimizers/genetic.hpp @@ -0,0 +1,31 @@ +#ifndef GENETIC_HPP +#define GENETIC_HPP + +#include "nlopt_boilerplate.hpp" + +namespace libnest2d { namespace opt { + +class GeneticOptimizer: public NloptOptimizer { +public: + inline explicit GeneticOptimizer(const StopCriteria& scr = {}): + NloptOptimizer(method2nloptAlg(Method::G_GENETIC), scr) {} + + inline GeneticOptimizer& localMethod(Method m) { + localmethod_ = m; + return *this; + } +}; + +template<> +struct OptimizerSubclass { using Type = GeneticOptimizer; }; + +template<> TOptimizer GlobalOptimizer( + Method localm, const StopCriteria& scr ) +{ + return GeneticOptimizer (scr).localMethod(localm); +} + +} +} + +#endif // GENETIC_HPP diff --git a/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp b/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp new file mode 100644 index 000000000..798bf9622 --- /dev/null +++ b/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp @@ -0,0 +1,176 @@ +#ifndef NLOPT_BOILERPLATE_HPP +#define NLOPT_BOILERPLATE_HPP + +#include +#include +#include + +#include + +namespace libnest2d { namespace opt { + +nlopt::algorithm method2nloptAlg(Method m) { + + switch(m) { + case Method::L_SIMPLEX: return nlopt::LN_NELDERMEAD; + case Method::L_SUBPLEX: return nlopt::LN_SBPLX; + case Method::G_GENETIC: return nlopt::GN_ESCH; + default: assert(false); throw(m); + } +} + +/** + * Optimizer based on NLopt. + * + * All the optimized types have to be convertible to double. + */ +class NloptOptimizer: public Optimizer { +protected: + nlopt::opt opt_; + std::vector lower_bounds_; + std::vector upper_bounds_; + std::vector initvals_; + nlopt::algorithm alg_; + Method localmethod_; + + using Base = Optimizer; + + friend Base; + + // ********************************************************************** */ + + // TODO: CHANGE FOR LAMBDAS WHEN WE WILL MOVE TO C++14 + + struct BoundsFunc { + NloptOptimizer& self; + inline explicit BoundsFunc(NloptOptimizer& o): self(o) {} + + template void operator()(int N, T& bounds) + { + self.lower_bounds_[N] = bounds.min(); + self.upper_bounds_[N] = bounds.max(); + } + }; + + struct InitValFunc { + NloptOptimizer& self; + inline explicit InitValFunc(NloptOptimizer& o): self(o) {} + + template void operator()(int N, T& initval) + { + self.initvals_[N] = initval; + } + }; + + struct ResultCopyFunc { + NloptOptimizer& self; + inline explicit ResultCopyFunc(NloptOptimizer& o): self(o) {} + + template void operator()(int N, T& resultval) + { + resultval = self.initvals_[N]; + } + }; + + struct FunvalCopyFunc { + using D = const std::vector; + D& params; + inline explicit FunvalCopyFunc(D& p): params(p) {} + + template void operator()(int N, T& resultval) + { + resultval = params[N]; + } + }; + + /* ********************************************************************** */ + + template + static double optfunc(const std::vector& params, + std::vector& grad, + void *data) + { + auto fnptr = static_cast*>(data); + auto funval = std::tuple(); + + // copy the obtained objectfunction arguments to the funval tuple. + metaloop::apply(FunvalCopyFunc(params), funval); + + auto ret = metaloop::callFunWithTuple(*fnptr, funval, + index_sequence_for()); + + return ret; + } + + template + Result optimize(Func&& func, + std::tuple initvals, + Bound... args) + { + lower_bounds_.resize(sizeof...(Args)); + upper_bounds_.resize(sizeof...(Args)); + initvals_.resize(sizeof...(Args)); + + opt_ = nlopt::opt(alg_, sizeof...(Args) ); + + // Copy the bounds which is obtained as a parameter pack in args into + // lower_bounds_ and upper_bounds_ + metaloop::apply(BoundsFunc(*this), args...); + + opt_.set_lower_bounds(lower_bounds_); + opt_.set_upper_bounds(upper_bounds_); + + nlopt::opt localopt; + switch(opt_.get_algorithm()) { + case nlopt::GN_MLSL: + case nlopt::GN_MLSL_LDS: + localopt = nlopt::opt(method2nloptAlg(localmethod_), + sizeof...(Args)); + localopt.set_lower_bounds(lower_bounds_); + localopt.set_upper_bounds(upper_bounds_); + opt_.set_local_optimizer(localopt); + default: ; + } + + switch(this->stopcr_.type) { + case StopLimitType::ABSOLUTE: + opt_.set_ftol_abs(stopcr_.stoplimit); break; + case StopLimitType::RELATIVE: + opt_.set_ftol_rel(stopcr_.stoplimit); break; + } + + if(this->stopcr_.max_iterations > 0) + opt_.set_maxeval(this->stopcr_.max_iterations ); + + // Take care of the initial values, copy them to initvals_ + metaloop::apply(InitValFunc(*this), initvals); + + switch(dir_) { + case OptDir::MIN: + opt_.set_min_objective(optfunc, &func); break; + case OptDir::MAX: + opt_.set_max_objective(optfunc, &func); break; + } + + Result result; + + auto rescode = opt_.optimize(initvals_, result.score); + result.resultcode = static_cast(rescode); + + metaloop::apply(ResultCopyFunc(*this), result.optimum); + + return result; + } + +public: + inline explicit NloptOptimizer(nlopt::algorithm alg, + StopCriteria stopcr = {}): + Base(stopcr), alg_(alg), localmethod_(Method::L_SIMPLEX) {} + +}; + +} +} + + +#endif // NLOPT_BOILERPLATE_HPP diff --git a/xs/src/libnest2d/libnest2d/optimizers/simplex.hpp b/xs/src/libnest2d/libnest2d/optimizers/simplex.hpp new file mode 100644 index 000000000..78b09b89a --- /dev/null +++ b/xs/src/libnest2d/libnest2d/optimizers/simplex.hpp @@ -0,0 +1,20 @@ +#ifndef SIMPLEX_HPP +#define SIMPLEX_HPP + +#include "nlopt_boilerplate.hpp" + +namespace libnest2d { namespace opt { + +class SimplexOptimizer: public NloptOptimizer { +public: + inline explicit SimplexOptimizer(const StopCriteria& scr = {}): + NloptOptimizer(method2nloptAlg(Method::L_SIMPLEX), scr) {} +}; + +template<> +struct OptimizerSubclass { using Type = SimplexOptimizer; }; + +} +} + +#endif // SIMPLEX_HPP diff --git a/xs/src/libnest2d/libnest2d/optimizers/subplex.hpp b/xs/src/libnest2d/libnest2d/optimizers/subplex.hpp new file mode 100644 index 000000000..841b04057 --- /dev/null +++ b/xs/src/libnest2d/libnest2d/optimizers/subplex.hpp @@ -0,0 +1,20 @@ +#ifndef SUBPLEX_HPP +#define SUBPLEX_HPP + +#include "nlopt_boilerplate.hpp" + +namespace libnest2d { namespace opt { + +class SubplexOptimizer: public NloptOptimizer { +public: + inline explicit SubplexOptimizer(const StopCriteria& scr = {}): + NloptOptimizer(method2nloptAlg(Method::L_SUBPLEX), scr) {} +}; + +template<> +struct OptimizerSubclass { using Type = SubplexOptimizer; }; + +} +} + +#endif // SUBPLEX_HPP diff --git a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp index d34301205..775e44e09 100644 --- a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp @@ -348,8 +348,9 @@ protected: };*/ auto reverseAddOthers = [&rsh, finish, start, &item](){ - for(size_t i = finish-1; i > start; i--) - ShapeLike::addVertex(rsh, item.vertex(i)); + for(auto i = finish-1; i > start; i--) + ShapeLike::addVertex(rsh, item.vertex( + static_cast(i))); }; // Final polygon construction... diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index f5701b904..7ad983b61 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -1,8 +1,13 @@ #ifndef NOFITPOLY_HPP #define NOFITPOLY_HPP +#ifndef NDEBUG +#include +#endif #include "placer_boilerplate.hpp" #include "../geometries_nfp.hpp" +#include +//#include namespace libnest2d { namespace strategies { @@ -17,10 +22,183 @@ struct NfpPConfig { TOP_RIGHT, }; - bool allow_rotations = false; + /// Which angles to try out for better results + std::vector rotations; + + /// Where to align the resulting packed pile Alignment alignment; + + NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}), + alignment(Alignment::CENTER) {} }; +// A class for getting a point on the circumference of the polygon (in log time) +template class EdgeCache { + using Vertex = TPoint; + using Coord = TCoord; + using Edge = _Segment; + + enum Corners { + BOTTOM, + LEFT, + RIGHT, + TOP, + NUM_CORNERS + }; + + mutable std::vector corners_; + + std::vector emap_; + std::vector distances_; + double full_distance_ = 0; + + void createCache(const RawShape& sh) { + auto first = ShapeLike::cbegin(sh); + auto next = first + 1; + auto endit = ShapeLike::cend(sh); + + distances_.reserve(ShapeLike::contourVertexCount(sh)); + + while(next != endit) { + emap_.emplace_back(*(first++), *(next++)); + full_distance_ += emap_.back().length(); + distances_.push_back(full_distance_); + } + } + + void fetchCorners() const { + if(!corners_.empty()) return; + + corners_ = std::vector(NUM_CORNERS, 0.0); + + std::vector idx_ud(emap_.size(), 0); + std::vector idx_lr(emap_.size(), 0); + + std::iota(idx_ud.begin(), idx_ud.end(), 0); + std::iota(idx_lr.begin(), idx_lr.end(), 0); + + std::sort(idx_ud.begin(), idx_ud.end(), + [this](unsigned idx1, unsigned idx2) + { + const Vertex& v1 = emap_[idx1].first(); + const Vertex& v2 = emap_[idx2].first(); + auto diff = getY(v1) - getY(v2); + if(std::abs(diff) <= std::numeric_limits::epsilon()) + return getX(v1) < getX(v2); + + return diff < 0; + }); + + std::sort(idx_lr.begin(), idx_lr.end(), + [this](unsigned idx1, unsigned idx2) + { + const Vertex& v1 = emap_[idx1].first(); + const Vertex& v2 = emap_[idx2].first(); + + auto diff = getX(v1) - getX(v2); + if(std::abs(diff) <= std::numeric_limits::epsilon()) + return getY(v1) < getY(v2); + + return diff < 0; + }); + + corners_[BOTTOM] = distances_[idx_ud.front()]/full_distance_; + corners_[TOP] = distances_[idx_ud.back()]/full_distance_; + corners_[LEFT] = distances_[idx_lr.front()]/full_distance_; + corners_[RIGHT] = distances_[idx_lr.back()]/full_distance_; + } + +public: + + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + + inline EdgeCache() = default; + + inline EdgeCache(const _Item& item) + { + createCache(item.transformedShape()); + } + + inline EdgeCache(const RawShape& sh) + { + createCache(sh); + } + + /** + * @brief Get a point on the circumference of a polygon. + * @param distance A relative distance from the starting point to the end. + * Can be from 0.0 to 1.0 where 0.0 is the starting point and 1.0 is the + * closing point (which should be eqvivalent with the starting point with + * closed polygons). + * @return Returns the coordinates of the point lying on the polygon + * circumference. + */ + inline Vertex coords(double distance) { + assert(distance >= .0 && distance <= 1.0); + + // distance is from 0.0 to 1.0, we scale it up to the full length of + // the circumference + double d = distance*full_distance_; + + // Magic: we find the right edge in log time + auto it = std::lower_bound(distances_.begin(), distances_.end(), d); + auto idx = it - distances_.begin(); // get the index of the edge + auto edge = emap_[idx]; // extrac the edge + + // Get the remaining distance on the target edge + auto ed = d - (idx > 0 ? *std::prev(it) : 0 ); + auto angle = edge.angleToXaxis(); + Vertex ret = edge.first(); + + // Get the point on the edge which lies in ed distance from the start + ret += { static_cast(std::round(ed*std::cos(angle))), + static_cast(std::round(ed*std::sin(angle))) }; + + return ret; + } + + inline double circumference() const BP2D_NOEXCEPT { return full_distance_; } + + inline double corner(Corners c) const BP2D_NOEXCEPT { + assert(c < NUM_CORNERS); + fetchCorners(); + return corners_[c]; + } + + inline const std::vector& corners() const BP2D_NOEXCEPT { + fetchCorners(); + return corners_; + } + +}; + +// Nfp for a bunch of polygons. If the polygons are convex, the nfp calculated +// for trsh can be the union of nfp-s calculated with each polygon +template +Nfp::Shapes nfp(const Container& polygons, const RawShape& trsh ) +{ + using Item = _Item; + + Nfp::Shapes nfps; + + for(Item& sh : polygons) { + auto subnfp = Nfp::noFitPolygon(sh.transformedShape(), + trsh); + #ifndef NDEBUG + auto vv = ShapeLike::isValid(sh.transformedShape()); + assert(vv.first); + + auto vnfp = ShapeLike::isValid(subnfp); + assert(vnfp.first); + #endif + + nfps = Nfp::merge(nfps, subnfp); + } + + return nfps; +} + template class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer, RawShape, _Box>, NfpPConfig> { @@ -32,9 +210,30 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer, using Box = _Box>; + const double norm_; + const double penality_; + + bool static wouldFit(const RawShape& chull, const RawShape& bin) { + auto bbch = ShapeLike::boundingBox(chull); + auto bbin = ShapeLike::boundingBox(bin); + auto d = bbin.minCorner() - bbch.minCorner(); + auto chullcpy = chull; + ShapeLike::translate(chullcpy, d); + return ShapeLike::isInside(chullcpy, bbin); + } + + bool static wouldFit(const RawShape& chull, const Box& bin) + { + auto bbch = ShapeLike::boundingBox(chull); + return bbch.width() <= bin.width() && bbch.height() <= bin.height(); + } + public: - inline explicit _NofitPolyPlacer(const BinType& bin): Base(bin) {} + inline explicit _NofitPolyPlacer(const BinType& bin): + Base(bin), + norm_(std::sqrt(ShapeLike::area(bin))), + penality_(1e6*norm_) {} PackResult trypack(Item& item) { @@ -47,88 +246,148 @@ public: can_pack = item.isInside(bin_); } else { - // place the new item outside of the print bed to make sure it is - // disjuct from the current merged pile - placeOutsideOfBin(item); + double global_score = penality_; - auto trsh = item.transformedShape(); + auto initial_tr = item.translation(); + auto initial_rot = item.rotation(); + Vertex final_tr = {0, 0}; + Radians final_rot = initial_rot; Nfp::Shapes nfps; -#ifndef NDEBUG -#ifdef DEBUG_EXPORT_NFP - Base::debug_items_.clear(); -#endif - auto v = ShapeLike::isValid(trsh); - assert(v.first); -#endif - for(Item& sh : items_) { - auto subnfp = Nfp::noFitPolygon(sh.transformedShape(), - trsh); -#ifndef NDEBUG -#ifdef DEBUG_EXPORT_NFP - Base::debug_items_.emplace_back(subnfp); -#endif - auto vv = ShapeLike::isValid(sh.transformedShape()); - assert(vv.first); + for(auto rot : config_.rotations) { - auto vnfp = ShapeLike::isValid(subnfp); - assert(vnfp.first); -#endif - nfps = Nfp::merge(nfps, subnfp); - } + item.translation(initial_tr); + item.rotation(initial_rot + rot); - double min_area = std::numeric_limits::max(); - Vertex tr = {0, 0}; + // place the new item outside of the print bed to make sure + // it is disjuct from the current merged pile + placeOutsideOfBin(item); - auto iv = Nfp::referenceVertex(trsh); + auto trsh = item.transformedShape(); - // place item on each the edge of this nfp - for(auto& nfp : nfps) - ShapeLike::foreachContourVertex(nfp, [&] - (Vertex& v) - { - Coord dx = getX(v) - getX(iv); - Coord dy = getY(v) - getY(iv); + nfps = nfp(items_, trsh); + auto iv = Nfp::referenceVertex(trsh); - Item placeditem(trsh); - placeditem.translate(Vertex(dx, dy)); + auto startpos = item.translation(); - if( placeditem.isInside(bin_) ) { - Nfp::Shapes m; - m.reserve(items_.size()); + std::vector> ecache; + ecache.reserve(nfps.size()); - for(Item& pi : items_) - m.emplace_back(pi.transformedShape()); + for(auto& nfp : nfps ) ecache.emplace_back(nfp); - m.emplace_back(placeditem.transformedShape()); + auto getNfpPoint = [&ecache](double relpos) { + auto relpfloor = std::floor(relpos); + auto nfp_idx = static_cast(relpfloor); + if(nfp_idx >= ecache.size()) nfp_idx--; + auto p = relpos - relpfloor; + return ecache[nfp_idx].coords(p); + }; -// auto b = ShapeLike::boundingBox(m); - -// auto a = static_cast(std::max(b.height(), -// b.width())); - - auto b = ShapeLike::convexHull(m); - auto a = ShapeLike::area(b); - - if(a < min_area) { - can_pack = true; - min_area = a; - tr = {dx, dy}; - } + Nfp::Shapes pile; + pile.reserve(items_.size()+1); + double pile_area = 0; + for(Item& mitem : items_) { + pile.emplace_back(mitem.transformedShape()); + pile_area += mitem.area(); } - }); -#ifndef NDEBUG - for(auto&nfp : nfps) { - auto val = ShapeLike::isValid(nfp); - if(!val.first) std::cout << val.second << std::endl; -#ifdef DEBUG_EXPORT_NFP - Base::debug_items_.emplace_back(nfp); -#endif + // Our object function for placement + auto objfunc = [&] (double relpos) + { + Vertex v = getNfpPoint(relpos); + auto d = v - iv; + d += startpos; + item.translation(d); + + pile.emplace_back(item.transformedShape()); + + double occupied_area = pile_area + item.area(); + auto ch = ShapeLike::convexHull(pile); + + pile.pop_back(); + + // The pack ratio -- how much is the convex hull occupied + double pack_rate = occupied_area/ShapeLike::area(ch); + + // ratio of waste + double waste = 1.0 - pack_rate; + + // Score is the square root of waste. This will extend the + // range of good (lower) values and shring the range of bad + // (larger) values. + auto score = std::sqrt(waste); + + if(!wouldFit(ch, bin_)) score = 2*penality_ - score; + + return score; + }; + + opt::StopCriteria stopcr; + stopcr.max_iterations = 1000; + stopcr.stoplimit = 0.01; + stopcr.type = opt::StopLimitType::RELATIVE; + opt::TOptimizer solver(stopcr); + + double optimum = 0; + double best_score = penality_; + + // double max_bound = 1.0*nfps.size(); + // Genetic should look like this: + /*auto result = solver.optimize_min(objfunc, + opt::initvals(0.0), + opt::bound(0.0, max_bound) + ); + + if(result.score < penality_) { + best_score = result.score; + optimum = std::get<0>(result.optimum); + }*/ + + // Local optimization with the four polygon corners as + // starting points + for(unsigned ch = 0; ch < ecache.size(); ch++) { + auto& cache = ecache[ch]; + + std::for_each(cache.corners().begin(), + cache.corners().end(), + [ch, &solver, &objfunc, + &best_score, &optimum] + (double pos) + { + try { + auto result = solver.optimize_min(objfunc, + opt::initvals(ch+pos), + opt::bound(ch, 1.0 + ch) + ); + + if(result.score < best_score) { + best_score = result.score; + optimum = std::get<0>(result.optimum); + } + } catch(std::exception& + #ifndef NDEBUG + e + #endif + ) { + #ifndef NDEBUG + std::cerr << "ERROR " << e.what() << std::endl; + #endif + } + }); + } + + if( best_score < global_score ) { + auto d = getNfpPoint(optimum) - iv; + d += startpos; + final_tr = d; + final_rot = initial_rot + rot; + can_pack = true; + global_score = best_score; + } } -#endif - item.translate(tr); + item.translation(final_tr); + item.rotation(final_rot); } if(can_pack) { @@ -138,10 +397,13 @@ public: return ret; } -private: + ~_NofitPolyPlacer() { + Nfp::Shapes m; + m.reserve(items_.size()); + + for(Item& item : items_) m.emplace_back(item.transformedShape()); + auto&& bb = ShapeLike::boundingBox(m); - void setInitialPosition(Item& item) { - Box&& bb = item.boundingBox(); Vertex ci, cb; switch(config_.alignment) { @@ -173,11 +435,23 @@ private: } auto d = cb - ci; + for(Item& item : items_) item.translate(d); + } + +private: + + void setInitialPosition(Item& item) { + Box&& bb = item.boundingBox(); + + Vertex ci = bb.minCorner(); + Vertex cb = bin_.minCorner(); + + auto&& d = cb - ci; item.translate(d); } void placeOutsideOfBin(Item& item) { - auto bb = item.boundingBox(); + auto&& bb = item.boundingBox(); Box binbb = ShapeLike::boundingBox(bin_); Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) }; diff --git a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp index 338667432..9d2cb626b 100644 --- a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp @@ -12,6 +12,8 @@ template>> > class PlacerBoilerplate { + mutable bool farea_valid_ = false; + mutable double farea_ = 0.0; public: using Item = _Item; using Vertex = TPoint; @@ -39,7 +41,10 @@ public: using ItemGroup = const Container&; - inline PlacerBoilerplate(const BinType& bin): bin_(bin) {} + inline PlacerBoilerplate(const BinType& bin, unsigned cap = 50): bin_(bin) + { + items_.reserve(cap); + } inline const BinType& bin() const BP2D_NOEXCEPT { return bin_; } @@ -53,7 +58,10 @@ public: bool pack(Item& item) { auto&& r = static_cast(this)->trypack(item); - if(r) items_.push_back(*(r.item_ptr_)); + if(r) { + items_.push_back(*(r.item_ptr_)); + farea_valid_ = false; + } return r; } @@ -62,14 +70,38 @@ public: r.item_ptr_->translation(r.move_); r.item_ptr_->rotation(r.rot_); items_.push_back(*(r.item_ptr_)); + farea_valid_ = false; } } - void unpackLast() { items_.pop_back(); } + void unpackLast() { + items_.pop_back(); + farea_valid_ = false; + } inline ItemGroup getItems() const { return items_; } - inline void clearItems() { items_.clear(); } + inline void clearItems() { + items_.clear(); + farea_valid_ = false; +#ifndef NDEBUG + debug_items_.clear(); +#endif + } + + inline double filledArea() const { + if(farea_valid_) return farea_; + else { + farea_ = .0; + std::for_each(items_.begin(), items_.end(), + [this] (Item& item) { + farea_ += item.area(); + }); + farea_valid_ = true; + } + + return farea_; + } #ifndef NDEBUG std::vector debug_items_; diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index 305b3403a..2d1ca08cb 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -2,6 +2,10 @@ #define DJD_HEURISTIC_HPP #include +#include +#include +#include + #include "selection_boilerplate.hpp" namespace libnest2d { namespace strategies { @@ -13,6 +17,20 @@ namespace libnest2d { namespace strategies { template class _DJDHeuristic: public SelectionBoilerplate { using Base = SelectionBoilerplate; + + class SpinLock { + std::atomic_flag& lck_; + public: + + inline SpinLock(std::atomic_flag& flg): lck_(flg) {} + + inline void lock() { + while(lck_.test_and_set(std::memory_order_acquire)) {} + } + + inline void unlock() { lck_.clear(std::memory_order_release); } + }; + public: using typename Base::Item; using typename Base::ItemRef; @@ -21,27 +39,54 @@ public: * @brief The Config for DJD heuristic. */ struct Config { - /// Max number of bins. - unsigned max_bins = 0; /** * If true, the algorithm will try to place pair and driplets in all * possible order. */ bool try_reverse_order = true; + + /** + * The initial fill proportion of the bin area that will be filled before + * trying items one by one, or pairs or triplets. + * + * The initial fill proportion suggested by + * [López-Camacho]\ + * (http://www.cs.stir.ac.uk/~goc/papers/EffectiveHueristic2DAOR2013.pdf) + * is one third of the area of bin. + */ + double initial_fill_proportion = 1.0/3.0; + + /** + * @brief How much is the acceptable waste incremented at each iteration + */ + double waste_increment = 0.1; + + /** + * @brief Allow parallel jobs for filling multiple bins. + * + * This will decrease the soution quality but can greatly boost up + * performance for large number of items. + */ + bool allow_parallel = true; + + /** + * @brief Always use parallel processing if the items don't fit into + * one bin. + */ + bool force_parallel = false; }; private: using Base::packed_bins_; using ItemGroup = typename Base::ItemGroup; - using Container = ItemGroup;//typename std::vector; + using Container = ItemGroup; Container store_; Config config_; - // The initial fill proportion of the bin area that will be filled before - // trying items one by one, or pairs or triplets. - static const double INITIAL_FILL_PROPORTION; + static const unsigned MAX_ITEMS_SEQUENTIALLY = 30; + static const unsigned MAX_VERTICES_SEQUENTIALLY = MAX_ITEMS_SEQUENTIALLY*20; public: @@ -61,7 +106,9 @@ public: using ItemList = std::list; const double bin_area = ShapeLike::area(bin); - const double w = bin_area * 0.1; + const double w = bin_area * config_.waste_increment; + + const double INITIAL_FILL_PROPORTION = config_.initial_fill_proportion; const double INITIAL_FILL_AREA = bin_area*INITIAL_FILL_PROPORTION; store_.clear(); @@ -74,24 +121,22 @@ public: return i1.area() > i2.area(); }); - ItemList not_packed(store_.begin(), store_.end()); + size_t glob_vertex_count = 0; + std::for_each(store_.begin(), store_.end(), + [&glob_vertex_count](const Item& item) { + glob_vertex_count += item.vertexCount(); + }); std::vector placers; - double free_area = 0; - double filled_area = 0; - double waste = 0; bool try_reverse = config_.try_reverse_order; // Will use a subroutine to add a new bin - auto addBin = [this, &placers, &free_area, - &filled_area, &bin, &pconfig]() + auto addBin = [this, &placers, &bin, &pconfig]() { placers.emplace_back(bin); packed_bins_.emplace_back(); placers.back().configure(pconfig); - free_area = ShapeLike::area(bin); - filled_area = 0; }; // Types for pairs and triplets @@ -136,8 +181,11 @@ public: }; auto tryOneByOne = // Subroutine to try adding items one by one. - [¬_packed, &bin_area, &free_area, &filled_area] - (Placer& placer, double waste) + [&bin_area] + (Placer& placer, ItemList& not_packed, + double waste, + double& free_area, + double& filled_area) { double item_area = 0; bool ret = false; @@ -160,9 +208,12 @@ public: }; auto tryGroupsOfTwo = // Try adding groups of two items into the bin. - [¬_packed, &bin_area, &free_area, &filled_area, &check_pair, + [&bin_area, &check_pair, try_reverse] - (Placer& placer, double waste) + (Placer& placer, ItemList& not_packed, + double waste, + double& free_area, + double& filled_area) { double item_area = 0, largest_area = 0, smallest_area = 0; double second_largest = 0, second_smallest = 0; @@ -259,12 +310,15 @@ public: }; auto tryGroupsOfThree = // Try adding groups of three items. - [¬_packed, &bin_area, &free_area, &filled_area, + [&bin_area, &check_pair, &check_triplet, try_reverse] - (Placer& placer, double waste) + (Placer& placer, ItemList& not_packed, + double waste, + double& free_area, + double& filled_area) { - - if(not_packed.size() < 3) return false; + auto np_size = not_packed.size(); + if(np_size < 3) return false; auto it = not_packed.begin(); // from const auto endit = not_packed.end(); // to @@ -275,6 +329,10 @@ public: std::vector wrong_pairs; std::vector wrong_triplets; + auto cap = np_size*np_size / 2 ; + wrong_pairs.reserve(cap); + wrong_triplets.reserve(cap); + // Will be true if a succesfull pack can be made. bool ret = false; @@ -445,88 +503,172 @@ public: return ret; }; - addBin(); - // Safety test: try to pack each item into an empty bin. If it fails // then it should be removed from the not_packed list - { auto it = not_packed.begin(); - while (it != not_packed.end()) { + { auto it = store_.begin(); + while (it != store_.end()) { Placer p(bin); if(!p.pack(*it)) { auto itmp = it++; - not_packed.erase(itmp); + store_.erase(itmp); } else it++; } } - auto makeProgress = [this, ¬_packed](Placer& placer) { - packed_bins_.back() = placer.getItems(); + int acounter = int(store_.size()); + std::atomic_flag flg = ATOMIC_FLAG_INIT; + SpinLock slock(flg); + + auto makeProgress = [this, &acounter, &slock] + (Placer& placer, size_t idx, int packednum) + { + + packed_bins_[idx] = placer.getItems(); #ifndef NDEBUG - packed_bins_.back().insert(packed_bins_.back().end(), + packed_bins_[idx].insert(packed_bins_[idx].end(), placer.getDebugItems().begin(), placer.getDebugItems().end()); #endif - this->progress_(not_packed.size()); + // TODO here should be a spinlock + slock.lock(); + acounter -= packednum; + this->progress_(acounter); + slock.unlock(); }; - while(!not_packed.empty()) { + double items_area = 0; + for(Item& item : store_) items_area += item.area(); - auto& placer = placers.back(); + // Number of bins that will definitely be needed + auto bincount_guess = unsigned(std::ceil(items_area / bin_area)); - {// Fill the bin up to INITIAL_FILL_PROPORTION of its capacity - auto it = not_packed.begin(); + // Do parallel if feasible + bool do_parallel = config_.allow_parallel && bincount_guess > 1 && + ((glob_vertex_count > MAX_VERTICES_SEQUENTIALLY || + store_.size() > MAX_ITEMS_SEQUENTIALLY) || + config_.force_parallel); - while(it != not_packed.end() && - filled_area < INITIAL_FILL_AREA) - { - if(placer.pack(*it)) { - filled_area += it->get().area(); - free_area = bin_area - filled_area; - auto itmp = it++; - not_packed.erase(itmp); - makeProgress(placer); - } else it++; + if(do_parallel) dout() << "Parallel execution..." << "\n"; + + // The DJD heuristic algorithm itself: + auto packjob = [INITIAL_FILL_AREA, bin_area, w, + &tryOneByOne, + &tryGroupsOfTwo, + &tryGroupsOfThree, + &makeProgress] + (Placer& placer, ItemList& not_packed, size_t idx) + { + bool can_pack = true; + + double filled_area = placer.filledArea(); + double free_area = bin_area - filled_area; + double waste = .0; + + while(!not_packed.empty() && can_pack) { + + {// Fill the bin up to INITIAL_FILL_PROPORTION of its capacity + auto it = not_packed.begin(); + + while(it != not_packed.end() && + filled_area < INITIAL_FILL_AREA) + { + if(placer.pack(*it)) { + filled_area += it->get().area(); + free_area = bin_area - filled_area; + auto itmp = it++; + not_packed.erase(itmp); + makeProgress(placer, idx, 1); + } else it++; + } + } + + // try pieses one by one + while(tryOneByOne(placer, not_packed, waste, free_area, + filled_area)) { + waste = 0; + makeProgress(placer, idx, 1); + } + + // try groups of 2 pieses + while(tryGroupsOfTwo(placer, not_packed, waste, free_area, + filled_area)) { + waste = 0; + makeProgress(placer, idx, 2); + } + + // try groups of 3 pieses + while(tryGroupsOfThree(placer, not_packed, waste, free_area, + filled_area)) { + waste = 0; + makeProgress(placer, idx, 3); + } + + if(waste < free_area) waste += w; + else if(!not_packed.empty()) can_pack = false; + } + + return can_pack; + }; + + size_t idx = 0; + ItemList remaining; + + if(do_parallel) { + std::vector not_packeds(bincount_guess); + + // Preallocating the bins + for(unsigned b = 0; b < bincount_guess; b++) { + addBin(); + ItemList& not_packed = not_packeds[b]; + for(unsigned idx = b; idx < store_.size(); idx+=bincount_guess) { + not_packed.push_back(store_[idx]); } } - // try pieses one by one - while(tryOneByOne(placer, waste)) { - waste = 0; - makeProgress(placer); + // The parallel job + auto job = [&placers, ¬_packeds, &packjob](unsigned idx) { + Placer& placer = placers[idx]; + ItemList& not_packed = not_packeds[idx]; + return packjob(placer, not_packed, idx); + }; + + // We will create jobs for each bin + std::vector> rets(bincount_guess); + + for(unsigned b = 0; b < bincount_guess; b++) { // launch the jobs + rets[b] = std::async(std::launch::async, job, b); } - // try groups of 2 pieses - while(tryGroupsOfTwo(placer, waste)) { - waste = 0; - makeProgress(placer); + for(unsigned fi = 0; fi < rets.size(); ++fi) { + rets[fi].wait(); + + // Collect remaining items while waiting for the running jobs + remaining.merge( not_packeds[fi], [](Item& i1, Item& i2) { + return i1.area() > i2.area(); + }); + } - // try groups of 3 pieses - while(tryGroupsOfThree(placer, waste)) { - waste = 0; - makeProgress(placer); + idx = placers.size(); + + // Try to put the remaining items into one of the packed bins + if(remaining.size() <= placers.size()) + for(size_t j = 0; j < idx && !remaining.empty(); j++) { + packjob(placers[j], remaining, j); } - if(waste < free_area) waste += w; - else if(!not_packed.empty()) addBin(); + } else { + remaining = ItemList(store_.begin(), store_.end()); + } + + while(!remaining.empty()) { + addBin(); + packjob(placers[idx], remaining, idx); idx++; } -// std::for_each(placers.begin(), placers.end(), -// [this](Placer& placer){ -// packed_bins_.push_back(placer.getItems()); -// }); } }; -/* - * The initial fill proportion suggested by - * [López-Camacho]\ - * (http://www.cs.stir.ac.uk/~goc/papers/EffectiveHueristic2DAOR2013.pdf) - * is one third of the area of bin. - */ -template -const double _DJDHeuristic::INITIAL_FILL_PROPORTION = 1.0/3.0; - } } diff --git a/xs/src/libnest2d/libnest2d/selections/filler.hpp b/xs/src/libnest2d/libnest2d/selections/filler.hpp index 96c5da4a1..94e30fd5b 100644 --- a/xs/src/libnest2d/libnest2d/selections/filler.hpp +++ b/xs/src/libnest2d/libnest2d/selections/filler.hpp @@ -33,7 +33,17 @@ public: store_.clear(); store_.reserve(last-first); - packed_bins_.clear(); + packed_bins_.emplace_back(); + + auto makeProgress = [this](PlacementStrategyLike& placer) { + packed_bins_.back() = placer.getItems(); +#ifndef NDEBUG + packed_bins_.back().insert(packed_bins_.back().end(), + placer.getDebugItems().begin(), + placer.getDebugItems().end()); +#endif + this->progress_(packed_bins_.back().size()); + }; std::copy(first, last, std::back_inserter(store_)); @@ -50,18 +60,22 @@ public: PlacementStrategyLike placer(bin); placer.configure(pconfig); - bool was_packed = false; - for(auto& item : store_ ) { - if(!placer.pack(item)) { - packed_bins_.push_back(placer.getItems()); + auto it = store_.begin(); + while(it != store_.end()) { + if(!placer.pack(*it)) { + if(packed_bins_.back().empty()) ++it; + makeProgress(placer); placer.clearItems(); - was_packed = placer.pack(item); - } else was_packed = true; + packed_bins_.emplace_back(); + } else { + makeProgress(placer); + ++it; + } } - if(was_packed) { - packed_bins_.push_back(placer.getItems()); - } +// if(was_packed) { +// packed_bins_.push_back(placer.getItems()); +// } } }; diff --git a/xs/src/libnest2d/tests/main.cpp b/xs/src/libnest2d/tests/main.cpp index 633fe0c97..20dab3529 100644 --- a/xs/src/libnest2d/tests/main.cpp +++ b/xs/src/libnest2d/tests/main.cpp @@ -1,6 +1,8 @@ #include -#include #include +#include + +//#define DEBUG_EXPORT_NFP #include #include @@ -8,6 +10,8 @@ #include "printer_parts.h" #include "benchmark.h" #include "svgtools.hpp" +//#include +//#include using namespace libnest2d; using ItemGroup = std::vector>; @@ -36,51 +40,122 @@ std::vector& stegoParts() { void arrangeRectangles() { using namespace libnest2d; - auto input = stegoParts(); - const int SCALE = 1000000; + std::vector rects = { + {80*SCALE, 80*SCALE}, + {60*SCALE, 90*SCALE}, + {70*SCALE, 30*SCALE}, + {80*SCALE, 60*SCALE}, + {60*SCALE, 60*SCALE}, + {60*SCALE, 40*SCALE}, + {40*SCALE, 40*SCALE}, + {10*SCALE, 10*SCALE}, + {10*SCALE, 10*SCALE}, + {10*SCALE, 10*SCALE}, + {10*SCALE, 10*SCALE}, + {10*SCALE, 10*SCALE}, + {5*SCALE, 5*SCALE}, + {5*SCALE, 5*SCALE}, + {5*SCALE, 5*SCALE}, + {5*SCALE, 5*SCALE}, + {5*SCALE, 5*SCALE}, + {5*SCALE, 5*SCALE}, + {5*SCALE, 5*SCALE}, + {20*SCALE, 20*SCALE} + }; - Box bin(210*SCALE, 250*SCALE); +// std::vector rects = { +// {20*SCALE, 10*SCALE}, +// {20*SCALE, 10*SCALE}, +// {20*SCALE, 20*SCALE}, +// }; - Coord min_obj_distance = 0; //6*SCALE; +// std::vector input { +// {{0, 0}, {0, 20*SCALE}, {10*SCALE, 0}, {0, 0}} +// }; - NfpPlacer::Config pconf; - pconf.alignment = NfpPlacer::Config::Alignment::TOP_LEFT; - Arranger arrange(bin, min_obj_distance, pconf); + std::vector input; + input.insert(input.end(), prusaParts().begin(), prusaParts().end()); +// input.insert(input.end(), stegoParts().begin(), stegoParts().end()); +// input.insert(input.end(), rects.begin(), rects.end()); -// arrange.progressIndicator([&arrange, &bin](unsigned r){ + Box bin(250*SCALE, 210*SCALE); + + Coord min_obj_distance = 6*SCALE; + + using Packer = Arranger; + + Packer::PlacementConfig pconf; + pconf.alignment = NfpPlacer::Config::Alignment::CENTER; +// pconf.rotations = {0.0, Pi/2.0, Pi, 3*Pi/2}; + Packer::SelectionConfig sconf; + sconf.allow_parallel = true; + sconf.force_parallel = false; + sconf.try_reverse_order = false; + Packer arrange(bin, min_obj_distance, pconf, sconf); + + arrange.progressIndicator([&](unsigned r){ // svg::SVGWriter::Config conf; // conf.mm_in_coord_units = SCALE; // svg::SVGWriter svgw(conf); // svgw.setSize(bin); // svgw.writePackGroup(arrange.lastResult()); -// svgw.save("out"); -// std::cout << "Remaining items: " << r << std::endl; -// }); +// svgw.save("debout"); + std::cout << "Remaining items: " << r << std::endl; + }).useMinimumBoundigBoxRotation(); Benchmark bench; bench.start(); - auto result = arrange(input.begin(), + auto result = arrange.arrange(input.begin(), input.end()); bench.stop(); - std::cout << bench.getElapsedSec() << std::endl; + std::vector eff; + eff.reserve(result.size()); + + auto bin_area = double(bin.height()*bin.width()); + for(auto& r : result) { + double a = 0; + std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); }); + eff.emplace_back(a/bin_area); + }; + + std::cout << bench.getElapsedSec() << " bin count: " << result.size() + << std::endl; + + std::cout << "Bin efficiency: ("; + for(double e : eff) std::cout << e*100.0 << "% "; + std::cout << ") Average: " + << std::accumulate(eff.begin(), eff.end(), 0.0)*100.0/result.size() + << " %" << std::endl; + + std::cout << "Bin usage: ("; + unsigned total = 0; + for(auto& r : result) { std::cout << r.size() << " "; total += r.size(); } + std::cout << ") Total: " << total << std::endl; for(auto& it : input) { auto ret = ShapeLike::isValid(it.transformedShape()); std::cout << ret.second << std::endl; } + if(total != input.size()) std::cout << "ERROR " << "could not pack " + << input.size() - total << " elements!" + << std::endl; + svg::SVGWriter::Config conf; conf.mm_in_coord_units = SCALE; svg::SVGWriter svgw(conf); svgw.setSize(bin); svgw.writePackGroup(result); +// std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);}); svgw.save("out"); } + + int main(void /*int argc, char **argv*/) { arrangeRectangles(); // findDegenerateCase(); diff --git a/xs/src/libnest2d/tests/printer_parts.cpp b/xs/src/libnest2d/tests/printer_parts.cpp index 13bc627ad..6d36bc1cd 100644 --- a/xs/src/libnest2d/tests/printer_parts.cpp +++ b/xs/src/libnest2d/tests/printer_parts.cpp @@ -3,596 +3,594 @@ const TestData PRINTER_PART_POLYGONS = { { - {120000000, 113954048}, - {130000000, 113954048}, - {130000000, 104954048}, - {129972610, 104431449}, - {128500000, 96045951}, - {121500000, 96045951}, - {120027389, 104431449}, - {120000000, 104954048}, - {120000000, 113954048}, + {-5000000, 8954050}, + {5000000, 8954050}, + {5000000, -45949}, + {4972609, -568550}, + {3500000, -8954050}, + {-3500000, -8954050}, + {-4972609, -568550}, + {-5000000, -45949}, + {-5000000, 8954050}, }, { - {61250000, 97000000}, - {70250000, 151000000}, - {175750000, 151000000}, - {188750000, 138000000}, - {188750000, 59000000}, - {70250000, 59000000}, - {61250000, 77000000}, - {61250000, 97000000}, + {-63750000, -8000000}, + {-54750000, 46000000}, + {50750000, 46000000}, + {63750000, 33000000}, + {63750000, -46000000}, + {-54750000, -46000000}, + {-63750000, -28000000}, + {-63750000, -8000000}, }, { - {72250000, 146512344}, - {93750000, 150987655}, - {177750000, 150987655}, - {177750000, 59012348}, - {72250000, 59012348}, - {72250000, 146512344}, + {-52750000, 41512348}, + {-31250000, 45987651}, + {52750000, 45987651}, + {52750000, -45987651}, + {-52750000, -45987651}, + {-52750000, 41512348}, }, { - {121099998, 119000000}, - {122832046, 119000000}, - {126016967, 113483596}, - {126721450, 112263397}, - {128828536, 108613792}, - {128838806, 108582153}, - {128871566, 108270568}, - {128899993, 108000000}, - {128500000, 102000000}, - {128447555, 101501014}, - {128292510, 101023834}, - {128100006, 100487052}, - {126030128, 96901916}, - {122622650, 91000000}, - {121099998, 91000000}, - {121099998, 119000000}, + {-3900000, 14000000}, + {-2167950, 14000000}, + {1721454, 7263400}, + {3828529, 3613790}, + {3838809, 3582149}, + {3871560, 3270569}, + {3900000, 3000000}, + {3500000, -3000000}, + {3471560, -3270565}, + {3447549, -3498986}, + {3292510, -3976167}, + {3099999, -4512949}, + {2530129, -5500000}, + {807565, -8483570}, + {-2377349, -14000000}, + {-3900000, -14000000}, + {-3900000, 14000000}, }, { - {93250000, 104000000}, - {99750000, 145500000}, - {106750000, 152500000}, - {135750000, 152500000}, - {141750000, 146500000}, - {156750000, 68000000}, - {156750000, 61142101}, - {156659606, 61051700}, - {153107894, 57500000}, - {143392105, 57500000}, - {104250000, 58500000}, - {93250000, 101000000}, - {93250000, 104000000}, + {-31750000, -1000000}, + {-25250000, 40500000}, + {-18250000, 47500000}, + {10750000, 47500000}, + {16750000, 41500000}, + {31750000, -37000000}, + {31750000, -43857898}, + {28107900, -47500000}, + {18392099, -47500000}, + {-20750000, -46500000}, + {-31750000, -4000000}, + {-31750000, -1000000}, }, { - {90375000, 90734603}, - {114074996, 129875000}, - {158324996, 129875000}, - {162574996, 125625000}, - {162574996, 122625000}, - {151574996, 80125000}, - {116074996, 80125000}, - {90375000, 80515396}, - {87425003, 85625000}, - {90375000, 90734603}, + {-34625000, -14265399}, + {-10924999, 24875000}, + {33325000, 24875000}, + {37575000, 20625000}, + {37575000, 17625000}, + {26575000, -24875000}, + {-8924999, -24875000}, + {-34625000, -24484600}, + {-37575000, -19375000}, + {-34625000, -14265399}, }, { - {111000000, 114000000}, - {114000000, 122000000}, - {139000000, 122000000}, - {139000000, 88000000}, - {114000000, 88000000}, - {111000000, 97000000}, - {111000000, 114000000}, + {-14000000, 9000000}, + {-11000000, 17000000}, + {14000000, 17000000}, + {14000000, -17000000}, + {-11000000, -17000000}, + {-14000000, -8000000}, + {-14000000, 9000000}, }, { - {119699996, 107227401}, - {124762199, 110150001}, - {130300003, 110150001}, - {130300003, 105650001}, - {129699996, 99850006}, - {119699996, 99850006}, - {119699996, 107227401}, + {-5300000, 2227401}, + {-237800, 5150001}, + {5299999, 5150001}, + {5299999, 650001}, + {4699999, -5149997}, + {-5300000, -5149997}, + {-5300000, 2227401}, }, { - {113000000, 123000000}, - {137000000, 123000000}, - {137000000, 87000000}, - {113000000, 87000000}, - {113000000, 123000000}, + {-12000000, 18000000}, + {12000000, 18000000}, + {12000000, -18000000}, + {-12000000, -18000000}, + {-12000000, 18000000}, }, { - {107000000, 104000000}, - {110000000, 127000000}, - {114000000, 131000000}, - {136000000, 131000000}, - {140000000, 127000000}, - {143000000, 104000000}, - {143000000, 79000000}, - {107000000, 79000000}, - {107000000, 104000000}, + {-18000000, -1000000}, + {-15000000, 22000000}, + {-11000000, 26000000}, + {11000000, 26000000}, + {15000000, 22000000}, + {18000000, -1000000}, + {18000000, -26000000}, + {-18000000, -26000000}, + {-18000000, -1000000}, }, { - {47500000, 135000000}, - {52500000, 140000000}, - {197500000, 140000000}, - {202500000, 135000000}, - {202500000, 72071098}, - {200428894, 70000000}, - {49571098, 70000000}, - {48570396, 71000701}, - {47500000, 72071098}, - {47500000, 135000000}, + {-77500000, 30000000}, + {-72500000, 35000000}, + {72500000, 35000000}, + {77500000, 30000000}, + {77500000, -32928901}, + {75428901, -35000000}, + {-75428901, -35000000}, + {-77500000, -32928901}, + {-77500000, 30000000}, }, { - {115054779, 101934379}, - {115218521, 102968223}, - {115489440, 103979270}, - {115864547, 104956466}, - {122900001, 119110900}, - {127099998, 119110900}, - {134135452, 104956466}, - {134510559, 103979270}, - {134781478, 102968223}, - {134945220, 101934379}, - {135000000, 100889099}, - {134945220, 99843818}, - {134781478, 98809982}, - {134510559, 97798927}, - {134135452, 96821731}, - {133660247, 95889099}, - {133090164, 95011245}, - {132431457, 94197792}, - {131691314, 93457649}, - {130877853, 92798927}, - {130000000, 92228851}, - {129067367, 91753646}, - {128090164, 91378540}, - {127079116, 91107620}, - {126045280, 90943878}, - {125000000, 90889099}, - {123954719, 90943878}, - {122920883, 91107620}, - {121909828, 91378540}, - {120932632, 91753646}, - {120000000, 92228851}, - {119122146, 92798927}, - {118308692, 93457649}, - {117568550, 94197792}, - {116909828, 95011245}, - {116339752, 95889099}, - {115864547, 96821731}, - {115489440, 97798927}, - {115218521, 98809982}, - {115054779, 99843818}, - {115000000, 100889099}, - {115054779, 101934379}, + {-9945219, -3065619}, + {-9781479, -2031780}, + {-9510560, -1020730}, + {-9135450, -43529}, + {-2099999, 14110899}, + {2099999, 14110899}, + {9135450, -43529}, + {9510560, -1020730}, + {9781479, -2031780}, + {9945219, -3065619}, + {10000000, -4110899}, + {9945219, -5156179}, + {9781479, -6190019}, + {9510560, -7201069}, + {9135450, -8178270}, + {8660249, -9110899}, + {8090169, -9988750}, + {7431449, -10802209}, + {6691309, -11542349}, + {5877850, -12201069}, + {5000000, -12771149}, + {4067369, -13246350}, + {3090169, -13621459}, + {2079119, -13892379}, + {1045279, -14056119}, + {0, -14110899}, + {-1045279, -14056119}, + {-2079119, -13892379}, + {-3090169, -13621459}, + {-4067369, -13246350}, + {-5000000, -12771149}, + {-5877850, -12201069}, + {-6691309, -11542349}, + {-7431449, -10802209}, + {-8090169, -9988750}, + {-8660249, -9110899}, + {-9135450, -8178270}, + {-9510560, -7201069}, + {-9781479, -6190019}, + {-9945219, -5156179}, + {-10000000, -4110899}, + {-9945219, -3065619}, }, { - {90807601, 99807609}, - {93500000, 144000000}, - {116816207, 152669006}, - {118230407, 152669006}, - {120351806, 150547698}, - {159192398, 111707107}, - {159192398, 110192390}, - {156500000, 66000000}, - {133183807, 57331001}, - {131769607, 57331001}, - {129648208, 59452301}, - {92525100, 96575378}, - {90807601, 98292892}, - {90807601, 99807609}, + {-34192394, -5192389}, + {-31499996, 39000000}, + {-8183795, 47668998}, + {-6769596, 47668998}, + {-4648197, 45547698}, + {34192394, 6707109}, + {34192394, 5192389}, + {31500003, -39000000}, + {8183803, -47668998}, + {6769603, -47668998}, + {4648202, -45547698}, + {-32474895, -8424619}, + {-34192394, -6707109}, + {-34192394, -5192389}, }, { - {101524497, 93089904}, - {107000000, 113217697}, - {113860298, 125099998}, - {114728599, 125900001}, - {134532012, 125900001}, - {136199996, 125099998}, - {143500000, 113599998}, - {148475494, 93089904}, - {148800003, 90099998}, - {148706604, 89211097}, - {148668899, 88852500}, - {148281295, 87659599}, - {147654098, 86573303}, - {146814804, 85641098}, - {145800003, 84903800}, - {144654098, 84393699}, - {143427200, 84132904}, - {142800003, 84099998}, - {107199996, 84099998}, - {106572799, 84132904}, - {105345901, 84393699}, - {104199996, 84903800}, - {103185195, 85641098}, - {102345901, 86573303}, - {101718704, 87659599}, - {101331100, 88852500}, - {101199996, 90099998}, - {101524497, 93089904}, + {-23475500, -11910099}, + {-18000000, 8217699}, + {-11139699, 20100000}, + {-10271400, 20899999}, + {9532010, 20899999}, + {11199999, 20100000}, + {18500000, 8600000}, + {23475500, -11910099}, + {23799999, -14899999}, + {23706600, -15788900}, + {23668899, -16147499}, + {23281299, -17340400}, + {22654100, -18426700}, + {21814800, -19358900}, + {20799999, -20096199}, + {19654100, -20606300}, + {18427200, -20867099}, + {17799999, -20899999}, + {-17799999, -20899999}, + {-18427200, -20867099}, + {-19654100, -20606300}, + {-20799999, -20096199}, + {-21814800, -19358900}, + {-22654100, -18426700}, + {-23281299, -17340400}, + {-23668899, -16147499}, + {-23799999, -14899999}, + {-23475500, -11910099}, }, { - {93000000, 115000000}, - {93065559, 115623733}, - {93259361, 116220207}, - {93572952, 116763359}, - {93992614, 117229431}, - {94500000, 117598083}, - {95072952, 117853172}, - {95686416, 117983566}, - {141000000, 121000000}, - {151000000, 121000000}, - {156007400, 117229431}, - {156427093, 116763359}, - {156740600, 116220207}, - {156934402, 115623733}, - {157000000, 115000000}, - {157000000, 92000000}, - {156934402, 91376296}, - {156740600, 90779800}, - {156427093, 90236602}, - {156007400, 89770599}, - {155500000, 89401901}, - {154927093, 89146797}, - {154313598, 89016403}, - {154000000, 89000000}, - {97000000, 89000000}, - {95686416, 89016403}, - {95072952, 89146797}, - {94500000, 89401901}, - {93992614, 89770599}, - {93572952, 90236602}, - {93259361, 90779800}, - {93065559, 91376296}, - {93000000, 92000000}, - {93000000, 115000000}, + {-32000000, 10000000}, + {-31934440, 10623733}, + {-31740640, 11220210}, + {-31427049, 11763360}, + {-31007389, 12229430}, + {-30500000, 12598079}, + {-29927051, 12853170}, + {-29313585, 12983570}, + {16000000, 16000000}, + {26000000, 16000000}, + {31007400, 12229430}, + {31427101, 11763360}, + {31740600, 11220210}, + {31934398, 10623733}, + {32000000, 10000000}, + {32000000, -13000000}, + {31934398, -13623699}, + {31740600, -14220199}, + {31427101, -14763399}, + {31007400, -15229400}, + {30500000, -15598100}, + {29927101, -15853200}, + {29313598, -15983600}, + {29000000, -16000000}, + {-28000000, -16000000}, + {-29313585, -15983600}, + {-29927051, -15853200}, + {-30500000, -15598100}, + {-31007389, -15229400}, + {-31427049, -14763399}, + {-31740640, -14220199}, + {-31934440, -13623699}, + {-32000000, -13000000}, + {-32000000, 10000000}, }, { - {88866210, 58568977}, - {88959899, 58828182}, - {89147277, 59346588}, - {127200073, 164616485}, - {137112792, 192039184}, - {139274505, 198019332}, - {139382049, 198291641}, - {139508483, 198563430}, - {139573425, 198688369}, - {139654052, 198832443}, - {139818634, 199096328}, - {139982757, 199327621}, - {140001708, 199352630}, - {140202392, 199598999}, - {140419342, 199833160}, - {140497497, 199910552}, - {140650848, 200053039}, - {140894866, 200256866}, - {141104309, 200412185}, - {141149047, 200443206}, - {141410888, 200611038}, - {141677795, 200759750}, - {141782348, 200812332}, - {141947143, 200889144}, - {142216400, 200999465}, - {142483123, 201091293}, - {142505554, 201098251}, - {142745178, 201165542}, - {143000671, 201223373}, - {143245880, 201265884}, - {143484039, 201295257}, - {143976715, 201319580}, - {156135131, 201319580}, - {156697082, 201287902}, - {156746368, 201282104}, - {157263000, 201190719}, - {157338623, 201172576}, - {157821411, 201026641}, - {157906188, 200995391}, - {158360565, 200797012}, - {158443420, 200754882}, - {158869171, 200505874}, - {158900756, 200485122}, - {159136413, 200318618}, - {159337127, 200159790}, - {159377288, 200125930}, - {159619628, 199905410}, - {159756286, 199767364}, - {159859008, 199656143}, - {160090606, 199378067}, - {160120849, 199338546}, - {160309295, 199072113}, - {160434875, 198871475}, - {160510070, 198740310}, - {160688232, 198385772}, - {160699096, 198361679}, - {160839782, 198012557}, - {160905487, 197817459}, - {160961578, 197625488}, - {161048004, 197249023}, - {161051574, 197229934}, - {161108856, 196831405}, - {161122985, 196667816}, - {161133789, 196435317}, - {161129669, 196085830}, - {161127685, 196046661}, - {161092742, 195669830}, - {161069946, 195514739}, - {161031829, 195308425}, - {160948211, 194965225}, - {159482635, 189756820}, - {152911407, 166403976}, - {145631286, 140531890}, - {119127441, 46342559}, - {110756378, 16593490}, - {110423187, 15409400}, - {109578002, 12405799}, - {109342315, 11568267}, - {108961059, 11279479}, - {108579803, 10990692}, - {107817291, 10413124}, - {106165161, 9161727}, - {105529724, 8680419}, - {103631866, 8680419}, - {102236145, 8680465}, - {95257537, 8680725}, - {92466064, 8680831}, - {88866210, 50380981}, - {88866210, 58568977}, + {-36133789, -46431022}, + {-36040100, -46171817}, + {-35852722, -45653411}, + {2200073, 59616485}, + {12112792, 87039184}, + {14274505, 93019332}, + {14382049, 93291641}, + {14508483, 93563430}, + {14573425, 93688369}, + {14654052, 93832443}, + {14818634, 94096328}, + {14982757, 94327621}, + {15001708, 94352630}, + {15202392, 94598999}, + {15419342, 94833160}, + {15497497, 94910552}, + {15650848, 95053039}, + {15894866, 95256866}, + {16104309, 95412185}, + {16149047, 95443206}, + {16410888, 95611038}, + {16677795, 95759750}, + {16782348, 95812332}, + {16947143, 95889144}, + {17216400, 95999465}, + {17483123, 96091293}, + {17505554, 96098251}, + {17745178, 96165542}, + {18000671, 96223373}, + {18245880, 96265884}, + {18484039, 96295257}, + {18976715, 96319580}, + {31135131, 96319580}, + {31697082, 96287902}, + {31746368, 96282104}, + {32263000, 96190719}, + {32338623, 96172576}, + {32821411, 96026641}, + {32906188, 95995391}, + {33360565, 95797012}, + {33443420, 95754882}, + {33869171, 95505874}, + {33900756, 95485122}, + {34136413, 95318618}, + {34337127, 95159790}, + {34377288, 95125930}, + {34619628, 94905410}, + {34756286, 94767364}, + {34859008, 94656143}, + {35090606, 94378067}, + {35120849, 94338546}, + {35309295, 94072113}, + {35434875, 93871475}, + {35510070, 93740310}, + {35688232, 93385772}, + {35699096, 93361679}, + {35839782, 93012557}, + {35905487, 92817459}, + {35961578, 92625488}, + {36048004, 92249023}, + {36051574, 92229934}, + {36108856, 91831405}, + {36122985, 91667816}, + {36133789, 91435317}, + {36129669, 91085830}, + {36127685, 91046661}, + {36092742, 90669830}, + {36069946, 90514739}, + {36031829, 90308425}, + {35948211, 89965225}, + {34482635, 84756820}, + {27911407, 61403976}, + {-5872558, -58657440}, + {-14243621, -88406509}, + {-14576812, -89590599}, + {-15421997, -92594200}, + {-15657684, -93431732}, + {-16038940, -93720520}, + {-16420196, -94009307}, + {-17182708, -94586875}, + {-18834838, -95838272}, + {-19470275, -96319580}, + {-21368133, -96319580}, + {-22763854, -96319534}, + {-29742462, -96319274}, + {-32533935, -96319168}, + {-36133789, -54619018}, + {-36133789, -46431022}, }, { - {99000000, 130500000}, - {101070701, 132570709}, - {118500000, 150000000}, - {142500000, 150000000}, - {151000000, 141500000}, - {151000000, 86000000}, - {150949996, 80500000}, - {142000000, 62785301}, - {139300003, 60000000}, - {110699996, 60000000}, - {107500000, 63285301}, - {101599998, 80500000}, - {99000000, 94535995}, - {99000000, 130500000}, + {-26000000, 25500000}, + {-6500000, 45000000}, + {17499998, 45000000}, + {23966310, 38533699}, + {26000000, 36500000}, + {26000000, -19000000}, + {25950000, -24500000}, + {17000000, -42214698}, + {14300000, -45000000}, + {-14299999, -45000000}, + {-17500000, -41714698}, + {-23400001, -24500000}, + {-26000000, -10464000}, + {-26000000, 25500000}, }, { - {99000000, 121636100}, - {99927795, 123777801}, - {108500000, 140300003}, - {109949996, 141750000}, - {138550003, 141750000}, - {140000000, 140300003}, - {151000000, 121045196}, - {151000000, 102250000}, - {141500000, 70492095}, - {139257904, 68250000}, - {110742095, 68250000}, - {108500000, 70492095}, - {99000000, 102250000}, - {99000000, 121636100}, + {-26000000, 16636100}, + {-25072200, 18777799}, + {-16500000, 35299999}, + {-15050000, 36750000}, + {13550000, 36750000}, + {15000000, 35299999}, + {26000000, 16045200}, + {26000000, -2750000}, + {16500000, -34507900}, + {14840600, -36167301}, + {14257900, -36750000}, + {-14257900, -36750000}, + {-16500000, -34507900}, + {-26000000, -2750000}, + {-26000000, 16636100}, }, { - {106937652, 123950103}, - {129644943, 125049896}, - {131230361, 125049896}, - {132803283, 124851196}, - {134338897, 124456901}, - {135812988, 123873298}, - {137202316, 123109497}, - {138484954, 122177597}, - {139640670, 121092300}, - {140651245, 119870697}, - {141500747, 118532104}, - {142175842, 117097595}, - {142665756, 115589698}, - {142962844, 114032402}, - {143062347, 112450103}, - {142962844, 110867797}, - {140810745, 93992263}, - {140683746, 93272232}, - {140506851, 92562797}, - {140280929, 91867439}, - {140007034, 91189529}, - {139686523, 90532394}, - {139320953, 89899200}, - {138912094, 89293052}, - {138461959, 88716903}, - {137972732, 88173553}, - {137446792, 87665664}, - {136886703, 87195693}, - {136295196, 86765930}, - {135675155, 86378479}, - {135029586, 86035232}, - {134361648, 85737854}, - {133674606, 85487777}, - {132971786, 85286300}, - {132256607, 85134201}, - {131532592, 85032501}, - {130803222, 84981498}, - {130437652, 84975097}, - {123937652, 84950103}, - {108437652, 84950103}, - {106937652, 86450103}, - {106937652, 123950103}, + {-18062349, 18950099}, + {4644938, 20049900}, + {6230361, 20049900}, + {7803279, 19851200}, + {9338899, 19456899}, + {10812990, 18873300}, + {12202310, 18109500}, + {13484951, 17177600}, + {14640670, 16092300}, + {15651250, 14870700}, + {16500749, 13532100}, + {17175849, 12097599}, + {17665750, 10589700}, + {17962850, 9032400}, + {18062349, 7450099}, + {17962850, 5867799}, + {15810750, -11007740}, + {15683750, -11727769}, + {15506849, -12437200}, + {15280929, -13132559}, + {15007040, -13810470}, + {14686531, -14467609}, + {14320949, -15100799}, + {13912099, -15706950}, + {13461959, -16283100}, + {12972730, -16826450}, + {12446790, -17334339}, + {11886699, -17804309}, + {11295190, -18234069}, + {10675149, -18621520}, + {10029590, -18964771}, + {9361650, -19262149}, + {8674600, -19512220}, + {7971780, -19713699}, + {7256609, -19865798}, + {6532589, -19967498}, + {5803222, -20018501}, + {5437650, -20024900}, + {-1062349, -20049900}, + {-16562349, -20049900}, + {-18062349, -18549900}, + {-18062349, 18950099}, }, { - {106937652, 146299896}, - {123937652, 146299896}, - {140280929, 96882560}, - {140506851, 96187202}, - {140683746, 95477767}, - {140810745, 94757736}, - {142962844, 77882202}, - {143062347, 76299896}, - {142962844, 74717597}, - {142665756, 73160301}, - {142175842, 71652404}, - {141500747, 70217895}, - {140651245, 68879302}, - {139640670, 67657699}, - {138484954, 66572402}, - {137202316, 65640502}, - {135812988, 64876701}, - {134338897, 64293098}, - {132803283, 63898799}, - {131230361, 63700099}, - {129644943, 63700099}, - {106937652, 64799896}, - {106937652, 146299896}, + {-18062349, 41299900}, + {-1062349, 41299900}, + {15280929, -8117440}, + {15506849, -8812799}, + {15683750, -9522230}, + {15810750, -10242259}, + {17962850, -27117799}, + {18062349, -28700099}, + {17962850, -30282400}, + {17665750, -31839700}, + {17175849, -33347599}, + {16500749, -34782100}, + {15651250, -36120700}, + {14640670, -37342300}, + {13484951, -38427600}, + {12202310, -39359500}, + {10812990, -40123298}, + {9338899, -40706901}, + {7803279, -41101200}, + {6230361, -41299900}, + {4644938, -41299900}, + {-18062349, -40200099}, + {-18062349, 41299900}, }, { - {113250000, 118057899}, - {115192138, 120000000}, - {129392135, 129000000}, - {136750000, 129000000}, - {136750000, 81000000}, - {129392135, 81000000}, - {115192138, 90000000}, - {113250000, 91942100}, - {113250000, 118057899}, + {-11750000, 13057900}, + {-9807860, 15000000}, + {4392139, 24000000}, + {11750000, 24000000}, + {11750000, -24000000}, + {4392139, -24000000}, + {-9807860, -15000000}, + {-11750000, -13057900}, + {-11750000, 13057900}, }, { - {112500000, 122500000}, - {137500000, 122500000}, - {137500000, 87500000}, - {112500000, 87500000}, - {112500000, 122500000}, + {-12500000, 17500000}, + {12500000, 17500000}, + {12500000, -17500000}, + {-12500000, -17500000}, + {-12500000, 17500000}, }, { - {101500000, 116500000}, - {111142143, 126000000}, - {114000000, 126000000}, - {143500000, 105500000}, - {148500000, 100500000}, - {148500000, 85500000}, - {147000000, 84000000}, - {101500000, 84000000}, - {101500000, 116500000}, + {-23500000, 11500000}, + {-13857859, 21000000}, + {-11000000, 21000000}, + {18500000, 500000}, + {23500000, -4500000}, + {23500000, -19500000}, + {22000000, -21000000}, + {-23500000, -21000000}, + {-23500000, 11500000}, }, { - {112000000, 110250000}, - {121000000, 111750000}, - {129000000, 111750000}, - {138000000, 110250000}, - {138000000, 105838462}, - {136376296, 103026062}, - {135350906, 101250000}, - {133618804, 98250000}, - {116501708, 98250000}, - {112000000, 106047180}, - {112000000, 110250000}, + {-13000000, 5250000}, + {-4000000, 6750000}, + {4000000, 6750000}, + {13000000, 5250000}, + {13000000, 838459}, + {11376299, -1973939}, + {10350899, -3750000}, + {8618800, -6750000}, + {-8498290, -6750000}, + {-13000000, 1047180}, + {-13000000, 5250000}, }, { - {100000000, 155500000}, - {103500000, 159000000}, - {143286804, 159000000}, - {150000000, 152286804}, - {150000000, 57713199}, - {143397705, 51110900}, - {143286804, 51000000}, - {103500000, 51000000}, - {100000000, 54500000}, - {100000000, 155500000}, + {-25000000, 50500000}, + {-21500000, 54000000}, + {18286800, 54000000}, + {25000000, 47286800}, + {25000000, -47286800}, + {18286800, -54000000}, + {-21500000, -54000000}, + {-25000000, -50500000}, + {-25000000, 50500000}, }, { - {106000000, 151000000}, - {108199996, 151000000}, - {139000000, 139000000}, - {144000000, 134000000}, - {144000000, 76000000}, - {139000000, 71000000}, - {108199996, 59000000}, - {106000000, 59000000}, - {106000000, 151000000}, + {-19000000, 46000000}, + {-16799999, 46000000}, + {14000000, 34000000}, + {19000000, 29000000}, + {19000000, -29000000}, + {14000000, -34000000}, + {-16799999, -46000000}, + {-19000000, -46000000}, + {-19000000, 46000000}, }, { - {117043830, 105836227}, - {117174819, 106663291}, - {117232467, 106914527}, - {117391548, 107472137}, - {117691642, 108253890}, - {117916351, 108717781}, - {118071800, 109000000}, - {118527862, 109702278}, - {119011909, 110304977}, - {119054840, 110353042}, - {119646957, 110945159}, - {120297721, 111472137}, - {120455482, 111583869}, - {121000000, 111928199}, - {121746109, 112308357}, - {122163162, 112480133}, - {122527862, 112608451}, - {123336708, 112825180}, - {124035705, 112941673}, - {124163772, 112956169}, - {125000000, 113000000}, - {125836227, 112956169}, - {125964294, 112941673}, - {126663291, 112825180}, - {127472137, 112608451}, - {127836837, 112480133}, - {128253890, 112308357}, - {129000000, 111928199}, - {129544525, 111583869}, - {129702285, 111472137}, - {130353042, 110945159}, - {130945159, 110353042}, - {130988082, 110304977}, - {131472137, 109702278}, - {131928192, 109000000}, - {132083648, 108717781}, - {132308364, 108253890}, - {132608444, 107472137}, - {132767532, 106914527}, - {132825180, 106663291}, - {132956176, 105836227}, - {133000000, 105000000}, - {132956176, 104163772}, - {132825180, 103336708}, - {132767532, 103085472}, - {132608444, 102527862}, - {132308364, 101746109}, - {132083648, 101282218}, - {131928192, 101000000}, - {131472137, 100297721}, - {130988082, 99695022}, - {130945159, 99646957}, - {130353042, 99054840}, - {129702285, 98527862}, - {129544525, 98416130}, - {129000000, 98071800}, - {128253890, 97691642}, - {127836837, 97519866}, - {127472137, 97391548}, - {126663291, 97174819}, - {125964294, 97058326}, - {125836227, 97043830}, - {125000000, 97000000}, - {124163772, 97043830}, - {124035705, 97058326}, - {123336708, 97174819}, - {122527862, 97391548}, - {122163162, 97519866}, - {121746109, 97691642}, - {121000000, 98071800}, - {120455482, 98416130}, - {120297721, 98527862}, - {119646957, 99054840}, - {119054840, 99646957}, - {119011909, 99695022}, - {118527862, 100297721}, - {118071800, 101000000}, - {117916351, 101282218}, - {117691642, 101746109}, - {117391548, 102527862}, - {117232467, 103085472}, - {117174819, 103336708}, - {117043830, 104163772}, - {117000000, 105000000}, - {117043830, 105836227}, + {-7956170, 836226}, + {-7825180, 1663290}, + {-7767529, 1914530}, + {-7608449, 2472140}, + {-7308360, 3253890}, + {-7083650, 3717780}, + {-6928199, 4000000}, + {-6472139, 4702280}, + {-5988090, 5304979}, + {-5945159, 5353040}, + {-5353040, 5945159}, + {-4702280, 6472139}, + {-4544519, 6583869}, + {-4000000, 6928199}, + {-3253890, 7308360}, + {-2836839, 7480130}, + {-2472140, 7608449}, + {-1663290, 7825180}, + {-964293, 7941669}, + {-836226, 7956170}, + {0, 8000000}, + {836226, 7956170}, + {964293, 7941669}, + {1663290, 7825180}, + {2472140, 7608449}, + {2836839, 7480130}, + {3253890, 7308360}, + {4000000, 6928199}, + {4544519, 6583869}, + {4702280, 6472139}, + {5353040, 5945159}, + {5945159, 5353040}, + {5988090, 5304979}, + {6472139, 4702280}, + {6928199, 4000000}, + {7083650, 3717780}, + {7308360, 3253890}, + {7608449, 2472140}, + {7767529, 1914530}, + {7825180, 1663290}, + {7956170, 836226}, + {8000000, 0}, + {7956170, -836226}, + {7825180, -1663290}, + {7767529, -1914530}, + {7608449, -2472140}, + {7308360, -3253890}, + {7083650, -3717780}, + {6928199, -4000000}, + {6472139, -4702280}, + {5988090, -5304979}, + {5945159, -5353040}, + {5353040, -5945159}, + {4702280, -6472139}, + {4544519, -6583869}, + {4000000, -6928199}, + {3253890, -7308360}, + {2836839, -7480130}, + {2472140, -7608449}, + {1663290, -7825180}, + {964293, -7941669}, + {836226, -7956170}, + {0, -8000000}, + {-836226, -7956170}, + {-964293, -7941669}, + {-1663290, -7825180}, + {-2472140, -7608449}, + {-2836839, -7480130}, + {-3253890, -7308360}, + {-4000000, -6928199}, + {-4544519, -6583869}, + {-4702280, -6472139}, + {-5353040, -5945159}, + {-5945159, -5353040}, + {-5988090, -5304979}, + {-6472139, -4702280}, + {-6928199, -4000000}, + {-7083650, -3717780}, + {-7308360, -3253890}, + {-7608449, -2472140}, + {-7767529, -1914530}, + {-7825180, -1663290}, + {-7956170, -836226}, + {-8000000, 0}, + {-7956170, 836226}, }, }; diff --git a/xs/src/libnest2d/tests/svgtools.hpp b/xs/src/libnest2d/tests/svgtools.hpp index 5ede726f3..f0db85217 100644 --- a/xs/src/libnest2d/tests/svgtools.hpp +++ b/xs/src/libnest2d/tests/svgtools.hpp @@ -50,7 +50,9 @@ public: if(conf_.origo_location == BOTTOMLEFT) for(unsigned i = 0; i < tsh.vertexCount(); i++) { auto v = tsh.vertex(i); - setY(v, -getY(v) + conf_.height*conf_.mm_in_coord_units); + auto d = static_cast( + std::round(conf_.height*conf_.mm_in_coord_units) ); + setY(v, -getY(v) + d); tsh.setVertex(i, v); } currentLayer() += ShapeLike::serialize(tsh.rawShape(), @@ -78,8 +80,8 @@ public: } void save(const std::string& filepath) { - unsigned lyrc = svg_layers_.size() > 1? 1 : 0; - unsigned last = svg_layers_.size() > 1? svg_layers_.size() : 0; + size_t lyrc = svg_layers_.size() > 1? 1 : 0; + size_t last = svg_layers_.size() > 1? svg_layers_.size() : 0; for(auto& lyr : svg_layers_) { std::fstream out(filepath + (lyrc > 0? std::to_string(lyrc) : "") + diff --git a/xs/src/libnest2d/tests/test.cpp b/xs/src/libnest2d/tests/test.cpp index 7ed7aa419..9aee6d799 100644 --- a/xs/src/libnest2d/tests/test.cpp +++ b/xs/src/libnest2d/tests/test.cpp @@ -253,7 +253,7 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon) ASSERT_TRUE(ShapeLike::isValid(leftp.rawShape()).first); ASSERT_EQ(leftp.vertexCount(), leftControl.vertexCount()); - for(size_t i = 0; i < leftControl.vertexCount(); i++) { + for(unsigned long 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))); } @@ -263,7 +263,7 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon) ASSERT_TRUE(ShapeLike::isValid(downp.rawShape()).first); ASSERT_EQ(downp.vertexCount(), downControl.vertexCount()); - for(size_t i = 0; i < downControl.vertexCount(); i++) { + for(unsigned long 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))); } @@ -696,6 +696,27 @@ TEST(GeometryAlgorithms, nfpConvexConvex) { } } +TEST(GeometryAlgorithms, pointOnPolygonContour) { + using namespace libnest2d; + + Rectangle input(10, 10); + + strategies::EdgeCache ecache(input); + + auto first = *input.begin(); + ASSERT_TRUE(getX(first) == getX(ecache.coords(0))); + ASSERT_TRUE(getY(first) == getY(ecache.coords(0))); + + auto last = *std::prev(input.end()); + ASSERT_TRUE(getX(last) == getX(ecache.coords(1.0))); + ASSERT_TRUE(getY(last) == getY(ecache.coords(1.0))); + + for(int i = 0; i <= 100; i++) { + auto v = ecache.coords(i*(0.01)); + ASSERT_TRUE(ShapeLike::touches(v, input.transformedShape())); + } +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index d130c837f..743b253e3 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -331,14 +331,20 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) { for(auto objinst : objptr->instances) { if(objinst) { Slic3r::TriangleMesh tmpmesh = rmesh; - objinst->transform_mesh(&tmpmesh); +// objinst->transform_mesh(&tmpmesh); ClipperLib::PolyNode pn; auto p = tmpmesh.convex_hull(); p.make_clockwise(); p.append(p.first_point()); pn.Contour = Slic3rMultiPoint_to_ClipperPath( p ); - ret.emplace_back(objinst, Item(std::move(pn))); + Item item(std::move(pn)); + item.rotation(objinst->rotation); + item.translation( { + ClipperLib::cInt(objinst->offset.x/SCALING_FACTOR), + ClipperLib::cInt(objinst->offset.y/SCALING_FACTOR) + }); + ret.emplace_back(objinst, item); } } } @@ -407,7 +413,7 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // std::cout << "}" << std::endl; // return true; - double area = 0; + bool hasbin = bb != nullptr && bb->defined; double area_max = 0; Item *biggest = nullptr; @@ -416,24 +422,25 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, std::vector> shapes; shapes.reserve(shapemap.size()); std::for_each(shapemap.begin(), shapemap.end(), - [&shapes, &area, min_obj_distance, &area_max, &biggest] + [&shapes, min_obj_distance, &area_max, &biggest,hasbin] (ShapeData2D::value_type& it) { - Item& item = it.second; - item.addOffset(min_obj_distance); - auto b = ShapeLike::boundingBox(item.transformedShape()); - auto a = b.width()*b.height(); - if(area_max < a) { - area_max = static_cast(a); - biggest = &item; + if(!hasbin) { + Item& item = it.second; + item.addOffset(min_obj_distance); + auto b = ShapeLike::boundingBox(item.transformedShape()); + auto a = b.width()*b.height(); + if(area_max < a) { + area_max = static_cast(a); + biggest = &item; + } } - area += b.width()*b.height(); shapes.push_back(std::ref(it.second)); }); Box bin; - if(bb != nullptr && bb->defined) { + if(hasbin) { // Scale up the bounding box to clipper scale. BoundingBoxf bbb = *bb; bbb.scale(1.0/SCALING_FACTOR); @@ -456,8 +463,13 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, using Arranger = Arranger; Arranger::PlacementConfig pcfg; - pcfg.alignment = Arranger::PlacementConfig::Alignment::BOTTOM_LEFT; - Arranger arranger(bin, min_obj_distance, pcfg); + Arranger::SelectionConfig scfg; + + scfg.try_reverse_order = false; + scfg.force_parallel = true; + pcfg.alignment = Arranger::PlacementConfig::Alignment::CENTER; + Arranger arranger(bin, min_obj_distance, pcfg, scfg); + arranger.useMinimumBoundigBoxRotation(); std::cout << "Arranging model..." << std::endl; bench.start(); @@ -483,18 +495,15 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // Get the tranformation data from the item object and scale it // appropriately - Radians rot = item.rotation(); auto off = item.translation(); - Pointf foff(off.X*SCALING_FACTOR + batch_offset, + Radians rot = item.rotation(); + Pointf foff(off.X*SCALING_FACTOR, off.Y*SCALING_FACTOR); // write the tranformation data into the model instance - inst_ptr->rotation += rot; - inst_ptr->offset += foff; - - // Debug - /*std::cout << "item " << idx << ": \n" << "\toffset_x: " - * << foff.x << "\n\toffset_y: " << foff.y << std::endl;*/ + inst_ptr->rotation = rot; + inst_ptr->offset = foff; + inst_ptr->offset_z = -batch_offset; } }; @@ -503,14 +512,23 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, if(first_bin_only) { applyResult(result.front(), 0); } else { + + const auto STRIDE_PADDING = 1.2; + const auto MIN_STRIDE = 100; + + auto h = STRIDE_PADDING * model.bounding_box().size().z; + h = h < MIN_STRIDE ? MIN_STRIDE : h; + Coord stride = static_cast(h); + Coord batch_offset = 0; + for(auto& group : result) { applyResult(group, batch_offset); // Only the first pack group can be placed onto the print bed. The // other objects which could not fit will be placed next to the // print bed - batch_offset += static_cast(2*bin.width()*SCALING_FACTOR); + batch_offset += stride; } } bench.stop(); @@ -1236,7 +1254,7 @@ void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) cons mesh->rotate_z(this->rotation); // rotate around mesh origin mesh->scale(this->scaling_factor); // scale around mesh origin if (!dont_translate) - mesh->translate(this->offset.x, this->offset.y, 0); + mesh->translate(this->offset.x, this->offset.y, this->offset_z); } BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mesh, bool dont_translate) const diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index 8b63c3641..42b0a9edb 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -201,8 +201,9 @@ public: double rotation; // Rotation around the Z axis, in radians around mesh center point double scaling_factor; Pointf offset; // in unscaled coordinates + double offset_z = 0; - ModelObject* get_object() const { return this->object; }; + ModelObject* get_object() const { return this->object; } // To be called on an external mesh void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 08802139d..ba720aa04 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -444,7 +444,7 @@ bool Print::apply_config(DynamicPrintConfig config) const ModelVolume &volume = *object->model_object()->volumes[volume_id]; if (this_region_config_set) { // If the new config for this volume differs from the other - // volume configs currently associated to this region, it means + // volume configs currently associated to this region, it means // the region subdivision does not make sense anymore. if (! this_region_config.equals(this->_region_config_from_model_volume(volume))) { rearrange_regions = true;