Add the full source of BambuStudio

using version 1.0.10
This commit is contained in:
lane.wei 2022-07-15 23:37:19 +08:00 committed by Lane.Wei
parent 30bcadab3e
commit 1555904bef
3771 changed files with 1251328 additions and 0 deletions

View file

@ -0,0 +1,28 @@
set(LIBNEST2D_SRCFILES
include/libnest2d/libnest2d.hpp
include/libnest2d/nester.hpp
include/libnest2d/geometry_traits.hpp
include/libnest2d/geometry_traits_nfp.hpp
include/libnest2d/common.hpp
include/libnest2d/optimizer.hpp
include/libnest2d/utils/metaloop.hpp
include/libnest2d/utils/rotfinder.hpp
include/libnest2d/utils/rotcalipers.hpp
include/libnest2d/placers/placer_boilerplate.hpp
include/libnest2d/placers/bottomleftplacer.hpp
include/libnest2d/placers/nfpplacer.hpp
include/libnest2d/selections/selection_boilerplate.hpp
include/libnest2d/selections/firstfit.hpp
include/libnest2d/backends/libslic3r/geometries.hpp
include/libnest2d/optimizers/nlopt/nlopt_boilerplate.hpp
include/libnest2d/optimizers/nlopt/simplex.hpp
include/libnest2d/optimizers/nlopt/subplex.hpp
include/libnest2d/optimizers/nlopt/genetic.hpp
src/libnest2d.cpp
)
add_library(libnest2d STATIC ${LIBNEST2D_SRCFILES})
target_include_directories(libnest2d PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
target_link_libraries(libnest2d PUBLIC NLopt::nlopt TBB::tbb Boost::boost libslic3r)
target_compile_definitions(libnest2d PUBLIC LIBNEST2D_THREADING_tbb LIBNEST2D_STATIC LIBNEST2D_OPTIMIZER_nlopt LIBNEST2D_GEOMETRIES_libslic3r)

165
src/libnest2d/LICENSE.txt Normal file
View file

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View file

@ -0,0 +1,283 @@
#ifndef CLIPPER_BACKEND_HPP
#define CLIPPER_BACKEND_HPP
#include <sstream>
#include <unordered_map>
#include <cassert>
#include <vector>
#include <iostream>
#include <libnest2d/geometry_traits.hpp>
#include <libnest2d/geometry_traits_nfp.hpp>
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/ClipperUtils.hpp>
namespace Slic3r {
template<class T, class En = void> struct IsVec_ : public std::false_type {};
template<class T> struct IsVec_< Vec<2, T> >: public std::true_type {};
template<class T>
static constexpr const bool IsVec = IsVec_<libnest2d::remove_cvref_t<T>>::value;
template<class T, class O> using VecOnly = std::enable_if_t<IsVec<T>, O>;
inline Point operator+(const Point& p1, const Point& p2) {
Point ret = p1;
ret += p2;
return ret;
}
inline Point operator -(const Point& p ) {
Point ret = p;
ret.x() = -ret.x();
ret.y() = -ret.y();
return ret;
}
inline Point operator-(const Point& p1, const Point& p2) {
Point ret = p1;
ret -= p2;
return ret;
}
inline Point& operator *=(Point& p, const Point& pa ) {
p.x() *= pa.x();
p.y() *= pa.y();
return p;
}
inline Point operator*(const Point& p1, const Point& p2) {
Point ret = p1;
ret *= p2;
return ret;
}
} // namespace Slic3r
namespace libnest2d {
template<class T> using Vec = Slic3r::Vec<2, T>;
// Aliases for convinience
using PointImpl = Slic3r::Point;
using PathImpl = Slic3r::Polygon;
using HoleStore = Slic3r::Polygons;
using PolygonImpl = Slic3r::ExPolygon;
template<> struct ShapeTag<Slic3r::Vec2crd> { using Type = PointTag; };
template<> struct ShapeTag<Slic3r::Point> { using Type = PointTag; };
template<> struct ShapeTag<std::vector<Slic3r::Vec2crd>> { using Type = PathTag; };
template<> struct ShapeTag<Slic3r::Polygon> { using Type = PathTag; };
template<> struct ShapeTag<Slic3r::Points> { using Type = PathTag; };
template<> struct ShapeTag<Slic3r::ExPolygon> { using Type = PolygonTag; };
template<> struct ShapeTag<Slic3r::ExPolygons> { using Type = MultiPolygonTag; };
// Type of coordinate units used by Clipper. Enough to specialize for point,
// the rest of the types will work (Path, Polygon)
template<> struct CoordType<Slic3r::Point> {
using Type = coord_t;
static const constexpr coord_t MM_IN_COORDS = 1000000;
};
template<> struct CoordType<Slic3r::Vec2crd> {
using Type = coord_t;
static const constexpr coord_t MM_IN_COORDS = 1000000;
};
// Enough to specialize for path, it will work for multishape and Polygon
template<> struct PointType<std::vector<Slic3r::Vec2crd>> { using Type = Slic3r::Vec2crd; };
template<> struct PointType<Slic3r::Polygon> { using Type = Slic3r::Point; };
template<> struct PointType<Slic3r::Points> { using Type = Slic3r::Point; };
// This is crucial. CountourType refers to itself by default, so we don't have
// to secialize for clipper Path. ContourType<PathImpl>::Type is PathImpl.
template<> struct ContourType<Slic3r::ExPolygon> { using Type = Slic3r::Polygon; };
// The holes are contained in Clipper::Paths
template<> struct HolesContainer<Slic3r::ExPolygon> { using Type = Slic3r::Polygons; };
template<>
struct OrientationType<Slic3r::Polygon> {
static const constexpr Orientation Value = Orientation::COUNTER_CLOCKWISE;
};
template<>
struct OrientationType<Slic3r::Points> {
static const constexpr Orientation Value = Orientation::COUNTER_CLOCKWISE;
};
template<>
struct ClosureType<Slic3r::Polygon> {
static const constexpr Closure Value = Closure::OPEN;
};
template<>
struct ClosureType<Slic3r::Points> {
static const constexpr Closure Value = Closure::OPEN;
};
template<> struct MultiShape<Slic3r::ExPolygon> { using Type = Slic3r::ExPolygons; };
template<> struct ContourType<Slic3r::ExPolygons> { using Type = Slic3r::Polygon; };
// Using the libnest2d default area implementation
#define DISABLE_BOOST_AREA
namespace shapelike {
template<>
inline void offset(Slic3r::ExPolygon& sh, coord_t distance, const PolygonTag&)
{
#define DISABLE_BOOST_OFFSET
auto res = Slic3r::offset_ex(sh, distance, Slic3r::ClipperLib::jtSquare);
if (!res.empty()) sh = res.front();
}
template<>
inline void offset(Slic3r::Polygon& sh, coord_t distance, const PathTag&)
{
auto res = Slic3r::offset(sh, distance, Slic3r::ClipperLib::jtSquare);
if (!res.empty()) sh = res.front();
}
// Tell libnest2d how to make string out of a ClipperPolygon object
template<> inline std::string toString(const Slic3r::ExPolygon& sh)
{
std::stringstream ss;
ss << "Contour {\n";
for(auto &p : sh.contour.points) {
ss << "\t" << p.x() << " " << p.y() << "\n";
}
ss << "}\n";
for(auto& h : sh.holes) {
ss << "Holes {\n";
for(auto p : h.points) {
ss << "\t{\n";
ss << "\t\t" << p.x() << " " << p.y() << "\n";
ss << "\t}\n";
}
ss << "}\n";
}
return ss.str();
}
template<>
inline Slic3r::ExPolygon create(const Slic3r::Polygon& path, const Slic3r::Polygons& holes)
{
Slic3r::ExPolygon p;
p.contour = path;
p.holes = holes;
return p;
}
template<> inline Slic3r::ExPolygon create(Slic3r::Polygon&& path, Slic3r::Polygons&& holes) {
Slic3r::ExPolygon p;
p.contour.points.swap(path.points);
p.holes.swap(holes);
return p;
}
template<>
inline const THolesContainer<PolygonImpl>& holes(const Slic3r::ExPolygon& sh)
{
return sh.holes;
}
template<> inline THolesContainer<PolygonImpl>& holes(Slic3r::ExPolygon& sh)
{
return sh.holes;
}
template<>
inline Slic3r::Polygon& hole(Slic3r::ExPolygon& sh, unsigned long idx)
{
return sh.holes[idx];
}
template<>
inline const Slic3r::Polygon& hole(const Slic3r::ExPolygon& sh, unsigned long idx)
{
return sh.holes[idx];
}
template<> inline size_t holeCount(const Slic3r::ExPolygon& sh)
{
return sh.holes.size();
}
template<> inline Slic3r::Polygon& contour(Slic3r::ExPolygon& sh)
{
return sh.contour;
}
template<>
inline const Slic3r::Polygon& contour(const Slic3r::ExPolygon& sh)
{
return sh.contour;
}
template<>
inline void reserve(Slic3r::Polygon& p, size_t vertex_capacity, const PathTag&)
{
p.points.reserve(vertex_capacity);
}
template<>
inline void addVertex(Slic3r::Polygon& sh, const PathTag&, const Slic3r::Point &p)
{
sh.points.emplace_back(p);
}
#define DISABLE_BOOST_TRANSLATE
template<>
inline void translate(Slic3r::ExPolygon& sh, const Slic3r::Point& offs)
{
sh.translate(offs);
}
#define DISABLE_BOOST_ROTATE
template<>
inline void rotate(Slic3r::ExPolygon& sh, const Radians& rads)
{
sh.rotate(rads);
}
} // namespace shapelike
namespace nfp {
#define DISABLE_BOOST_NFP_MERGE
template<>
inline TMultiShape<PolygonImpl> merge(const TMultiShape<PolygonImpl>& shapes)
{
return Slic3r::union_ex(shapes);
}
} // namespace nfp
} // namespace libnest2d
#define DISABLE_BOOST_CONVEX_HULL
//#define DISABLE_BOOST_SERIALIZE
//#define DISABLE_BOOST_UNSERIALIZE
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4244)
#pragma warning(disable: 4267)
#endif
// All other operators and algorithms are implemented with boost
#include <libnest2d/utils/boost_alg.hpp>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif // CLIPPER_BACKEND_HPP

View file

