Merge commit '4e901a9db778660d3471a49cd95d66f85b2dbc88'

This commit is contained in:
bubnikv 2018-08-01 13:16:30 +02:00
commit 3e2aedaaf0
22 changed files with 1248 additions and 783 deletions

View File

@ -2,8 +2,6 @@ cmake_minimum_required(VERSION 2.8)
project(Libnest2D) project(Libnest2D)
enable_testing()
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
# Update if necessary # Update if necessary
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long ") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long ")
@ -32,6 +30,7 @@ set(LIBNEST2D_SRCFILES
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/geometry_traits.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/geometry_traits.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/common.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/common.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizer.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/metaloop.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/placer_boilerplate.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/placer_boilerplate.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/bottomleftplacer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/bottomleftplacer.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/nfpplacer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/nfpplacer.hpp
@ -60,8 +59,7 @@ if(LIBNEST2D_GEOMETRIES_BACKEND STREQUAL "clipper")
include_directories(BEFORE ${CLIPPER_INCLUDE_DIRS}) include_directories(BEFORE ${CLIPPER_INCLUDE_DIRS})
include_directories(${Boost_INCLUDE_DIRS}) include_directories(${Boost_INCLUDE_DIRS})
list(APPEND LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.cpp list(APPEND LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/boost_alg.hpp) ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/boost_alg.hpp)
list(APPEND LIBNEST2D_LIBRARIES ${CLIPPER_LIBRARIES}) list(APPEND LIBNEST2D_LIBRARIES ${CLIPPER_LIBRARIES})
list(APPEND LIBNEST2D_HEADERS ${CLIPPER_INCLUDE_DIRS} list(APPEND LIBNEST2D_HEADERS ${CLIPPER_INCLUDE_DIRS}
@ -81,22 +79,12 @@ if(LIBNEST2D_OPTIMIZER_BACKEND STREQUAL "nlopt")
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/subplex.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/subplex.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/genetic.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/genetic.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/nlopt_boilerplate.hpp) ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/nlopt_boilerplate.hpp)
list(APPEND LIBNEST2D_LIBRARIES ${NLopt_LIBS} list(APPEND LIBNEST2D_LIBRARIES ${NLopt_LIBS})
# Threads::Threads
)
list(APPEND LIBNEST2D_HEADERS ${NLopt_INCLUDE_DIR}) list(APPEND LIBNEST2D_HEADERS ${NLopt_INCLUDE_DIR})
endif() endif()
# Currently we are outsourcing the non-convex NFP implementation from
# libnfporb and it needs libgmp to work
#find_package(GMP)
#if(GMP_FOUND)
# list(APPEND LIBNEST2D_LIBRARIES ${GMP_LIBRARIES})
# list(APPEND LIBNEST2D_HEADERS ${GMP_INCLUDE_DIR})
# add_definitions(-DLIBNFP_USE_RATIONAL)
#endif()
if(LIBNEST2D_UNITTESTS) if(LIBNEST2D_UNITTESTS)
enable_testing()
add_subdirectory(tests) add_subdirectory(tests)
endif() endif()

View File

@ -1,35 +0,0 @@
# Try to find the GMP libraries:
# GMP_FOUND - System has GMP lib
# GMP_INCLUDE_DIR - The GMP include directory
# GMP_LIBRARIES - Libraries needed to use GMP
if (GMP_INCLUDE_DIR AND GMP_LIBRARIES)
# Force search at every time, in case configuration changes
unset(GMP_INCLUDE_DIR CACHE)
unset(GMP_LIBRARIES CACHE)
endif (GMP_INCLUDE_DIR AND GMP_LIBRARIES)
find_path(GMP_INCLUDE_DIR NAMES gmp.h)
if(WIN32)
find_library(GMP_LIBRARIES NAMES libgmp.a gmp gmp.lib mpir mpir.lib)
else(WIN32)
if(STBIN)
message(STATUS "STBIN: ${STBIN}")
find_library(GMP_LIBRARIES NAMES libgmp.a gmp)
else(STBIN)
find_library(GMP_LIBRARIES NAMES libgmp.so gmp)
endif(STBIN)
endif(WIN32)
if(GMP_INCLUDE_DIR AND GMP_LIBRARIES)
set(GMP_FOUND TRUE)
endif(GMP_INCLUDE_DIR AND GMP_LIBRARIES)
if(GMP_FOUND)
message(STATUS "Configured GMP: ${GMP_LIBRARIES}")
else(GMP_FOUND)
message(STATUS "Could NOT find GMP")
endif(GMP_FOUND)
mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARIES)

View File

@ -535,17 +535,18 @@ void arrangeRectangles() {
proba[0].rotate(Pi/3); proba[0].rotate(Pi/3);
proba[1].rotate(Pi-Pi/3); proba[1].rotate(Pi-Pi/3);
// std::vector<Item> input(25, Rectangle(70*SCALE, 10*SCALE));
std::vector<Item> input; std::vector<Item> input;
input.insert(input.end(), prusaParts().begin(), prusaParts().end()); input.insert(input.end(), prusaParts().begin(), prusaParts().end());
// input.insert(input.end(), prusaExParts().begin(), prusaExParts().end()); // input.insert(input.end(), prusaExParts().begin(), prusaExParts().end());
input.insert(input.end(), stegoParts().begin(), stegoParts().end()); // input.insert(input.end(), stegoParts().begin(), stegoParts().end());
// input.insert(input.end(), rects.begin(), rects.end()); // input.insert(input.end(), rects.begin(), rects.end());
input.insert(input.end(), proba.begin(), proba.end()); // input.insert(input.end(), proba.begin(), proba.end());
// input.insert(input.end(), crasher.begin(), crasher.end()); // input.insert(input.end(), crasher.begin(), crasher.end());
Box bin(250*SCALE, 210*SCALE); Box bin(250*SCALE, 210*SCALE);
Coord min_obj_distance = 6*SCALE; auto min_obj_distance = static_cast<Coord>(0*SCALE);
using Placer = NfpPlacer; using Placer = NfpPlacer;
using Packer = Arranger<Placer, FirstFitSelection>; using Packer = Arranger<Placer, FirstFitSelection>;
@ -554,21 +555,45 @@ void arrangeRectangles() {
Packer::PlacementConfig pconf; Packer::PlacementConfig pconf;
pconf.alignment = Placer::Config::Alignment::CENTER; pconf.alignment = Placer::Config::Alignment::CENTER;
pconf.starting_point = Placer::Config::Alignment::CENTER; pconf.starting_point = Placer::Config::Alignment::BOTTOM_LEFT;
pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/};
pconf.object_function = [&bin](Placer::Pile pile, double area,
double norm, double penality) { double norm_2 = std::nan("");
pconf.object_function = [&bin, &norm_2](Placer::Pile pile, const Item& item,
double /*area*/, double norm, double penality) {
using pl = PointLike;
auto bb = ShapeLike::boundingBox(pile); auto bb = ShapeLike::boundingBox(pile);
auto ibb = item.boundingBox();
auto minc = ibb.minCorner();
auto maxc = ibb.maxCorner();
auto& sh = pile.back(); if(std::isnan(norm_2)) norm_2 = pow(norm, 2);
auto rv = Nfp::referenceVertex(sh);
auto c = bin.center(); // We get the distance of the reference point from the center of the
auto d = PointLike::distance(rv, c); // heat bed
double score = double(d)/norm; auto cc = bb.center();
auto top_left = PointImpl{getX(minc), getY(maxc)};
auto bottom_right = PointImpl{getX(maxc), getY(minc)};
auto a = pl::distance(ibb.maxCorner(), cc);
auto b = pl::distance(ibb.minCorner(), cc);
auto c = pl::distance(ibb.center(), cc);
auto d = pl::distance(top_left, cc);
auto e = pl::distance(bottom_right, cc);
auto area = bb.width() * bb.height() / norm_2;
auto min_dist = std::min({a, b, c, d, e}) / norm;
// The score will be the normalized distance which will be minimized,
// effectively creating a circle shaped pile of items
double score = 0.8*min_dist + 0.2*area;
// If it does not fit into the print bed we will beat it // If it does not fit into the print bed we will beat it
// with a large penality // with a large penality. If we would not do this, there would be only
// one big pile that doesn't care whether it fits onto the print bed.
if(!NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score; if(!NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score;
return score; return score;
@ -577,7 +602,7 @@ void arrangeRectangles() {
Packer::SelectionConfig sconf; Packer::SelectionConfig sconf;
// sconf.allow_parallel = false; // sconf.allow_parallel = false;
// sconf.force_parallel = false; // sconf.force_parallel = false;
// sconf.try_triplets = false; // sconf.try_triplets = true;
// sconf.try_reverse_order = true; // sconf.try_reverse_order = true;
// sconf.waste_increment = 0.005; // sconf.waste_increment = 0.005;
@ -630,7 +655,7 @@ void arrangeRectangles() {
<< " %" << std::endl; << " %" << std::endl;
std::cout << "Bin usage: ("; std::cout << "Bin usage: (";
unsigned total = 0; size_t total = 0;
for(auto& r : result) { std::cout << r.size() << " "; total += r.size(); } for(auto& r : result) { std::cout << r.size() << " "; total += r.size(); }
std::cout << ") Total: " << total << std::endl; std::cout << ") Total: " << total << std::endl;
@ -643,9 +668,11 @@ void arrangeRectangles() {
<< input.size() - total << " elements!" << input.size() - total << " elements!"
<< std::endl; << std::endl;
svg::SVGWriter::Config conf; using SVGWriter = svg::SVGWriter<PolygonImpl>;
SVGWriter::Config conf;
conf.mm_in_coord_units = SCALE; conf.mm_in_coord_units = SCALE;
svg::SVGWriter svgw(conf); SVGWriter svgw(conf);
svgw.setSize(bin); svgw.setSize(bin);
svgw.writePackGroup(result); svgw.writePackGroup(result);
// std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);}); // std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);});

View File

@ -6,7 +6,7 @@
#include <libnest2d/clipper_backend/clipper_backend.hpp> #include <libnest2d/clipper_backend/clipper_backend.hpp>
// We include the stock optimizers for local and global optimization // We include the stock optimizers for local and global optimization
#include <libnest2d/optimizers/simplex.hpp> // Local subplex for NfpPlacer #include <libnest2d/optimizers/simplex.hpp> // Local simplex for NfpPlacer
#include <libnest2d/optimizers/genetic.hpp> // Genetic for min. bounding box #include <libnest2d/optimizers/genetic.hpp> // Genetic for min. bounding box
#include <libnest2d/libnest2d.hpp> #include <libnest2d/libnest2d.hpp>

View File

@ -8,8 +8,16 @@
#ifdef __clang__ #ifdef __clang__
#undef _MSC_EXTENSIONS #undef _MSC_EXTENSIONS
#endif #endif
#include <boost/geometry.hpp>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4244)
#pragma warning(disable: 4267)
#endif
#include <boost/geometry.hpp>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
// this should be removed to not confuse the compiler // this should be removed to not confuse the compiler
// #include <libnest2d.h> // #include <libnest2d.h>
@ -461,15 +469,6 @@ inline bp2d::Shapes Nfp::merge(const bp2d::Shapes& shapes,
} }
#endif #endif
//#ifndef DISABLE_BOOST_MINKOWSKI_ADD
//template<>
//inline PolygonImpl& Nfp::minkowskiAdd(PolygonImpl& sh,
// const PolygonImpl& /*other*/)
//{
// return sh;
//}
//#endif
#ifndef DISABLE_BOOST_SERIALIZE #ifndef DISABLE_BOOST_SERIALIZE
template<> inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>( template<> inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>(
const PolygonImpl& sh, double scale) const PolygonImpl& sh, double scale)

View File

@ -1,58 +0,0 @@
//#include "clipper_backend.hpp"
//#include <atomic>
//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());
// for(auto np : p->Childs) {
// paths.emplace_back(np->Contour);
// }
// }
// return paths;
// }
// ClipperLib::Paths& getHoles(PolygonImpl& p) {
// return _getHoles(&p);
// }
// const ClipperLib::Paths& getHoles(const PolygonImpl& p) {
// return _getHoles(&p);
// }
//};
//}
//HoleCache holeCache;
//}

View File

@ -21,7 +21,7 @@ struct PolygonImpl {
PathImpl Contour; PathImpl Contour;
HoleStore Holes; HoleStore Holes;
inline PolygonImpl() {} inline PolygonImpl() = default;
inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {} inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {}
inline explicit PolygonImpl(const HoleStore& holes): inline explicit PolygonImpl(const HoleStore& holes):
@ -66,6 +66,19 @@ inline PointImpl operator-(const PointImpl& p1, const PointImpl& p2) {
ret -= p2; ret -= p2;
return ret; return ret;
} }
inline PointImpl& operator *=(PointImpl& p, const PointImpl& pa ) {
p.X *= pa.X;
p.Y *= pa.Y;
return p;
}
inline PointImpl operator*(const PointImpl& p1, const PointImpl& p2) {
PointImpl ret = p1;
ret *= p2;
return ret;
}
} }
namespace libnest2d { namespace libnest2d {
@ -135,7 +148,7 @@ inline void ShapeLike::reserve(PolygonImpl& sh, size_t vertex_capacity)
namespace _smartarea { namespace _smartarea {
template<Orientation o> template<Orientation o>
inline double area(const PolygonImpl& sh) { inline double area(const PolygonImpl& /*sh*/) {
return std::nan(""); return std::nan("");
} }
@ -220,22 +233,6 @@ inline void ShapeLike::offset(PolygonImpl& sh, TCoord<PointImpl> distance) {
} }
} }
//template<> // TODO make it support holes if this method will ever be needed.
//inline PolygonImpl Nfp::minkowskiDiff(const PolygonImpl& sh,
// const PolygonImpl& other)
//{
// #define DISABLE_BOOST_MINKOWSKI_ADD
// ClipperLib::Paths solution;
// ClipperLib::MinkowskiDiff(sh.Contour, other.Contour, solution);
// PolygonImpl ret;
// ret.Contour = solution.front();
// return sh;
//}
// Tell libnest2d how to make string out of a ClipperPolygon object // Tell libnest2d how to make string out of a ClipperPolygon object
template<> inline std::string ShapeLike::toString(const PolygonImpl& sh) { template<> inline std::string ShapeLike::toString(const PolygonImpl& sh) {
std::stringstream ss; std::stringstream ss;
@ -406,35 +403,12 @@ inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads)
} }
#define DISABLE_BOOST_NFP_MERGE #define DISABLE_BOOST_NFP_MERGE
template<> inline Nfp::Shapes<PolygonImpl> inline Nfp::Shapes<PolygonImpl> _merge(ClipperLib::Clipper& clipper) {
Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes, const PolygonImpl& sh)
{
Nfp::Shapes<PolygonImpl> retv; Nfp::Shapes<PolygonImpl> retv;
ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);
bool closed = true;
bool valid = false;
valid = clipper.AddPath(sh.Contour, ClipperLib::ptSubject, closed);
for(auto& hole : sh.Holes) {
valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed);
}
for(auto& path : shapes) {
valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
for(auto& hole : path.Holes) {
valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed);
}
}
if(!valid) throw GeometryException(GeomErr::MERGE);
ClipperLib::PolyTree result; ClipperLib::PolyTree result;
clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero); clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative);
retv.reserve(result.Total()); retv.reserve(static_cast<size_t>(result.Total()));
std::function<void(ClipperLib::PolyNode*, PolygonImpl&)> processHole; std::function<void(ClipperLib::PolyNode*, PolygonImpl&)> processHole;
@ -445,7 +419,8 @@ Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes, const PolygonImpl& sh)
retv.push_back(poly); retv.push_back(poly);
}; };
processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly) { processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly)
{
poly.Holes.push_back(pptr->Contour); poly.Holes.push_back(pptr->Contour);
poly.Holes.back().push_back(poly.Holes.back().front()); poly.Holes.back().push_back(poly.Holes.back().front());
for(auto c : pptr->Childs) processPoly(c); for(auto c : pptr->Childs) processPoly(c);
@ -463,6 +438,27 @@ Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes, const PolygonImpl& sh)
return retv; return retv;
} }
template<> inline Nfp::Shapes<PolygonImpl>
Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes)
{
ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);
bool closed = true;
bool valid = true;
for(auto& path : shapes) {
valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
for(auto& hole : path.Holes) {
valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed);
}
}
if(!valid) throw GeometryException(GeomErr::MERGE);
return _merge(clipper);
}
} }
//#define DISABLE_BOOST_SERIALIZE //#define DISABLE_BOOST_SERIALIZE

