mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-21 21:58:03 -06:00
Add the full source of BambuStudio
using version 1.0.10
This commit is contained in:
parent
30bcadab3e
commit
1555904bef
3771 changed files with 1251328 additions and 0 deletions
28
src/libnest2d/CMakeLists.txt
Normal file
28
src/libnest2d/CMakeLists.txt
Normal 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
165
src/libnest2d/LICENSE.txt
Normal 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.
|
|
@ -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
|
232
src/libnest2d/include/libnest2d/common.hpp
Normal file
232
src/libnest2d/include/libnest2d/common.hpp
Normal 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 °s): 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
|
1290
src/libnest2d/include/libnest2d/geometry_traits.hpp
Normal file
1290
src/libnest2d/include/libnest2d/geometry_traits.hpp
Normal file
File diff suppressed because it is too large
Load diff
799
src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp
Normal file
799
src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp
Normal 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
|
144
src/libnest2d/include/libnest2d/libnest2d.hpp
Normal file
144
src/libnest2d/include/libnest2d/libnest2d.hpp
Normal 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
|
894
src/libnest2d/include/libnest2d/nester.hpp
Normal file
894
src/libnest2d/include/libnest2d/nester.hpp
Normal 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
|
254
src/libnest2d/include/libnest2d/optimizer.hpp
Normal file
254
src/libnest2d/include/libnest2d/optimizer.hpp
Normal 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
|
34
src/libnest2d/include/libnest2d/optimizers/nlopt/genetic.hpp
Normal file
34
src/libnest2d/include/libnest2d/optimizers/nlopt/genetic.hpp
Normal 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
|
|
@ -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
|
20
src/libnest2d/include/libnest2d/optimizers/nlopt/simplex.hpp
Normal file
20
src/libnest2d/include/libnest2d/optimizers/nlopt/simplex.hpp
Normal 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
|
20
src/libnest2d/include/libnest2d/optimizers/nlopt/subplex.hpp
Normal file
20
src/libnest2d/include/libnest2d/optimizers/nlopt/subplex.hpp
Normal 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
|
63
src/libnest2d/include/libnest2d/parallel.hpp
Normal file
63
src/libnest2d/include/libnest2d/parallel.hpp
Normal 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
|
434
src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp
Normal file
434
src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp
Normal 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
|
1152
src/libnest2d/include/libnest2d/placers/nfpplacer.hpp
Normal file
1152
src/libnest2d/include/libnest2d/placers/nfpplacer.hpp
Normal file
File diff suppressed because it is too large
Load diff
140
src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp
Normal file
140
src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp
Normal 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
|
717
src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp
Normal file
717
src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp
Normal 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, ¬_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, ¬_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, ¬_packed](ItemListIt it) {
|
||||
return placer.trypack(*it, rem(it, not_packed));
|
||||
};
|
||||
|
||||
auto pack = [&placer, ¬_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, ¬_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
|
87
src/libnest2d/include/libnest2d/selections/filler.hpp
Normal file
87
src/libnest2d/include/libnest2d/selections/filler.hpp
Normal 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
|
179
src/libnest2d/include/libnest2d/selections/firstfit.hpp
Normal file
179
src/libnest2d/include/libnest2d/selections/firstfit.hpp
Normal 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
|
|
@ -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
|
533
src/libnest2d/include/libnest2d/utils/boost_alg.hpp
Normal file
533
src/libnest2d/include/libnest2d/utils/boost_alg.hpp
Normal 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
|
227
src/libnest2d/include/libnest2d/utils/metaloop.hpp
Normal file
227
src/libnest2d/include/libnest2d/utils/metaloop.hpp
Normal 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
|
372
src/libnest2d/include/libnest2d/utils/rotcalipers.hpp
Normal file
372
src/libnest2d/include/libnest2d/utils/rotcalipers.hpp
Normal 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
|
41
src/libnest2d/include/libnest2d/utils/rotfinder.hpp
Normal file
41
src/libnest2d/include/libnest2d/utils/rotfinder.hpp
Normal 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
|
26
src/libnest2d/src/libnest2d.cpp
Normal file
26
src/libnest2d/src/libnest2d.cpp
Normal 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);
|
||||
}
|
58
src/libnest2d/tools/benchmark.h
Normal file
58
src/libnest2d/tools/benchmark.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (C) Tamás Mészáros
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
#ifndef INCLUDE_BENCHMARK_H_
|
||||
#define INCLUDE_BENCHMARK_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <ratio>
|
||||
|
||||
/**
|
||||
* A class for doing benchmarks.
|
||||
*/
|
||||
class Benchmark {
|
||||
typedef std::chrono::high_resolution_clock Clock;
|
||||
typedef Clock::duration Duration;
|
||||
typedef Clock::time_point TimePoint;
|
||||
|
||||
TimePoint t1, t2;
|
||||
Duration d;
|
||||
|
||||
inline double to_sec(Duration d) {
|
||||
return d.count() * double(Duration::period::num) / Duration::period::den;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Measure time from the moment of this call.
|
||||
*/
|
||||
void start() { t1 = Clock::now(); }
|
||||
|
||||
/**
|
||||
* Measure time to the moment of this call.
|
||||
*/
|
||||
void stop() { t2 = Clock::now(); }
|
||||
|
||||
/**
|
||||
* Get the time elapsed between a start() end a stop() call.
|
||||
* @return Returns the elapsed time in seconds.
|
||||
*/
|
||||
double getElapsedSec() { d = t2 - t1; return to_sec(d); }
|
||||
};
|
||||
|
||||
|
||||
#endif /* INCLUDE_BENCHMARK_H_ */
|
148
src/libnest2d/tools/svgtools.hpp
Normal file
148
src/libnest2d/tools/svgtools.hpp
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue