1065 lines
35 KiB
C++
1065 lines
35 KiB
C++
#ifndef NOFITPOLY_HPP
|
|
#define NOFITPOLY_HPP
|
|
|
|
#include <cassert>
|
|
|
|
// For parallel for
|
|
#include <functional>
|
|
#include <iterator>
|
|
#include <future>
|
|
#include <atomic>
|
|
|
|
#ifndef NDEBUG
|
|
#include <iostream>
|
|
#endif
|
|
#include <libnest2d/geometry_traits_nfp.hpp>
|
|
#include <libnest2d/optimizer.hpp>
|
|
|
|
#include "placer_boilerplate.hpp"
|
|
|
|
// temporary
|
|
//#include "../tools/svgtools.hpp"
|
|
|
|
#ifdef USE_TBB
|
|
#include <tbb/parallel_for.h>
|
|
#elif defined(_OPENMP)
|
|
#include <omp.h>
|
|
#endif
|
|
|
|
namespace libnest2d {
|
|
|
|
namespace __parallel {
|
|
|
|
using std::function;
|
|
using std::iterator_traits;
|
|
template<class It>
|
|
using TIteratorValue = typename iterator_traits<It>::value_type;
|
|
|
|
template<class Iterator>
|
|
inline void enumerate(
|
|
Iterator from, Iterator to,
|
|
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 USE_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);
|
|
}
|
|
#elif defined(_OPENMP)
|
|
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);
|
|
}
|
|
#else
|
|
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
|
|
}
|
|
|
|
}
|
|
|
|
namespace placers {
|
|
|
|
template<class RawShape>
|
|
struct NfpPConfig {
|
|
|
|
using ItemGroup = _ItemGroup<RawShape>;
|
|
|
|
enum class Alignment {
|
|
CENTER,
|
|
BOTTOM_LEFT,
|
|
BOTTOM_RIGHT,
|
|
TOP_LEFT,
|
|
TOP_RIGHT,
|
|
DONT_ALIGN //!> Warning: parts may end up outside the bin with the
|
|
//! default object function.
|
|
};
|
|
|
|
/// Which angles to try out for better results.
|
|
std::vector<Radians> rotations;
|
|
|
|
/// Where to align the resulting packed pile.
|
|
Alignment alignment;
|
|
|
|
/// Where to start putting objects in the bin.
|
|
Alignment starting_point;
|
|
|
|
/**
|
|
* @brief A function object representing the fitting function in the
|
|
* placement optimization process. (Optional)
|
|
*
|
|
* This is the most versatile tool to configure the placer. The fitting
|
|
* function is evaluated many times when a new item is being placed into the
|
|
* bin. The output should be a rated score of the new item's position.
|
|
*
|
|
* This is not a mandatory option as there is a default fitting function
|
|
* that will optimize for the best pack efficiency. With a custom fitting
|
|
* function you can e.g. influence the shape of the arranged pile.
|
|
*
|
|
* \param item The only parameter is the candidate item which has info
|
|
* about its current position. Your job is to rate this position compared to
|
|
* the already packed items.
|
|
*
|
|
*/
|
|
std::function<double(const _Item<RawShape>&)> object_function;
|
|
|
|
/**
|
|
* @brief The quality of search for an optimal placement.
|
|
* This is a compromise slider between quality and speed. Zero is the
|
|
* fast and poor solution while 1.0 is the slowest but most accurate.
|
|
*/
|
|
float accuracy = 0.65f;
|
|
|
|
/**
|
|
* @brief If you want to see items inside other item's holes, you have to
|
|
* turn this switch on.
|
|
*
|
|
* This will only work if a suitable nfp implementation is provided.
|
|
* The library has no such implementation right now.
|
|
*/
|
|
bool explore_holes = false;
|
|
|
|
/**
|
|
* @brief If true, use all CPUs available. Run on a single core otherwise.
|
|
*/
|
|
bool parallel = true;
|
|
|
|
/**
|
|
* @brief before_packing Callback that is called just before a search for
|
|
* a new item's position is started. You can use this to create various
|
|
* cache structures and update them between subsequent packings.
|
|
*
|
|
* \param merged pile A polygon that is the union of all items in the bin.
|
|
*
|
|
* \param pile The items parameter is a container with all the placed
|
|
* polygons excluding the current candidate. You can for instance check the
|
|
* alignment with the candidate item or do anything else.
|
|
*
|
|
* \param remaining A container with the remaining items waiting to be
|
|
* placed. You can use some features about the remaining items to alter to
|
|
* score of the current placement. If you know that you have to leave place
|
|
* for other items as well, that might influence your decision about where
|
|
* the current candidate should be placed. E.g. imagine three big circles
|
|
* which you want to place into a box: you might place them in a triangle
|
|
* shape which has the maximum pack density. But if there is a 4th big
|
|
* circle than you won't be able to pack it. If you knew apriori that
|
|
* there four circles are to be placed, you would have placed the first 3
|
|
* into an L shape. This parameter can be used to make these kind of
|
|
* decisions (for you or a more intelligent AI).
|
|
*/
|
|
std::function<void(const nfp::Shapes<RawShape>&, // merged pile
|
|
const ItemGroup&, // packed items
|
|
const ItemGroup& // remaining items
|
|
)> before_packing;
|
|
|
|
NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}),
|
|
alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {}
|
|
};
|
|
|
|
/**
|
|
* A class for getting a point on the circumference of the polygon (in log time)
|
|
*
|
|
* This is a transformation of the provided polygon to be able to pinpoint
|
|
* locations on the circumference. The optimizer will pass a floating point
|
|
* value e.g. within <0,1> and we have to transform this value quickly into a
|
|
* coordinate on the circumference. By definition 0 should yield the first
|
|
* vertex and 1.0 would be the last (which should coincide with first).
|
|
*
|
|
* We also have to make this work for the holes of the captured polygon.
|
|
*/
|
|
template<class RawShape> class EdgeCache {
|
|
using Vertex = TPoint<RawShape>;
|
|
using Coord = TCoord<Vertex>;
|
|
using Edge = _Segment<Vertex>;
|
|
|
|
struct ContourCache {
|
|
mutable std::vector<double> corners;
|
|
std::vector<Edge> emap;
|
|
std::vector<double> distances;
|
|
double full_distance = 0;
|
|
} contour_;
|
|
|
|
std::vector<ContourCache> holes_;
|
|
|
|
double accuracy_ = 1.0;
|
|
|
|
static double length(const Edge &e)
|
|
{
|
|
return std::sqrt(e.template sqlength<double>());
|
|
}
|
|
|
|
void createCache(const RawShape& sh) {
|
|
{ // For the contour
|
|
auto first = shapelike::cbegin(sh);
|
|
auto next = std::next(first);
|
|
auto endit = shapelike::cend(sh);
|
|
|
|
contour_.distances.reserve(shapelike::contourVertexCount(sh));
|
|
|
|
while(next != endit) {
|
|
contour_.emap.emplace_back(*(first++), *(next++));
|
|
contour_.full_distance += length(contour_.emap.back());
|
|
contour_.distances.emplace_back(contour_.full_distance);
|
|
}
|
|
}
|
|
|
|
for(auto& h : shapelike::holes(sh)) { // For the holes
|
|
auto first = h.begin();
|
|
auto next = std::next(first);
|
|
auto endit = h.end();
|
|
|
|
ContourCache hc;
|
|
hc.distances.reserve(endit - first);
|
|
|
|
while(next != endit) {
|
|
hc.emap.emplace_back(*(first++), *(next++));
|
|
hc.full_distance += length(hc.emap.back());
|
|
hc.distances.emplace_back(hc.full_distance);
|
|
}
|
|
|
|
holes_.emplace_back(std::move(hc));
|
|
}
|
|
}
|
|
|
|
size_t stride(const size_t N) const {
|
|
using std::round;
|
|
using std::pow;
|
|
|
|
return static_cast<Coord>(
|
|
round(N/pow(N, pow(accuracy_, 1.0/3.0)))
|
|
);
|
|
}
|
|
|
|
void fetchCorners() const {
|
|
if(!contour_.corners.empty()) return;
|
|
|
|
const auto N = contour_.distances.size();
|
|
const auto S = stride(N);
|
|
|
|
contour_.corners.reserve(N / S + 1);
|
|
contour_.corners.emplace_back(0.0);
|
|
auto N_1 = N-1;
|
|
contour_.corners.emplace_back(0.0);
|
|
for(size_t i = 0; i < N_1; i += S) {
|
|
contour_.corners.emplace_back(
|
|
contour_.distances.at(i) / contour_.full_distance);
|
|
}
|
|
}
|
|
|
|
void fetchHoleCorners(unsigned hidx) const {
|
|
auto& hc = holes_[hidx];
|
|
if(!hc.corners.empty()) return;
|
|
|
|
const auto N = hc.distances.size();
|
|
auto N_1 = N-1;
|
|
const auto S = stride(N);
|
|
hc.corners.reserve(N / S + 1);
|
|
hc.corners.emplace_back(0.0);
|
|
for(size_t i = 0; i < N_1; i += S) {
|
|
hc.corners.emplace_back(
|
|
hc.distances.at(i) / hc.full_distance);
|
|
}
|
|
}
|
|
|
|
inline Vertex coords(const ContourCache& cache, double distance) const {
|
|
assert(distance >= .0 && distance <= 1.0);
|
|
if (cache.distances.empty() || cache.emap.empty()) return Vertex{};
|
|
if (distance > 1.0) distance = std::fmod(distance, 1.0);
|
|
|
|
// distance is from 0.0 to 1.0, we scale it up to the full length of
|
|
// the circumference
|
|
double d = distance*cache.full_distance;
|
|
|
|
auto& distances = cache.distances;
|
|
|
|
// Magic: we find the right edge in log time
|
|
auto it = std::lower_bound(distances.begin(), distances.end(), d);
|
|
auto idx = it - distances.begin(); // get the index of the edge
|
|
auto edge = cache.emap[idx]; // extrac the edge
|
|
|
|
// Get the remaining distance on the target edge
|
|
auto ed = d - (idx > 0 ? *std::prev(it) : 0 );
|
|
auto angle = edge.angleToXaxis();
|
|
Vertex ret = edge.first();
|
|
|
|
// Get the point on the edge which lies in ed distance from the start
|
|
ret += { static_cast<Coord>(std::round(ed*std::cos(angle))),
|
|
static_cast<Coord>(std::round(ed*std::sin(angle))) };
|
|
|
|
return ret;
|
|
}
|
|
|
|
public:
|
|
|
|
using iterator = std::vector<double>::iterator;
|
|
using const_iterator = std::vector<double>::const_iterator;
|
|
|
|
inline EdgeCache() = default;
|
|
|
|
inline EdgeCache(const _Item<RawShape>& item)
|
|
{
|
|
createCache(item.transformedShape());
|
|
}
|
|
|
|
inline EdgeCache(const RawShape& sh)
|
|
{
|
|
createCache(sh);
|
|
}
|
|
|
|
/// Resolution of returned corners. The stride is derived from this value.
|
|
void accuracy(double a /* within <0.0, 1.0>*/) { accuracy_ = a; }
|
|
|
|
/**
|
|
* @brief Get a point on the circumference of a polygon.
|
|
* @param distance A relative distance from the starting point to the end.
|
|
* Can be from 0.0 to 1.0 where 0.0 is the starting point and 1.0 is the
|
|
* closing point (which should be eqvivalent with the starting point with
|
|
* closed polygons).
|
|
* @return Returns the coordinates of the point lying on the polygon
|
|
* circumference.
|
|
*/
|
|
inline Vertex coords(double distance) const {
|
|
return coords(contour_, distance);
|
|
}
|
|
|
|
inline Vertex coords(unsigned hidx, double distance) const {
|
|
assert(hidx < holes_.size());
|
|
return coords(holes_[hidx], distance);
|
|
}
|
|
|
|
inline double circumference() const BP2D_NOEXCEPT {
|
|
return contour_.full_distance;
|
|
}
|
|
|
|
inline double circumference(unsigned hidx) const BP2D_NOEXCEPT {
|
|
return holes_[hidx].full_distance;
|
|
}
|
|
|
|
/// Get the normalized distance values for each vertex
|
|
inline const std::vector<double>& corners() const BP2D_NOEXCEPT {
|
|
fetchCorners();
|
|
return contour_.corners;
|
|
}
|
|
|
|
/// corners for a specific hole
|
|
inline const std::vector<double>&
|
|
corners(unsigned holeidx) const BP2D_NOEXCEPT {
|
|
fetchHoleCorners(holeidx);
|
|
return holes_[holeidx].corners;
|
|
}
|
|
|
|
/// The number of holes in the abstracted polygon
|
|
inline size_t holeCount() const BP2D_NOEXCEPT { return holes_.size(); }
|
|
|
|
};
|
|
|
|
template<nfp::NfpLevel lvl>
|
|
struct Lvl { static const nfp::NfpLevel value = lvl; };
|
|
|
|
template<class RawShape>
|
|
inline void correctNfpPosition(nfp::NfpResult<RawShape>& nfp,
|
|
const _Item<RawShape>& stationary,
|
|
const _Item<RawShape>& orbiter)
|
|
{
|
|
// The provided nfp is somewhere in the dark. We need to get it
|
|
// to the right position around the stationary shape.
|
|
// This is done by choosing the leftmost lowest vertex of the
|
|
// orbiting polygon to be touched with the rightmost upper
|
|
// vertex of the stationary polygon. In this configuration, the
|
|
// reference vertex of the orbiting polygon (which can be dragged around
|
|
// the nfp) will be its rightmost upper vertex that coincides with the
|
|
// rightmost upper vertex of the nfp. No proof provided other than Jonas
|
|
// Lindmark's reasoning about the reference vertex of nfp in his thesis
|
|
// ("No fit polygon problem" - section 2.1.9)
|
|
|
|
auto touch_sh = stationary.rightmostTopVertex();
|
|
auto touch_other = orbiter.leftmostBottomVertex();
|
|
auto dtouch = touch_sh - touch_other;
|
|
auto top_other = orbiter.rightmostTopVertex() + dtouch;
|
|
auto dnfp = top_other - nfp.second; // nfp.second is the nfp reference point
|
|
shapelike::translate(nfp.first, dnfp);
|
|
}
|
|
|
|
template<class RawShape>
|
|
inline void correctNfpPosition(nfp::NfpResult<RawShape>& nfp,
|
|
const RawShape& stationary,
|
|
const _Item<RawShape>& orbiter)
|
|
{
|
|
auto touch_sh = nfp::rightmostUpVertex(stationary);
|
|
auto touch_other = orbiter.leftmostBottomVertex();
|
|
auto dtouch = touch_sh - touch_other;
|
|
auto top_other = orbiter.rightmostTopVertex() + dtouch;
|
|
auto dnfp = top_other - nfp.second;
|
|
shapelike::translate(nfp.first, dnfp);
|
|
}
|
|
|
|
template<class RawShape, class Circle = _Circle<TPoint<RawShape>> >
|
|
Circle minimizeCircle(const RawShape& sh) {
|
|
using Point = TPoint<RawShape>;
|
|
using Coord = TCoord<Point>;
|
|
|
|
auto& ctr = sl::contour(sh);
|
|
if(ctr.empty()) return {{0, 0}, 0};
|
|
|
|
auto bb = sl::boundingBox(sh);
|
|
auto capprx = bb.center();
|
|
auto rapprx = pl::distance(bb.minCorner(), bb.maxCorner());
|
|
|
|
|
|
opt::StopCriteria stopcr;
|
|
stopcr.max_iterations = 30;
|
|
stopcr.relative_score_difference = 1e-3;
|
|
opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr);
|
|
|
|
std::vector<double> dists(ctr.size(), 0);
|
|
|
|
auto result = solver.optimize_min(
|
|
[capprx, rapprx, &ctr, &dists](double xf, double yf) {
|
|
auto xt = Coord( std::round(getX(capprx) + rapprx*xf) );
|
|
auto yt = Coord( std::round(getY(capprx) + rapprx*yf) );
|
|
|
|
Point centr(xt, yt);
|
|
|
|
unsigned i = 0;
|
|
for(auto v : ctr) {
|
|
dists[i++] = pl::distance(v, centr);
|
|
}
|
|
|
|
auto mit = std::max_element(dists.begin(), dists.end());
|
|
|
|
assert(mit != dists.end());
|
|
|
|
return *mit;
|
|
},
|
|
opt::initvals(0.0, 0.0),
|
|
opt::bound(-1.0, 1.0), opt::bound(-1.0, 1.0)
|
|
);
|
|
|
|
double oxf = std::get<0>(result.optimum);
|
|
double oyf = std::get<1>(result.optimum);
|
|
auto xt = Coord( std::round(getX(capprx) + rapprx*oxf) );
|
|
auto yt = Coord( std::round(getY(capprx) + rapprx*oyf) );
|
|
|
|
Point cc(xt, yt);
|
|
auto r = result.score;
|
|
|
|
return {cc, r};
|
|
}
|
|
|
|
template<class RawShape>
|
|
_Circle<TPoint<RawShape>> boundingCircle(const RawShape& sh) {
|
|
return minimizeCircle(sh);
|
|
}
|
|
|
|
template<class RawShape, class TBin = _Box<TPoint<RawShape>>>
|
|
class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin>,
|
|
RawShape, TBin, NfpPConfig<RawShape>> {
|
|
|
|
using Base = PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin>,
|
|
RawShape, TBin, NfpPConfig<RawShape>>;
|
|
|
|
DECLARE_PLACER(Base)
|
|
|
|
using Box = _Box<TPoint<RawShape>>;
|
|
|
|
using MaxNfpLevel = nfp::MaxNfpLevel<RawShape>;
|
|
|
|
public:
|
|
|
|
using Pile = nfp::Shapes<RawShape>;
|
|
|
|
private:
|
|
|
|
// Norming factor for the optimization function
|
|
const double norm_;
|
|
Pile merged_pile_;
|
|
|
|
public:
|
|
|
|
inline explicit _NofitPolyPlacer(const BinType& bin):
|
|
Base(bin),
|
|
norm_(std::sqrt(sl::area(bin)))
|
|
{
|
|
// In order to not have items out of bin, it will be shrinked by an
|
|
// very little empiric offset value.
|
|
// sl::offset(bin_, 1e-5 * norm_);
|
|
}
|
|
|
|
_NofitPolyPlacer(const _NofitPolyPlacer&) = default;
|
|
_NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default;
|
|
|
|
#ifndef BP2D_COMPILER_MSVC12 // MSVC2013 does not support default move ctors
|
|
_NofitPolyPlacer(_NofitPolyPlacer&&) = default;
|
|
_NofitPolyPlacer& operator=(_NofitPolyPlacer&&) = default;
|
|
#endif
|
|
|
|
static inline double overfit(const Box& bb, const RawShape& bin) {
|
|
auto bbin = sl::boundingBox(bin);
|
|
auto d = bbin.center() - bb.center();
|
|
_Rectangle<RawShape> rect(bb.width(), bb.height());
|
|
rect.translate(bb.minCorner() + d);
|
|
return sl::isInside(rect.transformedShape(), bin) ? -1.0 : 1;
|
|
}
|
|
|
|
static inline double overfit(const RawShape& chull, const RawShape& bin) {
|
|
auto bbch = sl::boundingBox(chull);
|
|
auto bbin = sl::boundingBox(bin);
|
|
auto d = bbch.center() - bbin.center();
|
|
auto chullcpy = chull;
|
|
sl::translate(chullcpy, d);
|
|
return sl::isInside(chullcpy, bin) ? -1.0 : 1.0;
|
|
}
|
|
|
|
static inline double overfit(const RawShape& chull, const Box& bin)
|
|
{
|
|
auto bbch = sl::boundingBox(chull);
|
|
return overfit(bbch, bin);
|
|
}
|
|
|
|
static inline double overfit(const Box& bb, const Box& bin)
|
|
{
|
|
auto wdiff = TCompute<RawShape>(bb.width()) - bin.width();
|
|
auto hdiff = TCompute<RawShape>(bb.height()) - bin.height();
|
|
double diff = .0;
|
|
if(wdiff > 0) diff += double(wdiff);
|
|
if(hdiff > 0) diff += double(hdiff);
|
|
|
|
return diff;
|
|
}
|
|
|
|
static inline double overfit(const Box& bb, const _Circle<Vertex>& bin)
|
|
{
|
|
double boxr = 0.5*pl::distance(bb.minCorner(), bb.maxCorner());
|
|
double diff = boxr - bin.radius();
|
|
return diff;
|
|
}
|
|
|
|
static inline double overfit(const RawShape& chull,
|
|
const _Circle<Vertex>& bin)
|
|
{
|
|
double r = boundingCircle(chull).radius();
|
|
double diff = r - bin.radius();
|
|
return diff;
|
|
}
|
|
|
|
template<class Range = ConstItemRange<typename Base::DefaultIter>>
|
|
PackResult trypack(Item& item,
|
|
const Range& remaining = Range()) {
|
|
auto result = _trypack(item, remaining);
|
|
|
|
// Experimental
|
|
// if(!result) repack(item, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
~_NofitPolyPlacer() {
|
|
clearItems();
|
|
}
|
|
|
|
inline void clearItems() {
|
|
finalAlign(bin_);
|
|
Base::clearItems();
|
|
}
|
|
|
|
private:
|
|
|
|
using Shapes = TMultiShape<RawShape>;
|
|
|
|
Shapes calcnfp(const Item &trsh, Lvl<nfp::NfpLevel::CONVEX_ONLY>)
|
|
{
|
|
using namespace nfp;
|
|
|
|
Shapes nfps(items_.size());
|
|
|
|
// /////////////////////////////////////////////////////////////////////
|
|
// TODO: this is a workaround and should be solved in Item with mutexes
|
|
// guarding the mutable members when writing them.
|
|
// /////////////////////////////////////////////////////////////////////
|
|
trsh.transformedShape();
|
|
trsh.referenceVertex();
|
|
trsh.rightmostTopVertex();
|
|
trsh.leftmostBottomVertex();
|
|
|
|
for(Item& itm : items_) {
|
|
itm.transformedShape();
|
|
itm.referenceVertex();
|
|
itm.rightmostTopVertex();
|
|
itm.leftmostBottomVertex();
|
|
}
|
|
// /////////////////////////////////////////////////////////////////////
|
|
|
|
__parallel::enumerate(items_.begin(), items_.end(),
|
|
[&nfps, &trsh](const Item& sh, size_t n)
|
|
{
|
|
auto& fixedp = sh.transformedShape();
|
|
auto& orbp = trsh.transformedShape();
|
|
auto subnfp_r = noFitPolygon<NfpLevel::CONVEX_ONLY>(fixedp, orbp);
|
|
correctNfpPosition(subnfp_r, sh, trsh);
|
|
nfps[n] = subnfp_r.first;
|
|
});
|
|
|
|
return nfp::merge(nfps);
|
|
}
|
|
|
|
|
|
template<class Level>
|
|
Shapes calcnfp(const Item &trsh, Level)
|
|
{ // Function for arbitrary level of nfp implementation
|
|
|
|
// TODO: implement
|
|
return {};
|
|
}
|
|
|
|
struct Optimum {
|
|
double relpos;
|
|
unsigned nfpidx;
|
|
int hidx;
|
|
Optimum(double pos, unsigned nidx):
|
|
relpos(pos), nfpidx(nidx), hidx(-1) {}
|
|
Optimum(double pos, unsigned nidx, int holeidx):
|
|
relpos(pos), nfpidx(nidx), hidx(holeidx) {}
|
|
};
|
|
|
|
class Optimizer: public opt::TOptimizer<opt::Method::L_SUBPLEX> {
|
|
public:
|
|
Optimizer(float accuracy = 1.f) {
|
|
opt::StopCriteria stopcr;
|
|
stopcr.max_iterations = unsigned(std::floor(1000 * accuracy));
|
|
stopcr.relative_score_difference = 1e-20;
|
|
this->stopcr_ = stopcr;
|
|
}
|
|
};
|
|
|
|
using Edges = EdgeCache<RawShape>;
|
|
|
|
template<class Range = ConstItemRange<typename Base::DefaultIter>>
|
|
PackResult _trypack(
|
|
Item& item,
|
|
const Range& remaining = Range()) {
|
|
|
|
PackResult ret;
|
|
|
|
bool can_pack = false;
|
|
double best_overfit = std::numeric_limits<double>::max();
|
|
|
|
ItemGroup remlist;
|
|
if(remaining.valid) {
|
|
remlist.insert(remlist.end(), remaining.from, remaining.to);
|
|
}
|
|
|
|
double global_score = std::numeric_limits<double>::max();
|
|
|
|
auto initial_tr = item.translation();
|
|
auto initial_rot = item.rotation();
|
|
Vertex final_tr = {0, 0};
|
|
Radians final_rot = initial_rot;
|
|
Shapes nfps;
|
|
|
|
auto& bin = bin_;
|
|
double norm = norm_;
|
|
auto pbb = sl::boundingBox(merged_pile_);
|
|
auto binbb = sl::boundingBox(bin);
|
|
|
|
// This is the kernel part of the object function that is
|
|
// customizable by the library client
|
|
std::function<double(const Item&)> _objfunc;
|
|
if(config_.object_function) _objfunc = config_.object_function;
|
|
else {
|
|
|
|
// Inside check has to be strict if no alignment was enabled
|
|
std::function<double(const Box&)> ins_check;
|
|
if(config_.alignment == Config::Alignment::DONT_ALIGN)
|
|
ins_check = [&binbb, norm](const Box& fullbb) {
|
|
double ret = 0;
|
|
if(!sl::isInside(fullbb, binbb))
|
|
ret += norm;
|
|
return ret;
|
|
};
|
|
else
|
|
ins_check = [&bin](const Box& fullbb) {
|
|
double miss = overfit(fullbb, bin);
|
|
miss = miss > 0? miss : 0;
|
|
return std::pow(miss, 2);
|
|
};
|
|
|
|
_objfunc = [norm, binbb, pbb, ins_check](const Item& item)
|
|
{
|
|
auto ibb = item.boundingBox();
|
|
auto fullbb = sl::boundingBox(pbb, ibb);
|
|
|
|
double score = pl::distance(ibb.center(),
|
|
binbb.center());
|
|
score /= norm;
|
|
|
|
score += ins_check(fullbb);
|
|
|
|
return score;
|
|
};
|
|
}
|
|
|
|
if(items_.empty()) {
|
|
setInitialPosition(item);
|
|
auto best_tr = item.translation();
|
|
auto best_rot = item.rotation();
|
|
best_overfit = overfit(item.transformedShape(), bin_);
|
|
|
|
for(auto rot : config_.rotations) {
|
|
item.translation(initial_tr);
|
|
item.rotation(initial_rot + rot);
|
|
setInitialPosition(item);
|
|
double of = 0.;
|
|
if ((of = overfit(item.transformedShape(), bin_)) < best_overfit) {
|
|
best_overfit = of;
|
|
best_tr = item.translation();
|
|
best_rot = item.rotation();
|
|
}
|
|
}
|
|
|
|
can_pack = best_overfit <= 0;
|
|
item.rotation(best_rot);
|
|
item.translation(best_tr);
|
|
} else {
|
|
|
|
Pile merged_pile = merged_pile_;
|
|
|
|
for(auto rot : config_.rotations) {
|
|
|
|
item.translation(initial_tr);
|
|
item.rotation(initial_rot + rot);
|
|
item.boundingBox(); // fill the bb cache
|
|
|
|
// place the new item outside of the print bed to make sure
|
|
// it is disjunct from the current merged pile
|
|
placeOutsideOfBin(item);
|
|
|
|
nfps = calcnfp(item, Lvl<MaxNfpLevel::value>());
|
|
|
|
auto iv = item.referenceVertex();
|
|
|
|
auto startpos = item.translation();
|
|
|
|
std::vector<Edges> ecache;
|
|
ecache.reserve(nfps.size());
|
|
|
|
for(auto& nfp : nfps ) {
|
|
ecache.emplace_back(nfp);
|
|
ecache.back().accuracy(config_.accuracy);
|
|
}
|
|
|
|
// Our object function for placement
|
|
auto rawobjfunc = [_objfunc, iv, startpos]
|
|
(Vertex v, Item& itm)
|
|
{
|
|
auto d = v - iv;
|
|
d += startpos;
|
|
itm.translation(d);
|
|
return _objfunc(itm);
|
|
};
|
|
|
|
auto getNfpPoint = [&ecache](const Optimum& opt)
|
|
{
|
|
return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) :
|
|
ecache[opt.nfpidx].coords(opt.hidx, opt.relpos);
|
|
};
|
|
|
|
auto alignment = config_.alignment;
|
|
|
|
auto boundaryCheck = [alignment, &merged_pile, &getNfpPoint,
|
|
&item, &bin, &iv, &startpos] (const Optimum& o)
|
|
{
|
|
auto v = getNfpPoint(o);
|
|
auto d = v - iv;
|
|
d += startpos;
|
|
item.translation(d);
|
|
|
|
merged_pile.emplace_back(item.transformedShape());
|
|
auto chull = sl::convexHull(merged_pile);
|
|
merged_pile.pop_back();
|
|
|
|
double miss = 0;
|
|
if(alignment == Config::Alignment::DONT_ALIGN)
|
|
miss = sl::isInside(chull, bin) ? -1.0 : 1.0;
|
|
else miss = overfit(chull, bin);
|
|
|
|
return miss;
|
|
};
|
|
|
|
Optimum optimum(0, 0);
|
|
double best_score = std::numeric_limits<double>::max();
|
|
std::launch policy = std::launch::deferred;
|
|
if(config_.parallel) policy |= std::launch::async;
|
|
|
|
if(config_.before_packing)
|
|
config_.before_packing(merged_pile, items_, remlist);
|
|
|
|
using OptResult = opt::Result<double>;
|
|
using OptResults = std::vector<OptResult>;
|
|
|
|
// Local optimization with the four polygon corners as
|
|
// starting points
|
|
for(unsigned ch = 0; ch < ecache.size(); ch++) {
|
|
auto& cache = ecache[ch];
|
|
|
|
OptResults results(cache.corners().size());
|
|
|
|
auto& rofn = rawobjfunc;
|
|
auto& nfpoint = getNfpPoint;
|
|
float accuracy = config_.accuracy;
|
|
|
|
__parallel::enumerate(
|
|
cache.corners().begin(),
|
|
cache.corners().end(),
|
|
[&results, &item, &rofn, &nfpoint, ch, accuracy]
|
|
(double pos, size_t n)
|
|
{
|
|
Optimizer solver(accuracy);
|
|
|
|
Item itemcpy = item;
|
|
auto contour_ofn = [&rofn, &nfpoint, ch, &itemcpy]
|
|
(double relpos)
|
|
{
|
|
Optimum op(relpos, ch);
|
|
return rofn(nfpoint(op), itemcpy);
|
|
};
|
|
|
|
try {
|
|
results[n] = solver.optimize_min(contour_ofn,
|
|
opt::initvals<double>(pos),
|
|
opt::bound<double>(0, 1.0)
|
|
);
|
|
} catch(std::exception& e) {
|
|
derr() << "ERROR: " << e.what() << "\n";
|
|
}
|
|
}, policy);
|
|
|
|
auto resultcomp =
|
|
[]( const OptResult& r1, const OptResult& r2 ) {
|
|
return r1.score < r2.score;
|
|
};
|
|
|
|
auto mr = *std::min_element(results.begin(), results.end(),
|
|
resultcomp);
|
|
|
|
if(mr.score < best_score) {
|
|
Optimum o(std::get<0>(mr.optimum), ch, -1);
|
|
double miss = boundaryCheck(o);
|
|
if(miss <= 0) {
|
|
best_score = mr.score;
|
|
optimum = o;
|
|
} else {
|
|
best_overfit = std::min(miss, best_overfit);
|
|
}
|
|
}
|
|
|
|
for(unsigned hidx = 0; hidx < cache.holeCount(); ++hidx) {
|
|
results.clear();
|
|
results.resize(cache.corners(hidx).size());
|
|
|
|
// TODO : use parallel for
|
|
__parallel::enumerate(cache.corners(hidx).begin(),
|
|
cache.corners(hidx).end(),
|
|
[&results, &item, &nfpoint,
|
|
&rofn, ch, hidx, accuracy]
|
|
(double pos, size_t n)
|
|
{
|
|
Optimizer solver(accuracy);
|
|
|
|
Item itmcpy = item;
|
|
auto hole_ofn =
|
|
[&rofn, &nfpoint, ch, hidx, &itmcpy]
|
|
(double pos)
|
|
{
|
|
Optimum opt(pos, ch, hidx);
|
|
return rofn(nfpoint(opt), itmcpy);
|
|
};
|
|
|
|
try {
|
|
results[n] = solver.optimize_min(hole_ofn,
|
|
opt::initvals<double>(pos),
|
|
opt::bound<double>(0, 1.0)
|
|
);
|
|
|
|
} catch(std::exception& e) {
|
|
derr() << "ERROR: " << e.what() << "\n";
|
|
}
|
|
}, policy);
|
|
|
|
auto hmr = *std::min_element(results.begin(),
|
|
results.end(),
|
|
resultcomp);
|
|
|
|
if(hmr.score < best_score) {
|
|
Optimum o(std::get<0>(hmr.optimum),
|
|
ch, hidx);
|
|
double miss = boundaryCheck(o);
|
|
if(miss <= 0.0) {
|
|
best_score = hmr.score;
|
|
optimum = o;
|
|
} else {
|
|
best_overfit = std::min(miss, best_overfit);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( best_score < global_score ) {
|
|
auto d = getNfpPoint(optimum) - iv;
|
|
d += startpos;
|
|
final_tr = d;
|
|
final_rot = initial_rot + rot;
|
|
can_pack = true;
|
|
global_score = best_score;
|
|
}
|
|
}
|
|
|
|
item.translation(final_tr);
|
|
item.rotation(final_rot);
|
|
}
|
|
|
|
if(can_pack) {
|
|
ret = PackResult(item);
|
|
merged_pile_ = nfp::merge(merged_pile_, item.transformedShape());
|
|
} else {
|
|
ret = PackResult(best_overfit);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
inline void finalAlign(const RawShape& pbin) {
|
|
auto bbin = sl::boundingBox(pbin);
|
|
finalAlign(bbin);
|
|
}
|
|
|
|
inline void finalAlign(_Circle<TPoint<RawShape>> cbin) {
|
|
if(items_.empty() ||
|
|
config_.alignment == Config::Alignment::DONT_ALIGN) return;
|
|
|
|
nfp::Shapes<RawShape> m;
|
|
m.reserve(items_.size());
|
|
for(Item& item : items_) m.emplace_back(item.transformedShape());
|
|
|
|
auto c = boundingCircle(sl::convexHull(m));
|
|
|
|
auto d = cbin.center() - c.center();
|
|
for(Item& item : items_) item.translate(d);
|
|
}
|
|
|
|
inline void finalAlign(Box bbin) {
|
|
if(items_.empty() ||
|
|
config_.alignment == Config::Alignment::DONT_ALIGN) return;
|
|
|
|
Box bb = items_.front().get().boundingBox();
|
|
for(Item& item : items_)
|
|
bb = sl::boundingBox(item.boundingBox(), bb);
|
|
|
|
Vertex ci, cb;
|
|
|
|
switch(config_.alignment) {
|
|
case Config::Alignment::CENTER: {
|
|
ci = bb.center();
|
|
cb = bbin.center();
|
|
break;
|
|
}
|
|
case Config::Alignment::BOTTOM_LEFT: {
|
|
ci = bb.minCorner();
|
|
cb = bbin.minCorner();
|
|
break;
|
|
}
|
|
case Config::Alignment::BOTTOM_RIGHT: {
|
|
ci = {getX(bb.maxCorner()), getY(bb.minCorner())};
|
|
cb = {getX(bbin.maxCorner()), getY(bbin.minCorner())};
|
|
break;
|
|
}
|
|
case Config::Alignment::TOP_LEFT: {
|
|
ci = {getX(bb.minCorner()), getY(bb.maxCorner())};
|
|
cb = {getX(bbin.minCorner()), getY(bbin.maxCorner())};
|
|
break;
|
|
}
|
|
case Config::Alignment::TOP_RIGHT: {
|
|
ci = bb.maxCorner();
|
|
cb = bbin.maxCorner();
|
|
break;
|
|
}
|
|
default: ; // DONT_ALIGN
|
|
}
|
|
|
|
auto d = cb - ci;
|
|
for(Item& item : items_) item.translate(d);
|
|
}
|
|
|
|
void setInitialPosition(Item& item) {
|
|
Box bb = item.boundingBox();
|
|
|
|
Vertex ci, cb;
|
|
auto bbin = sl::boundingBox(bin_);
|
|
|
|
switch(config_.starting_point) {
|
|
case Config::Alignment::CENTER: {
|
|
ci = bb.center();
|
|
cb = bbin.center();
|
|
break;
|
|
}
|
|
case Config::Alignment::BOTTOM_LEFT: {
|
|
ci = bb.minCorner();
|
|
cb = bbin.minCorner();
|
|
break;
|
|
}
|
|
case Config::Alignment::BOTTOM_RIGHT: {
|
|
ci = {getX(bb.maxCorner()), getY(bb.minCorner())};
|
|
cb = {getX(bbin.maxCorner()), getY(bbin.minCorner())};
|
|
break;
|
|
}
|
|
case Config::Alignment::TOP_LEFT: {
|
|
ci = {getX(bb.minCorner()), getY(bb.maxCorner())};
|
|
cb = {getX(bbin.minCorner()), getY(bbin.maxCorner())};
|
|
break;
|
|
}
|
|
case Config::Alignment::TOP_RIGHT: {
|
|
ci = bb.maxCorner();
|
|
cb = bbin.maxCorner();
|
|
break;
|
|
}
|
|
default:;
|
|
}
|
|
|
|
auto d = cb - ci;
|
|
item.translate(d);
|
|
}
|
|
|
|
void placeOutsideOfBin(Item& item) {
|
|
auto&& bb = item.boundingBox();
|
|
Box binbb = sl::boundingBox(bin_);
|
|
|
|
Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) };
|
|
|
|
Coord dx = getX(binbb.maxCorner()) - getX(v);
|
|
Coord dy = getY(binbb.maxCorner()) - getY(v);
|
|
|
|
item.translate({dx, dy});
|
|
}
|
|
|
|
};
|
|
|
|
|
|
}
|
|
}
|
|
|
|
#endif // NOFITPOLY_H
|