View File

@ -13,6 +13,7 @@
#if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L #if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L
#define BP2D_NOEXCEPT #define BP2D_NOEXCEPT
#define BP2D_CONSTEXPR #define BP2D_CONSTEXPR
#define BP2D_COMPILER_MSVC12
#elif __cplusplus >= 201103L #elif __cplusplus >= 201103L
#define BP2D_NOEXCEPT noexcept #define BP2D_NOEXCEPT noexcept
#define BP2D_CONSTEXPR constexpr #define BP2D_CONSTEXPR constexpr
@ -84,44 +85,6 @@ struct invoke_result {
template<class F, class...Args> template<class F, class...Args>
using invoke_result_t = typename invoke_result<F, Args...>::type; using invoke_result_t = typename invoke_result<F, Args...>::type;
/* ************************************************************************** */
/* C++14 std::index_sequence implementation: */
/* ************************************************************************** */
/**
* \brief C++11 conformant implementation of the index_sequence type from C++14
*/
template<size_t...Ints> 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<size_t...Nseq> struct genSeq;
// Recursive template to generate the list
template<size_t I, size_t...Nseq> struct genSeq<I, Nseq...> {
// Type will contain a genSeq with Nseq appended by one element
using Type = typename genSeq< I - 1, I - 1, Nseq...>::Type;
};
// Terminating recursion
template <size_t ... Nseq> struct genSeq<0, Nseq...> {
// If I is zero, Type will contain index_sequence with the fuly generated
// integer list.
using Type = index_sequence<Nseq...>;
};
/// Helper alias to make an index sequence from 0 to N
template<size_t N> using make_index_sequence = typename genSeq<N>::Type;
/// Helper alias to make an index sequence for a parameter pack
template<class...Args>
using index_sequence_for = make_index_sequence<sizeof...(Args)>;
/* ************************************************************************** */
/** /**
* A useful little tool for triggering static_assert error messages e.g. when * A useful little tool for triggering static_assert error messages e.g. when
* a mandatory template specialization (implementation) is missing. * a mandatory template specialization (implementation) is missing.
@ -229,7 +192,7 @@ public:
GeomErr errcode() const { return errcode_; } GeomErr errcode() const { return errcode_; }
virtual const char * what() const BP2D_NOEXCEPT override { const char * what() const BP2D_NOEXCEPT override {
return errorstr(errcode_).c_str(); return errorstr(errcode_).c_str();
} }
}; };

View File

@ -68,7 +68,7 @@ class _Box: PointPair<RawPoint> {
using PointPair<RawPoint>::p2; using PointPair<RawPoint>::p2;
public: public:
inline _Box() {} inline _Box() = default;
inline _Box(const RawPoint& p, const RawPoint& pp): inline _Box(const RawPoint& p, const RawPoint& pp):
PointPair<RawPoint>({p, pp}) {} PointPair<RawPoint>({p, pp}) {}
@ -97,7 +97,7 @@ class _Segment: PointPair<RawPoint> {
mutable Radians angletox_ = std::nan(""); mutable Radians angletox_ = std::nan("");
public: public:
inline _Segment() {} inline _Segment() = default;
inline _Segment(const RawPoint& p, const RawPoint& pp): inline _Segment(const RawPoint& p, const RawPoint& pp):
PointPair<RawPoint>({p, pp}) {} PointPair<RawPoint>({p, pp}) {}
@ -188,7 +188,7 @@ struct PointLike {
if( (y < y1 && y < y2) || (y > y1 && y > y2) ) if( (y < y1 && y < y2) || (y > y1 && y > y2) )
return {0, false}; return {0, false};
else if ((y == y1 && y == y2) && (x > x1 && x > x2)) if ((y == y1 && y == y2) && (x > x1 && x > x2))
ret = std::min( x-x1, x -x2); ret = std::min( x-x1, x -x2);
else if( (y == y1 && y == y2) && (x < x1 && x < x2)) else if( (y == y1 && y == y2) && (x < x1 && x < x2))
ret = -std::min(x1 - x, x2 - x); ret = -std::min(x1 - x, x2 - x);
@ -214,7 +214,7 @@ struct PointLike {
if( (x < x1 && x < x2) || (x > x1 && x > x2) ) if( (x < x1 && x < x2) || (x > x1 && x > x2) )
return {0, false}; return {0, false};
else if ((x == x1 && x == x2) && (y > y1 && y > y2)) if ((x == x1 && x == x2) && (y > y1 && y > y2))
ret = std::min( y-y1, y -y2); ret = std::min( y-y1, y -y2);
else if( (x == x1 && x == x2) && (y < y1 && y < y2)) else if( (x == x1 && x == x2) && (y < y1 && y < y2))
ret = -std::min(y1 - y, y2 - y); ret = -std::min(y1 - y, y2 - y);
@ -329,7 +329,7 @@ enum class Formats {
}; };
// This struct serves as a namespace. The only difference is that it can be // This struct serves as a namespace. The only difference is that it can be
// used in friend declarations. // used in friend declarations and can be aliased at class scope.
struct ShapeLike { struct ShapeLike {
template<class RawShape> template<class RawShape>
@ -361,6 +361,51 @@ struct ShapeLike {
return create<RawShape>(contour, {}); return create<RawShape>(contour, {});
} }
template<class RawShape>
static THolesContainer<RawShape>& holes(RawShape& /*sh*/)
{
static THolesContainer<RawShape> empty;
return empty;
}
template<class RawShape>
static const THolesContainer<RawShape>& holes(const RawShape& /*sh*/)
{
static THolesContainer<RawShape> empty;
return empty;
}
template<class RawShape>
static TContour<RawShape>& getHole(RawShape& sh, unsigned long idx)
{
return holes(sh)[idx];
}
template<class RawShape>
static const TContour<RawShape>& getHole(const RawShape& sh,
unsigned long idx)
{
return holes(sh)[idx];
}
template<class RawShape>
static size_t holeCount(const RawShape& sh)
{
return holes(sh).size();
}
template<class RawShape>
static TContour<RawShape>& getContour(RawShape& sh)
{
return sh;
}
template<class RawShape>
static const TContour<RawShape>& getContour(const RawShape& sh)
{
return sh;
}
// Optional, does nothing by default // Optional, does nothing by default
template<class RawShape> template<class RawShape>
static void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {} static void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {}
@ -402,7 +447,7 @@ struct ShapeLike {
} }
template<Formats, class RawShape> template<Formats, class RawShape>
static std::string serialize(const RawShape& /*sh*/, double scale=1) static std::string serialize(const RawShape& /*sh*/, double /*scale*/=1)
{ {
static_assert(always_false<RawShape>::value, static_assert(always_false<RawShape>::value,
"ShapeLike::serialize() unimplemented!"); "ShapeLike::serialize() unimplemented!");
@ -498,51 +543,6 @@ struct ShapeLike {
return RawShape(); return RawShape();
} }
template<class RawShape>
static THolesContainer<RawShape>& holes(RawShape& /*sh*/)
{
static THolesContainer<RawShape> empty;
return empty;
}
template<class RawShape>
static const THolesContainer<RawShape>& holes(const RawShape& /*sh*/)
{
static THolesContainer<RawShape> empty;
return empty;
}
template<class RawShape>
static TContour<RawShape>& getHole(RawShape& sh, unsigned long idx)
{
return holes(sh)[idx];
}
template<class RawShape>
static const TContour<RawShape>& getHole(const RawShape& sh,
unsigned long idx)
{
return holes(sh)[idx];
}
template<class RawShape>
static size_t holeCount(const RawShape& sh)
{
return holes(sh).size();
}
template<class RawShape>
static TContour<RawShape>& getContour(RawShape& sh)
{
return sh;
}
template<class RawShape>
static const TContour<RawShape>& getContour(const RawShape& sh)
{
return sh;
}
template<class RawShape> template<class RawShape>
static void rotate(RawShape& /*sh*/, const Radians& /*rads*/) static void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
{ {
@ -621,14 +621,12 @@ struct ShapeLike {
} }
template<class RawShape> template<class RawShape>
static double area(const Shapes<RawShape>& shapes) static inline double area(const Shapes<RawShape>& shapes)
{ {
double ret = 0; return std::accumulate(shapes.begin(), shapes.end(), 0.0,
std::accumulate(shapes.first(), shapes.end(), [](double a, const RawShape& b) {
[](const RawShape& a, const RawShape& b) { return a += area(b);
return area(a) + area(b);
}); });
return ret;
} }
template<class RawShape> // Potential O(1) implementation may exist template<class RawShape> // Potential O(1) implementation may exist

View File

@ -3,7 +3,9 @@
#include "geometry_traits.hpp" #include "geometry_traits.hpp"
#include <algorithm> #include <algorithm>
#include <functional>
#include <vector> #include <vector>
#include <iterator>
namespace libnest2d { namespace libnest2d {
@ -23,64 +25,22 @@ struct Nfp {
template<class RawShape> template<class RawShape>
using Shapes = typename ShapeLike::Shapes<RawShape>; using Shapes = typename ShapeLike::Shapes<RawShape>;
/// Minkowski addition (not used yet) /**
* Merge a bunch of polygons with the specified additional polygon.
*
* \tparam RawShape the Polygon data type.
* \param shc The pile of polygons that will be unified with sh.
* \param sh A single polygon to unify with shc.
*
* \return A set of polygons that is the union of the input polygons. Note that
* mostly it will be a set containing only one big polygon but if the input
* polygons are disjuct than the resulting set will contain more polygons.
*/
template<class RawShape> template<class RawShape>
static RawShape minkowskiDiff(const RawShape& sh, const RawShape& cother) static Shapes<RawShape> merge(const Shapes<RawShape>& /*shc*/)
{ {
using Vertex = TPoint<RawShape>; static_assert(always_false<RawShape>::value,
//using Coord = TCoord<Vertex>; "Nfp::merge(shapes, shape) unimplemented!");
using Edge = _Segment<Vertex>;
using sl = ShapeLike;
using std::signbit;
// Copy the orbiter (controur only), we will have to work on it
RawShape orbiter = sl::create(sl::getContour(cother));
// Make the orbiter reverse oriented
for(auto &v : sl::getContour(orbiter)) v = -v;
// An egde with additional data for marking it
struct MarkedEdge { Edge e; Radians turn_angle; bool is_turning_point; };
// Container for marked edges
using EdgeList = std::vector<MarkedEdge>;
EdgeList A, B;
auto fillEdgeList = [](EdgeList& L, const RawShape& poly) {
L.reserve(sl::contourVertexCount(poly));
auto it = sl::cbegin(poly);
auto nextit = std::next(it);
L.emplace_back({Edge(*it, *nextit), 0, false});
it++; nextit++;
while(nextit != sl::cend(poly)) {
Edge e(*it, *nextit);
auto& L_prev = L.back();
auto phi = L_prev.e.angleToXaxis();
auto phi_prev = e.angleToXaxis();
auto turn_angle = phi-phi_prev;
if(turn_angle > Pi) turn_angle -= 2*Pi;
L.emplace_back({
e,
turn_angle,
signbit(turn_angle) != signbit(L_prev.turn_angle)
});
it++; nextit++;
}
L.front().turn_angle = L.front().e.angleToXaxis() -
L.back().e.angleToXaxis();
if(L.front().turn_angle > Pi) L.front().turn_angle -= 2*Pi;
};
fillEdgeList(A, sh);
fillEdgeList(B, orbiter);
return sh;
} }
/** /**
@ -95,10 +55,12 @@ static RawShape minkowskiDiff(const RawShape& sh, const RawShape& cother)
* polygons are disjuct than the resulting set will contain more polygons. * polygons are disjuct than the resulting set will contain more polygons.
*/ */
template<class RawShape> template<class RawShape>
static Shapes<RawShape> merge(const Shapes<RawShape>& shc, const RawShape& sh) static Shapes<RawShape> merge(const Shapes<RawShape>& shc,
const RawShape& sh)
{ {
static_assert(always_false<RawShape>::value, auto m = merge(shc);
"Nfp::merge(shapes, shape) unimplemented!"); m.push_back(sh);
return merge(m);
} }
/** /**
@ -139,16 +101,20 @@ template<class RawShape>
static TPoint<RawShape> rightmostUpVertex(const RawShape& sh) static TPoint<RawShape> rightmostUpVertex(const RawShape& sh)
{ {
// find min x and min y vertex // find max x and max y vertex
auto it = std::max_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh), auto it = std::max_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh),
_vsort<RawShape>); _vsort<RawShape>);
return *it; return *it;
} }
template<class RawShape>
using NfpResult = std::pair<RawShape, TPoint<RawShape>>;
/// Helper function to get the NFP /// Helper function to get the NFP
template<NfpLevel nfptype, class RawShape> template<NfpLevel nfptype, class RawShape>
static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) static NfpResult<RawShape> noFitPolygon(const RawShape& sh,
const RawShape& other)
{ {
NfpImpl<RawShape, nfptype> nfp; NfpImpl<RawShape, nfptype> nfp;
return nfp(sh, other); return nfp(sh, other);
@ -167,44 +133,46 @@ static RawShape noFitPolygon(const RawShape& sh, const RawShape& other)
* \tparam RawShape the Polygon data type. * \tparam RawShape the Polygon data type.
* \param sh The stationary polygon * \param sh The stationary polygon
* \param cother The orbiting polygon * \param cother The orbiting polygon
* \return Returns the NFP of the two input polygons which have to be strictly * \return Returns a pair of the NFP and its reference vertex of the two input
* convex. The resulting NFP is proven to be convex as well in this case. * polygons which have to be strictly convex. The resulting NFP is proven to be
* convex as well in this case.
* *
*/ */
template<class RawShape> template<class RawShape>
static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother) static NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
const RawShape& other)
{ {
using Vertex = TPoint<RawShape>; using Edge = _Segment<Vertex>; using Vertex = TPoint<RawShape>; using Edge = _Segment<Vertex>;
using sl = ShapeLike;
RawShape other = cother;
// Make the other polygon counter-clockwise
std::reverse(ShapeLike::begin(other), ShapeLike::end(other));
RawShape rsh; // Final nfp placeholder RawShape rsh; // Final nfp placeholder
Vertex top_nfp;
std::vector<Edge> edgelist; std::vector<Edge> edgelist;
auto cap = ShapeLike::contourVertexCount(sh) + auto cap = sl::contourVertexCount(sh) + sl::contourVertexCount(other);
ShapeLike::contourVertexCount(other);
// Reserve the needed memory // Reserve the needed memory
edgelist.reserve(cap); edgelist.reserve(cap);
ShapeLike::reserve(rsh, static_cast<unsigned long>(cap)); sl::reserve(rsh, static_cast<unsigned long>(cap));
{ // place all edges from sh into edgelist { // place all edges from sh into edgelist
auto first = ShapeLike::cbegin(sh); auto first = sl::cbegin(sh);
auto next = first + 1; auto next = std::next(first);
auto endit = ShapeLike::cend(sh);
while(next != endit) edgelist.emplace_back(*(first++), *(next++)); while(next != sl::cend(sh)) {
edgelist.emplace_back(*(first), *(next));
++first; ++next;
}
} }
{ // place all edges from other into edgelist { // place all edges from other into edgelist
auto first = ShapeLike::cbegin(other); auto first = sl::cbegin(other);
auto next = first + 1; auto next = std::next(first);
auto endit = ShapeLike::cend(other);
while(next != endit) edgelist.emplace_back(*(first++), *(next++)); while(next != sl::cend(other)) {
edgelist.emplace_back(*(next), *(first));
++first; ++next;
}
} }
// Sort the edges by angle to X axis. // Sort the edges by angle to X axis.
@ -215,10 +183,16 @@ static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother)
}); });
// Add the two vertices from the first edge into the final polygon. // Add the two vertices from the first edge into the final polygon.
ShapeLike::addVertex(rsh, edgelist.front().first()); sl::addVertex(rsh, edgelist.front().first());
ShapeLike::addVertex(rsh, edgelist.front().second()); sl::addVertex(rsh, edgelist.front().second());
auto tmp = std::next(ShapeLike::begin(rsh)); // Sorting function for the nfp reference vertex search
auto& cmp = _vsort<RawShape>;
// the reference (rightmost top) vertex so far
top_nfp = *std::max_element(sl::cbegin(rsh), sl::cend(rsh), cmp );
auto tmp = std::next(sl::begin(rsh));
// Construct final nfp by placing each edge to the end of the previous // Construct final nfp by placing each edge to the end of the previous
for(auto eit = std::next(edgelist.begin()); for(auto eit = std::next(edgelist.begin());
@ -226,56 +200,325 @@ static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother)
++eit) ++eit)
{ {
auto d = *tmp - eit->first(); auto d = *tmp - eit->first();
auto p = eit->second() + d; Vertex p = eit->second() + d;
ShapeLike::addVertex(rsh, p); sl::addVertex(rsh, p);
// Set the new reference vertex
if(cmp(top_nfp, p)) top_nfp = p;
tmp = std::next(tmp); tmp = std::next(tmp);
} }
// Now we have an nfp somewhere in the dark. We need to get it return {rsh, top_nfp};
// to the right position around the stationary shape. }
// This is done by choosing the leftmost lowest vertex of the
// orbiting polygon to be touched with the rightmost upper
// vertex of the stationary polygon. In this configuration, the
// reference vertex of the orbiting polygon (which can be dragged around
// the nfp) will be its rightmost upper vertex that coincides with the
// rightmost upper vertex of the nfp. No proof provided other than Jonas
// Lindmark's reasoning about the reference vertex of nfp in his thesis
// ("No fit polygon problem" - section 2.1.9)
// TODO: dont do this here. Cache the rmu and lmd in Item and get translate template<class RawShape>
// the nfp after this call static NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
const RawShape& cother)
{
auto csh = sh; // Copy sh, we will sort the verices in the copy // Algorithms are from the original algorithm proposed in paper:
auto& cmp = _vsort<RawShape>; // https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf
std::sort(ShapeLike::begin(csh), ShapeLike::end(csh), cmp);
std::sort(ShapeLike::begin(other), ShapeLike::end(other), cmp);
// leftmost lower vertex of the stationary polygon // /////////////////////////////////////////////////////////////////////////
auto& touch_sh = *(std::prev(ShapeLike::end(csh))); // Algorithm 1: Obtaining the minkowski sum
// rightmost upper vertex of the orbiting polygon // /////////////////////////////////////////////////////////////////////////
auto& touch_other = *(ShapeLike::begin(other));
// Calculate the difference and move the orbiter to the touch position. // I guess this is not a full minkowski sum of the two input polygons by
auto dtouch = touch_sh - touch_other; // definition. This yields a subset that is compatible with the next 2
auto top_other = *(std::prev(ShapeLike::end(other))) + dtouch; // algorithms.
// Get the righmost upper vertex of the nfp and move it to the RMU of using Result = NfpResult<RawShape>;
// the orbiter because they should coincide. using Vertex = TPoint<RawShape>;
auto&& top_nfp = rightmostUpVertex(rsh); using Coord = TCoord<Vertex>;
auto dnfp = top_other - top_nfp; using Edge = _Segment<Vertex>;
std::for_each(ShapeLike::begin(rsh), ShapeLike::end(rsh), using sl = ShapeLike;
[&dnfp](Vertex& v) { v+= dnfp; } ); using std::signbit;
using std::sort;
using std::vector;
using std::ref;
using std::reference_wrapper;
return rsh; // TODO The original algorithms expects the stationary polygon in
// counter clockwise and the orbiter in clockwise order.
// So for preventing any further complication, I will make the input
// the way it should be, than make my way around the orientations.
// Reverse the stationary contour to counter clockwise
auto stcont = sl::getContour(cstationary);
std::reverse(stcont.begin(), stcont.end());
RawShape stationary;
sl::getContour(stationary) = stcont;
// Reverse the orbiter contour to counter clockwise
auto orbcont = sl::getContour(cother);
std::reverse(orbcont.begin(), orbcont.end());
// Copy the orbiter (contour only), we will have to work on it
RawShape orbiter;
sl::getContour(orbiter) = orbcont;
// Step 1: Make the orbiter reverse oriented
for(auto &v : sl::getContour(orbiter)) v = -v;
// An egde with additional data for marking it
struct MarkedEdge {
Edge e; Radians turn_angle = 0; bool is_turning_point = false;
MarkedEdge() = default;
MarkedEdge(const Edge& ed, Radians ta, bool tp):
e(ed), turn_angle(ta), is_turning_point(tp) {}
};
// Container for marked edges
using EdgeList = vector<MarkedEdge>;
EdgeList A, B;
// This is how an edge list is created from the polygons
auto fillEdgeList = [](EdgeList& L, const RawShape& poly, int dir) {
L.reserve(sl::contourVertexCount(poly));
auto it = sl::cbegin(poly);
auto nextit = std::next(it);
double turn_angle = 0;
bool is_turn_point = false;
while(nextit != sl::cend(poly)) {
L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
it++; nextit++;
}
auto getTurnAngle = [](const Edge& e1, const Edge& e2) {
auto phi = e1.angleToXaxis();
auto phi_prev = e2.angleToXaxis();
auto TwoPi = 2.0*Pi;
if(phi > Pi) phi -= TwoPi;
if(phi_prev > Pi) phi_prev -= TwoPi;
auto turn_angle = phi-phi_prev;
if(turn_angle > Pi) turn_angle -= TwoPi;
return phi-phi_prev;
};
if(dir > 0) {
auto eit = L.begin();
auto enext = std::next(eit);
eit->turn_angle = getTurnAngle(L.front().e, L.back().e);
while(enext != L.end()) {
enext->turn_angle = getTurnAngle( enext->e, eit->e);
enext->is_turning_point =
signbit(enext->turn_angle) != signbit(eit->turn_angle);
++eit; ++enext;
}
L.front().is_turning_point = signbit(L.front().turn_angle) !=
signbit(L.back().turn_angle);
} else {
std::cout << L.size() << std::endl;
auto eit = L.rbegin();
auto enext = std::next(eit);
eit->turn_angle = getTurnAngle(L.back().e, L.front().e);
while(enext != L.rend()) {
enext->turn_angle = getTurnAngle(enext->e, eit->e);
enext->is_turning_point =
signbit(enext->turn_angle) != signbit(eit->turn_angle);
std::cout << enext->is_turning_point << " " << enext->turn_angle << std::endl;
++eit; ++enext;
}
L.back().is_turning_point = signbit(L.back().turn_angle) !=
signbit(L.front().turn_angle);
}
};
// Step 2: Fill the edgelists
fillEdgeList(A, stationary, 1);
fillEdgeList(B, orbiter, -1);
// A reference to a marked edge that also knows its container
struct MarkedEdgeRef {
reference_wrapper<MarkedEdge> eref;
reference_wrapper<vector<MarkedEdgeRef>> container;
Coord dir = 1; // Direction modifier
inline Radians angleX() const { return eref.get().e.angleToXaxis(); }
inline const Edge& edge() const { return eref.get().e; }
inline Edge& edge() { return eref.get().e; }
inline bool isTurningPoint() const {
return eref.get().is_turning_point;
}
inline bool isFrom(const vector<MarkedEdgeRef>& cont ) {
return &(container.get()) == &cont;
}
inline bool eq(const MarkedEdgeRef& mr) {
return &(eref.get()) == &(mr.eref.get());
}
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
reference_wrapper<vector<MarkedEdgeRef>> ec):
eref(er), container(ec), dir(1) {}
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
reference_wrapper<vector<MarkedEdgeRef>> ec,
Coord d):
eref(er), container(ec), dir(d) {}
};
using EdgeRefList = vector<MarkedEdgeRef>;
// Comparing two marked edges
auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) {
return e1.angleX() < e2.angleX();
};
EdgeRefList Aref, Bref; // We create containers for the references
Aref.reserve(A.size()); Bref.reserve(B.size());
// Fill reference container for the stationary polygon
std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) {
Aref.emplace_back( ref(me), ref(Aref) );
});
// Fill reference container for the orbiting polygon
std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) {
Bref.emplace_back( ref(me), ref(Bref) );
});
struct EdgeGroup { typename EdgeRefList::const_iterator first, last; };
auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure
(const EdgeGroup& Q, const EdgeGroup& R, bool positive)
{
// Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)"
// Sort the containers of edge references and merge them.
// Q could be sorted only once and be reused here but we would still
// need to merge it with sorted(R).
EdgeRefList merged;
EdgeRefList S, seq;
merged.reserve((Q.last - Q.first) + (R.last - R.first));
merged.insert(merged.end(), Q.first, Q.last);
merged.insert(merged.end(), R.first, R.last);
sort(merged.begin(), merged.end(), sortfn);
// Step 2 "set i = 1, k = 1, direction = 1, s1 = q1"
// we dont use i, instead, q is an iterator into Q. k would be an index
// into the merged sequence but we use "it" as an iterator for that
// here we obtain references for the containers for later comparisons
const auto& Rcont = R.first->container.get();
const auto& Qcont = Q.first->container.get();
// Set the intial direction
Coord dir = positive? 1 : -1;
// roughly i = 1 (so q = Q.first) and s1 = q1 so S[0] = q;
auto q = Q.first;
S.push_back(*q++);
// Roughly step 3
while(q != Q.last) {
auto it = merged.begin();
while(it != merged.end() && !(it->eq(*(Q.first))) ) {
if(it->isFrom(Rcont)) {
auto s = *it;
s.dir = dir;
S.push_back(s);
}
if(it->eq(*q)) {
S.push_back(*q);
if(it->isTurningPoint()) dir = -dir;
if(q != Q.first) it += dir;
}
else it += dir;
}
++q; // "Set i = i + 1"
}
// Step 4:
// "Let starting edge r1 be in position si in sequence"
// whaaat? I guess this means the following:
S[0] = *R.first;
auto it = S.begin();
// "Set j = 1, next = 2, direction = 1, seq1 = si"
// we dont use j, seq is expanded dynamically.
dir = 1; auto next = std::next(R.first);
// Step 5:
// "If all si edges have been allocated to seqj" should mean that
// we loop until seq has equal size with S
while(seq.size() < S.size()) {
++it; if(it == S.end()) it = S.begin();
if(it->isFrom(Qcont)) {
seq.push_back(*it); // "If si is from Q, j = j + 1, seqj = si"
// "If si is a turning point in Q,
// direction = - direction, next = next + direction"
if(it->isTurningPoint()) { dir = -dir; next += dir; }
}
if(it->eq(*next) && dir == next->dir) { // "If si = direction.rnext"
// "j = j + 1, seqj = si, next = next + direction"
seq.push_back(*it); next += dir;
}
}
return seq;
};
EdgeGroup R{ Bref.begin(), Bref.begin() }, Q{ Aref.begin(), Aref.end() };
auto it = Bref.begin();
bool orientation = true;
EdgeRefList seqlist;
seqlist.reserve(3*(Aref.size() + Bref.size()));
while(it != Bref.end()) // This is step 3 and step 4 in one loop
if(it->isTurningPoint()) {
R = {R.last, it++};
auto seq = mink(Q, R, orientation);
// TODO step 6 (should be 5 shouldn't it?): linking edges from A
// I don't get this step
seqlist.insert(seqlist.end(), seq.begin(), seq.end());
orientation = !orientation;
} else ++it;
if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true);
// /////////////////////////////////////////////////////////////////////////
// Algorithm 2: breaking Minkowski sums into track line trips
// /////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Algorithm 3: finding the boundary of the NFP from track line trips
// /////////////////////////////////////////////////////////////////////////
return Result(stationary, Vertex());
} }
// Specializable NFP implementation class. Specialize it if you have a faster // Specializable NFP implementation class. Specialize it if you have a faster
// or better NFP implementation // or better NFP implementation
template<class RawShape, NfpLevel nfptype> template<class RawShape, NfpLevel nfptype>
struct NfpImpl { struct NfpImpl {
RawShape operator()(const RawShape& sh, const RawShape& other) { NfpResult<RawShape> operator()(const RawShape& sh, const RawShape& other)
{
static_assert(nfptype == NfpLevel::CONVEX_ONLY, static_assert(nfptype == NfpLevel::CONVEX_ONLY,
"Nfp::noFitPolygon() unimplemented!"); "Nfp::noFitPolygon() unimplemented!");

View File

@ -9,6 +9,7 @@
#include <functional> #include <functional>
#include "geometry_traits.hpp" #include "geometry_traits.hpp"
#include "optimizer.hpp"
namespace libnest2d { namespace libnest2d {
@ -27,6 +28,7 @@ class _Item {
using Coord = TCoord<TPoint<RawShape>>; using Coord = TCoord<TPoint<RawShape>>;
using Vertex = TPoint<RawShape>; using Vertex = TPoint<RawShape>;
using Box = _Box<Vertex>; using Box = _Box<Vertex>;
using sl = ShapeLike;
// The original shape that gets encapsulated. // The original shape that gets encapsulated.
RawShape sh_; RawShape sh_;
@ -56,6 +58,13 @@ class _Item {
}; };
mutable Convexity convexity_ = Convexity::UNCHECKED; mutable Convexity convexity_ = Convexity::UNCHECKED;
mutable TVertexConstIterator<RawShape> rmt_; // rightmost top vertex
mutable TVertexConstIterator<RawShape> lmb_; // leftmost bottom vertex
mutable bool rmt_valid_ = false, lmb_valid_ = false;
mutable struct BBCache {
Box bb; bool valid; Vertex tr;
BBCache(): valid(false), tr(0, 0) {}
} bb_cache_;
public: public:
@ -104,15 +113,15 @@ public:
* @param il The initializer list of vertices. * @param il The initializer list of vertices.
*/ */
inline _Item(const std::initializer_list< Vertex >& il): inline _Item(const std::initializer_list< Vertex >& il):
sh_(ShapeLike::create<RawShape>(il)) {} sh_(sl::create<RawShape>(il)) {}
inline _Item(const TContour<RawShape>& contour, inline _Item(const TContour<RawShape>& contour,
const THolesContainer<RawShape>& holes = {}): const THolesContainer<RawShape>& holes = {}):
sh_(ShapeLike::create<RawShape>(contour, holes)) {} sh_(sl::create<RawShape>(contour, holes)) {}
inline _Item(TContour<RawShape>&& contour, inline _Item(TContour<RawShape>&& contour,
THolesContainer<RawShape>&& holes): THolesContainer<RawShape>&& holes):
sh_(ShapeLike::create<RawShape>(std::move(contour), sh_(sl::create<RawShape>(std::move(contour),
std::move(holes))) {} std::move(holes))) {}
/** /**
@ -122,31 +131,31 @@ public:
*/ */
inline std::string toString() const inline std::string toString() const
{ {
return ShapeLike::toString(sh_); return sl::toString(sh_);
} }
/// Iterator tho the first contour vertex in the polygon. /// Iterator tho the first contour vertex in the polygon.
inline Iterator begin() const inline Iterator begin() const
{ {
return ShapeLike::cbegin(sh_); return sl::cbegin(sh_);
} }
/// Alias to begin() /// Alias to begin()
inline Iterator cbegin() const inline Iterator cbegin() const
{ {
return ShapeLike::cbegin(sh_); return sl::cbegin(sh_);
} }
/// Iterator to the last contour vertex. /// Iterator to the last contour vertex.
inline Iterator end() const inline Iterator end() const
{ {
return ShapeLike::cend(sh_); return sl::cend(sh_);
} }
/// Alias to end() /// Alias to end()
inline Iterator cend() const inline Iterator cend() const
{ {
return ShapeLike::cend(sh_); return sl::cend(sh_);
} }
/** /**
@ -161,7 +170,7 @@ public:
*/ */
inline Vertex vertex(unsigned long idx) const inline Vertex vertex(unsigned long idx) const
{ {
return ShapeLike::vertex(sh_, idx); return sl::vertex(sh_, idx);
} }
/** /**
@ -176,7 +185,7 @@ public:
inline void setVertex(unsigned long idx, const Vertex& v ) inline void setVertex(unsigned long idx, const Vertex& v )
{ {
invalidateCache(); invalidateCache();
ShapeLike::vertex(sh_, idx) = v; sl::vertex(sh_, idx) = v;
} }
/** /**
@ -191,7 +200,7 @@ public:
double ret ; double ret ;
if(area_cache_valid_) ret = area_cache_; if(area_cache_valid_) ret = area_cache_;
else { else {
ret = ShapeLike::area(offsettedShape()); ret = sl::area(offsettedShape());
area_cache_ = ret; area_cache_ = ret;
area_cache_valid_ = true; area_cache_valid_ = true;
} }
@ -203,7 +212,7 @@ public:
switch(convexity_) { switch(convexity_) {
case Convexity::UNCHECKED: case Convexity::UNCHECKED:
ret = ShapeLike::isConvex<RawShape>(ShapeLike::getContour(transformedShape())); ret = sl::isConvex<RawShape>(sl::getContour(transformedShape()));
convexity_ = ret? Convexity::TRUE : Convexity::FALSE; convexity_ = ret? Convexity::TRUE : Convexity::FALSE;
break; break;
case Convexity::TRUE: ret = true; break; case Convexity::TRUE: ret = true; break;
@ -213,7 +222,7 @@ public:
return ret; return ret;
} }
inline bool isHoleConvex(unsigned holeidx) const { inline bool isHoleConvex(unsigned /*holeidx*/) const {
return false; return false;
} }
@ -223,11 +232,11 @@ public:
/// The number of the outer ring vertices. /// The number of the outer ring vertices.
inline size_t vertexCount() const { inline size_t vertexCount() const {
return ShapeLike::contourVertexCount(sh_); return sl::contourVertexCount(sh_);
} }
inline size_t holeCount() const { inline size_t holeCount() const {
return ShapeLike::holeCount(sh_); return sl::holeCount(sh_);
} }
/** /**
@ -235,36 +244,33 @@ public:
* @param p * @param p
* @return * @return
*/ */
inline bool isPointInside(const Vertex& p) inline bool isPointInside(const Vertex& p) const
{ {
return ShapeLike::isInside(p, sh_); return sl::isInside(p, transformedShape());
} }
inline bool isInside(const _Item& sh) const inline bool isInside(const _Item& sh) const
{ {
return ShapeLike::isInside(transformedShape(), sh.transformedShape()); return sl::isInside(transformedShape(), sh.transformedShape());
} }
inline bool isInside(const _Box<TPoint<RawShape>>& box); inline bool isInside(const _Box<TPoint<RawShape>>& box) const;
inline void translate(const Vertex& d) BP2D_NOEXCEPT inline void translate(const Vertex& d) BP2D_NOEXCEPT
{ {
translation_ += d; has_translation_ = true; translation(translation() + d);
tr_cache_valid_ = false;
} }
inline void rotate(const Radians& rads) BP2D_NOEXCEPT inline void rotate(const Radians& rads) BP2D_NOEXCEPT
{ {
rotation_ += rads; rotation(rotation() + rads);
has_rotation_ = true;
tr_cache_valid_ = false;
} }
inline void addOffset(Coord distance) BP2D_NOEXCEPT inline void addOffset(Coord distance) BP2D_NOEXCEPT
{ {
offset_distance_ = distance; offset_distance_ = distance;
has_offset_ = true; has_offset_ = true;
offset_cache_valid_ = false; invalidateCache();
} }
inline void removeOffset() BP2D_NOEXCEPT { inline void removeOffset() BP2D_NOEXCEPT {
@ -286,6 +292,8 @@ public:
{ {
if(rotation_ != rot) { if(rotation_ != rot) {
rotation_ = rot; has_rotation_ = true; tr_cache_valid_ = false; rotation_ = rot; has_rotation_ = true; tr_cache_valid_ = false;
rmt_valid_ = false; lmb_valid_ = false;
bb_cache_.valid = false;
} }
} }
@ -293,6 +301,7 @@ public:
{ {
if(translation_ != tr) { if(translation_ != tr) {
translation_ = tr; has_translation_ = true; tr_cache_valid_ = false; translation_ = tr; has_translation_ = true; tr_cache_valid_ = false;
bb_cache_.valid = false;
} }
} }
@ -301,9 +310,10 @@ public:
if(tr_cache_valid_) return tr_cache_; if(tr_cache_valid_) return tr_cache_;
RawShape cpy = offsettedShape(); RawShape cpy = offsettedShape();
if(has_rotation_) ShapeLike::rotate(cpy, rotation_); if(has_rotation_) sl::rotate(cpy, rotation_);
if(has_translation_) ShapeLike::translate(cpy, translation_); if(has_translation_) sl::translate(cpy, translation_);
tr_cache_ = cpy; tr_cache_valid_ = true; tr_cache_ = cpy; tr_cache_valid_ = true;
rmt_valid_ = false; lmb_valid_ = false;
return tr_cache_; return tr_cache_;
} }
@ -321,23 +331,53 @@ public:
inline void resetTransformation() BP2D_NOEXCEPT inline void resetTransformation() BP2D_NOEXCEPT
{ {
has_translation_ = false; has_rotation_ = false; has_offset_ = false; has_translation_ = false; has_rotation_ = false; has_offset_ = false;
invalidateCache();
} }
inline Box boundingBox() const { inline Box boundingBox() const {
return ShapeLike::boundingBox(transformedShape()); if(!bb_cache_.valid) {
bb_cache_.bb = sl::boundingBox(transformedShape());
bb_cache_.tr = {0, 0};
bb_cache_.valid = true;
}
auto &bb = bb_cache_.bb; auto &tr = bb_cache_.tr;
return {bb.minCorner() + tr, bb.maxCorner() + tr};
}
inline Vertex referenceVertex() const {
return rightmostTopVertex();
}
inline Vertex rightmostTopVertex() const {
if(!rmt_valid_ || !tr_cache_valid_) { // find max x and max y vertex
auto& tsh = transformedShape();
rmt_ = std::max_element(sl::cbegin(tsh), sl::cend(tsh), vsort);
rmt_valid_ = true;
}
return *rmt_;
}
inline Vertex leftmostBottomVertex() const {
if(!lmb_valid_ || !tr_cache_valid_) { // find min x and min y vertex
auto& tsh = transformedShape();
lmb_ = std::min_element(sl::cbegin(tsh), sl::cend(tsh), vsort);
lmb_valid_ = true;
}
return *lmb_;
} }
//Static methods: //Static methods:
inline static bool intersects(const _Item& sh1, const _Item& sh2) inline static bool intersects(const _Item& sh1, const _Item& sh2)
{ {
return ShapeLike::intersects(sh1.transformedShape(), return sl::intersects(sh1.transformedShape(),
sh2.transformedShape()); sh2.transformedShape());
} }
inline static bool touches(const _Item& sh1, const _Item& sh2) inline static bool touches(const _Item& sh1, const _Item& sh2)
{ {
return ShapeLike::touches(sh1.transformedShape(), return sl::touches(sh1.transformedShape(),
sh2.transformedShape()); sh2.transformedShape());
} }
@ -346,12 +386,11 @@ private:
inline const RawShape& offsettedShape() const { inline const RawShape& offsettedShape() const {
if(has_offset_ ) { if(has_offset_ ) {
if(offset_cache_valid_) return offset_cache_; if(offset_cache_valid_) return offset_cache_;
else {
offset_cache_ = sh_; offset_cache_ = sh_;
ShapeLike::offset(offset_cache_, offset_distance_); sl::offset(offset_cache_, offset_distance_);
offset_cache_valid_ = true; offset_cache_valid_ = true;
return offset_cache_; return offset_cache_;
}
} }
return sh_; return sh_;
} }
@ -359,10 +398,23 @@ private:
inline void invalidateCache() const BP2D_NOEXCEPT inline void invalidateCache() const BP2D_NOEXCEPT
{ {
tr_cache_valid_ = false; tr_cache_valid_ = false;
lmb_valid_ = false; rmt_valid_ = false;
area_cache_valid_ = false; area_cache_valid_ = false;
offset_cache_valid_ = false; offset_cache_valid_ = false;
bb_cache_.valid = false;
convexity_ = Convexity::UNCHECKED; convexity_ = Convexity::UNCHECKED;
} }
static inline bool vsort(const Vertex& v1, const Vertex& v2)
{
Coord &&x1 = getX(v1), &&x2 = getX(v2);
Coord &&y1 = getY(v1), &&y2 = getY(v2);
auto diff = y1 - y2;
if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
return x1 < x2;
return diff < 0;
}
}; };
/** /**
@ -370,7 +422,6 @@ private:
*/ */
template<class RawShape> template<class RawShape>
class _Rectangle: public _Item<RawShape> { class _Rectangle: public _Item<RawShape> {
RawShape sh_;
using _Item<RawShape>::vertex; using _Item<RawShape>::vertex;
using TO = Orientation; using TO = Orientation;
public: public:
@ -415,7 +466,7 @@ public:
}; };
template<class RawShape> template<class RawShape>
inline bool _Item<RawShape>::isInside(const _Box<TPoint<RawShape>>& box) { inline bool _Item<RawShape>::isInside(const _Box<TPoint<RawShape>>& box) const {
_Rectangle<RawShape> rect(box.width(), box.height()); _Rectangle<RawShape> rect(box.width(), box.height());
return _Item<RawShape>::isInside(rect); return _Item<RawShape>::isInside(rect);
} }
@ -874,9 +925,8 @@ private:
Radians findBestRotation(Item& item) { Radians findBestRotation(Item& item) {
opt::StopCriteria stopcr; opt::StopCriteria stopcr;
stopcr.stoplimit = 0.01; stopcr.absolute_score_difference = 0.01;
stopcr.max_iterations = 10000; stopcr.max_iterations = 10000;
stopcr.type = opt::StopLimitType::RELATIVE;
opt::TOptimizer<opt::Method::G_GENETIC> solver(stopcr); opt::TOptimizer<opt::Method::G_GENETIC> solver(stopcr);
auto orig_rot = item.rotation(); auto orig_rot = item.rotation();
@ -910,7 +960,6 @@ private:
if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) { if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) {
item.removeOffset(); item.removeOffset();
}); });
} }
}; };