@ -0,0 +1,232 @@
#ifndef LIBNEST2D_CONFIG_HPP
#define LIBNEST2D_CONFIG_HPP
#ifndef NDEBUG
#include <iostream>
#endif
#include <stdexcept>
#include <string>
#include <cmath>
#include <type_traits>
#include <limits>
#define MAX_NUM_PLATES 50
#if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L
#define BP2D_NOEXCEPT
#define BP2D_CONSTEXPR
#define BP2D_COMPILER_MSVC12
#elif __cplusplus >= 201103L
#define BP2D_NOEXCEPT noexcept
#define BP2D_CONSTEXPR constexpr
#endif
/*
* Debugging output dout and derr definition
*/
//#ifndef NDEBUG
//# define dout std::cout
//# define derr std::cerr
//#else
//# define dout 0 && std::cout
//# define derr 0 && std::cerr
//#endif
namespace libnest2d {
struct DOut {
#ifndef NDEBUG
std::ostream& out = std::cout;
#endif
};
struct DErr {
#ifndef NDEBUG
std::ostream& out = std::cerr;
#endif
};
template<class T>
inline DOut&& operator<<( DOut&& out, T&& d) {
#ifndef NDEBUG
out.out << d;
#endif
return std::move(out);
}
template<class T>
inline DErr&& operator<<( DErr&& out, T&& d) {
#ifndef NDEBUG
out.out << d;
#endif
return std::move(out);
}
inline DOut dout() { return DOut(); }
inline DErr derr() { return DErr(); }
template< class T >
struct remove_cvref {
using type = typename std::remove_cv<
typename std::remove_reference<T>::type>::type;
};
template< class T >
using remove_cvref_t = typename remove_cvref<T>::type;
template< class T >
using remove_ref_t = typename std::remove_reference<T>::type;
template<bool B, class T>
using enable_if_t = typename std::enable_if<B, T>::type;
template<class F, class...Args>
struct invoke_result {
using type = typename std::result_of<F(Args...)>::type;
};
template<class F, class...Args>
using invoke_result_t = typename invoke_result<F, Args...>::type;
/**
* A useful little tool for triggering static_assert error messages e.g. when
* a mandatory template specialization (implementation) is missing.
*
* \tparam T A template argument that may come from and outer template method.
*/
template<class T> struct always_false { enum { value = false }; };
const double BP2D_CONSTEXPR Pi = 3.141592653589793238463; // 2*std::acos(0);
const double BP2D_CONSTEXPR Pi_2 = 2*Pi;
/**
* @brief Only for the Radian and Degrees classes to behave as doubles.
*/
class Double {
protected:
double val_;
public:
Double(): val_(double{}) { }
Double(double d) : val_(d) { }
operator double() const BP2D_NOEXCEPT { return val_; }
operator double&() BP2D_NOEXCEPT { return val_; }
};
class Degrees;
/**
* @brief Data type representing radians. It supports conversion to degrees.
*/
class Radians: public Double {
mutable double sin_ = std::nan(""), cos_ = std::nan("");
public:
Radians(double rads = Double() ): Double(rads) {}
inline Radians(const Degrees& degs);
inline operator Degrees();
inline double toDegrees();
inline double sin() const {
if(std::isnan(sin_)) {
cos_ = std::cos(val_);
sin_ = std::sin(val_);
}
return sin_;
}
inline double cos() const {
if(std::isnan(cos_)) {
cos_ = std::cos(val_);
sin_ = std::sin(val_);
}
return cos_;
}
};
/**
* @brief Data type representing degrees. It supports conversion to radians.
*/
class Degrees: public Double {
public:
Degrees(double deg = Double()): Double(deg) {}
Degrees(const Radians& rads): Double( rads * 180/Pi ) {}
inline double toRadians() { return Radians(*this);}
};
inline bool operator==(const Degrees& deg, const Radians& rads) {
Degrees deg2 = rads;
auto diff = std::abs(deg - deg2);
return diff < 0.0001;
}
inline bool operator==(const Radians& rads, const Degrees& deg) {
return deg == rads;
}
inline Radians::operator Degrees() { return *this * 180/Pi; }
inline Radians::Radians(const Degrees &degs): Double( degs * Pi/180) {}
inline double Radians::toDegrees() { return operator Degrees(); }
enum class GeomErr : std::size_t {
OFFSET,
MERGE,
NFP
};
const std::string ERROR_STR[] = {
"Offsetting could not be done! An invalid geometry may have been added.",
"Error while merging geometries!",
"No fit polygon cannot be calculated."
};
class GeometryException: public std::exception {
virtual const std::string& errorstr(GeomErr errcode) const BP2D_NOEXCEPT {
return ERROR_STR[static_cast<std::size_t>(errcode)];
}
GeomErr errcode_;
public:
GeometryException(GeomErr code): errcode_(code) {}
GeomErr errcode() const { return errcode_; }
const char * what() const BP2D_NOEXCEPT override {
return errorstr(errcode_).c_str();
}
};
struct ScalarTag {};
struct BigIntTag {};
struct RationalTag {};
template<class T> struct _NumTag {
using Type =
enable_if_t<std::is_arithmetic<T>::value, ScalarTag>;
};
template<class T> using NumTag = typename _NumTag<remove_cvref_t<T>>::Type;
/// A local version for abs that is garanteed to work with libnest2d types
template <class T> inline T abs(const T& v, ScalarTag)
{
return std::abs(v);
}
template<class T> inline T abs(const T& v) { return abs(v, NumTag<T>()); }
template<class T2, class T1> inline T2 cast(const T1& v, ScalarTag, ScalarTag)
{
return static_cast<T2>(v);
}
template<class T2, class T1> inline T2 cast(const T1& v) {
return cast<T2, T1>(v, NumTag<T1>(), NumTag<T2>());
}
}
#endif // LIBNEST2D_CONFIG_HPP

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,799 @@
#ifndef GEOMETRIES_NOFITPOLYGON_HPP
#define GEOMETRIES_NOFITPOLYGON_HPP
#include <algorithm>
#include <functional>
#include <vector>
#include <iterator>
#include <libnest2d/geometry_traits.hpp>
namespace libnest2d {
namespace __nfp {
// Do not specialize this...
template<class RawShape, class Unit = TCompute<RawShape>>
inline bool _vsort(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2)
{
Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2);
return y1 == y2 ? x1 < x2 : y1 < y2;
}
template<class EdgeList, class RawShape, class Vertex = TPoint<RawShape>>
inline void buildPolygon(const EdgeList& edgelist,
RawShape& rpoly,
Vertex& top_nfp)
{
namespace sl = shapelike;
auto& rsh = sl::contour(rpoly);
sl::reserve(rsh, 2 * edgelist.size());
// Add the two vertices from the first edge into the final polygon.
sl::addVertex(rsh, edgelist.front().first());
sl::addVertex(rsh, edgelist.front().second());
// 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
for(auto eit = std::next(edgelist.begin());
eit != edgelist.end();
++eit)
{
auto d = *tmp - eit->first();
Vertex p = eit->second() + d;
sl::addVertex(rsh, p);
// Set the new reference vertex
if(cmp(top_nfp, p)) top_nfp = p;
tmp = std::next(tmp);
}
}
template<class Container, class Iterator = typename Container::iterator>
void advance(Iterator& it, Container& cont, bool direction)
{
int dir = direction ? 1 : -1;
if(dir < 0 && it == cont.begin()) it = std::prev(cont.end());
else it += dir;
if(dir > 0 && it == cont.end()) it = cont.begin();
}
}
/// A collection of static methods for handling the no fit polygon creation.
namespace nfp {
const double BP2D_CONSTEXPR TwoPi = 2*Pi;
/// The complexity level of a polygon that an NFP implementation can handle.
enum class NfpLevel: unsigned {
CONVEX_ONLY,
ONE_CONVEX,
BOTH_CONCAVE,
ONE_CONVEX_WITH_HOLES,
BOTH_CONCAVE_WITH_HOLES
};
template<class RawShape>
using NfpResult = std::pair<RawShape, TPoint<RawShape>>;
template<class RawShape> struct MaxNfpLevel {
static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY;
};
// Shorthand for a pile of polygons
template<class RawShape>
using Shapes = TMultiShape<RawShape>;
/**
* 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 disjunct than the resulting set will contain more polygons.
*/
template<class RawShapes>
inline RawShapes merge(const RawShapes& /*shc*/)
{
static_assert(always_false<RawShapes>::value,
"Nfp::merge(shapes, shape) unimplemented!");
}
/**
* 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 disjunct than the resulting set will contain more polygons.
*/
template<class RawShape>
inline TMultiShape<RawShape> merge(const TMultiShape<RawShape>& shc,
const RawShape& sh)
{
auto m = nfp::merge(shc);
m.emplace_back(sh);
return nfp::merge(m);
}
/**
* Get the vertex of the polygon that is at the lowest values (bottom) in the Y
* axis and if there are more than one vertices on the same Y coordinate than
* the result will be the leftmost (with the highest X coordinate).
*/
template<class RawShape>
inline TPoint<RawShape> leftmostDownVertex(const RawShape& sh)
{
// find min x and min y vertex
auto it = std::min_element(shapelike::cbegin(sh), shapelike::cend(sh),
__nfp::_vsort<RawShape>);
return it == shapelike::cend(sh) ? TPoint<RawShape>() : *it;;
}
/**
* Get the vertex of the polygon that is at the highest values (top) in the Y
* axis and if there are more than one vertices on the same Y coordinate than
* the result will be the rightmost (with the lowest X coordinate).
*/
template<class RawShape>
TPoint<RawShape> rightmostUpVertex(const RawShape& sh)
{
// find max x and max y vertex
auto it = std::max_element(shapelike::cbegin(sh), shapelike::cend(sh),
__nfp::_vsort<RawShape>);
return it == shapelike::cend(sh) ? TPoint<RawShape>() : *it;
}
/**
* A method to get a vertex from a polygon that always maintains a relative
* position to the coordinate system: It is always the rightmost top vertex.
*
* This way it does not matter in what order the vertices are stored, the
* reference will be always the same for the same polygon.
*/
template<class RawShape>
inline TPoint<RawShape> referenceVertex(const RawShape& sh)
{
return rightmostUpVertex(sh);
}
/**
* The "trivial" Cuninghame-Green implementation of NFP for convex polygons.
*
* You can use this even if you provide implementations for the more complex
* cases (Through specializing the the NfpImpl struct). Currently, no other
* cases are covered in the library.
*
* Complexity should be no more than nlogn (std::sort) in the number of edges
* of the input polygons.
*
* \tparam RawShape the Polygon data type.
* \param sh The stationary polygon
* \param cother The orbiting polygon
* \return Returns a pair of the NFP and its reference vertex of the two input
* polygons which have to be strictly convex. The resulting NFP is proven to be
* convex as well in this case.
*
*/
template<class RawShape, class Ratio = double>
inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
const RawShape& other)
{
using Vertex = TPoint<RawShape>; using Edge = _Segment<Vertex>;
namespace sl = shapelike;
RawShape rsh; // Final nfp placeholder
Vertex top_nfp;
std::vector<Edge> edgelist;
auto cap = sl::contourVertexCount(sh) + sl::contourVertexCount(other);
// Reserve the needed memory
edgelist.reserve(cap);
sl::reserve(rsh, static_cast<unsigned long>(cap));
auto add_edge = [&edgelist](const Vertex &v1, const Vertex &v2) {
Edge e{v1, v2};
if (e.sqlength() > 0)
edgelist.emplace_back(e);
};
{ // place all edges from sh into edgelist
auto first = sl::cbegin(sh);
auto next = std::next(first);
while(next != sl::cend(sh)) {
// BBS: some polygons may have duplicate points which is not allowed here (for pcos1, pcos2 calculation)
if (*first != *next) add_edge(*(first), *(next));
++first; ++next;
}
if constexpr (ClosureTypeV<RawShape> == Closure::OPEN)
add_edge(*sl::rcbegin(sh), *sl::cbegin(sh));
}
{ // place all edges from other into edgelist
auto first = sl::cbegin(other);
auto next = std::next(first);
while(next != sl::cend(other)) {
if (*first != *next) add_edge(*(next), *(first));
++first; ++next;
}
if constexpr (ClosureTypeV<RawShape> == Closure::OPEN)
add_edge(*sl::cbegin(other), *sl::rcbegin(other));
}
std::sort(edgelist.begin(), edgelist.end(),
[](const Edge& e1, const Edge& e2)
{
const Vertex ax(1, 0); // Unit vector for the X axis
// get cectors from the edges
Vertex p1 = e1.second() - e1.first();
Vertex p2 = e2.second() - e2.first();
// Quadrant mapping array. The quadrant of a vector can be determined
// from the dot product of the vector and its perpendicular pair
// with the unit vector X axis. The products will carry the values
// lcos = dot(p, ax) = l * cos(phi) and
// lsin = -dotperp(p, ax) = l * sin(phi) where
// l is the length of vector p. From the signs of these values we can
// construct an index which has the sign of lcos as MSB and the
// sign of lsin as LSB. This index can be used to retrieve the actual
// quadrant where vector p resides using the following map:
// (+ is 0, - is 1)
// cos | sin | decimal | quadrant
// + | + | 0 | 0
// + | - | 1 | 3
// - | + | 2 | 1
// - | - | 3 | 2
std::array<int, 4> quadrants {0, 3, 1, 2 };
std::array<int, 2> q {0, 0}; // Quadrant indices for p1 and p2
using TDots = std::array<TCompute<Vertex>, 2>;
TDots lcos { pl::dot(p1, ax), pl::dot(p2, ax) };
TDots lsin { -pl::dotperp(p1, ax), -pl::dotperp(p2, ax) };
// Construct the quadrant indices for p1 and p2
for(size_t i = 0; i < 2; ++i)
if(lcos[i] == 0) q[i] = lsin[i] > 0 ? 1 : 3;
else if(lsin[i] == 0) q[i] = lcos[i] > 0 ? 0 : 2;
else q[i] = quadrants[((lcos[i] < 0) << 1) + (lsin[i] < 0)];
if(q[0] == q[1]) { // only bother if p1 and p2 are in the same quadrant
auto lsq1 = pl::magnsq(p1); // squared magnitudes, avoid sqrt
auto lsq2 = pl::magnsq(p2); // squared magnitudes, avoid sqrt
// We will actually compare l^2 * cos^2(phi) which saturates the
// cos function. But with the quadrant info we can get the sign back
int sign = q[0] == 1 || q[0] == 2 ? -1 : 1;
// If Ratio is an actual rational type, there is no precision loss
auto pcos1 = Ratio(lcos[0]) / lsq1 * sign * lcos[0];
auto pcos2 = Ratio(lcos[1]) / lsq2 * sign * lcos[1];
if constexpr (is_clockwise<RawShape>())
return q[0] < 2 ? pcos1 < pcos2 : pcos1 > pcos2;
else
return q[0] < 2 ? pcos1 > pcos2 : pcos1 < pcos2;
}
// If in different quadrants, compare the quadrant indices only.
if constexpr (is_clockwise<RawShape>())
return q[0] > q[1];
else
return q[0] < q[1];
});
__nfp::buildPolygon(edgelist, rsh, top_nfp);
return {rsh, top_nfp};
}
template<class RawShape>
NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
const RawShape& cother)
{
// Algorithms are from the original algorithm proposed in paper:
// https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf
// /////////////////////////////////////////////////////////////////////////
// Algorithm 1: Obtaining the minkowski sum
// /////////////////////////////////////////////////////////////////////////
// I guess this is not a full minkowski sum of the two input polygons by
// definition. This yields a subset that is compatible with the next 2
// algorithms.
using Result = NfpResult<RawShape>;
using Vertex = TPoint<RawShape>;
using Coord = TCoord<Vertex>;
using Edge = _Segment<Vertex>;
namespace sl = shapelike;
using std::signbit;
using std::sort;
using std::vector;
using std::ref;
using std::reference_wrapper;
// 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::contour(cstationary);
{
std::reverse(sl::begin(stcont), sl::end(stcont));
stcont.pop_back();
auto it = std::min_element(sl::begin(stcont), sl::end(stcont),
[](const Vertex& v1, const Vertex& v2) {
return getY(v1) < getY(v2);
});
std::rotate(sl::begin(stcont), it, sl::end(stcont));
sl::addVertex(stcont, sl::front(stcont));
}
RawShape stationary;
sl::contour(stationary) = stcont;
// Reverse the orbiter contour to counter clockwise
auto orbcont = sl::contour(cother);
{
std::reverse(orbcont.begin(), orbcont.end());
// Step 1: Make the orbiter reverse oriented
orbcont.pop_back();
auto it = std::min_element(orbcont.begin(), orbcont.end(),
[](const Vertex& v1, const Vertex& v2) {
return getY(v1) < getY(v2);
});
std::rotate(orbcont.begin(), it, orbcont.end());
orbcont.emplace_back(orbcont.front());
for(auto &v : orbcont) v = -v;
}
// Copy the orbiter (contour only), we will have to work on it
RawShape orbiter;
sl::contour(orbiter) = orbcont;
// An edge 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) {}
// debug
std::string label;
};
// 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& ppoly, int dir) {
auto& poly = sl::contour(ppoly);
L.reserve(sl::contourVertexCount(poly));
if(dir > 0) {
auto it = poly.begin();
auto nextit = std::next(it);
double turn_angle = 0;
bool is_turn_point = false;
while(nextit != poly.end()) {
L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
it++; nextit++;
}
} else {
auto it = sl::rbegin(poly);
auto nextit = std::next(it);
double turn_angle = 0;
bool is_turn_point = false;
while(nextit != sl::rend(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 turn_angle = phi-phi_prev;
if(turn_angle > Pi) turn_angle -= TwoPi;
if(turn_angle < -Pi) turn_angle += TwoPi;
return turn_angle;
};
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);
eit->is_turning_point =
signbit(enext->turn_angle) != signbit(eit->turn_angle);
++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);
int i = 1;
for(MarkedEdge& me : A) {
std::cout << "a" << i << ":\n\t"
<< getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
<< getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
<< "Turning point: " << (me.is_turning_point ? "yes" : "no")
<< std::endl;
me.label = "a"; me.label += std::to_string(i);
i++;
}
i = 1;
for(MarkedEdge& me : B) {
std::cout << "b" << i << ":\n\t"
<< getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
<< getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
<< "Turning point: " << (me.is_turning_point ? "yes" : "no")
<< std::endl;
me.label = "b"; me.label += std::to_string(i);
i++;
}
// 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) );
});
auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure
(const EdgeRefList& Q, const EdgeRefList& 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.size() + R.size());
merged.insert(merged.end(), R.begin(), R.end());
std::stable_sort(merged.begin(), merged.end(), sortfn);
merged.insert(merged.end(), Q.begin(), Q.end());
std::stable_sort(merged.begin(), merged.end(), sortfn);
// Step 2 "set i = 1, k = 1, direction = 1, s1 = q1"
// we don't 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.begin()->container.get();
const auto& Qcont = Q.begin()->container.get();
// Set the initial direction
Coord dir = 1;
// roughly i = 1 (so q = Q.begin()) and s1 = q1 so S[0] = q;
if(positive) {
auto q = Q.begin();
S.emplace_back(*q);
// Roughly step 3
std::cout << "merged size: " << merged.size() << std::endl;
auto mit = merged.begin();
for(bool finish = false; !finish && q != Q.end();) {
++q; // "Set i = i + 1"
while(!finish && mit != merged.end()) {
if(mit->isFrom(Rcont)) {
auto s = *mit;
s.dir = dir;
S.emplace_back(s);
}
if(mit->eq(*q)) {
S.emplace_back(*q);
if(mit->isTurningPoint()) dir = -dir;
if(q == Q.begin()) finish = true;
break;
}
mit += dir;
// __nfp::advance(mit, merged, dir > 0);
}
}
} else {
auto q = Q.rbegin();
S.emplace_back(*q);
// Roughly step 3
std::cout << "merged size: " << merged.size() << std::endl;
auto mit = merged.begin();
for(bool finish = false; !finish && q != Q.rend();) {
++q; // "Set i = i + 1"
while(!finish && mit != merged.end()) {
if(mit->isFrom(Rcont)) {
auto s = *mit;
s.dir = dir;
S.emplace_back(s);
}
if(mit->eq(*q)) {
S.emplace_back(*q);
S.back().dir = -1;
if(mit->isTurningPoint()) dir = -dir;
if(q == Q.rbegin()) finish = true;
break;
}
mit += dir;
// __nfp::advance(mit, merged, dir > 0);
}
}
}
// Step 4:
// "Let starting edge r1 be in position si in sequence"
// whaaat? I guess this means the following:
auto it = S.begin();
while(!it->eq(*R.begin())) ++it;
// "Set j = 1, next = 2, direction = 1, seq1 = si"
// we don't use j, seq is expanded dynamically.
dir = 1;
auto next = std::next(R.begin()); seq.emplace_back(*it);
// Step 5:
// "If all si edges have been allocated to seqj" should mean that
// we loop until seq has equal size with S
auto send = it; //it == S.begin() ? it : std::prev(it);
while(it != S.end()) {
++it; if(it == S.end()) it = S.begin();
if(it == send) break;
if(it->isFrom(Qcont)) {
seq.emplace_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;
// __nfp::advance(next, R, dir > 0);
}
}
if(it->eq(*next) /*&& dir == next->dir*/) { // "If si = direction.rnext"
// "j = j + 1, seqj = si, next = next + direction"
seq.emplace_back(*it);
next += dir;
// __nfp::advance(next, R, dir > 0);
}
}
return seq;
};
std::vector<EdgeRefList> seqlist;
seqlist.reserve(Bref.size());
EdgeRefList Bslope = Bref; // copy Bref, we will make a slope diagram
// make the slope diagram of B
std::sort(Bslope.begin(), Bslope.end(), sortfn);
auto slopeit = Bslope.begin(); // search for the first turning point
while(!slopeit->isTurningPoint() && slopeit != Bslope.end()) slopeit++;
if(slopeit == Bslope.end()) {
// no turning point means convex polygon.
seqlist.emplace_back(mink(Aref, Bref, true));
} else {
int dir = 1;
auto firstturn = Bref.begin();
while(!firstturn->eq(*slopeit)) ++firstturn;
assert(firstturn != Bref.end());
EdgeRefList bgroup; bgroup.reserve(Bref.size());
bgroup.emplace_back(*slopeit);
auto b_it = std::next(firstturn);
while(b_it != firstturn) {
if(b_it == Bref.end()) b_it = Bref.begin();
while(!slopeit->eq(*b_it)) {
__nfp::advance(slopeit, Bslope, dir > 0);
}
if(!slopeit->isTurningPoint()) {
bgroup.emplace_back(*slopeit);
} else {
if(!bgroup.empty()) {
if(dir > 0) bgroup.emplace_back(*slopeit);
for(auto& me : bgroup) {
std::cout << me.eref.get().label << ", ";
}
std::cout << std::endl;
seqlist.emplace_back(mink(Aref, bgroup, dir == 1 ? true : false));
bgroup.clear();
if(dir < 0) bgroup.emplace_back(*slopeit);
} else {
bgroup.emplace_back(*slopeit);
}
dir *= -1;
}
++b_it;
}
}
// 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
// /////////////////////////////////////////////////////////////////////////
for(auto& seq : seqlist) {
std::cout << "seqlist size: " << seq.size() << std::endl;
for(auto& s : seq) {
std::cout << (s.dir > 0 ? "" : "-") << s.eref.get().label << ", ";
}
std::cout << std::endl;
}
auto& seq = seqlist.front();
RawShape rsh;
Vertex top_nfp;
std::vector<Edge> edgelist; edgelist.reserve(seq.size());
for(auto& s : seq) {
edgelist.emplace_back(s.eref.get().e);
}
__nfp::buildPolygon(edgelist, rsh, top_nfp);
return Result(rsh, top_nfp);
}
// Specializable NFP implementation class. Specialize it if you have a faster
// or better NFP implementation
template<class RawShape, NfpLevel nfptype>
struct NfpImpl {
NfpResult<RawShape> operator()(const RawShape& sh, const RawShape& other)
{
static_assert(nfptype == NfpLevel::CONVEX_ONLY,
"Nfp::noFitPolygon() unimplemented!");
// Libnest2D has a default implementation for convex polygons and will
// use it if feasible.
return nfpConvexOnly(sh, other);
}
};
/// Helper function to get the NFP
template<NfpLevel nfptype, class RawShape>
inline NfpResult<RawShape> noFitPolygon(const RawShape& sh,
const RawShape& other)
{
NfpImpl<RawShape, nfptype> nfps;
return nfps(sh, other);
}
}
}
#endif // GEOMETRIES_NOFITPOLYGON_HPP

View file

@ -0,0 +1,144 @@
#ifndef LIBNEST2D_HPP
#define LIBNEST2D_HPP
// The type of backend should be set conditionally by the cmake configuriation
// for now we set it statically to clipper backend
#ifdef LIBNEST2D_GEOMETRIES_clipper
#include <libnest2d/backends/clipper/geometries.hpp>
#endif
#ifdef LIBNEST2D_GEOMETRIES_libslic3r
#include <libnest2d/backends/libslic3r/geometries.hpp>
#endif
#ifdef LIBNEST2D_OPTIMIZER_nlopt
// We include the stock optimizers for local and global optimization
#include <libnest2d/optimizers/nlopt/subplex.hpp> // Local subplex for NfpPlacer
#include <libnest2d/optimizers/nlopt/genetic.hpp> // Genetic for min. bounding box
#endif
#include <libnest2d/nester.hpp>
#include <libnest2d/placers/bottomleftplacer.hpp>
#include <libnest2d/placers/nfpplacer.hpp>
#include <libnest2d/selections/firstfit.hpp>
#include <libnest2d/selections/filler.hpp>
#include <libnest2d/selections/djd_heuristic.hpp>
namespace libnest2d {
using Point = PointImpl;
using Coord = TCoord<PointImpl>;
using Box = _Box<PointImpl>;
using Segment = _Segment<PointImpl>;
using Circle = _Circle<PointImpl>;
using MultiPolygon = TMultiShape<PolygonImpl>;
using Item = _Item<PolygonImpl>;
using Rectangle = _Rectangle<PolygonImpl>;
using PackGroup = _PackGroup<PolygonImpl>;
using FillerSelection = selections::_FillerSelection<PolygonImpl>;
using FirstFitSelection = selections::_FirstFitSelection<PolygonImpl>;
using DJDHeuristic = selections::_DJDHeuristic<PolygonImpl>;
template<class Bin> // Generic placer for arbitrary bin types
using _NfpPlacer = placers::_NofitPolyPlacer<PolygonImpl, Bin>;
// NfpPlacer is with Box bin
using NfpPlacer = _NfpPlacer<Box>;
// This supports only box shaped bins
using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>;
#ifdef LIBNEST2D_STATIC
extern template class _Nester<NfpPlacer, FirstFitSelection>;
extern template class _Nester<BottomLeftPlacer, FirstFitSelection>;
extern template std::size_t _Nester<NfpPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
extern template std::size_t _Nester<BottomLeftPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
#endif
template<class Placer = NfpPlacer, class Selector = FirstFitSelection>
struct NestConfig {
typename Placer::Config placer_config;
typename Selector::Config selector_config;
using Placement = typename Placer::Config;
using Selection = typename Selector::Config;
NestConfig() = default;
NestConfig(const typename Placer::Config &cfg) : placer_config{cfg} {}
NestConfig(const typename Selector::Config &cfg) : selector_config{cfg} {}
NestConfig(const typename Placer::Config & pcfg,
const typename Selector::Config &scfg)
: placer_config{pcfg}, selector_config{scfg} {}
};
struct NestControl {
ProgressFunction progressfn;
StopCondition stopcond = []{ return false; };
NestControl() = default;
NestControl(ProgressFunction pr) : progressfn{std::move(pr)} {}
NestControl(StopCondition sc) : stopcond{std::move(sc)} {}
NestControl(ProgressFunction pr, StopCondition sc)
: progressfn{std::move(pr)}, stopcond{std::move(sc)}
{}
};
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Iterator = std::vector<Item>::iterator>
std::size_t nest(Iterator from, Iterator to,
const typename Placer::BinType & bin,
Coord dist = 0,
const NestConfig<Placer, Selector> &cfg = {},
NestControl ctl = {})
{
_Nester<Placer, Selector> nester{bin, dist, cfg.placer_config, cfg.selector_config};
if(ctl.progressfn) nester.progressIndicator(ctl.progressfn);
if(ctl.stopcond) nester.stopCondition(ctl.stopcond);
return nester.execute(from, to);
}
#ifdef LIBNEST2D_STATIC
extern template class _Nester<NfpPlacer, FirstFitSelection>;
extern template class _Nester<BottomLeftPlacer, FirstFitSelection>;
extern template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box & bin,
Coord dist,
const NestConfig<NfpPlacer, FirstFitSelection> &cfg,
NestControl ctl);
extern template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box & bin,
Coord dist,
const NestConfig<BottomLeftPlacer, FirstFitSelection> &cfg,
NestControl ctl);
#endif
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Container = std::vector<Item>>
std::size_t nest(Container&& cont,
const typename Placer::BinType & bin,
Coord dist = 0,
const NestConfig<Placer, Selector> &cfg = {},
NestControl ctl = {})
{
return nest<Placer, Selector>(cont.begin(), cont.end(), bin, dist, cfg, ctl);
}
template<class T = double> enable_if_t<std::is_arithmetic<T>::value, TCoord<PointImpl>> mm(T val = T(1))
{
return TCoord<PointImpl>(val * CoordType<PointImpl>::MM_IN_COORDS);
}
}
#endif // LIBNEST2D_HPP

View file

@ -0,0 +1,894 @@
#ifndef NESTER_HPP
#define NESTER_HPP
#include <memory>
#include <vector>
#include <map>
#include <array>
#include <algorithm>
#include <functional>
#include <libnest2d/geometry_traits.hpp>
#define LARGE_COST_TO_REJECT 1e7
namespace libnest2d {
static const constexpr int BIN_ID_UNSET = -1;
static const constexpr int BIN_ID_UNFIT = -1;
/**
* \brief An item to be placed on a bin.
*
* It holds a copy of the original shape object but supports move construction
* from the shape objects if its an rvalue reference. This way we can construct
* the items without the cost of copying a potentially large amount of input.
*
* The results of some calculations are cached for maintaining fast run times.
* For this reason, memory demands are much higher but this should pay off.
*/
template<class RawShape>
class _Item {
using Coord = TCoord<TPoint<RawShape>>;
using Vertex = TPoint<RawShape>;
using Box = _Box<Vertex>;
using VertexConstIterator = typename TContour<RawShape>::const_iterator;
// The original shape that gets encapsulated.
RawShape sh_;
// Transformation data
Vertex translation_{0, 0};
Radians rotation_{0.0};
Coord inflation_{0};
// Info about whether the transformations will have to take place
// This is needed because if floating point is used, it is hard to say
// that a zero angle is not a rotation because of testing for equality.
bool has_rotation_ = false, has_translation_ = false, has_inflation_ = false;
// For caching the calculations as they can get pretty expensive.
mutable RawShape tr_cache_;
mutable bool tr_cache_valid_ = false;
mutable double area_cache_ = 0;
mutable bool area_cache_valid_ = false;
mutable RawShape inflate_cache_;
mutable bool inflate_cache_valid_ = false;
enum class Convexity: char {
UNCHECKED,
C_TRUE,
C_FALSE
};
mutable Convexity convexity_ = Convexity::UNCHECKED;
mutable VertexConstIterator rmt_; // rightmost top vertex
mutable VertexConstIterator lmb_; // leftmost bottom vertex
mutable bool rmt_valid_ = false, lmb_valid_ = false;
mutable struct BBCache {
Box bb; bool valid;
BBCache(): valid(false) {}
} bb_cache_;
int binid_{BIN_ID_UNSET}, priority_{0};
bool fixed_{false};
public:
int itemid_{ 0 };
int extrude_id{ 1 };
double height{ 0 };
double print_temp{ 0 };
double bed_temp{ 0 };
double vitrify_temp{ 0 }; // vitrify temperature
std::string name;
//BBS: virtual object to mark unprintable region on heatbed
bool is_virt_object{ false };
bool is_wipe_tower{ false };
/// The type of the shape which was handed over as the template argument.
using ShapeType = RawShape;
/**
* \brief Iterator type for the outer vertices.
*
* Only const iterators can be used. The _Item type is not intended to
* modify the carried shapes from the outside. The main purpose of this type
* is to cache the calculation results from the various operators it
* supports. Giving out a non const iterator would make it impossible to
* perform correct cache invalidation.
*/
using Iterator = VertexConstIterator;
/**
* @brief Get the orientation of the polygon.
*
* The orientation have to be specified as a specialization of the
* OrientationType struct which has a Value constant.
*
* @return The orientation type identifier for the _Item type.
*/
static BP2D_CONSTEXPR Orientation orientation() {
return OrientationType<TContour<RawShape>>::Value;
}
/**
* @brief Constructing an _Item form an existing raw shape. The shape will
* be copied into the _Item object.
* @param sh The original shape object.
*/
explicit inline _Item(const RawShape& sh): sh_(sh) {}
/**
* @brief Construction of an item by moving the content of the raw shape,
* assuming that it supports move semantics.
* @param sh The original shape object.
*/
explicit inline _Item(RawShape&& sh): sh_(std::move(sh)) {}
/**
* @brief Create an item from an initializer list.
* @param il The initializer list of vertices.
*/
inline _Item(const std::initializer_list< Vertex >& il):
sh_(sl::create<RawShape>(il)) {}
inline _Item(const TContour<RawShape>& contour,
const THolesContainer<RawShape>& holes = {}):
sh_(sl::create<RawShape>(contour, holes)) {}
inline _Item(TContour<RawShape>&& contour,
THolesContainer<RawShape>&& holes):
sh_(sl::create<RawShape>(std::move(contour), std::move(holes))) {}
inline bool isFixed() const noexcept { return fixed_; }
inline void markAsFixedInBin(int binid)
{
fixed_ = binid >= 0;
binid_ = binid;
}
inline void binId(int idx) { binid_ = idx; }
inline int binId() const noexcept { return binid_; }
inline void priority(int p) { priority_ = p; }
inline int priority() const noexcept { return priority_; }
inline void itemId(int idx) { itemid_ = idx; }
inline int itemId() const noexcept { return itemid_; }
/**
* @brief Convert the polygon to string representation. The format depends
* on the implementation of the polygon.
* @return
*/
inline std::string toString() const
{
return sl::toString(sh_);
}
/// Iterator tho the first contour vertex in the polygon.
inline Iterator begin() const
{
return sl::cbegin(sh_);
}
/// Alias to begin()
inline Iterator cbegin() const
{
return sl::cbegin(sh_);
}
/// Iterator to the last contour vertex.
inline Iterator end() const
{
return sl::cend(sh_);
}
/// Alias to end()
inline Iterator cend() const
{
return sl::cend(sh_);
}
/**
* @brief Get a copy of an outer vertex within the carried shape.
*
* Note that the vertex considered here is taken from the original shape
* that this item is constructed from. This means that no transformation is
* applied to the shape in this call.
*
* @param idx The index of the requested vertex.
* @return A copy of the requested vertex.
*/
inline Vertex vertex(unsigned long idx) const
{
return sl::vertex(sh_, idx);
}
/**
* @brief Modify a vertex.
*
* Note that this method will invalidate every cached calculation result
* including polygon offset and transformations.
*
* @param idx The index of the requested vertex.
* @param v The new vertex data.
*/
inline void setVertex(unsigned long idx, const Vertex& v )
{
invalidateCache();
sl::vertex(sh_, idx) = v;
}
/**
* @brief Calculate the shape area.
*
* The method returns absolute value and does not reflect polygon
* orientation. The result is cached, subsequent calls will have very little
* cost.
* @return The shape area in floating point double precision.
*/
inline double area() const {
double ret ;
if(area_cache_valid_) ret = area_cache_;
else {
ret = sl::area(infaltedShape());
area_cache_ = ret;
area_cache_valid_ = true;
}
return ret;
}
inline bool isContourConvex() const {
bool ret = false;
switch(convexity_) {
case Convexity::UNCHECKED:
ret = sl::isConvex(sl::contour(transformedShape()));
convexity_ = ret? Convexity::C_TRUE : Convexity::C_FALSE;
break;
case Convexity::C_TRUE: ret = true; break;
case Convexity::C_FALSE:;
}
return ret;
}
inline bool isHoleConvex(unsigned /*holeidx*/) const {
return false;
}
inline bool areHolesConvex() const {
return false;
}
/// The number of the outer ring vertices.
inline size_t vertexCount() const {
return sl::contourVertexCount(sh_);
}
inline size_t holeCount() const {
return sl::holeCount(sh_);
}
/**
* @brief isPointInside
* @param p
* @return
*/
inline bool isInside(const Vertex& p) const
{
return sl::isInside(p, transformedShape());
}
inline bool isInside(const _Item& sh) const
{
return sl::isInside(transformedShape(), sh.transformedShape());
}
inline bool isInside(const RawShape& sh) const
{
return sl::isInside(transformedShape(), sh);
}
inline bool isInside(const _Box<TPoint<RawShape>>& box) const;
inline bool isInside(const _Circle<TPoint<RawShape>>& box) const;
inline void translate(const Vertex& d) BP2D_NOEXCEPT
{
translation(translation() + d);
}
inline void rotate(const Radians& rads) BP2D_NOEXCEPT
{
rotation(rotation() + rads);
}
inline void inflation(Coord distance) BP2D_NOEXCEPT
{
inflation_ = distance;
has_inflation_ = true;
invalidateCache();
}
inline Coord inflation() const BP2D_NOEXCEPT {
return inflation_;
}
inline void inflate(Coord distance) BP2D_NOEXCEPT
{
inflation(inflation() + distance);
}
inline Radians rotation() const BP2D_NOEXCEPT
{
return rotation_;
}
inline TPoint<RawShape> translation() const BP2D_NOEXCEPT
{
return translation_;
}
inline void rotation(Radians rot) BP2D_NOEXCEPT
{
if(rotation_ != rot) {
rotation_ = rot; has_rotation_ = true; tr_cache_valid_ = false;
rmt_valid_ = false; lmb_valid_ = false;
bb_cache_.valid = false;
}
}
inline void translation(const TPoint<RawShape>& tr) BP2D_NOEXCEPT
{
if(translation_ != tr) {
translation_ = tr; has_translation_ = true; tr_cache_valid_ = false;
//bb_cache_.valid = false;
}
}
inline const RawShape& transformedShape() const
{
if(tr_cache_valid_) return tr_cache_;
RawShape cpy = infaltedShape();
if(has_rotation_) sl::rotate(cpy, rotation_);
if(has_translation_) sl::translate(cpy, translation_);
tr_cache_ = cpy; tr_cache_valid_ = true;
rmt_valid_ = false; lmb_valid_ = false;
return tr_cache_;
}
inline operator RawShape() const
{
return transformedShape();
}
inline const RawShape& rawShape() const BP2D_NOEXCEPT
{
return sh_;
}
inline void resetTransformation() BP2D_NOEXCEPT
{
has_translation_ = false; has_rotation_ = false; has_inflation_ = false;
invalidateCache();
}
inline Box boundingBox() const {
if(!bb_cache_.valid) {
if(!has_rotation_)
bb_cache_.bb = sl::boundingBox(infaltedShape());
else {
// TODO make sure this works
auto rotsh = infaltedShape();
sl::rotate(rotsh, rotation_);
bb_cache_.bb = sl::boundingBox(rotsh);
}
bb_cache_.valid = true;
}
auto &bb = bb_cache_.bb; auto &tr = translation_;
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:
inline static bool intersects(const _Item& sh1, const _Item& sh2)
{
return sl::intersects(sh1.transformedShape(),
sh2.transformedShape());
}
inline static bool touches(const _Item& sh1, const _Item& sh2)
{
return sl::touches(sh1.transformedShape(),
sh2.transformedShape());
}
private:
inline const RawShape& infaltedShape() const {
if(has_inflation_ ) {
if(inflate_cache_valid_) return inflate_cache_;
inflate_cache_ = sh_;
sl::offset(inflate_cache_, inflation_);
inflate_cache_valid_ = true;
return inflate_cache_;
}
return sh_;
}
inline void invalidateCache() const BP2D_NOEXCEPT
{
tr_cache_valid_ = false;
lmb_valid_ = false; rmt_valid_ = false;
area_cache_valid_ = false;
inflate_cache_valid_ = false;
bb_cache_.valid = false;
convexity_ = Convexity::UNCHECKED;
}
static inline bool vsort(const Vertex& v1, const Vertex& v2)
{
TCompute<Vertex> x1 = getX(v1), x2 = getX(v2);
TCompute<Vertex> y1 = getY(v1), y2 = getY(v2);
return y1 == y2 ? x1 < x2 : y1 < y2;
}
};
template<class Sh> Sh create_rect(TCoord<Sh> width, TCoord<Sh> height)
{
auto sh = sl::create<Sh>(
{{0, 0}, {0, height}, {width, height}, {width, 0}});
if constexpr (ClosureTypeV<Sh> == Closure::CLOSED)
sl::addVertex(sh, {0, 0});
if constexpr (OrientationTypeV<Sh> == Orientation::COUNTER_CLOCKWISE)
std::reverse(sl::begin(sh), sl::end(sh));
return sh;
}
/**
* \brief Subclass of _Item for regular rectangle items.
*/
template<class Sh>
class _Rectangle: public _Item<Sh> {
using _Item<Sh>::vertex;
using TO = Orientation;
public:
using Unit = TCoord<Sh>;
inline _Rectangle(Unit w, Unit h): _Item<Sh>{create_rect<Sh>(w, h)} {}
inline Unit width() const BP2D_NOEXCEPT {
return getX(vertex(2));
}
inline Unit height() const BP2D_NOEXCEPT {
return getY(vertex(2));
}
};
template<class RawShape>
inline bool _Item<RawShape>::isInside(const _Box<TPoint<RawShape>>& box) const {
return sl::isInside(boundingBox(), box);
}
template<class RawShape> inline bool
_Item<RawShape>::isInside(const _Circle<TPoint<RawShape>>& circ) const {
return sl::isInside(transformedShape(), circ);
}
template<class RawShape> using _ItemRef = std::reference_wrapper<_Item<RawShape>>;
template<class RawShape> using _ItemGroup = std::vector<_ItemRef<RawShape>>;
/**
* \brief A list of packed item vectors. Each vector represents a bin.
*/
template<class RawShape>
using _PackGroup = std::vector<std::vector<_ItemRef<RawShape>>>;
template<class Iterator>
struct ConstItemRange {
Iterator from;
Iterator to;
bool valid = false;
ConstItemRange() = default;
ConstItemRange(Iterator f, Iterator t): from(f), to(t), valid(true) {}
};
template<class Container>
inline ConstItemRange<typename Container::const_iterator>
rem(typename Container::const_iterator it, const Container& cont) {
return {std::next(it), cont.end()};
}
/**
* \brief A wrapper interface (trait) class for any placement strategy provider.
*
* If a client wants to use its own placement algorithm, all it has to do is to
* specialize this class template and define all the ten methods it has. It can
* use the strategies::PlacerBoilerplace class for creating a new placement
* strategy where only the constructor and the trypack method has to be provided
* and it will work out of the box.
*/
template<class PlacementStrategy>
class PlacementStrategyLike {
PlacementStrategy impl_;
public:
using RawShape = typename PlacementStrategy::ShapeType;
/// The item type that the placer works with.
using Item = _Item<RawShape>;
/// The placer's config type. Should be a simple struct but can be anything.
using Config = typename PlacementStrategy::Config;
/**
* \brief The type of the bin that the placer works with.
*
* Can be a box or an arbitrary shape or just a width or height without a
* second dimension if an infinite bin is considered.
*/
using BinType = typename PlacementStrategy::BinType;
/**
* \brief Pack result that can be used to accept or discard it. See trypack
* method.
*/
using PackResult = typename PlacementStrategy::PackResult;
using ItemGroup = _ItemGroup<RawShape>;
using DefaultIterator = typename ItemGroup::const_iterator;
/**
* @brief Constructor taking the bin and an optional configuration.
* @param bin The bin object whose type is defined by the placement strategy.
* @param config The configuration for the particular placer.
*/
explicit PlacementStrategyLike(const BinType& bin,
const Config& config = Config()):
impl_(bin)
{
configure(config);
}
/**
* @brief Provide a different configuration for the placer.
*
* Note that it depends on the particular placer implementation how it
* reacts to config changes in the middle of a calculation.
*
* @param config The configuration object defined by the placement strategy.
*/
inline void configure(const Config& config) { impl_.configure(config); }
/**
* Try to pack an item with a result object that contains the packing
* information for later accepting it.
*
* \param item_store A container of items that are intended to be packed
* later. Can be used by the placer to switch tactics. When it's knows that
* many items will come a greedy strategy may not be the best.
* \param from The iterator to the item from which the packing should start,
* including the pointed item
* \param count How many items should be packed. If the value is 1, than
* just the item pointed to by "from" argument should be packed.
*/
template<class Iter = DefaultIterator>
inline PackResult trypack(
Item& item,
const ConstItemRange<Iter>& remaining = ConstItemRange<Iter>())
{
return impl_.trypack(item, remaining);
}
/**
* @brief A method to accept a previously tried item (or items).
*
* If the pack result is a failure the method should ignore it.
* @param r The result of a previous trypack call.
*/
inline void accept(PackResult& r) { impl_.accept(r); }
/**
* @brief pack Try to pack and immediately accept it on success.
*
* A default implementation would be to call
* { auto&& r = trypack(...); accept(r); return r; } but we should let the
* implementor of the placement strategy to harvest any optimizations from
* the absence of an intermediate step. The above version can still be used
* in the implementation.
*
* @param item The item to pack.
* @return Returns true if the item was packed or false if it could not be
* packed.
*/
template<class Range = ConstItemRange<DefaultIterator>>
inline PackResult pack(
Item& item,
const Range& remaining = Range())
{
return impl_.pack(item, remaining);
}
/**
* This method makes possible to "preload" some items into the placer. It
* will not move these items but will consider them as already packed.
*/
inline void preload(const ItemGroup& packeditems)
{
impl_.preload(packeditems);
}
/// Unpack the last element (remove it from the list of packed items).
inline void unpackLast() { impl_.unpackLast(); }
/// Get the bin object.
inline const BinType& bin() const { return impl_.bin(); }
/// Set a new bin object.
inline void bin(const BinType& bin) { impl_.bin(bin); }
/// Get the packed items.
inline ItemGroup getItems() { return impl_.getItems(); }
/// Clear the packed items so a new session can be started.
inline void clearItems() { impl_.clearItems(); }
inline double filledArea() const { return impl_.filledArea(); }
inline double score() const { return impl_.score(); }
inline void plateID(int id) { impl_.plateID(id); }
inline int plateID() { return impl_.plateID(); }
};
// The progress function will be called with the number of placed items
using ProgressFunction = std::function<void(unsigned)>;
using StopCondition = std::function<bool(void)>;
using UnfitIndicator = std::function<void(std::string)>;
/**
* A wrapper interface (trait) class for any selections strategy provider.
*/
template<class SelectionStrategy>
class SelectionStrategyLike {
SelectionStrategy impl_;
public:
using RawShape = typename SelectionStrategy::ShapeType;
using Item = _Item<RawShape>;
using PackGroup = _PackGroup<RawShape>;
using Config = typename SelectionStrategy::Config;
/**
* @brief Provide a different configuration for the selection strategy.
*
* Note that it depends on the particular placer implementation how it
* reacts to config changes in the middle of a calculation.
*
* @param config The configuration object defined by the selection strategy.
*/
inline void configure(const Config& config) {
impl_.configure(config);
}
/**
* @brief A function callback which should be called whenever an item or
* a group of items where successfully packed.
* @param fn A function callback object taking one unsigned integer as the
* number of the remaining items to pack.
*/
void progressIndicator(ProgressFunction fn) { impl_.progressIndicator(fn); }
//BBS
void unfitIndicator(UnfitIndicator fn) { impl_.unfitIndicator(fn); }
void stopCondition(StopCondition cond) { impl_.stopCondition(cond); }
/**
* \brief A method to start the calculation on the input sequence.
*
* \tparam TPlacer The only mandatory template parameter is the type of
* placer compatible with the PlacementStrategyLike interface.
*
* \param first, last The first and last iterator if the input sequence. It
* can be only an iterator of a type convertible to Item.
* \param bin. The shape of the bin. It has to be supported by the placement
* strategy.
* \param An optional config object for the placer.
*/
template<class TPlacer, class TIterator,
class TBin = typename PlacementStrategyLike<TPlacer>::BinType,
class PConfig = typename PlacementStrategyLike<TPlacer>::Config>
inline void packItems(
TIterator first,
TIterator last,
TBin&& bin,
PConfig&& config = PConfig() )
{
impl_.template packItems<TPlacer>(first, last,
std::forward<TBin>(bin),
std::forward<PConfig>(config));
}
/**
* @brief Get the items for a particular bin.
* @param binIndex The index of the requested bin.
* @return Returns a list of all items packed into the requested bin.
*/
inline const PackGroup& getResult() const {
return impl_.getResult();
}
inline int lastPackedBinId() const {
return impl_.lastPackedBinId();
}
void clear() { impl_.clear(); }
};
/**
* The _Nester is the front-end class for the libnest2d library. It takes the
* input items and changes their transformations to be inside the provided bin.
*/
template<class PlacementStrategy, class SelectionStrategy >
class _Nester {
using TSel = SelectionStrategyLike<SelectionStrategy>;
TSel selector_;
public:
using Item = typename PlacementStrategy::Item;
using ShapeType = typename Item::ShapeType;
using ItemRef = std::reference_wrapper<Item>;
using TPlacer = PlacementStrategyLike<PlacementStrategy>;
using BinType = typename TPlacer::BinType;
using PlacementConfig = typename TPlacer::Config;
using SelectionConfig = typename TSel::Config;
using Coord = TCoord<TPoint<typename Item::ShapeType>>;
using PackGroup = _PackGroup<typename Item::ShapeType>;
using ResultType = PackGroup;
private:
BinType bin_;
PlacementConfig pconfig_;
Coord min_obj_distance_;
using SItem = typename SelectionStrategy::Item;
using TPItem = remove_cvref_t<Item>;
using TSItem = remove_cvref_t<SItem>;
StopCondition stopfn_;
template<class It> using TVal = remove_ref_t<typename It::value_type>;
template<class It, class Out>
using ItemIteratorOnly =
enable_if_t<std::is_convertible<TVal<It>&, TPItem&>::value, Out>;
public:
/**
* \brief Constructor taking the bin as the only mandatory parameter.
*
* \param bin The bin shape that will be used by the placers. The type
* of the bin should be one that is supported by the placer type.
*/
template<class TBinType = BinType,
class PConf = PlacementConfig,
class SConf = SelectionConfig>
_Nester(TBinType&& bin, Coord min_obj_distance = 0,
const PConf& pconfig = PConf(), const SConf& sconfig = SConf()):
bin_(std::forward<TBinType>(bin)),
pconfig_(pconfig),
min_obj_distance_(min_obj_distance)
{
static_assert( std::is_same<TPItem, TSItem>::value,
"Incompatible placement and selection strategy!");
selector_.configure(sconfig);
}
void configure(const PlacementConfig& pconf) { pconfig_ = pconf; }
void configure(const SelectionConfig& sconf) { selector_.configure(sconf); }
void configure(const PlacementConfig& pconf, const SelectionConfig& sconf)
{
pconfig_ = pconf;
selector_.configure(sconf);
}
void configure(const SelectionConfig& sconf, const PlacementConfig& pconf)
{
pconfig_ = pconf;
selector_.configure(sconf);
}
/**
* \brief Arrange an input sequence of _Item-s.
*
* To get the result, call the translation(), rotation() and binId()
* methods of each item. If only the transformed polygon is needed, call
* transformedShape() to get the properly transformed shapes.
*
* The number of groups in the pack group is the number of bins opened by
* the selection algorithm.
*/
template<class It>
inline ItemIteratorOnly<It, size_t> execute(It from, It to)
{
auto infl = static_cast<Coord>(std::ceil(min_obj_distance_/2.0));
if(infl > 0) std::for_each(from, to, [infl](Item& item) {
item.inflate(infl);
});
selector_.template packItems<PlacementStrategy>(
from, to, bin_, pconfig_);
if(min_obj_distance_ > 0) std::for_each(from, to, [infl](Item& item) {
item.inflate(-infl);
});
return selector_.getResult().size();
}
/// Set a progress indicator function object for the selector.
inline _Nester& progressIndicator(ProgressFunction func)
{
selector_.progressIndicator(func); return *this;
}
/// BBS: Set unfit indicator function object for the selector.
inline _Nester& unfitIndicator(UnfitIndicator func)
{
selector_.unfitIndicator(func); return *this;
}
/// Set a predicate to tell when to abort nesting.
inline _Nester& stopCondition(StopCondition fn)
{
stopfn_ = fn; selector_.stopCondition(fn); return *this;
}
inline const PackGroup& lastResult() const
{
return selector_.getResult();
}
inline int lastPackedBinId() const {
return selector_.lastPackedBinId();
}
};
}
#endif // NESTER_HPP

View file

@ -0,0 +1,254 @@
#ifndef OPTIMIZER_HPP
#define OPTIMIZER_HPP
#include <tuple>
#include <functional>
#include <limits>
#include <libnest2d/common.hpp>
namespace libnest2d { namespace opt {
using std::forward;
using std::tuple;
using std::make_tuple;
/// A Type trait for upper and lower limit of a numeric type.
template<class T, class B = void >
struct limits {
inline static T min() { return std::numeric_limits<T>::min(); }
inline static T max() { return std::numeric_limits<T>::max(); }
};
template<class T>
struct limits<T, enable_if_t<std::numeric_limits<T>::has_infinity, void>> {
inline static T min() { return -std::numeric_limits<T>::infinity(); }
inline static T max() { return std::numeric_limits<T>::infinity(); }
};
/// An interval of possible input values for optimization
template<class T>
class Bound {
T min_;
T max_;
public:
Bound(const T& min = limits<T>::min(),
const T& max = limits<T>::max()): min_(min), max_(max) {}
inline const T min() const BP2D_NOEXCEPT { return min_; }
inline const T max() const BP2D_NOEXCEPT { return max_; }
};
/**
* Helper function to make a Bound object with its type deduced automatically.
*/
template<class T>
inline Bound<T> bound(const T& min, const T& max) { return Bound<T>(min, max); }
/**
* This is the type of an input tuple for the object function. It holds the
* values and their type in each dimension.
*/
template<class...Args> using Input = tuple<Args...>;
template<class...Args>
inline tuple<Args...> initvals(Args...args) { return make_tuple(args...); }
/**
* @brief Specific optimization methods for which a default optimizer
* implementation can be instantiated.
*/
enum class Method {
L_SIMPLEX,
L_SUBPLEX,
G_GENETIC,
G_PARTICLE_SWARM
//...
};
/**
* @brief Info about result of an optimization. These codes are exactly the same
* as the nlopt codes for convinience.
*/
enum ResultCodes {
FAILURE = -1, /* generic failure code */
INVALID_ARGS = -2,
OUT_OF_MEMORY = -3,
ROUNDOFF_LIMITED = -4,
FORCED_STOP = -5,
SUCCESS = 1, /* generic success code */
STOPVAL_REACHED = 2,
FTOL_REACHED = 3,
XTOL_REACHED = 4,
MAXEVAL_REACHED = 5,
MAXTIME_REACHED = 6
};
/**
* \brief A type to hold the complete result of the optimization.
*/
template<class...Args>
struct Result {
ResultCodes resultcode;
tuple<Args...> optimum;
double score;
};
/**
* @brief A type for specifying the stop criteria.
*/
struct StopCriteria {
/// If the absolute value difference between two scores.
double absolute_score_difference = std::nan("");
/// If the relative value difference between two scores.
double relative_score_difference = std::nan("");
/// Stop if this value or better is found.
double stop_score = std::nan("");
/// A predicate that if evaluates to true, the optimization should terminate
/// and the best result found prior to termination should be returned.
std::function<bool()> stop_condition = [] { return false; };
/// The max allowed number of iterations.
unsigned max_iterations = 0;
};
/**
* \brief The Optimizer base class with CRTP pattern.
*/
template<class Subclass>
class Optimizer {
protected:
enum class OptDir{
MIN,
MAX
} dir_;
StopCriteria stopcr_;
public:
inline explicit Optimizer(const StopCriteria& scr = {}): stopcr_(scr) {}
/**
* \brief Optimize for minimum value of the provided objectfunction.
* \param objectfunction The function that will be searched for the minimum
* return value.
* \param initvals A tuple with the initial values for the search
* \param bounds A parameter pack with the bounds for each dimension.
* \return Returns a Result<Args...> structure.
* An example call would be:
* auto result = opt.optimize_min(
* [](tuple<double> x) // object function
* {
* return std::pow(std::get<0>(x), 2);
* },
* make_tuple(-0.5), // initial value
* {-1.0, 1.0} // search space bounds
* );
*/
template<class Func, class...Args>
inline Result<Args...> optimize_min(Func&& objectfunction,
Input<Args...> initvals,
Bound<Args>... bounds)
{
dir_ = OptDir::MIN;
return static_cast<Subclass*>(this)->template optimize<Func, Args...>(
forward<Func>(objectfunction), initvals, bounds... );
}
template<class Func, class...Args>
inline Result<Args...> optimize_min(Func&& objectfunction,
Input<Args...> initvals)
{
dir_ = OptDir::MIN;
return static_cast<Subclass*>(this)->template optimize<Func, Args...>(
forward<Func>(objectfunction), initvals, Bound<Args>()... );
}
template<class...Args, class Func>
inline Result<Args...> optimize_min(Func&& objectfunction)
{
dir_ = OptDir::MIN;
return static_cast<Subclass*>(this)->template optimize<Func, Args...>(
forward<Func>(objectfunction),
Input<Args...>(),
Bound<Args>()... );
}
/// Same as optimize_min but optimizes for maximum function value.
template<class Func, class...Args>
inline Result<Args...> optimize_max(Func&& objectfunction,
Input<Args...> initvals,
Bound<Args>... bounds)
{
dir_ = OptDir::MAX;
return static_cast<Subclass*>(this)->template optimize<Func, Args...>(
forward<Func>(objectfunction), initvals, bounds... );
}
template<class Func, class...Args>
inline Result<Args...> optimize_max(Func&& objectfunction,
Input<Args...> initvals)
{
dir_ = OptDir::MAX;
return static_cast<Subclass*>(this)->template optimize<Func, Args...>(
forward<Func>(objectfunction), initvals, Bound<Args>()... );
}
template<class...Args, class Func>
inline Result<Args...> optimize_max(Func&& objectfunction)
{
dir_ = OptDir::MAX;
return static_cast<Subclass*>(this)->template optimize<Func, Args...>(
forward<Func>(objectfunction),
Input<Args...>(),
Bound<Args>()... );
}
};
// Just to be able to instantiate an unimplemented method and generate compile
// error.
template<class T = void>
class DummyOptimizer : public Optimizer<DummyOptimizer<T>> {
friend class Optimizer<DummyOptimizer<T>>;
public:
DummyOptimizer() {
static_assert(always_false<T>::value, "Optimizer unimplemented!");
}
DummyOptimizer(const StopCriteria&) {
static_assert(always_false<T>::value, "Optimizer unimplemented!");
}
template<class Func, class...Args>
Result<Args...> optimize(Func&& /*func*/,
tuple<Args...> /*initvals*/,
Bound<Args>... /*args*/)
{
return Result<Args...>();
}
};
// Specializing this struct will tell what kind of optimizer to generate for
// a given method
template<Method m> struct OptimizerSubclass { using Type = DummyOptimizer<>; };
/// Optimizer type based on the method provided in parameter m.
template<Method m> using TOptimizer = typename OptimizerSubclass<m>::Type;
/// Global optimizer with an explicitly specified local method.
template<Method m>
inline TOptimizer<m> GlobalOptimizer(Method, const StopCriteria& scr = {})
{ // Need to be specialized in order to do anything useful.
return TOptimizer<m>(scr);
}
}
}
#endif // OPTIMIZER_HPP

View file

@ -0,0 +1,34 @@
#ifndef GENETIC_HPP
#define GENETIC_HPP
#include "nlopt_boilerplate.hpp"
namespace libnest2d { namespace opt {
class GeneticOptimizer: public NloptOptimizer {
public:
inline explicit GeneticOptimizer(const StopCriteria& scr = {}):
NloptOptimizer(method2nloptAlg(Method::G_GENETIC), scr) {}
inline GeneticOptimizer& localMethod(Method m) {
localmethod_ = m;
return *this;
}
inline void seed(unsigned long val) { nlopt::srand(val); }
};
template<>
struct OptimizerSubclass<Method::G_GENETIC> { using Type = GeneticOptimizer; };
template<>
inline TOptimizer<Method::G_GENETIC> GlobalOptimizer<Method::G_GENETIC>(
Method localm, const StopCriteria& scr )
{
return GeneticOptimizer (scr).localMethod(localm);
}
}
}
#endif // GENETIC_HPP

View file

@ -0,0 +1,200 @@
#ifndef 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>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#include <libnest2d/optimizer.hpp>
#include <cassert>
#include <libnest2d/utils/metaloop.hpp>
#include <utility>
namespace libnest2d { namespace opt {
inline nlopt::algorithm method2nloptAlg(Method m) {
switch(m) {
case Method::L_SIMPLEX: return nlopt::LN_NELDERMEAD;
case Method::L_SUBPLEX: return nlopt::LN_SBPLX;
case Method::G_GENETIC: return nlopt::GN_ESCH;
default: assert(false); throw(m);
}
}
/**
* Optimizer based on NLopt.
*
* All the optimized types have to be convertible to double.
*/
class NloptOptimizer: public Optimizer<NloptOptimizer> {
protected:
nlopt::opt opt_;
std::vector<double> lower_bounds_;
std::vector<double> upper_bounds_;
std::vector<double> initvals_;
nlopt::algorithm alg_;
Method localmethod_;
using Base = Optimizer<NloptOptimizer>;
friend Base;
// ********************************************************************** */
// TODO: CHANGE FOR LAMBDAS WHEN WE WILL MOVE TO C++14
struct BoundsFunc {
NloptOptimizer& self;
inline explicit BoundsFunc(NloptOptimizer& o): self(o) {}
template<class T> void operator()(int N, T& bounds)
{
self.lower_bounds_[N] = bounds.min();
self.upper_bounds_[N] = bounds.max();
}
};
struct InitValFunc {
NloptOptimizer& self;
inline explicit InitValFunc(NloptOptimizer& o): self(o) {}
template<class T> void operator()(int N, T& initval)
{
self.initvals_[N] = initval;
}
};
struct ResultCopyFunc {
NloptOptimizer& self;
inline explicit ResultCopyFunc(NloptOptimizer& o): self(o) {}
template<class T> void operator()(int N, T& resultval)
{
resultval = self.initvals_[N];
}
};
struct FunvalCopyFunc {
using D = const std::vector<double>;
D& params;
inline explicit FunvalCopyFunc(D& p): params(p) {}
template<class T> void operator()(int N, T& resultval)
{
resultval = params[N];
}
};
/* ********************************************************************** */
template<class Fn, class...Args>
static double optfunc(const std::vector<double>& params,
std::vector<double>& /*grad*/,
void *data)
{
using TData = std::pair<remove_ref_t<Fn>*, NloptOptimizer*>;
auto typeddata = static_cast<TData*>(data);
if(typeddata->second->stopcr_.stop_condition())
typeddata->second->opt_.force_stop();
auto fnptr = typeddata->first;
auto funval = std::tuple<Args...>();
// copy the obtained objectfunction arguments to the funval tuple.
metaloop::apply(FunvalCopyFunc(params), funval);
auto ret = metaloop::callFunWithTuple(*fnptr, funval,
index_sequence_for<Args...>());
return ret;
}
template<class Func, class...Args>
Result<Args...> optimize(Func&& func,
std::tuple<Args...> initvals,
Bound<Args>... args)
{
lower_bounds_.resize(sizeof...(Args));
upper_bounds_.resize(sizeof...(Args));
initvals_.resize(sizeof...(Args));
opt_ = nlopt::opt(alg_, sizeof...(Args) );
// Copy the bounds which is obtained as a parameter pack in args into
// lower_bounds_ and upper_bounds_
metaloop::apply(BoundsFunc(*this), args...);
opt_.set_lower_bounds(lower_bounds_);
opt_.set_upper_bounds(upper_bounds_);
nlopt::opt localopt;
switch(opt_.get_algorithm()) {
case nlopt::GN_MLSL:
case nlopt::GN_MLSL_LDS:
localopt = nlopt::opt(method2nloptAlg(localmethod_),
sizeof...(Args));
localopt.set_lower_bounds(lower_bounds_);
localopt.set_upper_bounds(upper_bounds_);
opt_.set_local_optimizer(localopt);
default: ;
}
double abs_diff = stopcr_.absolute_score_difference;
double rel_diff = stopcr_.relative_score_difference;
double stopval = stopcr_.stop_score;
if(!std::isnan(abs_diff)) opt_.set_ftol_abs(abs_diff);
if(!std::isnan(rel_diff)) opt_.set_ftol_rel(rel_diff);
if(!std::isnan(stopval)) opt_.set_stopval(stopval);
if(this->stopcr_.max_iterations > 0)
opt_.set_maxeval(this->stopcr_.max_iterations );
// Take care of the initial values, copy them to initvals_
metaloop::apply(InitValFunc(*this), initvals);
std::pair<remove_ref_t<Func>*, NloptOptimizer*> data =
std::make_pair(&func, this);
switch(dir_) {
case OptDir::MIN:
opt_.set_min_objective(optfunc<Func, Args...>, &data); break;
case OptDir::MAX:
opt_.set_max_objective(optfunc<Func, Args...>, &data); break;
}
Result<Args...> result;
nlopt::result rescode;
try {
rescode = opt_.optimize(initvals_, result.score);
result.resultcode = static_cast<ResultCodes>(rescode);
} catch( nlopt::forced_stop& ) {
result.resultcode = ResultCodes::FORCED_STOP;
}
metaloop::apply(ResultCopyFunc(*this), result.optimum);
return result;
}
public:
inline explicit NloptOptimizer(nlopt::algorithm alg,
StopCriteria stopcr = {}):
Base(stopcr), alg_(alg), localmethod_(Method::L_SIMPLEX) {}
};
}
}
#endif // NLOPT_BOILERPLATE_HPP

View file

@ -0,0 +1,20 @@
#ifndef SIMPLEX_HPP
#define SIMPLEX_HPP
#include "nlopt_boilerplate.hpp"
namespace libnest2d { namespace opt {
class SimplexOptimizer: public NloptOptimizer {
public:
inline explicit SimplexOptimizer(const StopCriteria& scr = {}):
NloptOptimizer(method2nloptAlg(Method::L_SIMPLEX), scr) {}
};
template<>
struct OptimizerSubclass<Method::L_SIMPLEX> { using Type = SimplexOptimizer; };
}
}
#endif // SIMPLEX_HPP

View file

@ -0,0 +1,20 @@
#ifndef SUBPLEX_HPP
#define SUBPLEX_HPP
#include "nlopt_boilerplate.hpp"
namespace libnest2d { namespace opt {
class SubplexOptimizer: public NloptOptimizer {
public:
inline explicit SubplexOptimizer(const StopCriteria& scr = {}):
NloptOptimizer(method2nloptAlg(Method::L_SUBPLEX), scr) {}
};
template<>
struct OptimizerSubclass<Method::L_SUBPLEX> { using Type = SubplexOptimizer; };
}
}
#endif // SUBPLEX_HPP

View file

@ -0,0 +1,63 @@
#ifndef LIBNEST2D_PARALLEL_HPP
#define LIBNEST2D_PARALLEL_HPP
#include <iterator>
#include <functional>
#include <future>
#ifdef LIBNEST2D_THREADING_tbb
#include <tbb/parallel_for.h>
#endif
#ifdef LIBNEST2D_THREADING_omp
#include <omp.h>
#endif
namespace libnest2d { namespace __parallel {
template<class It>
using TIteratorValue = typename std::iterator_traits<It>::value_type;
template<class Iterator>
inline void enumerate(
Iterator from, Iterator to,
std::function<void(TIteratorValue<Iterator>, size_t)> fn,
std::launch policy = std::launch::deferred | std::launch::async)
{
using TN = size_t;
auto iN = to-from;
TN N = iN < 0? 0 : TN(iN);
#ifdef LIBNEST2D_THREADING_tbb
if((policy & std::launch::async) == std::launch::async) {
tbb::parallel_for<TN>(0, N, [from, fn] (TN n) { fn(*(from + n), n); } );
} else {
for(TN n = 0; n < N; n++) fn(*(from + n), n);
}
#endif
#ifdef LIBNEST2D_THREADING_omp
if((policy & std::launch::async) == std::launch::async) {
#pragma omp parallel for
for(int n = 0; n < int(N); n++) fn(*(from + n), TN(n));
}
else {
for(TN n = 0; n < N; n++) fn(*(from + n), n);
}
#endif
#ifdef LIBNEST2D_THREADING_std
std::vector<std::future<void>> rets(N);
auto it = from;
for(TN b = 0; b < N; b++) {
rets[b] = std::async(policy, fn, *it++, unsigned(b));
}
for(TN fi = 0; fi < N; ++fi) rets[fi].wait();
#endif
}
}}
#endif //LIBNEST2D_PARALLEL_HPP

View file

@ -0,0 +1,434 @@
#ifndef BOTTOMLEFT_HPP
#define BOTTOMLEFT_HPP
#include <limits>
#include "placer_boilerplate.hpp"
namespace libnest2d { namespace placers {
template<class T, class = T> struct DefaultEpsilon {};
template<class T>
struct DefaultEpsilon<T, enable_if_t<std::is_integral<T>::value, T> > {
static const T Value = 1;
};
template<class T>
struct DefaultEpsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > {
static const T Value = 1e-3;
};
template<class RawShape>
struct BLConfig {
DECLARE_MAIN_TYPES(RawShape);
Coord min_obj_distance = 0;
Coord epsilon = DefaultEpsilon<Coord>::Value;
bool allow_rotations = false;
//BBS: sort function for selector
std::function<bool(_Item<RawShape>& i1, _Item<RawShape>& i2)> sortfunc;
//BBS: excluded region for V4 bed
std::vector<_Item<RawShape> > m_excluded_regions;
_ItemGroup<RawShape> m_excluded_items;
std::vector < _Item<RawShape> > m_nonprefered_regions;
};
template<class RawShape>
class _BottomLeftPlacer: public PlacerBoilerplate<
_BottomLeftPlacer<RawShape>,
RawShape, _Box<TPoint<RawShape>>,
BLConfig<RawShape> >
{
using Base = PlacerBoilerplate<_BottomLeftPlacer<RawShape>, RawShape,
_Box<TPoint<RawShape>>, BLConfig<RawShape>>;
DECLARE_PLACER(Base)
public:
explicit _BottomLeftPlacer(const BinType& bin): Base(bin) {}
template<class Range = ConstItemRange<typename Base::DefaultIter>>
PackResult trypack(Item& item,
const Range& = Range())
{
auto r = _trypack(item);
if(!r && Base::config_.allow_rotations) {
item.rotate(Degrees(90));
r =_trypack(item);
}
return r;
}
enum class Dir {
LEFT,
DOWN
};
inline RawShape leftPoly(const Item& item) const {
return toWallPoly(item, Dir::LEFT);
}
inline RawShape downPoly(const Item& item) const {
return toWallPoly(item, Dir::DOWN);
}
inline Coord availableSpaceLeft(const Item& item) {
return availableSpace(item, Dir::LEFT);
}
inline Coord availableSpaceDown(const Item& item) {
return availableSpace(item, Dir::DOWN);
}
double score() const { return score_; }
//BBS
void plateID(int id) { plate_id = id; }
int plateID() { return plate_id; }
protected:
double score_ = 0;
int plate_id = 0; // BBS
PackResult _trypack(Item& item) {
// Get initial position for item in the top right corner
setInitialPosition(item);
Coord d = availableSpaceDown(item);
auto eps = config_.epsilon;
bool can_move = d > eps;
bool can_be_packed = can_move;
bool left = true;
while(can_move) {
if(left) { // write previous down move and go down
item.translate({0, -d+eps});
d = availableSpaceLeft(item);
can_move = d > eps;
left = false;
} else { // write previous left move and go down
item.translate({-d+eps, 0});
d = availableSpaceDown(item);
can_move = d > eps;
left = true;
}
}
if(can_be_packed) {
Item trsh(item.transformedShape());
for(auto& v : trsh) can_be_packed = can_be_packed &&
getX(v) < bin_.width() &&
getY(v) < bin_.height();
}
return can_be_packed? PackResult(item) : PackResult();
}
void setInitialPosition(Item& item) {
auto bb = item.boundingBox();
Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) };
Coord dx = getX(bin_.maxCorner()) - getX(v);
Coord dy = getY(bin_.maxCorner()) - getY(v);
item.translate({dx, dy});
}
template<class C = Coord>
static enable_if_t<std::is_floating_point<C>::value, bool>
isInTheWayOf( const Item& item,
const Item& other,
const RawShape& scanpoly)
{
auto tsh = other.transformedShape();
return ( sl::intersects(tsh, scanpoly) ||
sl::isInside(tsh, scanpoly) ) &&
( !sl::intersects(tsh, item.rawShape()) &&
!sl::isInside(tsh, item.rawShape()) );
}
template<class C = Coord>
static enable_if_t<std::is_integral<C>::value, bool>
isInTheWayOf( const Item& item,
const Item& other,
const RawShape& scanpoly)
{
auto tsh = other.transformedShape();
bool inters_scanpoly = sl::intersects(tsh, scanpoly) &&
!sl::touches(tsh, scanpoly);
bool inters_item = sl::intersects(tsh, item.rawShape()) &&
!sl::touches(tsh, item.rawShape());
return ( inters_scanpoly ||
sl::isInside(tsh, scanpoly)) &&
( !inters_item &&
!sl::isInside(tsh, item.rawShape())
);
}
ItemGroup itemsInTheWayOf(const Item& item, const Dir dir) {
// Get the left or down polygon, that has the same area as the shadow
// of input item reflected to the left or downwards
auto&& scanpoly = dir == Dir::LEFT? leftPoly(item) :
downPoly(item);
ItemGroup ret; // packed items 'in the way' of item
ret.reserve(items_.size());
// Predicate to find items that are 'in the way' for left (down) move
auto predicate = [&scanpoly, &item](const Item& it) {
return isInTheWayOf(item, it, scanpoly);
};
// Get the items that are in the way for the left (or down) movement
std::copy_if(items_.begin(), items_.end(),
std::back_inserter(ret), predicate);
return ret;
}
Coord availableSpace(const Item& _item, const Dir dir) {
Item item (_item.transformedShape());
std::function<Coord(const Vertex&)> getCoord;
std::function< std::pair<Coord, bool>(const Segment&, const Vertex&) >
availableDistanceSV;
std::function< std::pair<Coord, bool>(const Vertex&, const Segment&) >
availableDistance;
if(dir == Dir::LEFT) {
getCoord = [](const Vertex& v) { return getX(v); };
availableDistance = pointlike::horizontalDistance<Vertex>;
availableDistanceSV = [](const Segment& s, const Vertex& v) {
auto ret = pointlike::horizontalDistance<Vertex>(v, s);
if(ret.second) ret.first = -ret.first;
return ret;
};
}
else {
getCoord = [](const Vertex& v) { return getY(v); };
availableDistance = pointlike::verticalDistance<Vertex>;
availableDistanceSV = [](const Segment& s, const Vertex& v) {
auto ret = pointlike::verticalDistance<Vertex>(v, s);
if(ret.second) ret.first = -ret.first;
return ret;
};
}
auto&& items_in_the_way = itemsInTheWayOf(item, dir);
// Comparison function for finding min vertex
auto cmp = [&getCoord](const Vertex& v1, const Vertex& v2) {
return getCoord(v1) < getCoord(v2);
};
// find minimum left or down coordinate of item
auto minvertex_it = std::min_element(item.begin(),
item.end(),
cmp);
// Get the initial distance in floating point
Coord m = getCoord(*minvertex_it);
// Check available distance for every vertex of item to the objects
// in the way for the nearest intersection
if(!items_in_the_way.empty()) { // This is crazy, should be optimized...
for(Item& pleft : items_in_the_way) {
// For all segments in items_to_left
assert(pleft.vertexCount() > 0);
auto trpleft_poly = pleft.transformedShape();
auto& trpleft = sl::contour(trpleft_poly);
auto first = sl::begin(trpleft);
auto next = first + 1;
auto endit = sl::end(trpleft);
while(next != endit) {
Segment seg(*(first++), *(next++));
for(auto& v : item) { // For all vertices in item
auto d = availableDistance(v, seg);
if(d.second && d.first < m) m = d.first;
}
}
}
auto first = item.begin();
auto next = first + 1;
auto endit = item.end();
// For all edges in item:
while(next != endit) {
Segment seg(*(first++), *(next++));
// for all shapes in items_to_left
for(Item& sh : items_in_the_way) {
assert(sh.vertexCount() > 0);
Item tsh(sh.transformedShape());
for(auto& v : tsh) { // For all vertices in item
auto d = availableDistanceSV(seg, v);
if(d.second && d.first < m) m = d.first;
}
}
}
}
return m;
}
/**
* Implementation of the left (and down) polygon as described by
* [López-Camacho et al. 2013]\
* (http://www.cs.stir.ac.uk/~goc/papers/EffectiveHueristic2DAOR2013.pdf)
* see algorithm 8 for details...
*/
RawShape toWallPoly(const Item& _item, const Dir dir) const {
// The variable names reflect the case of left polygon calculation.
//
// We will iterate through the item's vertices and search for the top
// and bottom vertices (or right and left if dir==Dir::DOWN).
// Save the relevant vertices and their indices into `bottom` and
// `top` vectors. In case of left polygon construction these will
// contain the top and bottom polygons which have the same vertical
// coordinates (in case there is more of them).
//
// We get the leftmost (or downmost) vertex from the `bottom` and `top`
// vectors and construct the final polygon.
Item item (_item.transformedShape());
auto getCoord = [dir](const Vertex& v) {
return dir == Dir::LEFT? getY(v) : getX(v);
};
Coord max_y = std::numeric_limits<Coord>::min();
Coord min_y = std::numeric_limits<Coord>::max();
using El = std::pair<size_t, std::reference_wrapper<const Vertex>>;
std::function<bool(const El&, const El&)> cmp;
if(dir == Dir::LEFT)
cmp = [](const El& e1, const El& e2) {
return getX(e1.second.get()) < getX(e2.second.get());
};
else
cmp = [](const El& e1, const El& e2) {
return getY(e1.second.get()) < getY(e2.second.get());
};
std::vector< El > top;
std::vector< El > bottom;
size_t idx = 0;
for(auto& v : item) { // Find the bottom and top vertices and save them
auto vref = std::cref(v);
auto vy = getCoord(v);
if( vy > max_y ) {
max_y = vy;
top.clear();
top.emplace_back(idx, vref);
}
else if(vy == max_y) { top.emplace_back(idx, vref); }
if(vy < min_y) {
min_y = vy;
bottom.clear();
bottom.emplace_back(idx, vref);
}
else if(vy == min_y) { bottom.emplace_back(idx, vref); }
idx++;
}
// Get the top and bottom leftmost vertices, or the right and left
// downmost vertices (if dir == Dir::DOWN)
auto topleft_it = std::min_element(top.begin(), top.end(), cmp);
auto bottomleft_it =
std::min_element(bottom.begin(), bottom.end(), cmp);
auto& topleft_vertex = topleft_it->second.get();
auto& bottomleft_vertex = bottomleft_it->second.get();
// Start and finish positions for the vertices that will be part of the
// new polygon
auto start = std::min(topleft_it->first, bottomleft_it->first);
auto finish = std::max(topleft_it->first, bottomleft_it->first);
RawShape ret;
// the return shape
auto& rsh = sl::contour(ret);
// reserve for all vertices plus 2 for the left horizontal wall, 2 for
// the additional vertices for maintaning min object distance
sl::reserve(rsh, finish-start+4);
auto addOthers_ = [&rsh, finish, start, &item](){
for(size_t i = start+1; i < finish; i++)
sl::addVertex(rsh, item.vertex(i));
};
auto reverseAddOthers_ = [&rsh, finish, start, &item](){
for(auto i = finish-1; i > start; i--)
sl::addVertex(rsh, item.vertex(static_cast<unsigned long>(i)));
};
auto addOthers = [&addOthers_, &reverseAddOthers_]() {
if constexpr (!is_clockwise<RawShape>())
addOthers_();
else
reverseAddOthers_();
};
// Final polygon construction...
// Clockwise polygon construction
sl::addVertex(rsh, topleft_vertex);
if(dir == Dir::LEFT) addOthers();
else {
sl::addVertex(rsh, {getX(topleft_vertex), 0});
sl::addVertex(rsh, {getX(bottomleft_vertex), 0});
}
sl::addVertex(rsh, bottomleft_vertex);
if(dir == Dir::LEFT) {
sl::addVertex(rsh, {0, getY(bottomleft_vertex)});
sl::addVertex(rsh, {0, getY(topleft_vertex)});
}
else addOthers();
// Close the polygon
if constexpr (ClosureTypeV<RawShape> == Closure::CLOSED)
sl::addVertex(rsh, topleft_vertex);
if constexpr (!is_clockwise<RawShape>())
std::reverse(rsh.begin(), rsh.end());
return ret;
}
};
}
}
#endif //BOTTOMLEFT_HPP

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,140 @@
#ifndef PLACER_BOILERPLATE_HPP
#define PLACER_BOILERPLATE_HPP
#include <libnest2d/nester.hpp>
namespace libnest2d { namespace placers {
struct EmptyConfig {};
template<class Subclass, class RawShape, class TBin, class Cfg = EmptyConfig>
class PlacerBoilerplate {
mutable bool farea_valid_ = false;
mutable double farea_ = 0.0;
public:
using ShapeType = RawShape;
using Item = _Item<RawShape>;
using Vertex = TPoint<RawShape>;
using Segment = _Segment<Vertex>;
using BinType = TBin;
using Coord = TCoord<Vertex>;
using Config = Cfg;
using ItemGroup = _ItemGroup<RawShape>;
using DefaultIter = typename ItemGroup::const_iterator;
class PackResult {
public:
Item *item_ptr_;
Vertex move_;
Radians rot_;
double overfit_;
friend class PlacerBoilerplate;
friend Subclass;
PackResult(Item& item):
item_ptr_(&item),
move_(item.translation()),
rot_(item.rotation()),
overfit_(1.0) {}
PackResult(double overfit = 1.0):
item_ptr_(nullptr), overfit_(overfit) {}
public:
operator bool() { return item_ptr_ != nullptr; }
double overfit() const { return overfit_; }
double score_ = -1;
double score() { return score_; }
int plate_id = 0; // BBS
};
inline PlacerBoilerplate(const BinType& bin, unsigned cap = 50): bin_(bin)
{
items_.reserve(cap);
}
inline const BinType& bin() const BP2D_NOEXCEPT { return bin_; }
template<class TB> inline void bin(TB&& b) {
bin_ = std::forward<BinType>(b);
}
inline void configure(const Config& config) BP2D_NOEXCEPT {
config_ = config;
}
template<class Range = ConstItemRange<DefaultIter>>
PackResult pack(Item& item, const Range& rem = Range()) {
auto&& r = static_cast<Subclass*>(this)->trypack(item, rem);
return r;
}
void preload(const ItemGroup& packeditems) {
items_.insert(items_.end(), packeditems.begin(), packeditems.end());
farea_valid_ = false;
}
void accept(PackResult& r) {
if(r) {
//r.item_ptr_->translation(r.move_);
//r.item_ptr_->rotation(r.rot_);
//items_.emplace_back(*(r.item_ptr_));
static_cast<Subclass*>(this)->accept(r);
farea_valid_ = false;
}
}
void unpackLast() {
items_.pop_back();
farea_valid_ = false;
}
inline const ItemGroup& getItems() const { return items_; }
inline void clearItems() {
items_.clear();
farea_valid_ = false;
}
inline double filledArea() const {
if(farea_valid_) return farea_;
else {
farea_ = .0;
std::for_each(items_.begin(), items_.end(),
[this] (Item& item) {
farea_ += item.area();
});
farea_valid_ = true;
}
return farea_;
}
public:
BinType bin_;
ItemGroup items_;
Cfg config_;
};
#define DECLARE_PLACER(Base) \
using Base::bin_; \
using Base::items_; \
using Base::config_; \
public: \
using typename Base::ShapeType; \
using typename Base::Item; \
using typename Base::ItemGroup; \
using typename Base::BinType; \
using typename Base::Config; \
using typename Base::Vertex; \
using typename Base::Segment; \
using typename Base::PackResult; \
using typename Base::Coord; \
private:
}
}
#endif // PLACER_BOILERPLATE_HPP

