parallel nesting can be enabled but fails with the current objectfunction.

This commit is contained in:
tamasmeszaros 2018-08-20 16:34:35 +02:00
parent e678368b23
commit 8617b0a409
21 changed files with 1833 additions and 490 deletions

View File

@ -93,6 +93,8 @@ if(LIBNEST2D_BUILD_EXAMPLES)
add_executable(example examples/main.cpp
# tools/libnfpglue.hpp
# tools/libnfpglue.cpp
tools/nfp_svgnest.hpp
tools/nfp_svgnest_glue.hpp
tools/svgtools.hpp
tests/printer_parts.cpp
tests/printer_parts.h

View File

@ -1,7 +1,6 @@
#include <iostream>
#include <string>
#include <fstream>
//#define DEBUG_EXPORT_NFP
#include <libnest2d.h>
@ -12,6 +11,8 @@
#include "libnest2d/rotfinder.hpp"
//#include "tools/libnfpglue.hpp"
//#include "tools/nfp_svgnest_glue.hpp"
using namespace libnest2d;
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
@ -53,10 +54,50 @@ void arrangeRectangles() {
const int SCALE = 1000000;
std::vector<Rectangle> rects = {
{60*SCALE, 200*SCALE},
{60*SCALE, 200*SCALE}
};
std::vector<Item> rects(100, {
{-9945219, -3065619},
{-9781479, -2031780},
{-9510560, -1020730},
{-9135450, -43529},
{-2099999, 14110899},
{2099999, 14110899},
{9135450, -43529},
{9510560, -1020730},
{9781479, -2031780},
{9945219, -3065619},
{10000000, -4110899},
{9945219, -5156179},
{9781479, -6190019},
{9510560, -7201069},
{9135450, -8178270},
{8660249, -9110899},
{8090169, -9988750},
{7431449, -10802209},
{6691309, -11542349},
{5877850, -12201069},
{5000000, -12771149},
{4067369, -13246350},
{3090169, -13621459},
{2079119, -13892379},
{1045279, -14056119},
{0, -14110899},
{-1045279, -14056119},
{-2079119, -13892379},
{-3090169, -13621459},
{-4067369, -13246350},
{-5000000, -12771149},
{-5877850, -12201069},
{-6691309, -11542349},
{-7431449, -10802209},
{-8090169, -9988750},
{-8660249, -9110899},
{-9135450, -8178270},
{-9510560, -7201069},
{-9781479, -6190019},
{-9945219, -5156179},
{-10000000, -4110899},
{-9945219, -3065619},
});
std::vector<Item> input;
input.insert(input.end(), prusaParts().begin(), prusaParts().end());
@ -84,7 +125,7 @@ void arrangeRectangles() {
// _Circle<PointImpl> bin({0, 0}, 125*SCALE);
auto min_obj_distance = static_cast<Coord>(0*SCALE);
auto min_obj_distance = static_cast<Coord>(1.5*SCALE);
using Placer = strategies::_NofitPolyPlacer<PolygonImpl, decltype(bin)>;
using Packer = Nester<Placer, FirstFitSelection>;
@ -95,14 +136,15 @@ void arrangeRectangles() {
pconf.alignment = Placer::Config::Alignment::CENTER;
pconf.starting_point = Placer::Config::Alignment::CENTER;
pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/};
pconf.accuracy = 1.0f;
pconf.accuracy = 0.5f;
pconf.parallel = true;
Packer::SelectionConfig sconf;
// sconf.allow_parallel = false;
// sconf.force_parallel = false;
// sconf.try_triplets = true;
// sconf.try_reverse_order = true;
// sconf.waste_increment = 0.005;
// sconf.waste_increment = 0.01;
arrange.configure(pconf, sconf);
@ -175,7 +217,6 @@ void arrangeRectangles() {
SVGWriter svgw(conf);
svgw.setSize(Box(250*SCALE, 210*SCALE));
svgw.writePackGroup(result);
// std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);});
svgw.save("out");
}

View File

@ -356,8 +356,7 @@ inline double area(const PolygonImpl& shape, const PolygonTag&)
#endif
template<>
inline bool isInside<PolygonImpl>(const PointImpl& point,
const PolygonImpl& shape)
inline bool isInside(const PointImpl& point, const PolygonImpl& shape)
{
return boost::geometry::within(point, shape);
}
@ -390,7 +389,8 @@ inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&)
}
template<>
inline bp2d::Box boundingBox<PolygonImpl>(const bp2d::Shapes& shapes)
inline bp2d::Box boundingBox<bp2d::Shapes>(const bp2d::Shapes& shapes,
const MultiPolygonTag&)
{
bp2d::Box b;
boost::geometry::envelope(shapes, b);
@ -400,7 +400,7 @@ inline bp2d::Box boundingBox<PolygonImpl>(const bp2d::Shapes& shapes)
#ifndef DISABLE_BOOST_CONVEX_HULL
template<>
inline PolygonImpl convexHull(const PolygonImpl& sh)
inline PolygonImpl convexHull(const PolygonImpl& sh, const PolygonTag&)
{
PolygonImpl ret;
boost::geometry::convex_hull(sh, ret);
@ -408,7 +408,8 @@ inline PolygonImpl convexHull(const PolygonImpl& sh)
}
template<>
inline PolygonImpl convexHull(const bp2d::Shapes& shapes)
inline PolygonImpl convexHull(const TMultiShape<PolygonImpl>& shapes,
const MultiPolygonTag&)
{
PolygonImpl ret;
boost::geometry::convex_hull(shapes, ret);
@ -416,34 +417,6 @@ inline PolygonImpl convexHull(const bp2d::Shapes& shapes)
}
#endif
#ifndef DISABLE_BOOST_ROTATE
template<>
inline void rotate(PolygonImpl& sh, const Radians& rads)
{
namespace trans = boost::geometry::strategy::transform;
PolygonImpl cpy = sh;
trans::rotate_transformer<boost::geometry::radian, Radians, 2, 2>
rotate(rads);
boost::geometry::transform(cpy, sh, rotate);
}
#endif
#ifndef DISABLE_BOOST_TRANSLATE
template<>
inline void translate(PolygonImpl& sh, const PointImpl& offs)
{
namespace trans = boost::geometry::strategy::transform;
PolygonImpl cpy = sh;
trans::translate_transformer<bp2d::Coord, 2, 2> translate(
bp2d::getX(offs), bp2d::getY(offs));
boost::geometry::transform(cpy, sh, translate);
}
#endif
#ifndef DISABLE_BOOST_OFFSET
template<>
inline void offset(PolygonImpl& sh, bp2d::Coord distance)
@ -515,14 +488,24 @@ template<> inline std::pair<bool, std::string> isValid(const PolygonImpl& sh)
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,
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
}

View File

@ -21,9 +21,6 @@ struct PolygonImpl {
PathImpl Contour;
HoleStore Holes;
using Tag = libnest2d::PolygonTag;
using PointType = PointImpl;
inline PolygonImpl() = default;
inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {}
@ -102,41 +99,49 @@ template<> struct PointType<PolygonImpl> {
using Type = PointImpl;
};
// Type of vertex iterator used by Clipper
template<> struct VertexIteratorType<PolygonImpl> {
using Type = ClipperLib::Path::iterator;
};
// Type of vertex iterator used by Clipper
template<> struct VertexConstIteratorType<PolygonImpl> {
using Type = ClipperLib::Path::const_iterator;
template<> struct PointType<PointImpl> {
using Type = PointImpl;
};
template<> struct CountourType<PolygonImpl> {
using Type = PathImpl;
};
template<> struct ShapeTag<PolygonImpl> { using Type = PolygonTag; };
template<> struct ShapeTag<TMultiShape<PolygonImpl>> {
using Type = MultiPolygonTag;
};
template<> struct PointType<TMultiShape<PolygonImpl>> {
using Type = PointImpl;
};
template<> struct HolesContainer<PolygonImpl> {
using Type = ClipperLib::Paths;
};
namespace pointlike {
// Tell binpack2d how to extract the X coord from a ClipperPoint object
// Tell libnest2d how to extract the X coord from a ClipperPoint object
template<> inline TCoord<PointImpl> x(const PointImpl& p)
{
return p.X;
}
// Tell binpack2d how to extract the Y coord from a ClipperPoint object
// Tell libnest2d how to extract the Y coord from a ClipperPoint object
template<> inline TCoord<PointImpl> y(const PointImpl& p)
{
return p.Y;
}
// Tell binpack2d how to extract the X coord from a ClipperPoint object
// Tell libnest2d how to extract the X coord from a ClipperPoint object
template<> inline TCoord<PointImpl>& x(PointImpl& p)
{
return p.X;
}
// Tell binpack2d how to extract the Y coord from a ClipperPoint object
// Tell libnest2d how to extract the Y coord from a ClipperPoint object
template<> inline TCoord<PointImpl>& y(PointImpl& p)
{
return p.Y;
@ -178,10 +183,6 @@ inline double area<Orientation::COUNTER_CLOCKWISE>(const PolygonImpl& sh) {
}
template<> struct HolesContainer<PolygonImpl> {
using Type = ClipperLib::Paths;
};
namespace shapelike {
template<> inline void reserve(PolygonImpl& sh, size_t vertex_capacity)
@ -189,7 +190,7 @@ template<> inline void reserve(PolygonImpl& sh, size_t vertex_capacity)
return sh.Contour.reserve(vertex_capacity);
}
// Tell binpack2d how to make string out of a ClipperPolygon object
// Tell libnest2d how to make string out of a ClipperPolygon object
template<> inline double area(const PolygonImpl& sh, const PolygonTag&)
{
return _smartarea::area<OrientationType<PolygonImpl>::Value>(sh);
@ -269,28 +270,6 @@ template<> inline std::string toString(const PolygonImpl& sh)
return ss.str();
}
template<> inline TVertexIterator<PolygonImpl> begin(PolygonImpl& sh)
{
return sh.Contour.begin();
}
template<> inline TVertexIterator<PolygonImpl> end(PolygonImpl& sh)
{
return sh.Contour.end();
}
template<>
inline TVertexConstIterator<PolygonImpl> cbegin(const PolygonImpl& sh)
{
return sh.Contour.cbegin();
}
template<> inline TVertexConstIterator<PolygonImpl> cend(
const PolygonImpl& sh)
{
return sh.Contour.cend();
}
template<>
inline PolygonImpl create(const PathImpl& path, const HoleStore& holes)
{
@ -410,8 +389,8 @@ inline void rotate(PolygonImpl& sh, const Radians& rads)
} // namespace shapelike
#define DISABLE_BOOST_NFP_MERGE
inline nfp::Shapes<PolygonImpl> _merge(ClipperLib::Clipper& clipper) {
nfp::Shapes<PolygonImpl> retv;
inline std::vector<PolygonImpl> _merge(ClipperLib::Clipper& clipper) {
shapelike::Shapes<PolygonImpl> retv;
ClipperLib::PolyTree result;
clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative);
@ -447,8 +426,8 @@ inline nfp::Shapes<PolygonImpl> _merge(ClipperLib::Clipper& clipper) {
namespace nfp {
template<> inline nfp::Shapes<PolygonImpl>
merge(const nfp::Shapes<PolygonImpl>& shapes)
template<> inline std::vector<PolygonImpl>
merge(const std::vector<PolygonImpl>& shapes)
{
ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);

View File

@ -22,34 +22,12 @@ template<class GeomType>
using TCoord = typename CoordType<remove_cvref_t<GeomType>>::Type;
/// Getting the type of point structure used by a shape.
template<class Shape> struct PointType { /*using Type = void;*/ };
template<class Sh> struct PointType { using Type = typename Sh::PointType; };
/// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
template<class Shape>
using TPoint = typename PointType<remove_cvref_t<Shape>>::Type;
/// Getting the VertexIterator type of a shape class.
template<class Shape> struct VertexIteratorType { /*using Type = void;*/ };
/// Getting the const vertex iterator for a shape class.
template<class Shape> struct VertexConstIteratorType {/* using Type = void;*/ };
/**
* TVertexIterator<Shape> as shorthand for
* `typename VertexIteratorType<Shape>::Type`
*/
template<class Shape>
using TVertexIterator =
typename VertexIteratorType<remove_cvref_t<Shape>>::Type;
/**
* \brief TVertexConstIterator<Shape> as shorthand for
* `typename VertexConstIteratorType<Shape>::Type`
*/
template<class ShapeClass>
using TVertexConstIterator =
typename VertexConstIteratorType<remove_cvref_t<ShapeClass>>::Type;
/**
* \brief A point pair base class for other point pairs (segment, box, ...).
* \tparam RawPoint The actual point type to use.
@ -61,9 +39,16 @@ struct PointPair {
};
struct PolygonTag {};
struct MultiPolygonTag {};
struct BoxTag {};
struct CircleTag {};
template<class Shape> struct ShapeTag { using Type = typename Shape::Tag; };
template<class S> using Tag = typename ShapeTag<S>::Type;
template<class S> struct MultiShape { using Type = std::vector<S>; };
template<class S> using TMultiShape = typename MultiShape<S>::Type;
/**
* \brief An abstraction of a box;
*/
@ -371,7 +356,7 @@ enum class Formats {
namespace shapelike {
template<class RawShape>
using Shapes = std::vector<RawShape>;
using Shapes = TMultiShape<RawShape>;
template<class RawShape>
inline RawShape create(const TContour<RawShape>& contour,
@ -455,27 +440,28 @@ namespace shapelike {
}
template<class RawShape>
inline TVertexIterator<RawShape> begin(RawShape& sh)
inline typename TContour<RawShape>::iterator begin(RawShape& sh)
{
return sh.begin();
return getContour(sh).begin();
}
template<class RawShape>
inline TVertexIterator<RawShape> end(RawShape& sh)
inline typename TContour<RawShape>::iterator end(RawShape& sh)
{
return sh.end();
return getContour(sh).end();
}
template<class RawShape>
inline TVertexConstIterator<RawShape> cbegin(const RawShape& sh)
inline typename TContour<RawShape>::const_iterator
cbegin(const RawShape& sh)
{
return sh.cbegin();
return getContour(sh).cbegin();
}
template<class RawShape>
inline TVertexConstIterator<RawShape> cend(const RawShape& sh)
inline typename TContour<RawShape>::const_iterator cend(const RawShape& sh)
{
return sh.cend();
return getContour(sh).cend();
}
template<class RawShape>
@ -559,27 +545,29 @@ namespace shapelike {
"ShapeLike::boundingBox(shape) unimplemented!");
}
template<class RawShape>
inline _Box<TPoint<RawShape>> boundingBox(const Shapes<RawShape>& /*sh*/)
template<class RawShapes>
inline _Box<TPoint<typename RawShapes::value_type>>
boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&)
{
static_assert(always_false<RawShape>::value,
static_assert(always_false<RawShapes>::value,
"ShapeLike::boundingBox(shapes) unimplemented!");
}
template<class RawShape>
inline RawShape convexHull(const RawShape& /*sh*/)
inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::convexHull(shape) unimplemented!");
return RawShape();
}
template<class RawShape>
inline RawShape convexHull(const Shapes<RawShape>& /*sh*/)
template<class RawShapes>
inline typename RawShapes::value_type
convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&)
{
static_assert(always_false<RawShape>::value,
static_assert(always_false<RawShapes>::value,
"ShapeLike::convexHull(shapes) unimplemented!");
return RawShape();
return typename RawShapes::value_type();
}
template<class RawShape>
@ -599,8 +587,7 @@ namespace shapelike {
template<class RawShape>
inline void offset(RawShape& /*sh*/, TCoord<TPoint<RawShape>> /*distance*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::offset() unimplemented!");
dout() << "The current geometry backend does not support offsetting!\n";
}
template<class RawShape>
@ -670,9 +657,9 @@ namespace shapelike {
}
template<class S> // Dispatch function
inline _Box<typename S::PointType> boundingBox(const S& sh)
inline _Box<TPoint<S>> boundingBox(const S& sh)
{
return boundingBox(sh, typename S::Tag());
return boundingBox(sh, Tag<S>() );
}
template<class Box>
@ -690,7 +677,7 @@ namespace shapelike {
template<class RawShape> // Dispatching function
inline double area(const RawShape& sh)
{
return area(sh, typename RawShape::Tag());
return area(sh, Tag<RawShape>());
}
template<class RawShape>
@ -702,6 +689,13 @@ namespace shapelike {
});
}
template<class RawShape>
inline auto convexHull(const RawShape& sh)
-> decltype(convexHull(sh, Tag<RawShape>())) // TODO: C++14 could deduce
{
return convexHull(sh, Tag<RawShape>());
}
template<class RawShape>
inline bool isInside(const TPoint<RawShape>& point,
const _Circle<TPoint<RawShape>>& circ)
@ -816,6 +810,16 @@ namespace shapelike {
}
}
#define DECLARE_MAIN_TYPES(T) \
using Polygon = T; \
using Point = TPoint<T>; \
using Coord = TCoord<Point>; \
using Contour = TContour<T>; \
using Box = _Box<Point>; \
using Circle = _Circle<Point>; \
using Segment = _Segment<Point>; \
using Polygons = TMultiShape<T>
}
#endif // GEOMETRY_TRAITS_HPP

View File

@ -27,8 +27,8 @@ inline bool _vsort(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2)
/// A collection of static methods for handling the no fit polygon creation.
namespace nfp {
namespace sl = shapelike;
namespace pl = pointlike;
//namespace sl = shapelike;
//namespace pl = pointlike;
/// The complexity level of a polygon that an NFP implementation can handle.
enum class NfpLevel: unsigned {
@ -49,7 +49,7 @@ template<class RawShape> struct MaxNfpLevel {
// Shorthand for a pile of polygons
template<class RawShape>
using Shapes = typename shapelike::Shapes<RawShape>;
using Shapes = TMultiShape<RawShape>;
/**
* Merge a bunch of polygons with the specified additional polygon.
@ -62,10 +62,10 @@ using Shapes = typename shapelike::Shapes<RawShape>;
* mostly it will be a set containing only one big polygon but if the input
* polygons are disjuct than the resulting set will contain more polygons.
*/
template<class RawShape>
inline Shapes<RawShape> merge(const Shapes<RawShape>& /*shc*/)
template<class RawShapes>
inline RawShapes merge(const RawShapes& /*shc*/)
{
static_assert(always_false<RawShape>::value,
static_assert(always_false<RawShapes>::value,
"Nfp::merge(shapes, shape) unimplemented!");
}
@ -81,12 +81,12 @@ inline Shapes<RawShape> merge(const Shapes<RawShape>& /*shc*/)
* polygons are disjuct than the resulting set will contain more polygons.
*/
template<class RawShape>
inline Shapes<RawShape> merge(const Shapes<RawShape>& shc,
const RawShape& sh)
inline TMultiShape<RawShape> merge(const TMultiShape<RawShape>& shc,
const RawShape& sh)
{
auto m = merge(shc);
auto m = nfp::merge(shc);
m.push_back(sh);
return merge(m);
return nfp::merge(m);
}
/**

View File

@ -31,6 +31,8 @@ class _Item {
using Vertex = TPoint<RawShape>;
using Box = _Box<Vertex>;
using VertexConstIterator = typename TContour<RawShape>::const_iterator;
// The original shape that gets encapsulated.
RawShape sh_;
@ -39,7 +41,7 @@ class _Item {
Radians rotation_;
Coord offset_distance_;
// Info about whether the tranformations will have to take place
// 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_offset_ = false;
@ -59,8 +61,8 @@ class _Item {
};
mutable Convexity convexity_ = Convexity::UNCHECKED;
mutable TVertexConstIterator<RawShape> rmt_; // rightmost top vertex
mutable TVertexConstIterator<RawShape> lmb_; // leftmost bottom vertex
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; Vertex tr;
@ -81,7 +83,7 @@ public:
* supports. Giving out a non const iterator would make it impossible to
* perform correct cache invalidation.
*/
using Iterator = TVertexConstIterator<RawShape>;
using Iterator = VertexConstIterator;
/**
* @brief Get the orientation of the polygon.
@ -110,7 +112,7 @@ public:
explicit inline _Item(RawShape&& sh): sh_(std::move(sh)) {}
/**
* @brief Create an item from an initilizer list.
* @brief Create an item from an initializer list.
* @param il The initializer list of vertices.
*/
inline _Item(const std::initializer_list< Vertex >& il):
@ -160,7 +162,7 @@ public:
}
/**
* @brief Get a copy of an outer vertex whithin the carried shape.
* @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
@ -245,7 +247,7 @@ public:
* @param p
* @return
*/
inline bool isPointInside(const Vertex& p) const
inline bool isInside(const Vertex& p) const
{
return sl::isInside(p, transformedShape());
}
@ -505,7 +507,7 @@ rem(typename Container::const_iterator it, const Container& cont) {
/**
* \brief A wrapper interface (trait) class for any placement strategy provider.
*
* If a client want's to use its own placement algorithm, all it has to do is to
* 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
@ -558,7 +560,7 @@ public:
* 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 startegy.
* @param config The configuration object defined by the placement strategy.
*/
inline void configure(const Config& config) { impl_.configure(config); }
@ -568,7 +570,7 @@ public:
*
* \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 startegy may not be the best.
* 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
@ -596,7 +598,7 @@ public:
* 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 intermadiate step. The above version can still be used
* the absence of an intermediate step. The above version can still be used
* in the implementation.
*
* @param item The item to pack.
@ -628,13 +630,6 @@ public:
inline double filledArea() const { return impl_.filledArea(); }
#ifndef NDEBUG
inline auto getDebugItems() -> decltype(impl_.debug_items_)&
{
return impl_.debug_items_;
}
#endif
};
// The progress function will be called with the number of placed items
@ -659,15 +654,15 @@ public:
* 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 startegy.
* @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 whenewer an item or
* a group of items where succesfully packed.
* @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.
*/
@ -680,7 +675,7 @@ public:
* 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 converitible to Item.
* 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.
@ -712,7 +707,7 @@ public:
/**
* @brief Get the items for a particular bin.
* @param binIndex The index of the requested bin.
* @return Returns a list of allitems packed into the requested bin.
* @return Returns a list of all items packed into the requested bin.
*/
inline ItemGroup itemsForBin(size_t binIndex) {
return impl_.itemsForBin(binIndex);
@ -754,7 +749,7 @@ using _IndexedPackGroup = std::vector<
>;
/**
* The Arranger is the frontend class for the binpack2d library. It takes the
* The Arranger is the front-end class for the libnest2d library. It takes the
* input items and outputs the items with the proper transformations to be
* inside the provided bin.
*/
@ -857,7 +852,7 @@ public:
return _execute(from, to);
}
/// Set a progress indicatior function object for the selector.
/// Set a progress indicator function object for the selector.
inline Nester& progressIndicator(ProgressFunction func)
{
selector_.progressIndicator(func); return *this;
@ -877,8 +872,8 @@ private:
template<class TIterator,
class IT = remove_cvref_t<typename TIterator::value_type>,
// This funtion will be used only if the iterators are pointing to
// a type compatible with the binpack2d::_Item template.
// This function will be used only if the iterators are pointing to
// a type compatible with the libnets2d::_Item template.
// This way we can use references to input elements as they will
// have to exist for the lifetime of this call.
class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT>
@ -904,8 +899,8 @@ private:
template<class TIterator,
class IT = remove_cvref_t<typename TIterator::value_type>,
// This funtion will be used only if the iterators are pointing to
// a type compatible with the binpack2d::_Item template.
// This function will be used only if the iterators are pointing to
// a type compatible with the libnest2d::_Item template.
// This way we can use references to input elements as they will
// have to exist for the lifetime of this call.
class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT>

View File

@ -67,11 +67,11 @@ class metaloop {
// need to wrap that in a type (see metaloop::Int).
/*
* A helper alias to create integer values wrapped as a type. It is nessecary
* 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 neccesary for this trick.
* partially specialized in a way that is necessary for this trick.
*/
template<int N> using Int = std::integral_constant<int, N>;
@ -88,7 +88,7 @@ public:
// It takes the real functor that can be specified in-place but only
// with C++14 because the second parameter's type will depend on the
// type of the parameter pack element that is processed. In C++14 we can
// specify this second parameter type as auto in the lamda parameter list.
// 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) {
@ -146,7 +146,7 @@ public:
* version of run is called which does not call itself anymore.
*
* If you are utterly annoyed, at least you have learned a super crazy
* functional metaprogramming pattern.
* functional meta-programming pattern.
*/
template<class...Args>
using MetaLoop = _MetaLoop<Int<sizeof...(Args)-1>, Args...>;

View File

@ -102,6 +102,9 @@ struct StopCriteria {
/// 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("");
unsigned max_iterations = 0;
};

View File

@ -142,10 +142,12 @@ protected:
default: ;
}
auto abs_diff = stopcr_.absolute_score_difference;
auto rel_diff = stopcr_.relative_score_difference;
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 );

View File

@ -7,9 +7,24 @@
namespace libnest2d { namespace strategies {
template<class T, class = T> struct Epsilon {};
template<class T>
struct Epsilon<T, enable_if_t<std::is_integral<T>::value, T> > {
static const T Value = 1;
};
template<class T>
struct Epsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > {
static const T Value = 1e-3;
};
template<class RawShape>
struct BLConfig {
TCoord<TPoint<RawShape>> min_obj_distance = 0;
DECLARE_MAIN_TYPES(RawShape);
Coord min_obj_distance = 0;
Coord epsilon = Epsilon<Coord>::Value;
bool allow_rotations = false;
};
@ -69,20 +84,21 @@ protected:
setInitialPosition(item);
Unit d = availableSpaceDown(item);
bool can_move = d > 1 /*std::numeric_limits<Unit>::epsilon()*/;
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+1});
item.translate({0, -d+eps});
d = availableSpaceLeft(item);
can_move = d > 1/*std::numeric_limits<Unit>::epsilon()*/;
can_move = d > eps;
left = false;
} else { // write previous left move and go down
item.translate({-d+1, 0});
item.translate({-d+eps, 0});
d = availableSpaceDown(item);
can_move = d > 1/*std::numeric_limits<Unit>::epsilon()*/;
can_move = d > eps;
left = true;
}
}

View File

@ -2,7 +2,15 @@
#define NOFITPOLY_HPP
#include <cassert>
#include <random>
// For caching nfps
#include <unordered_map>
// For parallel for
#include <functional>
#include <iterator>
#include <future>
#include <atomic>
#ifndef NDEBUG
#include <iostream>
@ -13,8 +21,84 @@
#include "tools/svgtools.hpp"
namespace libnest2d {
namespace libnest2d { namespace strategies {
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>, unsigned)> fn,
std::launch policy = std::launch::deferred | std::launch::async)
{
auto N = to-from;
std::vector<std::future<void>> rets(N);
auto it = from;
for(unsigned b = 0; b < N; b++) {
rets[b] = std::async(policy, fn, *it++, b);
}
for(unsigned fi = 0; fi < rets.size(); ++fi) rets[fi].wait();
}
}
namespace __itemhash {
using Key = size_t;
template<class S>
Key hash(const _Item<S>& item) {
using Point = TPoint<S>;
using Segment = _Segment<Point>;
static const int N = 26;
static const int M = N*N - 1;
std::string ret;
auto& rhs = item.rawShape();
auto& ctr = sl::getContour(rhs);
auto it = ctr.begin();
auto nx = std::next(it);
double circ = 0;
while(nx != ctr.end()) {
Segment seg(*it++, *nx++);
Radians a = seg.angleToXaxis();
double deg = Degrees(a);
int ms = 'A', ls = 'A';
while(deg > N) { ms++; deg -= N; }
ls += int(deg);
ret.push_back(char(ms)); ret.push_back(char(ls));
circ += seg.length();
}
it = ctr.begin(); nx = std::next(it);
while(nx != ctr.end()) {
Segment seg(*it++, *nx++);
auto l = int(M * seg.length() / circ);
int ms = 'A', ls = 'A';
while(l > N) { ms++; l -= N; }
ls += l;
ret.push_back(char(ms)); ret.push_back(char(ls));
}
return std::hash<std::string>()(ret);
}
template<class S>
using Hash = std::unordered_map<Key, nfp::NfpResult<S>>;
}
namespace strategies {
template<class RawShape>
struct NfpPConfig {
@ -71,7 +155,7 @@ struct NfpPConfig {
* decisions (for you or a more intelligent AI).
*
*/
std::function<double(nfp::Shapes<RawShape>&, const _Item<RawShape>&,
std::function<double(const nfp::Shapes<RawShape>&, const _Item<RawShape>&,
const ItemGroup&)>
object_function;
@ -80,7 +164,7 @@ struct NfpPConfig {
* 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 = 1.0;
float accuracy = 0.65f;
/**
* @brief If you want to see items inside other item's holes, you have to
@ -91,6 +175,11 @@ struct NfpPConfig {
*/
bool explore_holes = false;
/**
* @brief If true, use all CPUs available. Run on a single core otherwise.
*/
bool parallel = true;
NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}),
alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {}
};
@ -325,121 +414,8 @@ inline void correctNfpPosition(nfp::NfpResult<RawShape>& nfp,
shapelike::translate(nfp.first, dnfp);
}
template<class RawShape, class Container>
nfp::Shapes<RawShape> calcnfp( const Container& polygons,
const _Item<RawShape>& trsh,
Lvl<nfp::NfpLevel::CONVEX_ONLY>)
{
using Item = _Item<RawShape>;
using namespace nfp;
nfp::Shapes<RawShape> nfps;
// int pi = 0;
for(Item& sh : polygons) {
auto subnfp_r = noFitPolygon<NfpLevel::CONVEX_ONLY>(
sh.transformedShape(), trsh.transformedShape());
#ifndef NDEBUG
auto vv = sl::isValid(sh.transformedShape());
assert(vv.first);
auto vnfp = sl::isValid(subnfp_r.first);
assert(vnfp.first);
#endif
correctNfpPosition(subnfp_r, sh, trsh);
nfps = nfp::merge(nfps, subnfp_r.first);
// double SCALE = 1000000;
// using SVGWriter = svg::SVGWriter<RawShape>;
// SVGWriter::Config conf;
// conf.mm_in_coord_units = SCALE;
// SVGWriter svgw(conf);
// Box bin(250*SCALE, 210*SCALE);
// svgw.setSize(bin);
// for(int i = 0; i <= pi; i++) svgw.writeItem(polygons[i]);
// svgw.writeItem(trsh);
//// svgw.writeItem(Item(subnfp_r.first));
// for(auto& n : nfps) svgw.writeItem(Item(n));
// svgw.save("nfpout");
// pi++;
}
return nfps;
}
template<class RawShape, class Container, class Level>
nfp::Shapes<RawShape> calcnfp( const Container& polygons,
const _Item<RawShape>& trsh,
Level)
{
using namespace nfp;
using Item = _Item<RawShape>;
Shapes<RawShape> nfps;
auto& orb = trsh.transformedShape();
bool orbconvex = trsh.isContourConvex();
for(Item& sh : polygons) {
nfp::NfpResult<RawShape> subnfp;
auto& stat = sh.transformedShape();
if(sh.isContourConvex() && orbconvex)
subnfp = nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(stat, orb);
else if(orbconvex)
subnfp = nfp::noFitPolygon<NfpLevel::ONE_CONVEX>(stat, orb);
else
subnfp = nfp::noFitPolygon<Level::value>(stat, orb);
correctNfpPosition(subnfp, sh, trsh);
nfps = nfp::merge(nfps, subnfp.first);
}
return nfps;
// using Item = _Item<RawShape>;
// using sl = ShapeLike;
// Nfp::Shapes<RawShape> nfps, stationary;
// for(Item& sh : polygons) {
// stationary = Nfp::merge(stationary, sh.transformedShape());
// }
// for(RawShape& sh : stationary) {
//// auto vv = sl::isValid(sh);
//// std::cout << vv.second << std::endl;
// Nfp::NfpResult<RawShape> subnfp;
// bool shconvex = sl::isConvex<RawShape>(sl::getContour(sh));
// if(shconvex && trsh.isContourConvex()) {
// subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(
// sh, trsh.transformedShape());
// } else if(trsh.isContourConvex()) {
// subnfp = Nfp::noFitPolygon<NfpLevel::ONE_CONVEX>(
// sh, trsh.transformedShape());
// }
// else {
// subnfp = Nfp::noFitPolygon<Level::value>( sh,
// trsh.transformedShape());
// }
// correctNfpPosition(subnfp, sh, trsh);
// nfps = Nfp::merge(nfps, subnfp.first);
// }
// return nfps;
}
template<class RawShape>
_Circle<TPoint<RawShape>> minimizeCircle(const RawShape& sh) {
template<class RawShape, class Circle = _Circle<TPoint<RawShape>> >
Circle minimizeCircle(const RawShape& sh) {
using Point = TPoint<RawShape>;
using Coord = TCoord<Point>;
@ -507,9 +483,19 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin
using Box = _Box<TPoint<RawShape>>;
using MaxNfpLevel = nfp::MaxNfpLevel<RawShape>;
using ItemKeys = std::vector<__itemhash::Key>;
// Norming factor for the optimization function
const double norm_;
using MaxNfpLevel = nfp::MaxNfpLevel<RawShape>;
// Caching calculated nfps
__itemhash::Hash<RawShape> nfpcache_;
// Storing item hash keys
ItemKeys item_keys_;
public:
using Pile = nfp::Shapes<RawShape>;
@ -526,60 +512,290 @@ public:
_NofitPolyPlacer& operator=(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default;
#endif
bool static inline wouldFit(const Box& bb, const RawShape& bin) {
auto bbin = sl::boundingBox<RawShape>(bin);
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<RawShape>(rect.transformedShape(), bin);
return sl::isInside(rect.transformedShape(), bin) ? -1.0 : 1;
}
bool static inline wouldFit(const RawShape& chull, const RawShape& bin) {
auto bbch = sl::boundingBox<RawShape>(chull);
auto bbin = sl::boundingBox<RawShape>(bin);
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<RawShape>(chullcpy, bin);
return sl::isInside(chullcpy, bin) ? -1.0 : 1.0;
}
bool static inline wouldFit(const RawShape& chull, const Box& bin)
static inline double overfit(const RawShape& chull, const Box& bin)
{
auto bbch = sl::boundingBox<RawShape>(chull);
return wouldFit(bbch, bin);
auto bbch = sl::boundingBox(chull);
return overfit(bbch, bin);
}
bool static inline wouldFit(const Box& bb, const Box& bin)
static inline double overfit(const Box& bb, const Box& bin)
{
return bb.width() <= bin.width() && bb.height() <= bin.height();
auto wdiff = double(bb.width() - bin.width());
auto hdiff = double(bb.height() - bin.height());
double diff = 0;
if(wdiff > 0) diff += wdiff;
if(hdiff > 0) diff += hdiff;
return diff;
}
bool static inline wouldFit(const Box& bb, const _Circle<Vertex>& bin)
static inline double overfit(const Box& bb, const _Circle<Vertex>& bin)
{
return sl::isInside<RawShape>(bb, bin);
double boxr = 0.5*pl::distance(bb.minCorner(), bb.maxCorner());
double diff = boxr - bin.radius();
return diff;
}
bool static inline wouldFit(const RawShape& chull,
static inline double overfit(const RawShape& chull,
const _Circle<Vertex>& bin)
{
return boundingCircle(chull).radius() < bin.radius();
double r = boundingCircle(chull).radius();
double diff = r - bin.radius();
return diff;
}
template<class Range = ConstItemRange<typename Base::DefaultIter>>
PackResult trypack(
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>;
using ItemRef = std::reference_wrapper<Item>;
using ItemWithHash = const std::pair<ItemRef, __itemhash::Key>;
Shapes calcnfp(const ItemWithHash itsh, Lvl<nfp::NfpLevel::CONVEX_ONLY>)
{
using namespace nfp;
Shapes nfps;
const Item& trsh = itsh.first;
// nfps.reserve(polygons.size());
// unsigned idx = 0;
for(Item& sh : items_) {
// auto ik = item_keys_[idx++] + itsh.second;
// auto fnd = nfpcache_.find(ik);
// nfp::NfpResult<RawShape> subnfp_r;
// if(fnd == nfpcache_.end()) {
auto subnfp_r = noFitPolygon<NfpLevel::CONVEX_ONLY>(
sh.transformedShape(), trsh.transformedShape());
// nfpcache_[ik] = subnfp_r;
// } else {
// subnfp_r = fnd->second;
// }
correctNfpPosition(subnfp_r, sh, trsh);
// nfps.emplace_back(subnfp_r.first);
nfps = nfp::merge(nfps, subnfp_r.first);
}
// nfps = nfp::merge(nfps);
return nfps;
}
template<class Level>
Shapes calcnfp( const ItemWithHash itsh, Level)
{ // Function for arbitrary level of nfp implementation
using namespace nfp;
Shapes nfps;
const Item& trsh = itsh.first;
auto& orb = trsh.transformedShape();
bool orbconvex = trsh.isContourConvex();
for(Item& sh : items_) {
nfp::NfpResult<RawShape> subnfp;
auto& stat = sh.transformedShape();
if(sh.isContourConvex() && orbconvex)
subnfp = nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(stat, orb);
else if(orbconvex)
subnfp = nfp::noFitPolygon<NfpLevel::ONE_CONVEX>(stat, orb);
else
subnfp = nfp::noFitPolygon<Level::value>(stat, orb);
correctNfpPosition(subnfp, sh, trsh);
nfps = nfp::merge(nfps, subnfp.first);
}
return nfps;
}
// Very much experimental
void repack(Item& item, PackResult& result) {
if((sl::area(bin_) - this->filledArea()) >= item.area()) {
auto prev_func = config_.object_function;
unsigned iter = 0;
ItemGroup backup_rf = items_;
std::vector<Item> backup_cpy;
for(Item& itm : items_) backup_cpy.emplace_back(itm);
auto ofn = [this, &item, &result, &iter, &backup_cpy, &backup_rf]
(double ratio)
{
auto& bin = bin_;
iter++;
config_.object_function = [bin, ratio](
nfp::Shapes<RawShape>& pile,
const Item& item,
const ItemGroup& /*remaining*/)
{
pile.emplace_back(item.transformedShape());
auto ch = sl::convexHull(pile);
auto pbb = sl::boundingBox(pile);
pile.pop_back();
double parea = 0.5*(sl::area(ch) + sl::area(pbb));
double pile_area = std::accumulate(
pile.begin(), pile.end(), item.area(),
[](double sum, const RawShape& sh){
return sum + sl::area(sh);
});
// The pack ratio -- how much is the convex hull occupied
double pack_rate = (pile_area)/parea;
// ratio of waste
double waste = 1.0 - pack_rate;
// Score is the square root of waste. This will extend the
// range of good (lower) values and shrink the range of bad
// (larger) values.
auto wscore = std::sqrt(waste);
auto ibb = item.boundingBox();
auto bbb = sl::boundingBox(bin);
auto c = ibb.center();
double norm = 0.5*pl::distance(bbb.minCorner(),
bbb.maxCorner());
double dscore = pl::distance(c, pbb.center()) / norm;
return ratio*wscore + (1.0 - ratio) * dscore;
};
auto bb = sl::boundingBox(bin);
double norm = bb.width() + bb.height();
auto items = items_;
clearItems();
auto it = items.begin();
while(auto pr = _trypack(*it++)) {
this->accept(pr); if(it == items.end()) break;
}
auto count_diff = items.size() - items_.size();
double score = count_diff;
if(count_diff == 0) {
result = _trypack(item);
if(result) {
std::cout << "Success" << std::endl;
score = 0.0;
} else {
score += result.overfit() / norm;
}
} else {
result = PackResult();
items_ = backup_rf;
for(unsigned i = 0; i < items_.size(); i++) {
items_[i].get() = backup_cpy[i];
}
}
std::cout << iter << " repack result: " << score << " "
<< ratio << " " << count_diff << std::endl;
return score;
};
opt::StopCriteria stopcr;
stopcr.max_iterations = 30;
stopcr.stop_score = 1e-20;
opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr);
solver.optimize_min(ofn, opt::initvals(0.5),
opt::bound(0.0, 1.0));
// optimize
config_.object_function = prev_func;
}
}
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() {
opt::StopCriteria stopcr;
stopcr.max_iterations = 200;
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();
auto remlist = ItemGroup(remaining.from, remaining.to);
size_t itemhash = __itemhash::hash(item);
if(items_.empty()) {
setInitialPosition(item);
can_pack = item.isInside(bin_);
best_overfit = overfit(item.transformedShape(), bin_);
can_pack = best_overfit <= 0;
} else {
double global_score = std::numeric_limits<double>::max();
@ -588,7 +804,7 @@ public:
auto initial_rot = item.rotation();
Vertex final_tr = {0, 0};
Radians final_rot = initial_rot;
nfp::Shapes<RawShape> nfps;
Shapes nfps;
for(auto rot : config_.rotations) {
@ -596,17 +812,16 @@ public:
item.rotation(initial_rot + rot);
// place the new item outside of the print bed to make sure
// it is disjuct from the current merged pile
// it is disjunct from the current merged pile
placeOutsideOfBin(item);
auto trsh = item.transformedShape();
nfps = calcnfp({item, itemhash}, Lvl<MaxNfpLevel::value>());
nfps = calcnfp(items_, item, Lvl<MaxNfpLevel::value>());
auto iv = nfp::referenceVertex(trsh);
auto iv = item.referenceVertex();
auto startpos = item.translation();
std::vector<EdgeCache<RawShape>> ecache;
std::vector<Edges> ecache;
ecache.reserve(nfps.size());
for(auto& nfp : nfps ) {
@ -614,14 +829,54 @@ public:
ecache.back().accuracy(config_.accuracy);
}
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) {}
Shapes pile;
pile.reserve(items_.size()+1);
// double pile_area = 0;
for(Item& mitem : items_) {
pile.emplace_back(mitem.transformedShape());
// pile_area += mitem.area();
}
auto merged_pile = nfp::merge(pile);
auto& bin = bin_;
double norm = norm_;
// This is the kernel part of the object function that is
// customizable by the library client
auto _objfunc = config_.object_function?
config_.object_function :
[norm, /*pile_area,*/ bin, merged_pile](
const Pile& /*pile*/,
const Item& item,
const ItemGroup& /*remaining*/)
{
auto ibb = item.boundingBox();
auto binbb = sl::boundingBox(bin);
auto mp = merged_pile;
mp.emplace_back(item.transformedShape());
auto fullbb = sl::boundingBox(mp);
double score = pl::distance(ibb.center(), binbb.center());
score /= norm;
double miss = overfit(fullbb, bin);
miss = miss > 0? miss : 0;
score += std::pow(miss, 2);
return score;
};
// Our object function for placement
auto rawobjfunc =
[item, _objfunc, iv,
startpos, remlist, pile] (Vertex v)
{
auto d = v - iv;
d += startpos;
Item itm = item;
itm.translation(d);
return _objfunc(pile, itm, remlist);
};
auto getNfpPoint = [&ecache](const Optimum& opt)
@ -630,58 +885,10 @@ public:
ecache[opt.nfpidx].coords(opt.hidx, opt.relpos);
};
nfp::Shapes<RawShape> pile;
pile.reserve(items_.size()+1);
double pile_area = 0;
for(Item& mitem : items_) {
pile.emplace_back(mitem.transformedShape());
pile_area += mitem.area();
}
auto merged_pile = nfp::merge(pile);
// This is the kernel part of the object function that is
// customizable by the library client
auto _objfunc = config_.object_function?
config_.object_function :
[this, &merged_pile, &pile_area](
nfp::Shapes<RawShape>& /*pile*/,
const Item& item,
const ItemGroup& /*remaining*/)
auto boundaryCheck =
[&merged_pile, &getNfpPoint, &item, &bin, &iv, &startpos]
(const Optimum& o)
{
merged_pile.emplace_back(item.transformedShape());
auto ch = sl::convexHull(merged_pile);
merged_pile.pop_back();
// The pack ratio -- how much is the convex hull occupied
double pack_rate = (pile_area + item.area())/sl::area(ch);
// ratio of waste
double waste = 1.0 - pack_rate;
// Score is the square root of waste. This will extend the
// range of good (lower) values and shring the range of bad
// (larger) values.
auto score = std::sqrt(waste);
if(!wouldFit(ch, bin_)) score += norm_;
return score;
};
// Our object function for placement
auto rawobjfunc = [&] (Vertex v)
{
auto d = v - iv;
d += startpos;
item.translation(d);
double score = _objfunc(pile, item, remlist);
return score;
};
auto boundaryCheck = [&](const Optimum& o) {
auto v = getNfpPoint(o);
auto d = v - iv;
d += startpos;
@ -691,84 +898,111 @@ public:
auto chull = sl::convexHull(merged_pile);
merged_pile.pop_back();
return wouldFit(chull, bin_);
return overfit(chull, bin);
};
opt::StopCriteria stopcr;
stopcr.max_iterations = 200;
stopcr.relative_score_difference = 1e-20;
opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr);
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;
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];
auto contour_ofn = [&rawobjfunc, &getNfpPoint, ch]
auto contour_ofn = [rawobjfunc, getNfpPoint, ch]
(double relpos)
{
return rawobjfunc(getNfpPoint(Optimum(relpos, ch)));
};
std::for_each(cache.corners().begin(),
cache.corners().end(),
[ch, &contour_ofn, &solver, &best_score,
&optimum, &boundaryCheck] (double pos)
OptResults results(cache.corners().size());
__parallel::enumerate(
cache.corners().begin(),
cache.corners().end(),
[&contour_ofn, &results]
(double pos, unsigned n)
{
Optimizer solver;
try {
auto result = solver.optimize_min(contour_ofn,
results[n] = solver.optimize_min(contour_ofn,
opt::initvals<double>(pos),
opt::bound<double>(0, 1.0)
);
if(result.score < best_score) {
Optimum o(std::get<0>(result.optimum), ch, -1);
if(boundaryCheck(o)) {
best_score = result.score;
optimum = o;
}
}
} 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) {
auto hole_ofn =
[&rawobjfunc, &getNfpPoint, ch, hidx]
[rawobjfunc, getNfpPoint, ch, hidx]
(double pos)
{
Optimum opt(pos, ch, hidx);
return rawobjfunc(getNfpPoint(opt));
};
std::for_each(cache.corners(hidx).begin(),
results.clear();
results.resize(cache.corners(hidx).size());
// TODO : use parallel for
__parallel::enumerate(cache.corners(hidx).begin(),
cache.corners(hidx).end(),
[&hole_ofn, &solver, &best_score,
&optimum, ch, hidx, &boundaryCheck]
(double pos)
[&hole_ofn, &results]
(double pos, unsigned n)
{
Optimizer solver;
try {
auto result = solver.optimize_min(hole_ofn,
results[n] = solver.optimize_min(hole_ofn,
opt::initvals<double>(pos),
opt::bound<double>(0, 1.0)
);
if(result.score < best_score) {
Optimum o(std::get<0>(result.optimum),
ch, hidx);
if(boundaryCheck(o)) {
best_score = result.score;
optimum = o;
}
}
} 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);
}
}
}
}
@ -788,22 +1022,14 @@ public:
if(can_pack) {
ret = PackResult(item);
item_keys_.emplace_back(itemhash);
} else {
ret = PackResult(best_overfit);
}
return ret;
}
~_NofitPolyPlacer() {
clearItems();
}
inline void clearItems() {
finalAlign(bin_);
Base::clearItems();
}
private:
inline void finalAlign(const RawShape& pbin) {
auto bbin = sl::boundingBox(pbin);
finalAlign(bbin);
@ -826,7 +1052,7 @@ private:
nfp::Shapes<RawShape> m;
m.reserve(items_.size());
for(Item& item : items_) m.emplace_back(item.transformedShape());
auto&& bb = sl::boundingBox<RawShape>(m);
auto&& bb = sl::boundingBox(m);
Vertex ci, cb;

View File

@ -26,15 +26,21 @@ 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()) {}
PackResult(): item_ptr_(nullptr) {}
PackResult(double overfit = 1.0):
item_ptr_(nullptr), overfit_(overfit) {}
public:
operator bool() { return item_ptr_ != nullptr; }
double overfit() const { return overfit_; }
};
inline PlacerBoilerplate(const BinType& bin, unsigned cap = 50): bin_(bin)
@ -82,9 +88,6 @@ public:
inline void clearItems() {
items_.clear();
farea_valid_ = false;
#ifndef NDEBUG
debug_items_.clear();
#endif
}
inline double filledArea() const {
@ -101,10 +104,6 @@ public:
return farea_;
}
#ifndef NDEBUG
std::vector<Item> debug_items_;
#endif
protected:
BinType bin_;

View File

@ -493,8 +493,7 @@ public:
std::array<typename ItemList::iterator, 3>
candidates = {it, it2, it3};
auto tryPack = [&placer, &candidates, &not_packed,
&pack](
auto tryPack = [&placer, &candidates, &pack](
const decltype(indices)& idx)
{
std::array<bool, 3> packed = {false};
@ -569,11 +568,7 @@ public:
{
packed_bins_[idx] = placer.getItems();
#ifndef NDEBUG
packed_bins_[idx].insert(packed_bins_[idx].end(),
placer.getDebugItems().begin(),
placer.getDebugItems().end());
#endif
// TODO here should be a spinlock
slock.lock();
acounter -= packednum;

View File

@ -68,13 +68,13 @@ public:
}
auto it = store_.begin();
while(it != store_.end()) {
bool was_packed = false;
size_t j = 0;
while(!was_packed) {
for(size_t j = 0; j < placers.size() && !was_packed; j++) {
if((was_packed =
placers[j].pack(*it, rem(it, store_) )))
for(; j < placers.size() && !was_packed; j++) {
if((was_packed = placers[j].pack(*it, rem(it, store_) )))
makeProgress(placers[j], j);
}
@ -82,6 +82,7 @@ public:
placers.emplace_back(bin);
placers.back().configure(pconfig);
packed_bins_.emplace_back();
j = placers.size() - 1;
}
}
++it;

View File

@ -5,6 +5,7 @@
#include "printer_parts.h"
#include <libnest2d/geometry_traits_nfp.hpp>
//#include "../tools/libnfpglue.hpp"
//#include "../tools/nfp_svgnest_glue.hpp"
std::vector<libnest2d::Item>& prusaParts() {
static std::vector<libnest2d::Item> ret;
@ -219,21 +220,21 @@ TEST(GeometryAlgorithms, IsPointInsidePolygon) {
Point p = {1, 1};
ASSERT_TRUE(rect.isPointInside(p));
ASSERT_TRUE(rect.isInside(p));
p = {11, 11};
ASSERT_FALSE(rect.isPointInside(p));
ASSERT_FALSE(rect.isInside(p));
p = {11, 12};
ASSERT_FALSE(rect.isPointInside(p));
ASSERT_FALSE(rect.isInside(p));
p = {3, 3};
ASSERT_TRUE(rect.isPointInside(p));
ASSERT_TRUE(rect.isInside(p));
}

View File

@ -56,7 +56,7 @@ libnfporb::point_t scale(const libnfporb::point_t& p, long double factor) {
NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
{
using Vertex = PointImpl;
namespace sl = shapelike;
NfpR ret;
@ -85,7 +85,7 @@ NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
// this can throw
auto nfp = libnfporb::generateNFP(pstat, porb, true);
auto &ct = ShapeLike::getContour(ret.first);
auto &ct = sl::getContour(ret.first);
ct.reserve(nfp.front().size()+1);
for(auto v : nfp.front()) {
v = scale(v, refactor);
@ -94,7 +94,7 @@ NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
ct.push_back(ct.front());
std::reverse(ct.begin(), ct.end());
auto &rholes = ShapeLike::holes(ret.first);
auto &rholes = sl::holes(ret.first);
for(size_t hidx = 1; hidx < nfp.size(); ++hidx) {
if(nfp[hidx].size() >= 3) {
rholes.emplace_back();
@ -110,31 +110,31 @@ NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
}
}
ret.second = Nfp::referenceVertex(ret.first);
ret.second = nfp::referenceVertex(ret.first);
} catch(std::exception& e) {
std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl;
// auto ch_stat = ShapeLike::convexHull(sh);
// auto ch_orb = ShapeLike::convexHull(cother);
ret = Nfp::nfpConvexOnly(sh, cother);
ret = nfp::nfpConvexOnly(sh, cother);
}
return ret;
}
NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY>::operator()(
NfpR nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::CONVEX_ONLY>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{
return _nfp(sh, cother);//nfpConvexOnly(sh, cother);
}
NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX>::operator()(
NfpR nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::ONE_CONVEX>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{
return _nfp(sh, cother);
}
NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE>::operator()(
NfpR nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::BOTH_CONCAVE>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{
return _nfp(sh, cother);

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,77 @@
#ifndef NFP_SVGNEST_GLUE_HPP
#define NFP_SVGNEST_GLUE_HPP
#include "nfp_svgnest.hpp"
#include <libnest2d/clipper_backend/clipper_backend.hpp>
namespace libnest2d {
namespace __svgnest {
//template<> struct _Tol<double> {
// static const BP2D_CONSTEXPR TCoord<PointImpl> Value = 1000000;
//};
}
namespace nfp {
using NfpR = NfpResult<PolygonImpl>;
template<> struct NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY> {
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) {
// return nfpConvexOnly(sh, cother);
namespace sl = shapelike;
using alg = __svgnest::_alg<PolygonImpl>;
std::cout << "Itt vagyok" << std::endl;
auto nfp_p = alg::noFitPolygon(sl::getContour(sh),
sl::getContour(cother), false, false);
PolygonImpl nfp_cntr;
nfp_cntr.Contour = nfp_p.front();
std::cout << "Contour size: " << nfp_cntr.Contour.size() << std::endl;
return {nfp_cntr, referenceVertex(nfp_cntr)};
}
};
template<> struct NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX> {
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) {
// return nfpConvexOnly(sh, cother);
namespace sl = shapelike;
using alg = __svgnest::_alg<PolygonImpl>;
std::cout << "Itt vagyok" << std::endl;
auto nfp_p = alg::noFitPolygon(sl::getContour(sh),
sl::getContour(cother), false, false);
PolygonImpl nfp_cntr;
nfp_cntr.Contour = nfp_p.front();
return {nfp_cntr, referenceVertex(nfp_cntr)};
}
};
template<>
struct NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE> {
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) {
namespace sl = shapelike;
using alg = __svgnest::_alg<PolygonImpl>;
auto nfp_p = alg::noFitPolygon(sl::getContour(sh),
sl::getContour(cother), true, false);
PolygonImpl nfp_cntr;
nfp_cntr.Contour = nfp_p.front();
return {nfp_cntr, referenceVertex(nfp_cntr)};
}
};
template<> struct MaxNfpLevel<PolygonImpl> {
// static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::BOTH_CONCAVE;
static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY;
};
}}
#endif // NFP_SVGNEST_GLUE_HPP

View File

@ -270,6 +270,8 @@ void fillConfig(PConf& pcfg) {
// The accuracy of optimization.
// Goes from 0.0 to 1.0 and scales performance as well
pcfg.accuracy = 0.65f;
pcfg.parallel = false;
}
template<class TBin>
@ -291,6 +293,7 @@ protected:
std::vector<double> areacache_;
SpatIndex rtree_;
double norm_;
Pile pile_cache_;
public:
_ArrBase(const TBin& bin, Distance dist,
@ -317,23 +320,26 @@ public:
std::function<void(unsigned)> progressind):
_ArrBase<Box>(bin, dist, progressind)
{
pconf_.object_function = [this, bin] (
Pile& pile,
const Item &item,
const ItemGroup& rem) {
// pconf_.object_function = [this, bin] (
// const Pile& pile_c,
// const Item &item,
// const ItemGroup& rem) {
auto result = objfunc(bin.center(), bin_area_, pile,
item, norm_, areacache_, rtree_, rem);
double score = std::get<0>(result);
auto& fullbb = std::get<1>(result);
// auto& pile = pile_cache_;
// if(pile.size() != pile_c.size()) pile = pile_c;
auto wdiff = fullbb.width() - bin.width();
auto hdiff = fullbb.height() - bin.height();
if(wdiff > 0) score += std::pow(wdiff, 2) / norm_;
if(hdiff > 0) score += std::pow(hdiff, 2) / norm_;
// auto result = objfunc(bin.center(), bin_area_, pile,
// item, norm_, areacache_, rtree_, rem);
// double score = std::get<0>(result);
// auto& fullbb = std::get<1>(result);
return score;
};
// auto wdiff = fullbb.width() - bin.width();
// auto hdiff = fullbb.height() - bin.height();
// if(wdiff > 0) score += std::pow(wdiff, 2) / norm_;
// if(hdiff > 0) score += std::pow(hdiff, 2) / norm_;
// return score;
// };
pck_.configure(pconf_);
}
@ -350,10 +356,13 @@ public:
_ArrBase<lnCircle>(bin, dist, progressind) {
pconf_.object_function = [this, &bin] (
Pile& pile,
const Pile& pile_c,
const Item &item,
const ItemGroup& rem) {
auto& pile = pile_cache_;
if(pile.size() != pile_c.size()) pile = pile_c;
auto result = objfunc(bin.center(), bin_area_, pile, item, norm_,
areacache_, rtree_, rem);
double score = std::get<0>(result);
@ -393,10 +402,13 @@ public:
_ArrBase<PolygonImpl>(bin, dist, progressind)
{
pconf_.object_function = [this, &bin] (
Pile& pile,
const Pile& pile_c,
const Item &item,
const ItemGroup& rem) {
auto& pile = pile_cache_;
if(pile.size() != pile_c.size()) pile = pile_c;
auto binbb = sl::boundingBox(bin);
auto result = objfunc(binbb.center(), bin_area_, pile, item, norm_,
areacache_, rtree_, rem);
@ -417,10 +429,13 @@ public:
_ArrBase<Box>(Box(0, 0), dist, progressind)
{
this->pconf_.object_function = [this] (
Pile& pile,
const Pile& pile_c,
const Item &item,
const ItemGroup& rem) {
auto& pile = pile_cache_;
if(pile.size() != pile_c.size()) pile = pile_c;
auto result = objfunc({0, 0}, 0, pile, item, norm_,
areacache_, rtree_, rem);
return std::get<0>(result);