View File

@ -0,0 +1,227 @@
#ifndef METALOOP_HPP
#define METALOOP_HPP
#include "common.hpp"
#include <tuple>
#include <functional>
namespace libnest2d {
/* ************************************************************************** */
/* C++14 std::index_sequence implementation: */
/* ************************************************************************** */
/**
* \brief C++11 conformant implementation of the index_sequence type from C++14
*/
template<size_t...Ints> 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<size_t...Nseq> struct genSeq;
// Recursive template to generate the list
template<size_t I, size_t...Nseq> struct genSeq<I, Nseq...> {
// Type will contain a genSeq with Nseq appended by one element
using Type = typename genSeq< I - 1, I - 1, Nseq...>::Type;
};
// Terminating recursion
template <size_t ... Nseq> struct genSeq<0, Nseq...> {
// If I is zero, Type will contain index_sequence with the fuly generated
// integer list.
using Type = index_sequence<Nseq...>;
};
/// Helper alias to make an index sequence from 0 to N
template<size_t N> using make_index_sequence = typename genSeq<N>::Type;
/// Helper alias to make an index sequence for a parameter pack
template<class...Args>
using index_sequence_for = make_index_sequence<sizeof...(Args)>;
/* ************************************************************************** */
namespace opt {
using std::forward;
using std::tuple;
using std::get;
using std::tuple_element;
/**
* @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<int N> using Int = std::integral_constant<int, N>;
/*
* 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<int N, class Fn> 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>(fn)) {}
template<class T> 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<T>(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 <typename Idx, class...Args>
class _MetaLoop {};
// Implementation for the first element of Args...
template <class...Args>
class _MetaLoop<Int<0>, Args...> {
public:
const static BP2D_CONSTEXPR int N = 0;
const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1;
template<class Tup, class Fn>
void run( Tup&& valtup, Fn&& fn) {
MapFn<ARGNUM-N, Fn> {forward<Fn>(fn)} (get<ARGNUM-N>(valtup));
}
};
// Implementation for the N-th element of Args...
template <int N, class...Args>
class _MetaLoop<Int<N>, Args...> {
public:
const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1;
template<class Tup, class Fn>
void run(Tup&& valtup, Fn&& fn) {
MapFn<ARGNUM-N, Fn> {forward<Fn>(fn)} (std::get<ARGNUM-N>(valtup));
// Recursive call to process the next element of Args
_MetaLoop<Int<N-1>, Args...> ().run(forward<Tup>(valtup),
forward<Fn>(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<class...Args>
using MetaLoop = _MetaLoop<Int<sizeof...(Args)-1>, 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(<the mapping function>, <arbitrary number of arguments of any type>);
* For example:
*
* struct mapfunc {
* template<class T> 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<class...Args, class Fn>
inline static void apply(Fn&& fn, Args&&...args) {
MetaLoop<Args...>().run(tuple<Args&&...>(forward<Args>(args)...),
forward<Fn>(fn));
}
/// The version of apply with a tuple rvalue reference.
template<class...Args, class Fn>
inline static void apply(Fn&& fn, tuple<Args...>&& tup) {
MetaLoop<Args...>().run(std::move(tup), forward<Fn>(fn));
}
/// The version of apply with a tuple lvalue reference.
template<class...Args, class Fn>
inline static void apply(Fn&& fn, tuple<Args...>& tup) {
MetaLoop<Args...>().run(tup, forward<Fn>(fn));
}
/// The version of apply with a tuple const reference.
template<class...Args, class Fn>
inline static void apply(Fn&& fn, const tuple<Args...>& tup) {
MetaLoop<Args...>().run(tup, forward<Fn>(fn));
}
/**
* Call a function with its arguments encapsualted in a tuple.
*/
template<class Fn, class Tup, std::size_t...Is>
inline static auto
callFunWithTuple(Fn&& fn, Tup&& tup, index_sequence<Is...>) ->
decltype(fn(std::get<Is>(tup)...))
{
return fn(std::get<Is>(tup)...);
}
};
}
}
#endif // METALOOP_HPP