View file

@ -0,0 +1,717 @@
#ifndef DJD_HEURISTIC_HPP
#define DJD_HEURISTIC_HPP
#include <list>
#include <future>
#include <atomic>
#include <functional>
#include "selection_boilerplate.hpp"
namespace libnest2d { namespace selections {
/**
* Selection heuristic based on [López-Camacho]\
* (http://www.cs.stir.ac.uk/~goc/papers/EffectiveHueristic2DAOR2013.pdf)
*/
template<class RawShape>
class _DJDHeuristic: public SelectionBoilerplate<RawShape> {
using Base = SelectionBoilerplate<RawShape>;
class SpinLock {
std::atomic_flag& lck_;
public:
inline SpinLock(std::atomic_flag& flg): lck_(flg) {}
inline void lock() {
while(lck_.test_and_set(std::memory_order_acquire)) {}
}
inline void unlock() { lck_.clear(std::memory_order_release); }
};
public:
using typename Base::Item;
using ItemRef = std::reference_wrapper<Item>;
/**
* @brief The Config for DJD heuristic.
*/
struct Config {
/**
* If true, the algorithm will try to place pair and triplets in all
* possible order. It will have a hugely negative impact on performance.
*/
bool try_reverse_order = true;
/**
* @brief try_pairs Whether to try pairs of items to pack. It will add
* a quadratic component to the complexity.
*/
bool try_pairs = true;
/**
* @brief Whether to try groups of 3 items to pack. This could be very
* slow for large number of items (>100) as it adds a cubic component
* to the complexity.
*/
bool try_triplets = false;
/**
* The initial fill proportion of the bin area that will be filled before
* trying items one by one, or pairs or triplets.
*
* The initial fill proportion suggested by
* [López-Camacho]\
* (http://www.cs.stir.ac.uk/~goc/papers/EffectiveHueristic2DAOR2013.pdf)
* is one third of the area of bin.
*/
double initial_fill_proportion = 1.0/3.0;
/**
* @brief How much is the acceptable waste incremented at each iteration
*/
double waste_increment = 0.1;
/**
* @brief Allow parallel jobs for filling multiple bins.
*
* This will decrease the soution quality but can greatly boost up
* performance for large number of items.
*/
bool allow_parallel = true;
/**
* @brief Always use parallel processing if the items don't fit into
* one bin.
*/
bool force_parallel = false;
};
private:
using Base::packed_bins_;
using ItemGroup = typename Base::ItemGroup;
using Container = ItemGroup;
Container store_;
Config config_;
static const unsigned MAX_ITEMS_SEQUENTIALLY = 30;
static const unsigned MAX_VERTICES_SEQUENTIALLY = MAX_ITEMS_SEQUENTIALLY*20;
public:
inline void configure(const Config& config) {
config_ = config;
}
template<class TPlacer, class TIterator,
class TBin = typename PlacementStrategyLike<TPlacer>::BinType,
class PConfig = typename PlacementStrategyLike<TPlacer>::Config>
void packItems( TIterator first,
TIterator last,
const TBin& bin,
PConfig&& pconfig = PConfig() )
{
using Placer = PlacementStrategyLike<TPlacer>;
using ItemList = std::list<ItemRef>;
const double bin_area = sl::area(bin);
const double w = bin_area * config_.waste_increment;
const double INITIAL_FILL_PROPORTION = config_.initial_fill_proportion;
const double INITIAL_FILL_AREA = bin_area*INITIAL_FILL_PROPORTION;
store_.clear();
store_.reserve(last-first);
// TODO: support preloading
packed_bins_.clear();
std::copy(first, last, std::back_inserter(store_));
std::sort(store_.begin(), store_.end(), [](Item& i1, Item& i2) {
return i1.area() > i2.area();
});
size_t glob_vertex_count = 0;
std::for_each(store_.begin(), store_.end(),
[&glob_vertex_count](const Item& item) {
glob_vertex_count += item.vertexCount();
});
std::vector<Placer> placers;
bool try_reverse = config_.try_reverse_order;
// Will use a subroutine to add a new bin
auto addBin = [this, &placers, &bin, &pconfig]()
{
placers.emplace_back(bin);
packed_bins_.emplace_back();
placers.back().configure(pconfig);
};
// Types for pairs and triplets
using TPair = std::tuple<ItemRef, ItemRef>;
using TTriplet = std::tuple<ItemRef, ItemRef, ItemRef>;
// Method for checking a pair whether it was a pack failure.
auto check_pair = [](const std::vector<TPair>& wrong_pairs,
ItemRef i1, ItemRef i2)
{
return std::any_of(wrong_pairs.begin(), wrong_pairs.end(),
[&i1, &i2](const TPair& pair)
{
Item& pi1 = std::get<0>(pair), &pi2 = std::get<1>(pair);
Item& ri1 = i1, &ri2 = i2;
return (&pi1 == &ri1 && &pi2 == &ri2) ||
(&pi1 == &ri2 && &pi2 == &ri1);
});
};
// Method for checking if a triplet was a pack failure
auto check_triplet = [](
const std::vector<TTriplet>& wrong_triplets,
ItemRef i1,
ItemRef i2,
ItemRef i3)
{
return std::any_of(wrong_triplets.begin(),
wrong_triplets.end(),
[&i1, &i2, &i3](const TTriplet& tripl)
{
Item& pi1 = std::get<0>(tripl);
Item& pi2 = std::get<1>(tripl);
Item& pi3 = std::get<2>(tripl);
Item& ri1 = i1, &ri2 = i2, &ri3 = i3;
return (&pi1 == &ri1 && &pi2 == &ri2 && &pi3 == &ri3) ||
(&pi1 == &ri1 && &pi2 == &ri3 && &pi3 == &ri2) ||
(&pi1 == &ri2 && &pi2 == &ri1 && &pi3 == &ri3) ||
(&pi1 == &ri3 && &pi2 == &ri2 && &pi3 == &ri1);
});
};
using ItemListIt = typename ItemList::iterator;
auto largestPiece = [](ItemListIt it, ItemList& not_packed) {
return it == not_packed.begin()? std::next(it) : not_packed.begin();
};
auto secondLargestPiece = [&largestPiece](ItemListIt it,
ItemList& not_packed) {
auto ret = std::next(largestPiece(it, not_packed));
return ret == it? std::next(ret) : ret;
};
auto smallestPiece = [](ItemListIt it, ItemList& not_packed) {
auto last = std::prev(not_packed.end());
return it == last? std::prev(it) : last;
};
auto secondSmallestPiece = [&smallestPiece](ItemListIt it,
ItemList& not_packed) {
auto ret = std::prev(smallestPiece(it, not_packed));
return ret == it? std::prev(ret) : ret;
};
auto tryOneByOne = // Subroutine to try adding items one by one.
[&bin_area]
(Placer& placer, ItemList& not_packed,
double waste,
double& free_area,
double& filled_area)
{
double item_area = 0;
bool ret = false;
auto it = not_packed.begin();
auto pack = [&placer, &not_packed](ItemListIt it) {
return placer.pack(*it, rem(it, not_packed));
};
while(it != not_packed.end() && !ret &&
free_area - (item_area = it->get().area()) <= waste)
{
if(item_area <= free_area && pack(it) ) {
free_area -= item_area;
filled_area = bin_area - free_area;
ret = true;
} else
it++;
}
if(ret) not_packed.erase(it);
return ret;
};
auto tryGroupsOfTwo = // Try adding groups of two items into the bin.
[&bin_area, &check_pair, &largestPiece, &smallestPiece,
try_reverse]
(Placer& placer, ItemList& not_packed,
double waste,
double& free_area,
double& filled_area)
{
double item_area = 0;
const auto endit = not_packed.end();
if(not_packed.size() < 2)
return false; // No group of two items
double largest_area = not_packed.front().get().area();
auto itmp = not_packed.begin(); itmp++;
double second_largest = itmp->get().area();
if( free_area - second_largest - largest_area > waste)
return false; // If even the largest two items do not fill
// the bin to the desired waste than we can end here.
bool ret = false;
auto it = not_packed.begin();
auto it2 = it;
std::vector<TPair> wrong_pairs;
using std::placeholders::_1;
auto trypack = [&placer, &not_packed](ItemListIt it) {
return placer.trypack(*it, rem(it, not_packed));
};
while(it != endit && !ret &&
free_area - (item_area = it->get().area()) -
largestPiece(it, not_packed)->get().area() <= waste)
{
if(item_area + smallestPiece(it, not_packed)->get().area() >
free_area ) { it++; continue; }
auto pr = trypack(it);
// First would fit
it2 = not_packed.begin();
double item2_area = 0;
while(it2 != endit && pr && !ret && free_area -
(item2_area = it2->get().area()) - item_area <= waste)
{
double area_sum = item_area + item2_area;
if(it == it2 || area_sum > free_area ||
check_pair(wrong_pairs, *it, *it2)) {
it2++; continue;
}
placer.accept(pr);
auto pr2 = trypack(it2);
if(!pr2) {
placer.unpackLast(); // remove first
if(try_reverse) {
pr2 = trypack(it2);
if(pr2) {
placer.accept(pr2);
auto pr12 = trypack(it);
if(pr12) {
placer.accept(pr12);
ret = true;
} else {
placer.unpackLast();
}
}
}
} else {
placer.accept(pr2); ret = true;
}
if(ret)
{ // Second fits as well
free_area -= area_sum;
filled_area = bin_area - free_area;
} else {
wrong_pairs.emplace_back(*it, *it2);
it2++;
}
}
if(!ret) it++;
}
if(ret) { not_packed.erase(it); not_packed.erase(it2); }
return ret;
};
auto tryGroupsOfThree = // Try adding groups of three items.
[&bin_area,
&smallestPiece, &largestPiece,
&secondSmallestPiece, &secondLargestPiece,
&check_pair, &check_triplet, try_reverse]
(Placer& placer, ItemList& not_packed,
double waste,
double& free_area,
double& filled_area)
{
auto np_size = not_packed.size();
if(np_size < 3) return false;
auto it = not_packed.begin(); // from
const auto endit = not_packed.end(); // to
auto it2 = it, it3 = it;
// Containers for pairs and triplets that were tried before and
// do not work.
std::vector<TPair> wrong_pairs;
std::vector<TTriplet> wrong_triplets;
auto cap = np_size*np_size / 2 ;
wrong_pairs.reserve(cap);
wrong_triplets.reserve(cap);
// Will be true if a succesfull pack can be made.
bool ret = false;
auto area = [](const ItemListIt& it) {
return it->get().area();
};
auto trypack = [&placer, &not_packed](ItemListIt it) {
return placer.trypack(*it, rem(it, not_packed));
};
auto pack = [&placer, &not_packed](ItemListIt it) {
return placer.pack(*it, rem(it, not_packed));
};
while (it != endit && !ret) { // drill down 1st level
// We need to determine in each iteration the largest, second
// largest, smallest and second smallest item in terms of area.
Item& largest = *largestPiece(it, not_packed);
Item& second_largest = *secondLargestPiece(it, not_packed);
double area_of_two_largest =
largest.area() + second_largest.area();
// Check if there is enough free area for the item and the two
// largest item
if(free_area - area(it) - area_of_two_largest > waste)
break;
// Determine the area of the two smallest item.
Item& smallest = *smallestPiece(it, not_packed);
Item& second_smallest = *secondSmallestPiece(it, not_packed);
// Check if there is enough free area for the item and the two
// smallest item.
double area_of_two_smallest =
smallest.area() + second_smallest.area();
if(area(it) + area_of_two_smallest > free_area) {
it++; continue;
}
auto pr = trypack(it);
// Check for free area and try to pack the 1st item...
if(!pr) { it++; continue; }
it2 = not_packed.begin();
double rem2_area = free_area - largest.area();
double a2_sum = 0;
while(it2 != endit && !ret &&
rem2_area - (a2_sum = area(it) + area(it2)) <= waste) {
// Drill down level 2
if(a2_sum != area(it) + area(it2)) throw -1;
if(it == it2 || check_pair(wrong_pairs, *it, *it2)) {
it2++; continue;
}
if(a2_sum + smallest.area() > free_area) {
it2++; continue;
}
bool can_pack2 = false;
placer.accept(pr);
auto pr2 = trypack(it2);
auto pr12 = pr;
if(!pr2) {
placer.unpackLast(); // remove first
if(try_reverse) {
pr2 = trypack(it2);
if(pr2) {
placer.accept(pr2);
pr12 = trypack(it);
if(pr12) can_pack2 = true;
placer.unpackLast();
}
}
} else {
placer.unpackLast();
can_pack2 = true;
}
if(!can_pack2) {
wrong_pairs.emplace_back(*it, *it2);
it2++;
continue;
}
// Now we have packed a group of 2 items.
// The 'smallest' variable now could be identical with
// it2 but we don't bother with that
it3 = not_packed.begin();
double a3_sum = 0;
while(it3 != endit && !ret &&
free_area - (a3_sum = a2_sum + area(it3)) <= waste) {
// 3rd level
if(it3 == it || it3 == it2 ||
check_triplet(wrong_triplets, *it, *it2, *it3))
{ it3++; continue; }
if(a3_sum > free_area) { it3++; continue; }
placer.accept(pr12); placer.accept(pr2);
bool can_pack3 = pack(it3);
if(!can_pack3) {
placer.unpackLast();
placer.unpackLast();
}
if(!can_pack3 && try_reverse) {
std::array<size_t, 3> indices = {0, 1, 2};
std::array<typename ItemList::iterator, 3>
candidates = {it, it2, it3};
auto tryPack = [&placer, &candidates, &pack](
const decltype(indices)& idx)
{
std::array<bool, 3> packed = {false};
for(auto id : idx) packed.at(id) =
pack(candidates[id]);
bool check =
std::all_of(packed.begin(),
packed.end(),
[](bool b) { return b; });
if(!check) for(bool b : packed) if(b)
placer.unpackLast();
return check;
};
while (!can_pack3 && std::next_permutation(
indices.begin(),
indices.end())){
can_pack3 = tryPack(indices);
};
}
if(can_pack3) {
// finishit
free_area -= a3_sum;
filled_area = bin_area - free_area;
ret = true;
} else {
wrong_triplets.emplace_back(*it, *it2, *it3);
it3++;
}
} // 3rd while
if(!ret) it2++;
} // Second while
if(!ret) it++;
} // First while
if(ret) { // If we eventually succeeded, remove all the packed ones.
not_packed.erase(it);
not_packed.erase(it2);
not_packed.erase(it3);
}
return ret;
};
this->template remove_unpackable_items<Placer>(store_, bin, pconfig);
int acounter = int(store_.size());
std::atomic_flag flg = ATOMIC_FLAG_INIT;
SpinLock slock(flg);
auto makeProgress = [this, &acounter, &slock]
(Placer& placer, size_t idx, int packednum)
{
packed_bins_[idx] = placer.getItems();
// TODO here should be a spinlock
slock.lock();
acounter -= packednum;
this->progress_(acounter);
slock.unlock();
};
double items_area = 0;
for(Item& item : store_) items_area += item.area();
// Number of bins that will definitely be needed
auto bincount_guess = unsigned(std::ceil(items_area / bin_area));
// Do parallel if feasible
bool do_parallel = config_.allow_parallel && bincount_guess > 1 &&
((glob_vertex_count > MAX_VERTICES_SEQUENTIALLY ||
store_.size() > MAX_ITEMS_SEQUENTIALLY) ||
config_.force_parallel);
if(do_parallel) dout() << "Parallel execution..." << "\n";
bool do_pairs = config_.try_pairs;
bool do_triplets = config_.try_triplets;
StopCondition stopcond = this->stopcond_;
// The DJD heuristic algorithm itself:
auto packjob = [INITIAL_FILL_AREA, bin_area, w, do_triplets, do_pairs,
stopcond,
&tryOneByOne,
&tryGroupsOfTwo,
&tryGroupsOfThree,
&makeProgress]
(Placer& placer, ItemList& not_packed, size_t idx)
{
double filled_area = placer.filledArea();
double free_area = bin_area - filled_area;
double waste = .0;
bool lasttry = false;
while(!not_packed.empty() && !stopcond()) {
{// Fill the bin up to INITIAL_FILL_PROPORTION of its capacity
auto it = not_packed.begin();
while(it != not_packed.end() && !stopcond() &&
filled_area < INITIAL_FILL_AREA)
{
if(placer.pack(*it, rem(it, not_packed))) {
filled_area += it->get().area();
free_area = bin_area - filled_area;
it = not_packed.erase(it);
makeProgress(placer, idx, 1);
} else it++;
}
}
// try pieces one by one
while(tryOneByOne(placer, not_packed, waste, free_area,
filled_area)) {
waste = 0; lasttry = false;
makeProgress(placer, idx, 1);
}
// try groups of 2 pieces
while(do_pairs &&
tryGroupsOfTwo(placer, not_packed, waste, free_area,
filled_area)) {
waste = 0; lasttry = false;
makeProgress(placer, idx, 2);
}
// try groups of 3 pieces
while(do_triplets &&
tryGroupsOfThree(placer, not_packed, waste, free_area,
filled_area)) {
waste = 0; lasttry = false;
makeProgress(placer, idx, 3);
}
waste += w;
if(!lasttry && waste > free_area) lasttry = true;
else if(lasttry) break;
}
};
size_t idx = 0;
ItemList remaining;
if(do_parallel) {
std::vector<ItemList> not_packeds(bincount_guess);
// Preallocating the bins
for(unsigned b = 0; b < bincount_guess; b++) {
addBin();
ItemList& not_packed = not_packeds[b];
for(unsigned idx = b; idx < store_.size(); idx+=bincount_guess) {
not_packed.emplace_back(store_[idx]);
}
}
// The parallel job
auto job = [&placers, &not_packeds, &packjob](unsigned idx) {
Placer& placer = placers[idx];
ItemList& not_packed = not_packeds[idx];
return packjob(placer, not_packed, idx);
};
// We will create jobs for each bin
std::vector<std::future<void>> rets(bincount_guess);
for(unsigned b = 0; b < bincount_guess; b++) { // launch the jobs
rets[b] = std::async(std::launch::async, job, b);
}
for(unsigned fi = 0; fi < rets.size(); ++fi) {
rets[fi].wait();
// Collect remaining items while waiting for the running jobs
remaining.merge( not_packeds[fi], [](Item& i1, Item& i2) {
return i1.area() > i2.area();
});
}
idx = placers.size();
// Try to put the remaining items into one of the packed bins
if(remaining.size() <= placers.size())
for(size_t j = 0; j < idx && !remaining.empty(); j++) {
packjob(placers[j], remaining, j);
}
} else {
remaining = ItemList(store_.begin(), store_.end());
}
while(!remaining.empty()) {
addBin();
packjob(placers[idx], remaining, idx); idx++;
}
int binid = 0;
for(auto &bin : packed_bins_) {
for(Item& itm : bin) itm.binId(binid);
binid++;
}
}
};
}
}
#endif // DJD_HEURISTIC_HPP