View File

@ -10,8 +10,7 @@ namespace libnest2d { namespace opt {
using std::forward; using std::forward;
using std::tuple; using std::tuple;
using std::get; using std::make_tuple;
using std::tuple_element;
/// A Type trait for upper and lower limit of a numeric type. /// A Type trait for upper and lower limit of a numeric type.
template<class T, class B = void > template<class T, class B = void >
@ -51,176 +50,7 @@ inline Bound<T> bound(const T& min, const T& max) { return Bound<T>(min, max); }
template<class...Args> using Input = tuple<Args...>; template<class...Args> using Input = tuple<Args...>;
template<class...Args> template<class...Args>
inline tuple<Args...> initvals(Args...args) { return std::make_tuple(args...); } inline tuple<Args...> initvals(Args...args) { return 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<int N> using Int = std::integral_constant<int, N>;
/*
* 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<int N, class Fn> 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>(fn)) {}
template<class T> 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<T>(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 <typename Idx, class...Args>
class _MetaLoop {};
// Implementation for the first element of Args...
template <class...Args>
class _MetaLoop<Int<0>, Args...> {
public:
const static BP2D_CONSTEXPR int N = 0;
const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1;
template<class Tup, class Fn>
void run( Tup&& valtup, Fn&& fn) {
MapFn<ARGNUM-N, Fn> {forward<Fn>(fn)} (get<ARGNUM-N>(valtup));
}
};
// Implementation for the N-th element of Args...
template <int N, class...Args>
class _MetaLoop<Int<N>, Args...> {
public:
const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1;
template<class Tup, class Fn>
void run(Tup&& valtup, Fn&& fn) {
MapFn<ARGNUM-N, Fn> {forward<Fn>(fn)} (std::get<ARGNUM-N>(valtup));
// Recursive call to process the next element of Args
_MetaLoop<Int<N-1>, Args...> ().run(forward<Tup>(valtup),
forward<Fn>(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<class...Args>
using MetaLoop = _MetaLoop<Int<sizeof...(Args)-1>, 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(<the mapping function>, <arbitrary number of arguments of any type>);
* For example:
*
* struct mapfunc {
* template<class T> 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<class...Args, class Fn>
inline static void apply(Fn&& fn, Args&&...args) {
MetaLoop<Args...>().run(tuple<Args&&...>(forward<Args>(args)...),
forward<Fn>(fn));
}
/// The version of apply with a tuple rvalue reference.
template<class...Args, class Fn>
inline static void apply(Fn&& fn, tuple<Args...>&& tup) {
MetaLoop<Args...>().run(std::move(tup), forward<Fn>(fn));
}
/// The version of apply with a tuple lvalue reference.
template<class...Args, class Fn>
inline static void apply(Fn&& fn, tuple<Args...>& tup) {
MetaLoop<Args...>().run(tup, forward<Fn>(fn));
}
/// The version of apply with a tuple const reference.
template<class...Args, class Fn>
inline static void apply(Fn&& fn, const tuple<Args...>& tup) {
MetaLoop<Args...>().run(tup, forward<Fn>(fn));
}
/**
* Call a function with its arguments encapsualted in a tuple.
*/
template<class Fn, class Tup, std::size_t...Is>
inline static auto
callFunWithTuple(Fn&& fn, Tup&& tup, index_sequence<Is...>) ->
decltype(fn(std::get<Is>(tup)...))
{
return fn(std::get<Is>(tup)...);
}
};
/** /**
* @brief Specific optimization methods for which a default optimizer * @brief Specific optimization methods for which a default optimizer
@ -257,29 +87,20 @@ enum ResultCodes {
template<class...Args> template<class...Args>
struct Result { struct Result {
ResultCodes resultcode; ResultCodes resultcode;
std::tuple<Args...> optimum; tuple<Args...> optimum;
double score; 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. * @brief A type for specifying the stop criteria.
*/ */
struct StopCriteria { struct StopCriteria {
/// Relative or absolute termination error /// If the absolute value difference between two scores.
StopLimitType type = StopLimitType::RELATIVE; double absolute_score_difference = std::nan("");
/// The error value that is interpredted depending on the type property. /// If the relative value difference between two scores.
double stoplimit = 0.0001; double relative_score_difference = std::nan("");
unsigned max_iterations = 0; unsigned max_iterations = 0;
}; };
@ -310,11 +131,11 @@ public:
* \return Returns a Result<Args...> structure. * \return Returns a Result<Args...> structure.
* An example call would be: * An example call would be:
* auto result = opt.optimize_min( * auto result = opt.optimize_min(
* [](std::tuple<double> x) // object function * [](tuple<double> x) // object function
* { * {
* return std::pow(std::get<0>(x), 2); * return std::pow(std::get<0>(x), 2);
* }, * },
* std::make_tuple(-0.5), // initial value * make_tuple(-0.5), // initial value
* {-1.0, 1.0} // search space bounds * {-1.0, 1.0} // search space bounds
* ); * );
*/ */
@ -390,10 +211,14 @@ public:
static_assert(always_false<T>::value, "Optimizer unimplemented!"); static_assert(always_false<T>::value, "Optimizer unimplemented!");
} }
DummyOptimizer(const StopCriteria&) {
static_assert(always_false<T>::value, "Optimizer unimplemented!");
}
template<class Func, class...Args> template<class Func, class...Args>
Result<Args...> optimize(Func&& func, Result<Args...> optimize(Func&& /*func*/,
std::tuple<Args...> initvals, tuple<Args...> /*initvals*/,
Bound<Args>... args) Bound<Args>... /*args*/)
{ {
return Result<Args...>(); return Result<Args...>();
} }

View File

@ -1,15 +1,25 @@
#ifndef NLOPT_BOILERPLATE_HPP #ifndef NLOPT_BOILERPLATE_HPP
#define NLOPT_BOILERPLATE_HPP #define NLOPT_BOILERPLATE_HPP
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4244)
#pragma warning(disable: 4267)
#endif
#include <nlopt.hpp> #include <nlopt.hpp>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#include <libnest2d/optimizer.hpp> #include <libnest2d/optimizer.hpp>
#include <cassert> #include <cassert>
#include "libnest2d/metaloop.hpp"
#include <utility> #include <utility>
namespace libnest2d { namespace opt { namespace libnest2d { namespace opt {
nlopt::algorithm method2nloptAlg(Method m) { inline nlopt::algorithm method2nloptAlg(Method m) {
switch(m) { switch(m) {
case Method::L_SIMPLEX: return nlopt::LN_NELDERMEAD; case Method::L_SIMPLEX: return nlopt::LN_NELDERMEAD;
@ -87,7 +97,7 @@ protected:
template<class Fn, class...Args> template<class Fn, class...Args>
static double optfunc(const std::vector<double>& params, static double optfunc(const std::vector<double>& params,
std::vector<double>& grad, std::vector<double>& /*grad*/,
void *data) void *data)
{ {
auto fnptr = static_cast<remove_ref_t<Fn>*>(data); auto fnptr = static_cast<remove_ref_t<Fn>*>(data);
@ -132,12 +142,10 @@ protected:
default: ; default: ;
} }
switch(this->stopcr_.type) { auto abs_diff = stopcr_.absolute_score_difference;
case StopLimitType::ABSOLUTE: auto rel_diff = stopcr_.relative_score_difference;
opt_.set_ftol_abs(stopcr_.stoplimit); break; if(!std::isnan(abs_diff)) opt_.set_ftol_abs(abs_diff);
case StopLimitType::RELATIVE: if(!std::isnan(rel_diff)) opt_.set_ftol_rel(rel_diff);
opt_.set_ftol_rel(stopcr_.stoplimit); break;
}
if(this->stopcr_.max_iterations > 0) if(this->stopcr_.max_iterations > 0)
opt_.set_maxeval(this->stopcr_.max_iterations ); opt_.set_maxeval(this->stopcr_.max_iterations );

View File

@ -6,6 +6,10 @@
#endif #endif
#include "placer_boilerplate.hpp" #include "placer_boilerplate.hpp"
#include "../geometry_traits_nfp.hpp" #include "../geometry_traits_nfp.hpp"
#include "libnest2d/optimizer.hpp"
#include <cassert>
#include "tools/svgtools.hpp"
namespace libnest2d { namespace strategies { namespace libnest2d { namespace strategies {
@ -20,15 +24,62 @@ struct NfpPConfig {
TOP_RIGHT, TOP_RIGHT,
}; };
/// Which angles to try out for better results /// Which angles to try out for better results.
std::vector<Radians> rotations; std::vector<Radians> rotations;
/// Where to align the resulting packed pile /// Where to align the resulting packed pile.
Alignment alignment; Alignment alignment;
/// Where to start putting objects in the bin.
Alignment starting_point; Alignment starting_point;
std::function<double(const Nfp::Shapes<RawShape>&, double, double, double)> /**
* @brief A function object representing the fitting function in the
* placement optimization process. (Optional)
*
* This is the most versatile tool to configure the placer. The fitting
* function is evaluated many times when a new item is being placed into the
* bin. The output should be a rated score of the new item's position.
*
* This is not a mandatory option as there is a default fitting function
* that will optimize for the best pack efficiency. With a custom fitting
* function you can e.g. influence the shape of the arranged pile.
*
* \param shapes The first parameter is a container with all the placed
* polygons including the current candidate. You can calculate a bounding
* box or convex hull on this pile of polygons.
*
* \param item The second parameter is the candidate item. Note that
* calling transformedShape() on this second argument returns an identical
* shape as calling shapes.back(). These would not be the same objects only
* identical shapes! Using the second parameter is a lot faster due to
* caching some properties of the polygon (area, etc...)
*
* \param occupied_area The third parameter is the sum of areas of the
* items in the first parameter so you don't have to iterate through them
* if you only need their area.
*
* \param norm A norming factor for physical dimensions. E.g. if your score
* is the distance between the item and the bin center, you should divide
* that distance with the norming factor. If the score is an area than
* divide it with the square of the norming factor. Imagine it as a unit of
* distance.
*
* \param penality The fifth parameter is the amount of minimum penality if
* the arranged pile would't fit into the bin. You can use the wouldFit()
* function to check this. Note that the pile can be outside the bin's
* boundaries while the placement algorithm is running. Your job is only to
* check if the pile could be translated into a position in the bin where
* all the items would be inside. For a box shaped bin you can use the
* pile's bounding box to check whether it's width and height is small
* enough. If the pile would not fit, you have to make sure that the
* resulting score will be higher then the penality value. A good solution
* would be to set score = 2*penality-score in case the pile wouldn't fit
* into the bin.
*
*/
std::function<double(Nfp::Shapes<RawShape>&, const _Item<RawShape>&,
double, double, double)>
object_function; object_function;
/** /**
@ -38,11 +89,30 @@ struct NfpPConfig {
*/ */
float accuracy = 1.0; float accuracy = 1.0;
/**
* @brief If you want to see items inside other item's holes, you have to
* turn this switch on.
*
* This will only work if a suitable nfp implementation is provided.
* The library has no such implementation right now.
*/
bool explore_holes = false;
NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}), NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}),
alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {} alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {}
}; };
// A class for getting a point on the circumference of the polygon (in log time) /**
* A class for getting a point on the circumference of the polygon (in log time)
*
* This is a transformation of the provided polygon to be able to pinpoint
* locations on the circumference. The optimizer will pass a floating point
* value e.g. within <0,1> and we have to transform this value quickly into a
* coordinate on the circumference. By definition 0 should yield the first
* vertex and 1.0 would be the last (which should coincide with first).
*
* We also have to make this work for the holes of the captured polygon.
*/
template<class RawShape> class EdgeCache { template<class RawShape> class EdgeCache {
using Vertex = TPoint<RawShape>; using Vertex = TPoint<RawShape>;
using Coord = TCoord<Vertex>; using Coord = TCoord<Vertex>;
@ -93,18 +163,22 @@ template<class RawShape> class EdgeCache {
void fetchCorners() const { void fetchCorners() const {
if(!contour_.corners.empty()) return; if(!contour_.corners.empty()) return;
// TODO Accuracy contour_.corners.reserve(contour_.distances.size() / 3 + 1);
contour_.corners = contour_.distances; for(size_t i = 0; i < contour_.distances.size() - 1; i += 3) {
for(auto& d : contour_.corners) d /= contour_.full_distance; contour_.corners.emplace_back(
contour_.distances.at(i) / contour_.full_distance);
}
} }
void fetchHoleCorners(unsigned hidx) const { void fetchHoleCorners(unsigned hidx) const {
auto& hc = holes_[hidx]; auto& hc = holes_[hidx];
if(!hc.corners.empty()) return; if(!hc.corners.empty()) return;
// TODO Accuracy hc.corners.reserve(hc.distances.size() / 3 + 1);
hc.corners = hc.distances; for(size_t i = 0; i < hc.distances.size() - 1; i += 3) {
for(auto& d : hc.corners) d /= hc.full_distance; hc.corners.emplace_back(
hc.distances.at(i) / hc.full_distance);
}
} }
inline Vertex coords(const ContourCache& cache, double distance) const { inline Vertex coords(const ContourCache& cache, double distance) const {
@ -176,24 +250,64 @@ public:
return holes_[hidx].full_distance; return holes_[hidx].full_distance;
} }
/// Get the normalized distance values for each vertex
inline const std::vector<double>& corners() const BP2D_NOEXCEPT { inline const std::vector<double>& corners() const BP2D_NOEXCEPT {
fetchCorners(); fetchCorners();
return contour_.corners; return contour_.corners;
} }
/// corners for a specific hole
inline const std::vector<double>& inline const std::vector<double>&
corners(unsigned holeidx) const BP2D_NOEXCEPT { corners(unsigned holeidx) const BP2D_NOEXCEPT {
fetchHoleCorners(holeidx); fetchHoleCorners(holeidx);
return holes_[holeidx].corners; return holes_[holeidx].corners;
} }
inline unsigned holeCount() const BP2D_NOEXCEPT { return holes_.size(); } /// The number of holes in the abstracted polygon
inline size_t holeCount() const BP2D_NOEXCEPT { return holes_.size(); }
}; };
template<NfpLevel lvl> template<NfpLevel lvl>
struct Lvl { static const NfpLevel value = lvl; }; struct Lvl { static const NfpLevel value = lvl; };
template<class RawShape>
inline void correctNfpPosition(Nfp::NfpResult<RawShape>& nfp,
const _Item<RawShape>& stationary,
const _Item<RawShape>& orbiter)
{
// The provided nfp is somewhere in the dark. We need to get it
// to the right position around the stationary shape.
// This is done by choosing the leftmost lowest vertex of the
// orbiting polygon to be touched with the rightmost upper
// vertex of the stationary polygon. In this configuration, the
// reference vertex of the orbiting polygon (which can be dragged around
// the nfp) will be its rightmost upper vertex that coincides with the
// rightmost upper vertex of the nfp. No proof provided other than Jonas
// Lindmark's reasoning about the reference vertex of nfp in his thesis
// ("No fit polygon problem" - section 2.1.9)
auto touch_sh = stationary.rightmostTopVertex();
auto touch_other = orbiter.leftmostBottomVertex();
auto dtouch = touch_sh - touch_other;
auto top_other = orbiter.rightmostTopVertex() + dtouch;
auto dnfp = top_other - nfp.second; // nfp.second is the nfp reference point
ShapeLike::translate(nfp.first, dnfp);
}
template<class RawShape>
inline void correctNfpPosition(Nfp::NfpResult<RawShape>& nfp,
const RawShape& stationary,
const _Item<RawShape>& orbiter)
{
auto touch_sh = Nfp::rightmostUpVertex(stationary);
auto touch_other = orbiter.leftmostBottomVertex();
auto dtouch = touch_sh - touch_other;
auto top_other = orbiter.rightmostTopVertex() + dtouch;
auto dnfp = top_other - nfp.second;
ShapeLike::translate(nfp.first, dnfp);
}
template<class RawShape, class Container> template<class RawShape, class Container>
Nfp::Shapes<RawShape> nfp( const Container& polygons, Nfp::Shapes<RawShape> nfp( const Container& polygons,
const _Item<RawShape>& trsh, const _Item<RawShape>& trsh,
@ -203,18 +317,35 @@ Nfp::Shapes<RawShape> nfp( const Container& polygons,
Nfp::Shapes<RawShape> nfps; Nfp::Shapes<RawShape> nfps;
//int pi = 0;
for(Item& sh : polygons) { for(Item& sh : polygons) {
auto subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>( auto subnfp_r = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(
sh.transformedShape(), trsh.transformedShape()); sh.transformedShape(), trsh.transformedShape());
#ifndef NDEBUG #ifndef NDEBUG
auto vv = ShapeLike::isValid(sh.transformedShape()); auto vv = ShapeLike::isValid(sh.transformedShape());
assert(vv.first); assert(vv.first);
auto vnfp = ShapeLike::isValid(subnfp); auto vnfp = ShapeLike::isValid(subnfp_r.first);
assert(vnfp.first); assert(vnfp.first);
#endif #endif
nfps = Nfp::merge(nfps, subnfp); correctNfpPosition(subnfp_r, sh, trsh);
nfps = Nfp::merge(nfps, subnfp_r.first);
// double SCALE = 1000000;
// using SVGWriter = svg::SVGWriter<RawShape>;
// SVGWriter::Config conf;
// conf.mm_in_coord_units = SCALE;
// SVGWriter svgw(conf);
// Box bin(250*SCALE, 210*SCALE);
// svgw.setSize(bin);
// for(int i = 0; i <= pi; i++) svgw.writeItem(polygons[i]);
// svgw.writeItem(trsh);
//// svgw.writeItem(Item(subnfp_r.first));
// for(auto& n : nfps) svgw.writeItem(Item(n));
// svgw.save("nfpout");
// pi++;
} }
return nfps; return nfps;
@ -227,42 +358,65 @@ Nfp::Shapes<RawShape> nfp( const Container& polygons,
{ {
using Item = _Item<RawShape>; using Item = _Item<RawShape>;
Nfp::Shapes<RawShape> nfps, stationary; Nfp::Shapes<RawShape> nfps;
auto& orb = trsh.transformedShape();
bool orbconvex = trsh.isContourConvex();
for(Item& sh : polygons) { for(Item& sh : polygons) {
stationary = Nfp::merge(stationary, sh.transformedShape()); Nfp::NfpResult<RawShape> subnfp;
} auto& stat = sh.transformedShape();
std::cout << "pile size: " << stationary.size() << std::endl; if(sh.isContourConvex() && orbconvex)
for(RawShape& sh : stationary) { subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(stat, orb);
else if(orbconvex)
subnfp = Nfp::noFitPolygon<NfpLevel::ONE_CONVEX>(stat, orb);
else
subnfp = Nfp::noFitPolygon<Level::value>(stat, orb);
RawShape subnfp; correctNfpPosition(subnfp, sh, trsh);
// if(sh.isContourConvex() && trsh.isContourConvex()) {
// subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(
// sh.transformedShape(), trsh.transformedShape());
// } else {
subnfp = Nfp::noFitPolygon<Level::value>( sh/*.transformedShape()*/,
trsh.transformedShape());
// }
// #ifndef NDEBUG nfps = Nfp::merge(nfps, subnfp.first);
// auto vv = ShapeLike::isValid(sh.transformedShape());
// assert(vv.first);
// auto vnfp = ShapeLike::isValid(subnfp);
// assert(vnfp.first);
// #endif
// auto vnfp = ShapeLike::isValid(subnfp);
// if(!vnfp.first) {
// std::cout << vnfp.second << std::endl;
// std::cout << ShapeLike::toString(subnfp) << std::endl;
// }
nfps = Nfp::merge(nfps, subnfp);
} }
return nfps; return nfps;
// using Item = _Item<RawShape>;
// using sl = ShapeLike;
// Nfp::Shapes<RawShape> nfps, stationary;
// for(Item& sh : polygons) {
// stationary = Nfp::merge(stationary, sh.transformedShape());
// }
// for(RawShape& sh : stationary) {
//// auto vv = sl::isValid(sh);
//// std::cout << vv.second << std::endl;
// Nfp::NfpResult<RawShape> subnfp;
// bool shconvex = sl::isConvex<RawShape>(sl::getContour(sh));
// if(shconvex && trsh.isContourConvex()) {
// subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(
// sh, trsh.transformedShape());
// } else if(trsh.isContourConvex()) {
// subnfp = Nfp::noFitPolygon<NfpLevel::ONE_CONVEX>(
// sh, trsh.transformedShape());
// }
// else {
// subnfp = Nfp::noFitPolygon<Level::value>( sh,
// trsh.transformedShape());
// }
// correctNfpPosition(subnfp, sh, trsh);
// nfps = Nfp::merge(nfps, subnfp.first);
// }
// return nfps;
} }
template<class RawShape> template<class RawShape>
@ -283,13 +437,21 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
public: public:
using Pile = const Nfp::Shapes<RawShape>&; using Pile = Nfp::Shapes<RawShape>;
inline explicit _NofitPolyPlacer(const BinType& bin): inline explicit _NofitPolyPlacer(const BinType& bin):
Base(bin), Base(bin),
norm_(std::sqrt(ShapeLike::area<RawShape>(bin))), norm_(std::sqrt(ShapeLike::area<RawShape>(bin))),
penality_(1e6*norm_) {} penality_(1e6*norm_) {}
_NofitPolyPlacer(const _NofitPolyPlacer&) = default;
_NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default;
#ifndef BP2D_COMPILER_MSVC12 // MSVC2013 does not support default move ctors
_NofitPolyPlacer(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default;
_NofitPolyPlacer& operator=(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default;
#endif
bool static inline wouldFit(const RawShape& chull, const RawShape& bin) { bool static inline wouldFit(const RawShape& chull, const RawShape& bin) {
auto bbch = ShapeLike::boundingBox<RawShape>(chull); auto bbch = ShapeLike::boundingBox<RawShape>(chull);
auto bbin = ShapeLike::boundingBox<RawShape>(bin); auto bbin = ShapeLike::boundingBox<RawShape>(bin);
@ -363,7 +525,7 @@ public:
auto getNfpPoint = [&ecache](const Optimum& opt) auto getNfpPoint = [&ecache](const Optimum& opt)
{ {
return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) : return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) :
ecache[opt.nfpidx].coords(opt.nfpidx, opt.relpos); ecache[opt.nfpidx].coords(opt.hidx, opt.relpos);
}; };
Nfp::Shapes<RawShape> pile; Nfp::Shapes<RawShape> pile;
@ -378,8 +540,9 @@ public:
// customizable by the library client // customizable by the library client
auto _objfunc = config_.object_function? auto _objfunc = config_.object_function?
config_.object_function : config_.object_function :
[this](const Nfp::Shapes<RawShape>& pile, double occupied_area, [this](Nfp::Shapes<RawShape>& pile, Item,
double /*norm*/, double penality) double occupied_area, double /*norm*/,
double penality)
{ {
auto ch = ShapeLike::convexHull(pile); auto ch = ShapeLike::convexHull(pile);
@ -406,22 +569,22 @@ public:
d += startpos; d += startpos;
item.translation(d); item.translation(d);
pile.emplace_back(item.transformedShape()); // pile.emplace_back(item.transformedShape());
double occupied_area = pile_area + item.area(); double occupied_area = pile_area + item.area();
double score = _objfunc(pile, occupied_area, double score = _objfunc(pile, item, occupied_area,
norm_, penality_); norm_, penality_);
pile.pop_back(); // pile.pop_back();
return score; return score;
}; };
opt::StopCriteria stopcr; opt::StopCriteria stopcr;
stopcr.max_iterations = 1000; stopcr.max_iterations = 1000;
stopcr.stoplimit = 0.001; stopcr.absolute_score_difference = 1e-20*norm_;
stopcr.type = opt::StopLimitType::RELATIVE; // stopcr.relative_score_difference = 1e-20;
opt::TOptimizer<opt::Method::L_SIMPLEX> solver(stopcr); opt::TOptimizer<opt::Method::L_SIMPLEX> solver(stopcr);
Optimum optimum(0, 0); Optimum optimum(0, 0);
@ -458,6 +621,14 @@ public:
} catch(std::exception& e) { } catch(std::exception& e) {
derr() << "ERROR: " << e.what() << "\n"; derr() << "ERROR: " << e.what() << "\n";
} }
// auto sc = contour_ofn(pos);
// if(sc < best_score) {
// best_score = sc;
// optimum.relpos = pos;
// optimum.nfpidx = ch;
// optimum.hidx = -1;
// }
}); });
for(unsigned hidx = 0; hidx < cache.holeCount(); ++hidx) { for(unsigned hidx = 0; hidx < cache.holeCount(); ++hidx) {
@ -490,6 +661,13 @@ public:
} catch(std::exception& e) { } catch(std::exception& e) {
derr() << "ERROR: " << e.what() << "\n"; derr() << "ERROR: " << e.what() << "\n";
} }
// auto sc = hole_ofn(pos);
// if(sc < best_score) {
// best_score = sc;
// optimum.relpos = pos;
// optimum.nfpidx = ch;
// optimum.hidx = hidx;
// }
}); });
} }
} }