View file

@ -0,0 +1,87 @@
#ifndef FILLER_HPP
#define FILLER_HPP
#include "selection_boilerplate.hpp"
namespace libnest2d { namespace selections {
template<class RawShape>
class _FillerSelection: public SelectionBoilerplate<RawShape> {
using Base = SelectionBoilerplate<RawShape>;
public:
using typename Base::Item;
using Config = int; //dummy
private:
using Base::packed_bins_;
using typename Base::ItemGroup;
using Container = ItemGroup;
Container store_;
public:
void configure(const Config& /*config*/) { }
template<class TPlacer, class TIterator,
class TBin = typename PlacementStrategyLike<TPlacer>::BinType,
class PConfig = typename PlacementStrategyLike<TPlacer>::Config>
void packItems(TIterator first,
TIterator last,
TBin&& bin,
PConfig&& pconfig = PConfig())
{
using Placer = PlacementStrategyLike<TPlacer>;
store_.clear();
auto total = last-first;
store_.reserve(total);
// TODO: support preloading
packed_bins_.clear();
packed_bins_.emplace_back();
auto makeProgress = [this, &total](
PlacementStrategyLike<TPlacer>& placer)
{
packed_bins_.back() = placer.getItems();
#ifndef NDEBUG
packed_bins_.back().insert(packed_bins_.back().end(),
placer.getDebugItems().begin(),
placer.getDebugItems().end());
#endif
this->progress_(--total);
};
std::copy(first, last, std::back_inserter(store_));
auto sortfunc = [](Item& i1, Item& i2) {
return i1.area() > i2.area();
};
this->template remove_unpackable_items<Placer>(store_, bin, pconfig);
std::sort(store_.begin(), store_.end(), sortfunc);
Placer placer(bin);
placer.configure(pconfig);
auto it = store_.begin();
while(it != store_.end() && !this->stopcond_()) {
if(!placer.pack(*it, {std::next(it), store_.end()})) {
if(packed_bins_.back().empty()) ++it;
placer.clearItems();
packed_bins_.emplace_back();
} else {
makeProgress(placer);
++it;
}
}
}
};
}
}
#endif //BOTTOMLEFT_HPP