View File

@ -256,14 +256,14 @@ public:
if(not_packed.size() < 2) if(not_packed.size() < 2)
return false; // No group of two items return false; // No group of two items
else {
double largest_area = not_packed.front().get().area(); double largest_area = not_packed.front().get().area();
auto itmp = not_packed.begin(); itmp++; auto itmp = not_packed.begin(); itmp++;
double second_largest = itmp->get().area(); double second_largest = itmp->get().area();
if( free_area - second_largest - largest_area > waste) if( free_area - second_largest - largest_area > waste)
return false; // If even the largest two items do not fill return false; // If even the largest two items do not fill
// the bin to the desired waste than we can end here. // the bin to the desired waste than we can end here.
}
bool ret = false; bool ret = false;
auto it = not_packed.begin(); auto it = not_packed.begin();
@ -481,7 +481,7 @@ public:
{ {
std::array<bool, 3> packed = {false}; std::array<bool, 3> packed = {false};
for(auto id : idx) packed[id] = for(auto id : idx) packed.at(id) =
placer.pack(candidates[id]); placer.pack(candidates[id]);
bool check = bool check =
@ -537,8 +537,7 @@ public:
while (it != store_.end()) { while (it != store_.end()) {
Placer p(bin); Placer p(bin);
if(!p.pack(*it)) { if(!p.pack(*it)) {
auto itmp = it++; it = store_.erase(it);
store_.erase(itmp);
} else it++; } else it++;
} }
} }
@ -605,8 +604,7 @@ public:
if(placer.pack(*it)) { if(placer.pack(*it)) {
filled_area += it->get().area(); filled_area += it->get().area();
free_area = bin_area - filled_area; free_area = bin_area - filled_area;
auto itmp = it++; it = not_packed.erase(it);
not_packed.erase(itmp);
makeProgress(placer, idx, 1); makeProgress(placer, idx, 1);
} else it++; } else it++;
} }