View file

@ -0,0 +1,179 @@
#ifndef FIRSTFIT_HPP
#define FIRSTFIT_HPP
#include "selection_boilerplate.hpp"
namespace libnest2d { namespace selections {
template<class RawShape>
class _FirstFitSelection: public SelectionBoilerplate<RawShape> {
using Base = SelectionBoilerplate<RawShape>;
public:
using typename Base::Item;
using Config = int; //dummy
private:
using Base::packed_bins_;
using typename Base::ItemGroup;
using Container = ItemGroup;//typename std::vector<_Item<RawShape>>;
Container store_;
public:
void configure(const Config& /*config*/) { }
template<class TPlacer, class TIterator,
class TBin = typename PlacementStrategyLike<TPlacer>::BinType,
class PConfig = typename PlacementStrategyLike<TPlacer>::Config>
void packItems(TIterator first,
TIterator last,
TBin&& bin,
PConfig&& pconfig = PConfig())
{
using Placer = PlacementStrategyLike<TPlacer>;
store_.clear();
store_.reserve(last-first);
std::vector<Placer> placers;
placers.reserve(last-first);
typename Base::PackGroup fixed_bins;
std::for_each(first, last, [this, &fixed_bins](Item& itm) {
if (itm.isFixed()) {
if (itm.binId() < 0) itm.binId(0);
auto binidx = size_t(itm.binId());
while (fixed_bins.size() <= binidx)
fixed_bins.emplace_back();
fixed_bins[binidx].emplace_back(itm);
}
else {
store_.emplace_back(itm);
}
});
std::for_each(pconfig.m_excluded_regions.begin(), pconfig.m_excluded_regions.end(), [this, &pconfig](Item& itm) {
pconfig.m_excluded_items.emplace_back(itm);
});
// If the packed_items array is not empty we have to create as many
// placers as there are elements in packed bins and preload each item
// into the appropriate placer
//for(ItemGroup& ig : fixed_bins) {
// placers.emplace_back(bin);
// placers.back().configure(pconfig);
// placers.back().preload(ig);
//}
std::function<bool(Item& i1, Item& i2)> sortfunc;
if (pconfig.sortfunc)
sortfunc = pconfig.sortfunc;
else {
sortfunc = [](Item& i1, Item& i2) {
int p1 = i1.priority(), p2 = i2.priority();
if (p1 != p2)
return p1 > p2;
return i1.bed_temp != i2.bed_temp ? (i1.bed_temp > i2.bed_temp) :
(i1.height != i2.height ? (i1.height < i2.height) : (i1.area() > i2.area()));
};
}
std::sort(store_.begin(), store_.end(), sortfunc);
int item_id = 0;
auto makeProgress = [this, &item_id](Placer &placer, size_t bin_idx) {
packed_bins_[bin_idx] = placer.getItems();
this->last_packed_bin_id_ = int(bin_idx);
this->progress_(static_cast<unsigned>(item_id));
};
auto& cancelled = this->stopcond_;
this->template remove_unpackable_items<Placer>(store_, bin, pconfig);
for (auto it = store_.begin(); it != store_.end() && !cancelled(); ++it) {
// skip unpackable item
if (it->get().binId() == BIN_ID_UNFIT)
continue;
bool was_packed = false;
int best_bed_id = -1;
int bed_id_firstfit = -1;
double score = LARGE_COST_TO_REJECT, best_score = LARGE_COST_TO_REJECT;
double score_all_plates = 0, score_all_plates_best = std::numeric_limits<double>::max();
typename Placer::PackResult result, result_best, result_firstfit;
size_t j = 0;
while(!was_packed && !cancelled()) {
for(; j < placers.size() && !was_packed && !cancelled(); j++) {
result = placers[j].pack(*it, rem(it, store_));
score = result.score();
score_all_plates = std::accumulate(placers.begin(), placers.begin() + j, score,
[](double sum, const Placer& elem) { return sum + elem.score(); });
if(score >= 0 && score < LARGE_COST_TO_REJECT) {
if (bed_id_firstfit == -1) {
bed_id_firstfit = j;
result_firstfit = result;
}
if (score_all_plates < score_all_plates_best) {
best_score = score;
score_all_plates_best = score_all_plates;
best_bed_id = j;
result_best = result;
}
}
}
if (best_bed_id == MAX_NUM_PLATES) {
// item is not fit because we have tried all possible plates to find a good enough fit
if (bed_id_firstfit == MAX_NUM_PLATES) {
it->get().binId(BIN_ID_UNFIT);
//if (this->unfitindicator_)
// this->unfitindicator_(it->get().name + " bed_id_firstfit == MAX_NUM_PLATES" + ",best_score=" + std::to_string(best_score));
break;
}
else {
// best bed is invalid, but firstfit bed is OK, use the latter
best_bed_id = bed_id_firstfit;
result_best = result_firstfit;
}
}
if(best_bed_id>=0)
{
was_packed = true;
j = best_bed_id;
it->get().binId(int(j));
it->get().itemId(item_id++);
placers[j].accept(result_best);
makeProgress(placers[j], j);
}
if(!was_packed){
//if (this->unfitindicator_)
// this->unfitindicator_(it->get().name + " ,plate_id=" + std::to_string(j) + ",score=" + std::to_string(score)
// + ", score_all_plates=" + std::to_string(score_all_plates)
// + ", overfit=" + std::to_string(result.overfit()));
placers.emplace_back(bin);
placers.back().plateID(placers.size() - 1);
placers.back().configure(pconfig);
if (fixed_bins.size() >= placers.size())
placers.back().preload(fixed_bins[placers.size() - 1]);
//placers.back().preload(pconfig.m_excluded_items);
packed_bins_.emplace_back();
j = placers.size() - 1;
}
}
}
}
};
}
}
#endif // FIRSTFIT_HPP