View File

@ -52,7 +52,7 @@ public:
auto total = last-first; auto total = last-first;
auto makeProgress = [this, &total](Placer& placer, size_t idx) { auto makeProgress = [this, &total](Placer& placer, size_t idx) {
packed_bins_[idx] = placer.getItems(); packed_bins_[idx] = placer.getItems();
this->progress_(--total); this->progress_(static_cast<unsigned>(--total));
}; };
// Safety test: try to pack each item into an empty bin. If it fails // Safety test: try to pack each item into an empty bin. If it fails
@ -61,8 +61,7 @@ public:
while (it != store_.end()) { while (it != store_.end()) {
Placer p(bin); Placer p(bin);
if(!p.pack(*it)) { if(!p.pack(*it)) {
auto itmp = it++; it = store_.erase(it);
store_.erase(itmp);
} else it++; } else it++;
} }
} }

View File

@ -682,7 +682,9 @@ void testNfp(const std::vector<ItemPair>& testdata) {
auto&& nfp = Nfp::noFitPolygon<lvl>(stationary.rawShape(), auto&& nfp = Nfp::noFitPolygon<lvl>(stationary.rawShape(),
orbiter.transformedShape()); orbiter.transformedShape());
auto v = ShapeLike::isValid(nfp); strategies::correctNfpPosition(nfp, stationary, orbiter);
auto v = ShapeLike::isValid(nfp.first);
if(!v.first) { if(!v.first) {
std::cout << v.second << std::endl; std::cout << v.second << std::endl;
@ -690,7 +692,7 @@ void testNfp(const std::vector<ItemPair>& testdata) {
ASSERT_TRUE(v.first); ASSERT_TRUE(v.first);
Item infp(nfp); Item infp(nfp.first);
int i = 0; int i = 0;
auto rorbiter = orbiter.transformedShape(); auto rorbiter = orbiter.transformedShape();
@ -742,6 +744,15 @@ TEST(GeometryAlgorithms, nfpConvexConvex) {
// testNfp<NfpLevel::BOTH_CONCAVE, 1000>(nfp_concave_testdata); // testNfp<NfpLevel::BOTH_CONCAVE, 1000>(nfp_concave_testdata);
//} //}
TEST(GeometryAlgorithms, nfpConcaveConcave) {
using namespace libnest2d;
// Rectangle r1(10, 10);
// Rectangle r2(20, 20);
// auto result = Nfp::nfpSimpleSimple(r1.transformedShape(),
// r2.transformedShape());
}
TEST(GeometryAlgorithms, pointOnPolygonContour) { TEST(GeometryAlgorithms, pointOnPolygonContour) {
using namespace libnest2d; using namespace libnest2d;

View File

@ -49,18 +49,18 @@ libnfporb::point_t scale(const libnfporb::point_t& p, long double factor) {
long double px = p.x_.val(); long double px = p.x_.val();
long double py = p.y_.val(); long double py = p.y_.val();
#endif #endif
return libnfporb::point_t(px*factor, py*factor); return {px*factor, py*factor};
} }
} }
PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother) NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
{ {
using Vertex = PointImpl; using Vertex = PointImpl;
PolygonImpl ret; NfpR ret;
// try { try {
libnfporb::polygon_t pstat, porb; libnfporb::polygon_t pstat, porb;
boost::geometry::convert(sh, pstat); boost::geometry::convert(sh, pstat);
@ -85,7 +85,7 @@ PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
// this can throw // this can throw
auto nfp = libnfporb::generateNFP(pstat, porb, true); auto nfp = libnfporb::generateNFP(pstat, porb, true);
auto &ct = ShapeLike::getContour(ret); auto &ct = ShapeLike::getContour(ret.first);
ct.reserve(nfp.front().size()+1); ct.reserve(nfp.front().size()+1);
for(auto v : nfp.front()) { for(auto v : nfp.front()) {
v = scale(v, refactor); v = scale(v, refactor);
@ -94,10 +94,10 @@ PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
ct.push_back(ct.front()); ct.push_back(ct.front());
std::reverse(ct.begin(), ct.end()); std::reverse(ct.begin(), ct.end());
auto &rholes = ShapeLike::holes(ret); auto &rholes = ShapeLike::holes(ret.first);
for(size_t hidx = 1; hidx < nfp.size(); ++hidx) { for(size_t hidx = 1; hidx < nfp.size(); ++hidx) {
if(nfp[hidx].size() >= 3) { if(nfp[hidx].size() >= 3) {
rholes.push_back({}); rholes.emplace_back();
auto& h = rholes.back(); auto& h = rholes.back();
h.reserve(nfp[hidx].size()+1); h.reserve(nfp[hidx].size()+1);
@ -110,73 +110,48 @@ PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
} }
} }
auto& cmp = vsort; ret.second = Nfp::referenceVertex(ret.first);
std::sort(pstat.outer().begin(), pstat.outer().end(), cmp);
std::sort(porb.outer().begin(), porb.outer().end(), cmp);
// leftmost lower vertex of the stationary polygon } catch(std::exception& e) {
auto& touch_sh = scale(pstat.outer().back(), refactor); std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl;
// rightmost upper vertex of the orbiting polygon
auto& touch_other = scale(porb.outer().front(), refactor);
// Calculate the difference and move the orbiter to the touch position.
auto dtouch = touch_sh - touch_other;
auto _top_other = scale(porb.outer().back(), refactor) + dtouch;
Vertex top_other(getX(_top_other), getY(_top_other));
// Get the righmost upper vertex of the nfp and move it to the RMU of
// the orbiter because they should coincide.
auto&& top_nfp = Nfp::rightmostUpVertex(ret);
auto dnfp = top_other - top_nfp;
std::for_each(ShapeLike::begin(ret), ShapeLike::end(ret),
[&dnfp](Vertex& v) { v+= dnfp; } );
for(auto& h : ShapeLike::holes(ret))
std::for_each( h.begin(), h.end(),
[&dnfp](Vertex& v) { v += dnfp; } );
// } catch(std::exception& e) {
// std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl;
// auto ch_stat = ShapeLike::convexHull(sh); // auto ch_stat = ShapeLike::convexHull(sh);
// auto ch_orb = ShapeLike::convexHull(cother); // auto ch_orb = ShapeLike::convexHull(cother);
// ret = Nfp::nfpConvexOnly(ch_stat, ch_orb); ret = Nfp::nfpConvexOnly(sh, cother);
// } }
return ret; return ret;
} }
PolygonImpl Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY>::operator()( NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{ {
return _nfp(sh, cother);//nfpConvexOnly(sh, cother); return _nfp(sh, cother);//nfpConvexOnly(sh, cother);
} }
PolygonImpl Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX>::operator()( NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{ {
return _nfp(sh, cother); return _nfp(sh, cother);
} }
PolygonImpl Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE>::operator()( NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{ {
return _nfp(sh, cother); return _nfp(sh, cother);
} }
PolygonImpl //PolygonImpl
Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX_WITH_HOLES>::operator()( //Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX_WITH_HOLES>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) // const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{ //{
return _nfp(sh, cother); // return _nfp(sh, cother);
} //}
PolygonImpl //PolygonImpl
Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE_WITH_HOLES>::operator()( //Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE_WITH_HOLES>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) // const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{ //{
return _nfp(sh, cother); // return _nfp(sh, cother);
} //}
} }

View File

@ -5,37 +5,39 @@
namespace libnest2d { namespace libnest2d {
PolygonImpl _nfp(const PolygonImpl& sh, const PolygonImpl& cother); using NfpR = Nfp::NfpResult<PolygonImpl>;
NfpR _nfp(const PolygonImpl& sh, const PolygonImpl& cother);
template<> template<>
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY> { struct Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY> {
PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother); NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
}; };
template<> template<>
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX> { struct Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX> {
PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother); NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
}; };
template<> template<>
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE> { struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE> {
PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother); NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
}; };
template<> //template<>
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX_WITH_HOLES> { //struct Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX_WITH_HOLES> {
PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother); // NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother);
}; //};
template<> //template<>
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE_WITH_HOLES> { //struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE_WITH_HOLES> {
PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother); // NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother);
}; //};
template<> struct Nfp::MaxNfpLevel<PolygonImpl> { template<> struct Nfp::MaxNfpLevel<PolygonImpl> {
static const BP2D_CONSTEXPR NfpLevel value = static const BP2D_CONSTEXPR NfpLevel value =
// NfpLevel::CONVEX_ONLY; // NfpLevel::CONVEX_ONLY;
NfpLevel::BOTH_CONCAVE_WITH_HOLES; NfpLevel::BOTH_CONCAVE;
}; };
} }

View File

@ -5,11 +5,17 @@
#include <fstream> #include <fstream>
#include <string> #include <string>
#include <libnest2d.h> #include <libnest2d/libnest2d.hpp>
namespace libnest2d { namespace svg { namespace libnest2d { namespace svg {
template<class RawShape>
class SVGWriter { class SVGWriter {
using Item = _Item<RawShape>;
using Coord = TCoord<TPoint<RawShape>>;
using Box = _Box<TPoint<RawShape>>;
using PackGroup = _PackGroup<RawShape>;
public: public:
enum OrigoLocation { enum OrigoLocation {

View File

@ -538,32 +538,98 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb,
// effect, the arrange procedure is a lot faster (we do not need to // effect, the arrange procedure is a lot faster (we do not need to
// calculate the convex hulls) // calculate the convex hulls)
pcfg.object_function = [bin, hasbin]( pcfg.object_function = [bin, hasbin](
NfpPlacer::Pile pile, // The currently arranged pile NfpPlacer::Pile& pile, // The currently arranged pile
Item item,
double /*area*/, // Sum area of items (not needed) double /*area*/, // Sum area of items (not needed)
double norm, // A norming factor for physical dimensions double norm, // A norming factor for physical dimensions
double penality) // Min penality in case of bad arrangement double penality) // Min penality in case of bad arrangement
{ {
auto bb = ShapeLike::boundingBox(pile); using pl = PointLike;
// We get the current item that's being evaluated. static const double BIG_ITEM_TRESHOLD = 0.2;
auto& sh = pile.back(); static const double GRAVITY_RATIO = 0.5;
static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO;
// We retrieve the reference point of this item // We will treat big items (compared to the print bed) differently
auto rv = Nfp::referenceVertex(sh); NfpPlacer::Pile bigs;
bigs.reserve(pile.size());
for(auto& p : pile) {
auto pbb = ShapeLike::boundingBox(p);
auto na = std::sqrt(pbb.width()*pbb.height())/norm;
if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p);
}
// We get the distance of the reference point from the center of the // Candidate item bounding box
// heat bed auto ibb = item.boundingBox();
auto c = bin.center();
auto d = PointLike::distance(rv, c);
// The score will be the normalized distance which will be minimized, // Calculate the full bounding box of the pile with the candidate item
// effectively creating a circle shaped pile of items pile.emplace_back(item.transformedShape());
double score = double(d)/norm; auto fullbb = ShapeLike::boundingBox(pile);
pile.pop_back();
// The bounding box of the big items (they will accumulate in the center
// of the pile
auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs);
// The size indicator of the candidate item. This is not the area,
// but almost...
auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm;
// Will hold the resulting score
double score = 0;
if(itemnormarea > BIG_ITEM_TRESHOLD) {
// This branch is for the bigger items..
// Here we will use the closest point of the item bounding box to
// the already arranged pile. So not the bb center nor the a choosen
// corner but whichever is the closest to the center. This will
// prevent unwanted strange arrangements.
auto minc = ibb.minCorner(); // bottom left corner
auto maxc = ibb.maxCorner(); // top right corner
// top left and bottom right corners
auto top_left = PointImpl{getX(minc), getY(maxc)};
auto bottom_right = PointImpl{getX(maxc), getY(minc)};
auto cc = fullbb.center(); // The gravity center
// Now the distnce of the gravity center will be calculated to the
// five anchor points and the smallest will be chosen.
std::array<double, 5> dists;
dists[0] = pl::distance(minc, cc);
dists[1] = pl::distance(maxc, cc);
dists[2] = pl::distance(ibb.center(), cc);
dists[3] = pl::distance(top_left, cc);
dists[4] = pl::distance(bottom_right, cc);
auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
// Density is the pack density: how big is the arranged pile
auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
// The score is a weighted sum of the distance from pile center
// and the pile size
score = GRAVITY_RATIO * dist + DENSITY_RATIO * density;
} else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) {
// If there are no big items, only small, we should consider the
// density here as well to not get silly results
auto bindist = pl::distance(ibb.center(), bin.center()) / norm;
auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density;
} else {
// Here there are the small items that should be placed around the
// already processed bigger items.
// No need to play around with the anchor points, the center will be
// just fine for small items
score = pl::distance(ibb.center(), bigbb.center()) / norm;
}
// If it does not fit into the print bed we will beat it // If it does not fit into the print bed we will beat it
// with a large penality. If we would not do this, there would be only // with a large penality. If we would not do this, there would be only
// one big pile that doesn't care whether it fits onto the print bed. // one big pile that doesn't care whether it fits onto the print bed.
if(hasbin && !NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score; if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score;
return score; return score;
}; };