View file

@ -0,0 +1,69 @@
#ifndef SELECTION_BOILERPLATE_HPP
#define SELECTION_BOILERPLATE_HPP
#include <atomic>
#include <libnest2d/nester.hpp>
namespace libnest2d { namespace selections {
template<class RawShape>
class SelectionBoilerplate {
public:
using ShapeType = RawShape;
using Item = _Item<RawShape>;
using ItemGroup = _ItemGroup<RawShape>;
using PackGroup = _PackGroup<RawShape>;
inline const PackGroup& getResult() const {
return packed_bins_;
}
inline int lastPackedBinId() const { return last_packed_bin_id_; }
inline void progressIndicator(ProgressFunction fn) { progress_ = fn; }
inline void stopCondition(StopCondition cond) { stopcond_ = cond; }
inline void unfitIndicator(UnfitIndicator fn) { unfitindicator_ = fn; }
inline void clear() { packed_bins_.clear(); }
protected:
template<class Placer, class Container, class Bin, class PCfg>
void remove_unpackable_items(Container &c, const Bin &bin, const PCfg& pcfg)
{
// Safety test: try to pack each item into an empty bin. If it fails
// then it should be removed from the list
Placer p{ bin };
p.configure(pcfg);
//p.preload(pcfg.m_excluded_items);
auto it = c.begin();
while (it != c.end() && !stopcond_()) {
// WARNING: The copy of itm needs to be created before Placer.
// Placer is working with references and its destructor still
// manipulates the item this is why the order of stack creation
// matters here.
const Item& itm = *it;
Item cpy{itm};
auto result = p.pack(cpy);
if (itm.area() <= 0 || !result) {
static_cast<Item&>(*it).binId(BIN_ID_UNFIT);
}
it++;
}
}
PackGroup packed_bins_;
ProgressFunction progress_ = [](unsigned){};
StopCondition stopcond_ = []() { return false; };
UnfitIndicator unfitindicator_;
int last_packed_bin_id_ = -1;
};
}
}
#endif // SELECTION_BOILERPLATE_HPP

View file

@ -0,0 +1,533 @@
#ifndef BOOST_ALG_HPP
#define BOOST_ALG_HPP
#ifndef DISABLE_BOOST_SERIALIZE
#include <sstream>
#endif
#ifdef __clang__
#undef _MSC_EXTENSIONS
#endif
#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
// #include "../libnest2d.hpp"
namespace bp2d {
using libnest2d::TCoord;
using libnest2d::PointImpl;
using Coord = TCoord<PointImpl>;
using libnest2d::PolygonImpl;
using libnest2d::PathImpl;
using libnest2d::Orientation;
using libnest2d::OrientationType;
using libnest2d::OrientationTypeV;
using libnest2d::ClosureType;
using libnest2d::Closure;
using libnest2d::ClosureTypeV;
using libnest2d::getX;
using libnest2d::getY;
using libnest2d::setX;
using libnest2d::setY;
using Box = libnest2d::_Box<PointImpl>;
using Segment = libnest2d::_Segment<PointImpl>;
using Shapes = libnest2d::nfp::Shapes<PolygonImpl>;
}
/**
* We have to make all the libnest2d geometry types available to boost. The real
* models of the geometries remain the same if a conforming model for libnest2d
* was defined by the library client. Boost is used only as an optional
* implementer of some algorithms that can be implemented by the model itself
* if a faster alternative exists.
*
* However, boost has its own type traits and we have to define the needed
* specializations to be able to use boost::geometry. This can be done with the
* already provided model.
*/
namespace boost {
namespace geometry {
namespace traits {
/* ************************************************************************** */
/* Point concept adaptaion ************************************************** */
/* ************************************************************************** */
template<> struct tag<bp2d::PointImpl> {
using type = point_tag;
};
template<> struct coordinate_type<bp2d::PointImpl> {
using type = bp2d::Coord;
};
template<> struct coordinate_system<bp2d::PointImpl> {
using type = cs::cartesian;
};
template<> struct dimension<bp2d::PointImpl>: boost::mpl::int_<2> {};
template<>
struct access<bp2d::PointImpl, 0 > {
static inline bp2d::Coord get(bp2d::PointImpl const& a) {
return libnest2d::getX(a);
}
static inline void set(bp2d::PointImpl& a,
bp2d::Coord const& value) {
libnest2d::setX(a, value);
}
};
template<>
struct access<bp2d::PointImpl, 1 > {
static inline bp2d::Coord get(bp2d::PointImpl const& a) {
return libnest2d::getY(a);
}
static inline void set(bp2d::PointImpl& a,
bp2d::Coord const& value) {
libnest2d::setY(a, value);
}
};
/* ************************************************************************** */
/* Box concept adaptaion **************************************************** */
/* ************************************************************************** */
template<> struct tag<bp2d::Box> {
using type = box_tag;
};
template<> struct point_type<bp2d::Box> {
using type = bp2d::PointImpl;
};
template<> struct indexed_access<bp2d::Box, min_corner, 0> {
static inline bp2d::Coord get(bp2d::Box const& box) {
return bp2d::getX(box.minCorner());
}
static inline void set(bp2d::Box &box, bp2d::Coord const& coord) {
bp2d::setX(box.minCorner(), coord);
}
};
template<> struct indexed_access<bp2d::Box, min_corner, 1> {
static inline bp2d::Coord get(bp2d::Box const& box) {
return bp2d::getY(box.minCorner());
}
static inline void set(bp2d::Box &box, bp2d::Coord const& coord) {
bp2d::setY(box.minCorner(), coord);
}
};
template<> struct indexed_access<bp2d::Box, max_corner, 0> {
static inline bp2d::Coord get(bp2d::Box const& box) {
return bp2d::getX(box.maxCorner());
}
static inline void set(bp2d::Box &box, bp2d::Coord const& coord) {
bp2d::setX(box.maxCorner(), coord);
}
};
template<> struct indexed_access<bp2d::Box, max_corner, 1> {
static inline bp2d::Coord get(bp2d::Box const& box) {
return bp2d::getY(box.maxCorner());
}
static inline void set(bp2d::Box &box, bp2d::Coord const& coord) {
bp2d::setY(box.maxCorner(), coord);
}
};
/* ************************************************************************** */
/* Segment concept adaptaion ************************************************ */
/* ************************************************************************** */
template<> struct tag<bp2d::Segment> {
using type = segment_tag;
};
template<> struct point_type<bp2d::Segment> {
using type = bp2d::PointImpl;
};
template<> struct indexed_access<bp2d::Segment, 0, 0> {
static inline bp2d::Coord get(bp2d::Segment const& seg) {
return bp2d::getX(seg.first());
}
static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) {
auto p = seg.first(); bp2d::setX(p, coord); seg.first(p);
}
};
template<> struct indexed_access<bp2d::Segment, 0, 1> {
static inline bp2d::Coord get(bp2d::Segment const& seg) {
return bp2d::getY(seg.first());
}
static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) {
auto p = seg.first(); bp2d::setY(p, coord); seg.first(p);
}
};
template<> struct indexed_access<bp2d::Segment, 1, 0> {
static inline bp2d::Coord get(bp2d::Segment const& seg) {
return bp2d::getX(seg.second());
}
static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) {
auto p = seg.second(); bp2d::setX(p, coord); seg.second(p);
}
};
template<> struct indexed_access<bp2d::Segment, 1, 1> {
static inline bp2d::Coord get(bp2d::Segment const& seg) {
return bp2d::getY(seg.second());
}
static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) {
auto p = seg.second(); bp2d::setY(p, coord); seg.second(p);
}
};
/* ************************************************************************** */
/* Polygon concept adaptation *********************************************** */
/* ************************************************************************** */
// Connversion between libnest2d::Orientation and order_selector ///////////////
template<bp2d::Orientation> struct ToBoostOrienation {};
template<>
struct ToBoostOrienation<bp2d::Orientation::CLOCKWISE> {
static const order_selector Value = clockwise;
};
template<>
struct ToBoostOrienation<bp2d::Orientation::COUNTER_CLOCKWISE> {
static const order_selector Value = counterclockwise;
};
template<bp2d::Closure> struct ToBoostClosure {};
template<> struct ToBoostClosure<bp2d::Closure::OPEN> {
static const constexpr closure_selector Value = closure_selector::open;
};
template<> struct ToBoostClosure<bp2d::Closure::CLOSED> {
static const constexpr closure_selector Value = closure_selector::closed;
};
// Ring implementation /////////////////////////////////////////////////////////
// Boost would refer to ClipperLib::Path (alias bp2d::PolygonImpl) as a ring
template<> struct tag<bp2d::PathImpl> {
using type = ring_tag;
};
template<> struct point_order<bp2d::PathImpl> {
static const order_selector value =
ToBoostOrienation<bp2d::OrientationTypeV<bp2d::PathImpl>>::Value;
};
// All our Paths should be closed for the bin packing application
template<> struct closure<bp2d::PathImpl> {
static const constexpr closure_selector value =
ToBoostClosure< bp2d::ClosureTypeV<bp2d::PathImpl> >::Value;
};
// Polygon implementation //////////////////////////////////////////////////////
template<> struct tag<bp2d::PolygonImpl> {
using type = polygon_tag;
};
template<> struct exterior_ring<bp2d::PolygonImpl> {
static inline bp2d::PathImpl& get(bp2d::PolygonImpl& p) {
return libnest2d::shapelike::contour(p);
}
static inline bp2d::PathImpl const& get(bp2d::PolygonImpl const& p) {
return libnest2d::shapelike::contour(p);
}
};
template<> struct ring_const_type<bp2d::PolygonImpl> {
using type = const bp2d::PathImpl&;
};
template<> struct ring_mutable_type<bp2d::PolygonImpl> {
using type = bp2d::PathImpl&;
};
template<> struct interior_const_type<bp2d::PolygonImpl> {
using type = const libnest2d::THolesContainer<bp2d::PolygonImpl>&;
};
template<> struct interior_mutable_type<bp2d::PolygonImpl> {
using type = libnest2d::THolesContainer<bp2d::PolygonImpl>&;
};
template<>
struct interior_rings<bp2d::PolygonImpl> {
static inline libnest2d::THolesContainer<bp2d::PolygonImpl>& get(
bp2d::PolygonImpl& p)
{
return libnest2d::shapelike::holes(p);
}
static inline const libnest2d::THolesContainer<bp2d::PolygonImpl>& get(
bp2d::PolygonImpl const& p)
{
return libnest2d::shapelike::holes(p);
}
};
/* ************************************************************************** */
/* MultiPolygon concept adaptation ****************************************** */
/* ************************************************************************** */
template<> struct tag<bp2d::Shapes> {
using type = multi_polygon_tag;
};
} // traits
} // geometry
// This is an addition to the ring implementation of Polygon concept
template<>
struct range_value<bp2d::PathImpl> {
using type = bp2d::PointImpl;
};
template<>
struct range_value<bp2d::Shapes> {
using type = bp2d::PolygonImpl;
};
} // boost
/* ************************************************************************** */
/* Algorithms *************************************************************** */
/* ************************************************************************** */
namespace libnest2d { // Now the algorithms that boost can provide...
//namespace pointlike {
//template<>
//inline double distance(const PointImpl& p1, const PointImpl& p2 )
//{
// return boost::geometry::distance(p1, p2);
//}
//template<>
//inline double distance(const PointImpl& p, const bp2d::Segment& seg )
//{
// return boost::geometry::distance(p, seg);
//}
//}
namespace shapelike {
// Tell libnest2d how to make string out of a ClipperPolygon object
template<>
inline bool intersects(const PathImpl& sh1, const PathImpl& sh2)
{
return boost::geometry::intersects(sh1, sh2);
}
// Tell libnest2d how to make string out of a ClipperPolygon object
template<>
inline bool intersects(const PolygonImpl& sh1, const PolygonImpl& sh2)
{
return boost::geometry::intersects(sh1, sh2);
}
// Tell libnest2d how to make string out of a ClipperPolygon object
template<>
inline bool intersects(const bp2d::Segment& s1, const bp2d::Segment& s2)
{
return boost::geometry::intersects(s1, s2);
}
#ifndef DISABLE_BOOST_AREA
template<>
inline double area(const PolygonImpl& shape, const PolygonTag&)
{
return boost::geometry::area(shape);
}
#endif
template<>
inline bool isInside(const PointImpl& point, const PolygonImpl& shape,
const PointTag&, const PolygonTag&)
{
return boost::geometry::within(point, shape);
}
template<>
inline bool isInside(const PolygonImpl& sh1, const PolygonImpl& sh2,
const PolygonTag&, const PolygonTag&)
{
return boost::geometry::within(sh1, sh2);
}
template<>
inline bool touches(const PolygonImpl& sh1, const PolygonImpl& sh2)
{
return boost::geometry::touches(sh1, sh2);
}
template<>
inline bool touches( const PointImpl& point, const PolygonImpl& shape)
{
return boost::geometry::touches(point, shape);
}
#ifndef DISABLE_BOOST_BOUNDING_BOX
template<>
inline bp2d::Box boundingBox(const PathImpl& sh, const PathTag&)
{
bp2d::Box b;
boost::geometry::envelope(sh, b);
return b;
}
template<>
inline bp2d::Box boundingBox<bp2d::Shapes>(const bp2d::Shapes& shapes,
const MultiPolygonTag&)
{
bp2d::Box b;
boost::geometry::envelope(shapes, b);
return b;
}
#endif
#ifndef DISABLE_BOOST_CONVEX_HULL
template<>
inline PathImpl convexHull(const PathImpl& sh, const PathTag&)
{
PathImpl ret;
boost::geometry::convex_hull(sh, ret);
return ret;
}
template<>
inline PolygonImpl convexHull(const TMultiShape<PolygonImpl>& shapes,
const MultiPolygonTag&)
{
PolygonImpl ret;
boost::geometry::convex_hull(shapes, ret);
return ret;
}
#endif
#ifndef DISABLE_BOOST_OFFSET
template<>
inline void offset(PolygonImpl& sh, bp2d::Coord distance)
{
PolygonImpl cpy = sh;
boost::geometry::buffer(cpy, sh, distance);
}
#endif
#ifndef DISABLE_BOOST_SERIALIZE
template<> inline std::string serialize<libnest2d::Formats::SVG>(
const PolygonImpl& sh, double scale, std::string fill, std::string stroke, float stroke_width)
{
std::stringstream ss;
std::string style = "fill: "+fill+"; stroke: "+stroke+"; stroke-width: "+std::to_string(stroke_width)+"px; ";
using namespace boost::geometry;
using Pointf = model::point<double, 2, cs::cartesian>;
using Polygonf = model::polygon<Pointf>;
Polygonf::ring_type ring;
Polygonf::inner_container_type holes;
ring.reserve(shapelike::contourVertexCount(sh));
for(auto it = shapelike::cbegin(sh); it != shapelike::cend(sh); it++) {
auto& v = *it;
ring.emplace_back(getX(v)*scale, getY(v)*scale);
};
auto H = shapelike::holes(sh);
for(PathImpl& h : H ) {
Polygonf::ring_type hf;
for(auto it = h.begin(); it != h.end(); it++) {
auto& v = *it;
hf.emplace_back(getX(v)*scale, getY(v)*scale);
};
holes.emplace_back(std::move(hf));
}
Polygonf poly;
poly.outer() = ring;
poly.inners() = holes;
auto svg_data = boost::geometry::svg(poly, style);
ss << svg_data << std::endl;
return ss.str();
}
#endif
#ifndef DISABLE_BOOST_UNSERIALIZE
template<>
inline void unserialize<libnest2d::Formats::SVG>(
PolygonImpl& sh,
const std::string& str)
{
}
#endif
template<> inline std::pair<bool, std::string> isValid(const PolygonImpl& sh)
{
std::string message;
bool ret = boost::geometry::is_valid(sh, message);
return {ret, message};
}
}
namespace nfp {
#ifndef DISABLE_BOOST_NFP_MERGE
// Warning: I could not get boost union_ to work. Geometries will overlap.
template<>
inline bp2d::Shapes nfp::merge(const bp2d::Shapes& shapes,
const PolygonImpl& sh)
{
bp2d::Shapes retv;
boost::geometry::union_(shapes, sh, retv);
return retv;
}
template<>
inline bp2d::Shapes nfp::merge(const bp2d::Shapes& shapes)
{
bp2d::Shapes retv;
boost::geometry::union_(shapes, shapes.back(), retv);
return retv;
}
#endif
}
}
#endif // BOOST_ALG_HPP

View file

@ -0,0 +1,227 @@
#ifndef METALOOP_HPP
#define METALOOP_HPP
#include <libnest2d/common.hpp>
#include <tuple>
#include <functional>
namespace libnest2d {
/* ************************************************************************** */
/* C++14 std::index_sequence implementation: */
/* ************************************************************************** */
/**
* \brief C++11 compatible 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 necessary
* 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 necessary 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 lambda 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 meta-programming 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

@ -0,0 +1,372 @@
#ifndef ROTCALIPERS_HPP
#define ROTCALIPERS_HPP
#include <numeric>
#include <functional>
#include <array>
#include <cmath>
#include <libnest2d/geometry_traits.hpp>
namespace libnest2d {
template<class Pt, class Unit = TCompute<Pt>> class RotatedBox {
Pt axis_;
Unit bottom_ = Unit(0), right_ = Unit(0);
public:
RotatedBox() = default;
RotatedBox(const Pt& axis, Unit b, Unit r):
axis_(axis), bottom_(b), right_(r) {}
inline long double area() const {
long double asq = pl::magnsq<Pt, long double>(axis_);
return cast<long double>(bottom_) * cast<long double>(right_) / asq;
}
inline long double width() const {
return abs(bottom_) / std::sqrt(pl::magnsq<Pt, long double>(axis_));
}
inline long double height() const {
return abs(right_) / std::sqrt(pl::magnsq<Pt, long double>(axis_));
}
inline Unit bottom_extent() const { return bottom_; }
inline Unit right_extent() const { return right_; }
inline const Pt& axis() const { return axis_; }
inline Radians angleToX() const {
double ret = std::atan2(getY(axis_), getX(axis_));
auto s = std::signbit(ret);
if(s) ret += Pi_2;
return -ret;
}
};
template <class Poly, class Pt = TPoint<Poly>, class Unit = TCompute<Pt>>
Poly removeCollinearPoints(const Poly& sh, Unit eps = Unit(0))
{
Poly ret; sl::reserve(ret, sl::contourVertexCount(sh));
Pt eprev = *sl::cbegin(sh) - *std::prev(sl::cend(sh));
auto it = sl::cbegin(sh);
auto itx = std::next(it);
if(itx != sl::cend(sh)) while (it != sl::cend(sh))
{
Pt enext = *itx - *it;
auto dp = pl::dotperp<Pt, Unit>(eprev, enext);
if(abs(dp) > eps) sl::addVertex(ret, *it);
eprev = enext;
if (++itx == sl::cend(sh)) itx = sl::cbegin(sh);
++it;
}
return ret;
}
// The area of the bounding rectangle with the axis dir and support vertices
template<class Pt, class Unit = TCompute<Pt>, class R = TCompute<Pt>>
inline R rectarea(const Pt& w, // the axis
const Pt& vb, const Pt& vr,
const Pt& vt, const Pt& vl)
{
Unit a = pl::dot<Pt, Unit>(w, vr - vl);
Unit b = pl::dot<Pt, Unit>(-pl::perp(w), vt - vb);
R m = R(a) / pl::magnsq<Pt, Unit>(w);
m = m * b;
return m;
};
template<class Pt,
class Unit = TCompute<Pt>,
class R = TCompute<Pt>,
class It = typename std::vector<Pt>::const_iterator>
inline R rectarea(const Pt& w, const std::array<It, 4>& rect)
{
return rectarea<Pt, Unit, R>(w, *rect[0], *rect[1], *rect[2], *rect[3]);
}
template<class Pt, class Unit = TCompute<Pt>, class R = TCompute<Pt>>
inline R rectarea(const Pt& w, // the axis
const Unit& a,
const Unit& b)
{
R m = R(a) / pl::magnsq<Pt, Unit>(w);
m = m * b;
return m;
};
template<class R, class Pt, class Unit>
inline R rectarea(const RotatedBox<Pt, Unit> &rb)
{
return rectarea<Pt, Unit, R>(rb.axis(), rb.bottom_extent(), rb.right_extent());
};
// This function is only applicable to counter-clockwise oriented convex
// polygons where only two points can be collinear witch each other.
template <class RawShape,
class Unit = TCompute<RawShape>,
class Ratio = TCompute<RawShape>,
class VisitFn>
void rotcalipers(const RawShape& sh, VisitFn &&visitfn)
{
using Point = TPoint<RawShape>;
using Iterator = typename TContour<RawShape>::const_iterator;
using pointlike::dot; using pointlike::magnsq; using pointlike::perp;
// Get the first and the last vertex iterator
auto first = sl::cbegin(sh);
auto last = std::prev(sl::cend(sh));
// Check conditions and return undefined box if input is not sane.
if(last == first) return;
if(getX(*first) == getX(*last) && getY(*first) == getY(*last)) --last;
if(last - first < 2) return;
RawShape shcpy; // empty at this point
{
Point p = *first, q = *std::next(first), r = *last;
// Determine orientation from first 3 vertex (should be consistent)
Unit d = (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) -
(Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p));
if(d > 0) {
// The polygon is clockwise. A flip is needed (for now)
sl::reserve(shcpy, last - first);
auto it = last; while(it != first) sl::addVertex(shcpy, *it--);
sl::addVertex(shcpy, *first);
first = sl::cbegin(shcpy); last = std::prev(sl::cend(shcpy));
}
}
// Cyclic iterator increment
auto inc = [&first, &last](Iterator& it) {
if(it == last) it = first; else ++it;
};
// Cyclic previous iterator
auto prev = [&first, &last](Iterator it) {
return it == first ? last : std::prev(it);
};
// Cyclic next iterator
auto next = [&first, &last](Iterator it) {
return it == last ? first : std::next(it);
};
// Establish initial (axis aligned) rectangle support verices by determining
// polygon extremes:
auto it = first;
Iterator minX = it, maxX = it, minY = it, maxY = it;
do { // Linear walk through the vertices and save the extreme positions
Point v = *it, d = v - *minX;
if(getX(d) < 0 || (getX(d) == 0 && getY(d) < 0)) minX = it;
d = v - *maxX;
if(getX(d) > 0 || (getX(d) == 0 && getY(d) > 0)) maxX = it;
d = v - *minY;
if(getY(d) < 0 || (getY(d) == 0 && getX(d) > 0)) minY = it;
d = v - *maxY;
if(getY(d) > 0 || (getY(d) == 0 && getX(d) < 0)) maxY = it;
} while(++it != std::next(last));
// Update the vertices defining the bounding rectangle. The rectangle with
// the smallest rotation is selected and the supporting vertices are
// returned in the 'rect' argument.
auto update = [&next, &inc]
(const Point& w, std::array<Iterator, 4>& rect)
{
Iterator B = rect[0], Bn = next(B);
Iterator R = rect[1], Rn = next(R);
Iterator T = rect[2], Tn = next(T);
Iterator L = rect[3], Ln = next(L);
Point b = *Bn - *B, r = *Rn - *R, t = *Tn - *T, l = *Ln - *L;
Point pw = perp(w);
using Pt = Point;
Unit dotwpb = dot<Pt, Unit>( w, b), dotwpr = dot<Pt, Unit>(-pw, r);
Unit dotwpt = dot<Pt, Unit>(-w, t), dotwpl = dot<Pt, Unit>( pw, l);
Unit dw = magnsq<Pt, Unit>(w);
std::array<Ratio, 4> angles;
angles[0] = (Ratio(dotwpb) / magnsq<Pt, Unit>(b)) * dotwpb;
angles[1] = (Ratio(dotwpr) / magnsq<Pt, Unit>(r)) * dotwpr;
angles[2] = (Ratio(dotwpt) / magnsq<Pt, Unit>(t)) * dotwpt;
angles[3] = (Ratio(dotwpl) / magnsq<Pt, Unit>(l)) * dotwpl;
using AngleIndex = std::pair<Ratio, size_t>;
std::vector<AngleIndex> A; A.reserve(4);
for (size_t i = 3, j = 0; j < 4; i = j++) {
if(rect[i] != rect[j] && angles[i] < dw) {
auto iv = std::make_pair(angles[i], i);
auto it = std::lower_bound(A.begin(), A.end(), iv,
[](const AngleIndex& ai,
const AngleIndex& aj)
{
return ai.first > aj.first;
});
A.insert(it, iv);
}
}
// The polygon is supposed to be a rectangle.
if(A.empty()) return false;
auto amin = A.front().first;
auto imin = A.front().second;
for(auto& a : A) if(a.first == amin) inc(rect[a.second]);
std::rotate(rect.begin(), rect.begin() + imin, rect.end());
return true;
};
Point w(1, 0);
std::array<Iterator, 4> rect = {minY, maxX, maxY, minX};
{
Unit a = dot<Point, Unit>(w, *rect[1] - *rect[3]);
Unit b = dot<Point, Unit>(-perp(w), *rect[2] - *rect[0]);
if (!visitfn(RotatedBox<Point, Unit>{w, a, b}))
return;
}
// An edge might be examined twice in which case the algorithm terminates.
size_t c = 0, count = last - first + 1;
std::vector<bool> edgemask(count, false);
while(c++ < count)
{
// Update the support vertices, if cannot be updated, break the cycle.
if(! update(w, rect)) break;
size_t eidx = size_t(rect[0] - first);
if(edgemask[eidx]) break;
edgemask[eidx] = true;
// get the unnormalized direction vector
w = *rect[0] - *prev(rect[0]);
Unit a = dot<Point, Unit>(w, *rect[1] - *rect[3]);
Unit b = dot<Point, Unit>(-perp(w), *rect[2] - *rect[0]);
if (!visitfn(RotatedBox<Point, Unit>{w, a, b}))
break;
}
}
// This function is only applicable to counter-clockwise oriented convex
// polygons where only two points can be collinear witch each other.
template <class S,
class Unit = TCompute<S>,
class Ratio = TCompute<S>>
RotatedBox<TPoint<S>, Unit> minAreaBoundingBox(const S& sh)
{
RotatedBox<TPoint<S>, Unit> minbox;
Ratio minarea = std::numeric_limits<Unit>::max();
auto minfn = [&minarea, &minbox](const RotatedBox<TPoint<S>, Unit> &rbox){
Ratio area = rectarea<Ratio>(rbox);
if (area <= minarea) {
minarea = area;
minbox = rbox;
}
return true; // continue search
};
rotcalipers<S, Unit, Ratio>(sh, minfn);
return minbox;
}
template <class RawShape> Radians minAreaBoundingBoxRotation(const RawShape& sh)
{
return minAreaBoundingBox(sh).angleToX();
}
// Function to find a rotation for a shape that makes it fit into a box.
//
// The method is based on finding a pair of rotations from the rotating calipers
// algorithm such that the aspect ratio is changing from being smaller than
// that of the target to being bigger or vice versa. So that the correct
// AR is somewhere between the obtained pair of angles. Then bisecting that
// interval is sufficient to find the correct angle.
//
// The argument eps is the absolute error limit for the searched angle interval.
template<class S, class Unit = TCompute<S>, class Ratio = TCompute<S>>
Radians fitIntoBoxRotation(const S &shape, const _Box<TPoint<S>> &box, Radians eps = 1e-4)
{
constexpr auto get_aspect_r = [](const auto &b) -> double {
return double(b.width()) / b.height();
};
auto aspect_r = get_aspect_r(box);
RotatedBox<TPoint<S>, Unit> prev_rbox;
Radians a_from = 0., a_to = 0.;
auto visitfn = [&](const RotatedBox<TPoint<S>, Unit> &rbox) {
bool lower_prev = get_aspect_r(prev_rbox) < aspect_r;
bool lower_current = get_aspect_r(rbox) < aspect_r;
if (lower_prev != lower_current) {
a_from = prev_rbox.angleToX();
a_to = rbox.angleToX();
return false;
}
return true;
};
rotcalipers<S, Unit, Ratio>(shape, visitfn);
auto rot_shape_bb = [&shape](Radians r) {
auto s = shape;
sl::rotate(s, r);
return sl::boundingBox(s);
};
auto rot_aspect_r = [&rot_shape_bb, &get_aspect_r](Radians r) {
return get_aspect_r(rot_shape_bb(r));
};
// Lets bisect the retrieved interval where the correct aspect ratio is.
double ar_from = rot_aspect_r(a_from);
auto would_fit = [&box](const _Box<TPoint<S>> &b) {
return b.width() < box.width() && b.height() < box.height();
};
Radians middle = (a_from + a_to) / 2.;
_Box<TPoint<S>> box_middle = rot_shape_bb(middle);
while (!would_fit(box_middle) && std::abs(a_to - a_from) > eps)
{
double ar_middle = get_aspect_r(box_middle);
if ((ar_from < aspect_r) != (ar_middle < aspect_r))
a_to = middle;
else
a_from = middle;
ar_from = rot_aspect_r(a_from);
middle = (a_from + a_to) / 2.;
box_middle = rot_shape_bb(middle);
}
return middle;
}
} // namespace libnest2d
#endif // ROTCALIPERS_HPP

View file

@ -0,0 +1,41 @@
#ifndef ROTFINDER_HPP
#define ROTFINDER_HPP
#include <libnest2d/libnest2d.hpp>
#include <libnest2d/optimizer.hpp>
#include <iterator>
namespace libnest2d {
template<class RawShape>
Radians findBestRotation(_Item<RawShape>& item) {
opt::StopCriteria stopcr;
stopcr.absolute_score_difference = 0.01;
stopcr.max_iterations = 10000;
opt::TOptimizer<opt::Method::G_GENETIC> solver(stopcr);
auto orig_rot = item.rotation();
auto result = solver.optimize_min([&item, &orig_rot](Radians rot){
item.rotation(orig_rot + rot);
auto bb = item.boundingBox();
return std::sqrt(bb.height()*bb.width());
}, opt::initvals(Radians(0)), opt::bound<Radians>(-Pi/2, Pi/2));
item.rotation(orig_rot);
return std::get<0>(result.optimum);
}
template<class Iterator>
void findMinimumBoundingBoxRotations(Iterator from, Iterator to) {
using V = typename std::iterator_traits<Iterator>::value_type;
std::for_each(from, to, [](V& item){
Radians rot = findBestRotation(item);
item.rotate(rot);
});
}
}
#endif // ROTFINDER_HPP

View file

@ -0,0 +1,26 @@
#include <libnest2d/libnest2d.hpp>
namespace libnest2d {
template class _Nester<NfpPlacer, FirstFitSelection>;
template class _Nester<BottomLeftPlacer, FirstFitSelection>;
template std::size_t _Nester<NfpPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
template std::size_t _Nester<BottomLeftPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box & bin,
Coord dist,
const NestConfig<NfpPlacer, FirstFitSelection> &cfg,
NestControl ctl);
template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box & bin,
Coord dist,
const NestConfig<BottomLeftPlacer, FirstFitSelection> &cfg,
NestControl ctl);
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (C) Tamás Mészáros
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef INCLUDE_BENCHMARK_H_
#define INCLUDE_BENCHMARK_H_
#include <chrono>
#include <ratio>
/**
* A class for doing benchmarks.
*/
class Benchmark {
typedef std::chrono::high_resolution_clock Clock;
typedef Clock::duration Duration;
typedef Clock::time_point TimePoint;
TimePoint t1, t2;
Duration d;
inline double to_sec(Duration d) {
return d.count() * double(Duration::period::num) / Duration::period::den;
}
public:
/**
* Measure time from the moment of this call.
*/
void start() { t1 = Clock::now(); }
/**
* Measure time to the moment of this call.
*/
void stop() { t2 = Clock::now(); }
/**
* Get the time elapsed between a start() end a stop() call.
* @return Returns the elapsed time in seconds.
*/
double getElapsedSec() { d = t2 - t1; return to_sec(d); }
};
#endif /* INCLUDE_BENCHMARK_H_ */

View file

@ -0,0 +1,148 @@
#ifndef SVGTOOLS_HPP
#define SVGTOOLS_HPP
#include <iostream>
#include <fstream>
#include <string>
#include <libnest2d/nester.hpp>
namespace libnest2d { namespace svg {
template<class RawShape>
class SVGWriter {
using Item = _Item<RawShape>;
using Coord = TCoord<TPoint<RawShape>>;
using Box = _Box<TPoint<RawShape>>;
using PackGroup = _PackGroup<RawShape>;
public:
enum OrigoLocation {
TOPLEFT,
BOTTOMLEFT
};
struct Config {
OrigoLocation origo_location;
Coord mm_in_coord_units;
double width, height;
Config():
origo_location(BOTTOMLEFT), mm_in_coord_units(1000000),
width(500), height(500) {}
};
private:
Config conf_;
std::vector<std::string> svg_layers_;
bool finished_ = false;
public:
SVGWriter(const Config& conf = Config()):
conf_(conf) {}
void setSize(const Box& box) {
conf_.height = static_cast<double>(box.height()) /
conf_.mm_in_coord_units;
conf_.width = static_cast<double>(box.width()) /
conf_.mm_in_coord_units;
}
void writeShape(RawShape tsh, std::string fill = "none", std::string stroke = "black", float stroke_width = 1) {
if(svg_layers_.empty()) addLayer();
if(conf_.origo_location == BOTTOMLEFT) {
auto d = static_cast<Coord>(
std::round(conf_.height*conf_.mm_in_coord_units) );
auto& contour = shapelike::contour(tsh);
for(auto& v : contour) setY(v, -getY(v) + d);
auto& holes = shapelike::holes(tsh);
for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d);
}
currentLayer() +=
shapelike::serialize<Formats::SVG>(tsh,
1.0 / conf_.mm_in_coord_units, fill, stroke, stroke_width) +
"\n";
}
void writeItem(const Item& item, std::string fill = "none", std::string stroke = "black", float stroke_width = 1) {
writeShape(item.transformedShape(), fill, stroke, stroke_width);
}
void writePackGroup(const PackGroup& result) {
for(auto r : result) {
addLayer();
for(Item& sh : r) {
writeItem(sh);
}
finishLayer();
}
}
template<class ItemIt> void writeItems(ItemIt from, ItemIt to) {
auto it = from;
PackGroup pg;
while(it != to) {
if(it->binId() == BIN_ID_UNSET) continue;
while(pg.size() <= size_t(it->binId())) pg.emplace_back();
pg[it->binId()].emplace_back(*it);
++it;
}
writePackGroup(pg);
}
void draw_text(float x,float y, const std::string text, const std::string color, int font_size)
{
char s[500];
sprintf(s,
"<text x=\"%f\" y=\"%f\" font-family=\"sans-serif\" font-size=\"%dpx\" fill=\"%s\">%s</text>\n",
x,y, font_size, color.c_str(), text.c_str());
currentLayer() += s;
}
void addLayer() {
svg_layers_.emplace_back(header());
finished_ = false;
}
void finishLayer() {
currentLayer() += "\n</svg>\n";
finished_ = true;
}
void save(const std::string& filepath) {
size_t lyrc = svg_layers_.size() > 1? 1 : 0;
size_t last = svg_layers_.size() > 1? svg_layers_.size() : 0;
for(auto& lyr : svg_layers_) {
std::fstream out(filepath + (lyrc > 0? std::to_string(lyrc) : "") +
".svg", std::fstream::out);
if(out.is_open()) out << lyr;
if(lyrc == last && !finished_) out << "\n</svg>\n";
out.flush(); out.close(); lyrc++;
};
}
private:
std::string& currentLayer() { return svg_layers_.back(); }
const std::string header() const {
std::string svg_header =
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg height=")raw";
svg_header += std::to_string(conf_.height) + "\" width=\"" + std::to_string(conf_.width) + "\" ";
svg_header += R"raw(xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">)raw";
return svg_header;
}
};
}
}
#endif // SVGTOOLS_HPP