diff --git a/xs/src/libnest2d/CMakeLists.txt b/xs/src/libnest2d/CMakeLists.txt index 0a181f4ab..cd3e35b97 100644 --- a/xs/src/libnest2d/CMakeLists.txt +++ b/xs/src/libnest2d/CMakeLists.txt @@ -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 diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index 37096019d..57be7a208 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -1,7 +1,6 @@ #include #include #include - //#define DEBUG_EXPORT_NFP #include @@ -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>; @@ -53,10 +54,50 @@ void arrangeRectangles() { const int SCALE = 1000000; - std::vector rects = { - {60*SCALE, 200*SCALE}, - {60*SCALE, 200*SCALE} - }; + std::vector 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 input; input.insert(input.end(), prusaParts().begin(), prusaParts().end()); @@ -84,7 +125,7 @@ void arrangeRectangles() { // _Circle bin({0, 0}, 125*SCALE); - auto min_obj_distance = static_cast(0*SCALE); + auto min_obj_distance = static_cast(1.5*SCALE); using Placer = strategies::_NofitPolyPlacer; using Packer = Nester; @@ -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"); } diff --git a/xs/src/libnest2d/libnest2d/boost_alg.hpp b/xs/src/libnest2d/libnest2d/boost_alg.hpp index 7da1036f0..bb0403b06 100644 --- a/xs/src/libnest2d/libnest2d/boost_alg.hpp +++ b/xs/src/libnest2d/libnest2d/boost_alg.hpp @@ -356,8 +356,7 @@ inline double area(const PolygonImpl& shape, const PolygonTag&) #endif template<> -inline bool isInside(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(const bp2d::Shapes& shapes) +inline bp2d::Box boundingBox(const bp2d::Shapes& shapes, + const MultiPolygonTag&) { bp2d::Box b; boost::geometry::envelope(shapes, b); @@ -400,7 +400,7 @@ inline bp2d::Box boundingBox(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& 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 - 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 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 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 } diff --git a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp index 4238212ad..745fd2108 100644 --- a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp +++ b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp @@ -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 { using Type = PointImpl; }; -// Type of vertex iterator used by Clipper -template<> struct VertexIteratorType { - using Type = ClipperLib::Path::iterator; -}; - -// Type of vertex iterator used by Clipper -template<> struct VertexConstIteratorType { - using Type = ClipperLib::Path::const_iterator; +template<> struct PointType { + using Type = PointImpl; }; template<> struct CountourType { using Type = PathImpl; }; +template<> struct ShapeTag { using Type = PolygonTag; }; + +template<> struct ShapeTag> { + using Type = MultiPolygonTag; +}; + +template<> struct PointType> { + using Type = PointImpl; +}; + +template<> struct HolesContainer { + 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 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 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& 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& y(PointImpl& p) { return p.Y; @@ -178,10 +183,6 @@ inline double area(const PolygonImpl& sh) { } -template<> struct HolesContainer { - 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::Value>(sh); @@ -269,28 +270,6 @@ template<> inline std::string toString(const PolygonImpl& sh) return ss.str(); } -template<> inline TVertexIterator begin(PolygonImpl& sh) -{ - return sh.Contour.begin(); -} - -template<> inline TVertexIterator end(PolygonImpl& sh) -{ - return sh.Contour.end(); -} - -template<> -inline TVertexConstIterator cbegin(const PolygonImpl& sh) -{ - return sh.Contour.cbegin(); -} - -template<> inline TVertexConstIterator 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 _merge(ClipperLib::Clipper& clipper) { - nfp::Shapes retv; +inline std::vector _merge(ClipperLib::Clipper& clipper) { + shapelike::Shapes retv; ClipperLib::PolyTree result; clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative); @@ -447,8 +426,8 @@ inline nfp::Shapes _merge(ClipperLib::Clipper& clipper) { namespace nfp { -template<> inline nfp::Shapes -merge(const nfp::Shapes& shapes) +template<> inline std::vector +merge(const std::vector& shapes) { ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 0826cbd4b..d16257731 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -22,34 +22,12 @@ template using TCoord = typename CoordType>::Type; /// Getting the type of point structure used by a shape. -template struct PointType { /*using Type = void;*/ }; +template struct PointType { using Type = typename Sh::PointType; }; /// TPoint as shorthand for `typename PointType::Type`. template using TPoint = typename PointType>::Type; -/// Getting the VertexIterator type of a shape class. -template struct VertexIteratorType { /*using Type = void;*/ }; - -/// Getting the const vertex iterator for a shape class. -template struct VertexConstIteratorType {/* using Type = void;*/ }; - -/** - * TVertexIterator as shorthand for - * `typename VertexIteratorType::Type` - */ -template -using TVertexIterator = -typename VertexIteratorType>::Type; - -/** - * \brief TVertexConstIterator as shorthand for - * `typename VertexConstIteratorType::Type` - */ -template -using TVertexConstIterator = -typename VertexConstIteratorType>::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 struct ShapeTag { using Type = typename Shape::Tag; }; +template using Tag = typename ShapeTag::Type; + +template struct MultiShape { using Type = std::vector; }; +template using TMultiShape = typename MultiShape::Type; + /** * \brief An abstraction of a box; */ @@ -371,7 +356,7 @@ enum class Formats { namespace shapelike { template - using Shapes = std::vector; + using Shapes = TMultiShape; template inline RawShape create(const TContour& contour, @@ -455,27 +440,28 @@ namespace shapelike { } template - inline TVertexIterator begin(RawShape& sh) + inline typename TContour::iterator begin(RawShape& sh) { - return sh.begin(); + return getContour(sh).begin(); } template - inline TVertexIterator end(RawShape& sh) + inline typename TContour::iterator end(RawShape& sh) { - return sh.end(); + return getContour(sh).end(); } template - inline TVertexConstIterator cbegin(const RawShape& sh) + inline typename TContour::const_iterator + cbegin(const RawShape& sh) { - return sh.cbegin(); + return getContour(sh).cbegin(); } template - inline TVertexConstIterator cend(const RawShape& sh) + inline typename TContour::const_iterator cend(const RawShape& sh) { - return sh.cend(); + return getContour(sh).cend(); } template @@ -559,27 +545,29 @@ namespace shapelike { "ShapeLike::boundingBox(shape) unimplemented!"); } - template - inline _Box> boundingBox(const Shapes& /*sh*/) + template + inline _Box> + boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) { - static_assert(always_false::value, + static_assert(always_false::value, "ShapeLike::boundingBox(shapes) unimplemented!"); } template - inline RawShape convexHull(const RawShape& /*sh*/) + inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&) { static_assert(always_false::value, "ShapeLike::convexHull(shape) unimplemented!"); return RawShape(); } - template - inline RawShape convexHull(const Shapes& /*sh*/) + template + inline typename RawShapes::value_type + convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&) { - static_assert(always_false::value, + static_assert(always_false::value, "ShapeLike::convexHull(shapes) unimplemented!"); - return RawShape(); + return typename RawShapes::value_type(); } template @@ -599,8 +587,7 @@ namespace shapelike { template inline void offset(RawShape& /*sh*/, TCoord> /*distance*/) { - static_assert(always_false::value, - "ShapeLike::offset() unimplemented!"); + dout() << "The current geometry backend does not support offsetting!\n"; } template @@ -670,9 +657,9 @@ namespace shapelike { } template // Dispatch function - inline _Box boundingBox(const S& sh) + inline _Box> boundingBox(const S& sh) { - return boundingBox(sh, typename S::Tag()); + return boundingBox(sh, Tag() ); } template @@ -690,7 +677,7 @@ namespace shapelike { template // Dispatching function inline double area(const RawShape& sh) { - return area(sh, typename RawShape::Tag()); + return area(sh, Tag()); } template @@ -702,6 +689,13 @@ namespace shapelike { }); } + template + inline auto convexHull(const RawShape& sh) + -> decltype(convexHull(sh, Tag())) // TODO: C++14 could deduce + { + return convexHull(sh, Tag()); + } + template inline bool isInside(const TPoint& point, const _Circle>& circ) @@ -816,6 +810,16 @@ namespace shapelike { } } +#define DECLARE_MAIN_TYPES(T) \ + using Polygon = T; \ + using Point = TPoint; \ + using Coord = TCoord; \ + using Contour = TContour; \ + using Box = _Box; \ + using Circle = _Circle; \ + using Segment = _Segment; \ + using Polygons = TMultiShape + } #endif // GEOMETRY_TRAITS_HPP diff --git a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp index 6cac374ae..b9dfd2185 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp @@ -27,8 +27,8 @@ inline bool _vsort(const TPoint& v1, const TPoint& 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 struct MaxNfpLevel { // Shorthand for a pile of polygons template -using Shapes = typename shapelike::Shapes; +using Shapes = TMultiShape; /** * Merge a bunch of polygons with the specified additional polygon. @@ -62,10 +62,10 @@ using Shapes = typename shapelike::Shapes; * 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 -inline Shapes merge(const Shapes& /*shc*/) +template +inline RawShapes merge(const RawShapes& /*shc*/) { - static_assert(always_false::value, + static_assert(always_false::value, "Nfp::merge(shapes, shape) unimplemented!"); } @@ -81,12 +81,12 @@ inline Shapes merge(const Shapes& /*shc*/) * polygons are disjuct than the resulting set will contain more polygons. */ template -inline Shapes merge(const Shapes& shc, - const RawShape& sh) +inline TMultiShape merge(const TMultiShape& shc, + const RawShape& sh) { - auto m = merge(shc); + auto m = nfp::merge(shc); m.push_back(sh); - return merge(m); + return nfp::merge(m); } /** diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index d2850d4ed..42255cbb4 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -31,6 +31,8 @@ class _Item { using Vertex = TPoint; using Box = _Box; + using VertexConstIterator = typename TContour::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 rmt_; // rightmost top vertex - mutable TVertexConstIterator 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; + 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, - // 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::value, IT> @@ -904,8 +899,8 @@ private: template, - // 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::value, IT> diff --git a/xs/src/libnest2d/libnest2d/metaloop.hpp b/xs/src/libnest2d/libnest2d/metaloop.hpp index 18755525c..d88988ba1 100644 --- a/xs/src/libnest2d/libnest2d/metaloop.hpp +++ b/xs/src/libnest2d/libnest2d/metaloop.hpp @@ -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 using Int = std::integral_constant; @@ -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)) {} template 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 using MetaLoop = _MetaLoop, Args...>; diff --git a/xs/src/libnest2d/libnest2d/optimizer.hpp b/xs/src/libnest2d/libnest2d/optimizer.hpp index c8ed2e378..90d2f2ff9 100644 --- a/xs/src/libnest2d/libnest2d/optimizer.hpp +++ b/xs/src/libnest2d/libnest2d/optimizer.hpp @@ -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; }; diff --git a/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp b/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp index 737ca6e3c..1a0f06e02 100644 --- a/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp @@ -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 ); diff --git a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp index af1678372..0ba9eb3c0 100644 --- a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp @@ -7,9 +7,24 @@ namespace libnest2d { namespace strategies { +template struct Epsilon {}; + +template +struct Epsilon::value, T> > { + static const T Value = 1; +}; + +template +struct Epsilon::value, T> > { + static const T Value = 1e-3; +}; + template struct BLConfig { - TCoord> min_obj_distance = 0; + DECLARE_MAIN_TYPES(RawShape); + + Coord min_obj_distance = 0; + Coord epsilon = Epsilon::Value; bool allow_rotations = false; }; @@ -69,20 +84,21 @@ protected: setInitialPosition(item); Unit d = availableSpaceDown(item); - bool can_move = d > 1 /*std::numeric_limits::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::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::epsilon()*/; + can_move = d > eps; left = true; } } diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 638d606e0..b9e0ba8f1 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -2,7 +2,15 @@ #define NOFITPOLY_HPP #include -#include + +// For caching nfps +#include + +// For parallel for +#include +#include +#include +#include #ifndef NDEBUG #include @@ -13,8 +21,84 @@ #include "tools/svgtools.hpp" +namespace libnest2d { -namespace libnest2d { namespace strategies { +namespace __parallel { + +using std::function; +using std::iterator_traits; +template +using TIteratorValue = typename iterator_traits::value_type; + +template +inline void enumerate( + Iterator from, Iterator to, + function, unsigned)> fn, + std::launch policy = std::launch::deferred | std::launch::async) +{ + auto N = to-from; + std::vector> 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 +Key hash(const _Item& item) { + using Point = TPoint; + using Segment = _Segment; + + 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()(ret); +} + +template +using Hash = std::unordered_map>; + +} + +namespace strategies { template struct NfpPConfig { @@ -71,7 +155,7 @@ struct NfpPConfig { * decisions (for you or a more intelligent AI). * */ - std::function&, const _Item&, + std::function&, const _Item&, 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& nfp, shapelike::translate(nfp.first, dnfp); } -template -nfp::Shapes calcnfp( const Container& polygons, - const _Item& trsh, - Lvl) -{ - using Item = _Item; - using namespace nfp; - - nfp::Shapes nfps; - -// int pi = 0; - for(Item& sh : polygons) { - auto subnfp_r = noFitPolygon( - 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; -// 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 -nfp::Shapes calcnfp( const Container& polygons, - const _Item& trsh, - Level) -{ - using namespace nfp; - using Item = _Item; - - Shapes nfps; - - auto& orb = trsh.transformedShape(); - bool orbconvex = trsh.isContourConvex(); - - for(Item& sh : polygons) { - nfp::NfpResult subnfp; - auto& stat = sh.transformedShape(); - - if(sh.isContourConvex() && orbconvex) - subnfp = nfp::noFitPolygon(stat, orb); - else if(orbconvex) - subnfp = nfp::noFitPolygon(stat, orb); - else - subnfp = nfp::noFitPolygon(stat, orb); - - correctNfpPosition(subnfp, sh, trsh); - - nfps = nfp::merge(nfps, subnfp.first); - } - - return nfps; - - -// using Item = _Item; -// using sl = ShapeLike; - -// Nfp::Shapes 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 subnfp; -// bool shconvex = sl::isConvex(sl::getContour(sh)); -// if(shconvex && trsh.isContourConvex()) { -// subnfp = Nfp::noFitPolygon( -// sh, trsh.transformedShape()); -// } else if(trsh.isContourConvex()) { -// subnfp = Nfp::noFitPolygon( -// sh, trsh.transformedShape()); -// } -// else { -// subnfp = Nfp::noFitPolygon( sh, -// trsh.transformedShape()); -// } - -// correctNfpPosition(subnfp, sh, trsh); - -// nfps = Nfp::merge(nfps, subnfp.first); -// } - -// return nfps; -} - -template -_Circle> minimizeCircle(const RawShape& sh) { +template> > +Circle minimizeCircle(const RawShape& sh) { using Point = TPoint; using Coord = TCoord; @@ -507,9 +483,19 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer>; + using MaxNfpLevel = nfp::MaxNfpLevel; + + using ItemKeys = std::vector<__itemhash::Key>; + + // Norming factor for the optimization function const double norm_; - using MaxNfpLevel = nfp::MaxNfpLevel; + // Caching calculated nfps + __itemhash::Hash nfpcache_; + + // Storing item hash keys + ItemKeys item_keys_; + public: using Pile = nfp::Shapes; @@ -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(bin); + static inline double overfit(const Box& bb, const RawShape& bin) { + auto bbin = sl::boundingBox(bin); auto d = bbin.center() - bb.center(); _Rectangle rect(bb.width(), bb.height()); rect.translate(bb.minCorner() + d); - return sl::isInside(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(chull); - auto bbin = sl::boundingBox(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(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(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& bin) + static inline double overfit(const Box& bb, const _Circle& bin) { - - return sl::isInside(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& bin) { - return boundingCircle(chull).radius() < bin.radius(); + double r = boundingCircle(chull).radius(); + double diff = r - bin.radius(); + return diff; } template> - 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; + using ItemRef = std::reference_wrapper; + using ItemWithHash = const std::pair; + + Shapes calcnfp(const ItemWithHash itsh, Lvl) + { + 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 subnfp_r; +// if(fnd == nfpcache_.end()) { + + auto subnfp_r = noFitPolygon( + 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 + 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 subnfp; + auto& stat = sh.transformedShape(); + + if(sh.isContourConvex() && orbconvex) + subnfp = nfp::noFitPolygon(stat, orb); + else if(orbconvex) + subnfp = nfp::noFitPolygon(stat, orb); + else + subnfp = nfp::noFitPolygon(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 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& 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 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 { + public: + Optimizer() { + opt::StopCriteria stopcr; + stopcr.max_iterations = 200; + stopcr.relative_score_difference = 1e-20; + this->stopcr_ = stopcr; + } + }; + + using Edges = EdgeCache; + + template> + PackResult _trypack( Item& item, const Range& remaining = Range()) { PackResult ret; bool can_pack = false; + double best_overfit = std::numeric_limits::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::max(); @@ -588,7 +804,7 @@ public: auto initial_rot = item.rotation(); Vertex final_tr = {0, 0}; Radians final_rot = initial_rot; - nfp::Shapes 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()); - nfps = calcnfp(items_, item, Lvl()); - auto iv = nfp::referenceVertex(trsh); + auto iv = item.referenceVertex(); auto startpos = item.translation(); - std::vector> ecache; + std::vector 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 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& /*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 solver(stopcr); - Optimum optimum(0, 0); double best_score = std::numeric_limits::max(); + std::launch policy = std::launch::deferred; + if(config_.parallel) policy |= std::launch::async; + + using OptResult = opt::Result; + using OptResults = std::vector; // 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(pos), opt::bound(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(pos), opt::bound(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 m; m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); - auto&& bb = sl::boundingBox(m); + auto&& bb = sl::boundingBox(m); Vertex ci, cb; diff --git a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp index 1a0730d88..44e2bc1b0 100644 --- a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp @@ -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 debug_items_; -#endif - protected: BinType bin_; diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index 8c02dc373..846b00bad 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -493,8 +493,7 @@ public: std::array candidates = {it, it2, it3}; - auto tryPack = [&placer, &candidates, ¬_packed, - &pack]( + auto tryPack = [&placer, &candidates, &pack]( const decltype(indices)& idx) { std::array 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; diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index 1312f9874..eb820a518 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -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; diff --git a/xs/src/libnest2d/tests/test.cpp b/xs/src/libnest2d/tests/test.cpp index 197ff6598..b85bbc111 100644 --- a/xs/src/libnest2d/tests/test.cpp +++ b/xs/src/libnest2d/tests/test.cpp @@ -5,6 +5,7 @@ #include "printer_parts.h" #include //#include "../tools/libnfpglue.hpp" +//#include "../tools/nfp_svgnest_glue.hpp" std::vector& prusaParts() { static std::vector 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)); } diff --git a/xs/src/libnest2d/tools/libnfpglue.cpp b/xs/src/libnest2d/tools/libnfpglue.cpp index 18656fd40..31733acf9 100644 --- a/xs/src/libnest2d/tools/libnfpglue.cpp +++ b/xs/src/libnest2d/tools/libnfpglue.cpp @@ -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::operator()( +NfpR nfp::NfpImpl::operator()( const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) { return _nfp(sh, cother);//nfpConvexOnly(sh, cother); } -NfpR Nfp::NfpImpl::operator()( +NfpR nfp::NfpImpl::operator()( const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) { return _nfp(sh, cother); } -NfpR Nfp::NfpImpl::operator()( +NfpR nfp::NfpImpl::operator()( const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) { return _nfp(sh, cother); diff --git a/xs/src/libnest2d/tools/libnfpglue.hpp b/xs/src/libnest2d/tools/libnfpglue.hpp index 75f639445..1ff033cb9 100644 --- a/xs/src/libnest2d/tools/libnfpglue.hpp +++ b/xs/src/libnest2d/tools/libnfpglue.hpp @@ -5,22 +5,22 @@ namespace libnest2d { -using NfpR = Nfp::NfpResult; +using NfpR = nfp::NfpResult; NfpR _nfp(const PolygonImpl& sh, const PolygonImpl& cother); template<> -struct Nfp::NfpImpl { +struct nfp::NfpImpl { NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); }; template<> -struct Nfp::NfpImpl { +struct nfp::NfpImpl { NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); }; template<> -struct Nfp::NfpImpl { +struct nfp::NfpImpl { NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); }; @@ -34,7 +34,7 @@ struct Nfp::NfpImpl { // NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother); //}; -template<> struct Nfp::MaxNfpLevel { +template<> struct nfp::MaxNfpLevel { static const BP2D_CONSTEXPR NfpLevel value = // NfpLevel::CONVEX_ONLY; NfpLevel::BOTH_CONCAVE; diff --git a/xs/src/libnest2d/tools/nfp_svgnest.hpp b/xs/src/libnest2d/tools/nfp_svgnest.hpp new file mode 100644 index 000000000..8ab571c00 --- /dev/null +++ b/xs/src/libnest2d/tools/nfp_svgnest.hpp @@ -0,0 +1,1004 @@ +#ifndef NFP_SVGNEST_HPP +#define NFP_SVGNEST_HPP + +#include +#include + +#include + +namespace libnest2d { + +namespace __svgnest { + +using std::sqrt; +using std::min; +using std::max; +using std::abs; +using std::isnan; + +//template struct _Scale { +// static const BP2D_CONSTEXPR long long Value = 1000000; +//}; + +template struct _alg { + using Contour = TContour; + using Point = TPoint; + using iCoord = TCoord; + using Coord = double; + using Shapes = nfp::Shapes; + + static const Coord TOL; + +#define dNAN std::nan("") + + struct Vector { + Coord x, y; + bool marked = false; + Vector() = default; + Vector(Coord X, Coord Y): x(X), y(Y) {} + Vector(const Point& p): x(getX(p)), y(getY(p)) {} + operator Point() const { return {iCoord(x), iCoord(y)}; } + Vector& operator=(const Point& p) { + x = getX(p), y = getY(p); return *this; + } + Vector(std::initializer_list il): + x(*il.begin()), y(*std::next(il.begin())) {} + }; + + static inline Coord x(const Point& p) { return Coord(getX(p)); } + static inline Coord y(const Point& p) { return Coord(getY(p)); } + + static inline Coord x(const Vector& p) { return p.x; } + static inline Coord y(const Vector& p) { return p.y; } + + class Cntr { + std::vector v_; + public: + Cntr(const Contour& c) { + v_.reserve(c.size()); + std::transform(c.begin(), c.end(), std::back_inserter(v_), + [](const Point& p) { + return Vector(double(x(p))/1e6, double(y(p))/1e6); + }); + } + Cntr() = default; + + Coord offsetx = 0; + Coord offsety = 0; + size_t size() const { return v_.size(); } + bool empty() const { return v_.empty(); } + typename Contour::const_iterator begin() const { return v_.cbegin(); } + typename Contour::const_iterator end() const { return v_.cend(); } + Vector& operator[](size_t idx) { return v_[idx]; } + const Vector& operator[](size_t idx) const { return v_[idx]; } + template + void emplace_back(Args&&...args) { + v_.emplace_back(std::forward(args)...); + } + template + void push(Args&&...args) { + v_.emplace_back(std::forward(args)...); + } + void clear() { v_.clear(); } + + operator Contour() const { + Contour cnt; + cnt.reserve(v_.size()); + std::transform(v_.begin(), v_.end(), std::back_inserter(cnt), + [](const Vector& vertex) { + return Point(iCoord(vertex.x*1e6), iCoord(vertex.y*1e6)); + }); + return cnt; + } + }; + + inline static bool _almostEqual(Coord a, Coord b, + Coord tolerance = TOL) + { + return std::abs(a - b) < tolerance; + } + + // returns true if p lies on the line segment defined by AB, + // but not at any endpoints may need work! + static bool _onSegment(const Vector& A, const Vector& B, const Vector& p) { + + // vertical line + if(_almostEqual(A.x, B.x) && _almostEqual(p.x, A.x)) { + if(!_almostEqual(p.y, B.y) && !_almostEqual(p.y, A.y) && + p.y < max(B.y, A.y) && p.y > min(B.y, A.y)){ + return true; + } + else{ + return false; + } + } + + // horizontal line + if(_almostEqual(A.y, B.y) && _almostEqual(p.y, A.y)){ + if(!_almostEqual(p.x, B.x) && !_almostEqual(p.x, A.x) && + p.x < max(B.x, A.x) && p.x > min(B.x, A.x)){ + return true; + } + else{ + return false; + } + } + + //range check + if((p.x < A.x && p.x < B.x) || (p.x > A.x && p.x > B.x) || + (p.y < A.y && p.y < B.y) || (p.y > A.y && p.y > B.y)) + return false; + + // exclude end points + if((_almostEqual(p.x, A.x) && _almostEqual(p.y, A.y)) || + (_almostEqual(p.x, B.x) && _almostEqual(p.y, B.y))) + return false; + + + double cross = (p.y - A.y) * (B.x - A.x) - (p.x - A.x) * (B.y - A.y); + + if(abs(cross) > TOL) return false; + + double dot = (p.x - A.x) * (B.x - A.x) + (p.y - A.y)*(B.y - A.y); + + if(dot < 0 || _almostEqual(dot, 0)) return false; + + double len2 = (B.x - A.x)*(B.x - A.x) + (B.y - A.y)*(B.y - A.y); + + if(dot > len2 || _almostEqual(dot, len2)) return false; + + return true; + } + + // return true if point is in the polygon, false if outside, and null if exactly on a point or edge + static int pointInPolygon(const Vector& point, const Cntr& polygon) { + if(polygon.size() < 3){ + return 0; + } + + bool inside = false; + Coord offsetx = polygon.offsetx; + Coord offsety = polygon.offsety; + + for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j=i++) { + auto xi = polygon[i].x + offsetx; + auto yi = polygon[i].y + offsety; + auto xj = polygon[j].x + offsetx; + auto yj = polygon[j].y + offsety; + + if(_almostEqual(xi, point.x) && _almostEqual(yi, point.y)){ + return 0; // no result + } + + if(_onSegment({xi, yi}, {xj, yj}, point)){ + return 0; // exactly on the segment + } + + if(_almostEqual(xi, xj) && _almostEqual(yi, yj)){ // ignore very small lines + continue; + } + + bool intersect = ((yi > point.y) != (yj > point.y)) && + (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi); + if (intersect) inside = !inside; + } + + return inside? 1 : -1; + } + + static bool intersect(const Cntr& A, const Cntr& B){ + Contour a = A, b = B; + return shapelike::intersects(shapelike::create(a), shapelike::create(b)); + } + + static Vector _normalizeVector(const Vector& v) { + if(_almostEqual(v.x*v.x + v.y*v.y, Coord(1))){ + return Point(v); // given vector was already a unit vector + } + auto len = sqrt(v.x*v.x + v.y*v.y); + auto inverse = 1/len; + + return { Coord(v.x*inverse), Coord(v.y*inverse) }; + } + + static double pointDistance( const Vector& p, + const Vector& s1, + const Vector& s2, + Vector normal, + bool infinite = false) + { + normal = _normalizeVector(normal); + + Vector dir = { + normal.y, + -normal.x + }; + + auto pdot = p.x*dir.x + p.y*dir.y; + auto s1dot = s1.x*dir.x + s1.y*dir.y; + auto s2dot = s2.x*dir.x + s2.y*dir.y; + + auto pdotnorm = p.x*normal.x + p.y*normal.y; + auto s1dotnorm = s1.x*normal.x + s1.y*normal.y; + auto s2dotnorm = s2.x*normal.x + s2.y*normal.y; + + if(!infinite){ + if (((pdots1dot || _almostEqual(pdot, s1dot)) && + (pdot>s2dot || _almostEqual(pdot, s2dot)))) + { + // dot doesn't collide with segment, + // or lies directly on the vertex + return dNAN; + } + if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && + (pdotnorm>s1dotnorm && pdotnorm>s2dotnorm)) + { + return double(min(pdotnorm - s1dotnorm, pdotnorm - s2dotnorm)); + } + if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && + (pdotnorm EFmax){ + return dNAN; + } + + double overlap = 0; + + if((ABmax > EFmax && ABmin < EFmin) || (EFmax > ABmax && EFmin < ABmin)) + { + overlap = 1; + } + else{ + auto minMax = min(ABmax, EFmax); + auto maxMin = max(ABmin, EFmin); + + auto maxMax = max(ABmax, EFmax); + auto minMin = min(ABmin, EFmin); + + overlap = (minMax-maxMin)/(maxMax-minMin); + } + + auto crossABE = (E.y - A.y) * (B.x - A.x) - (E.x - A.x) * (B.y - A.y); + auto crossABF = (F.y - A.y) * (B.x - A.x) - (F.x - A.x) * (B.y - A.y); + + // lines are colinear + if(_almostEqual(crossABE,0) && _almostEqual(crossABF,0)){ + + Vector ABnorm = {B.y-A.y, A.x-B.x}; + Vector EFnorm = {F.y-E.y, E.x-F.x}; + + auto ABnormlength = sqrt(ABnorm.x*ABnorm.x + ABnorm.y*ABnorm.y); + ABnorm.x /= ABnormlength; + ABnorm.y /= ABnormlength; + + auto EFnormlength = sqrt(EFnorm.x*EFnorm.x + EFnorm.y*EFnorm.y); + EFnorm.x /= EFnormlength; + EFnorm.y /= EFnormlength; + + // segment normals must point in opposite directions + if(abs(ABnorm.y * EFnorm.x - ABnorm.x * EFnorm.y) < TOL && + ABnorm.y * EFnorm.y + ABnorm.x * EFnorm.x < 0){ + // normal of AB segment must point in same direction as + // given direction vector + auto normdot = ABnorm.y * direction.y + ABnorm.x * direction.x; + // the segments merely slide along eachother + if(_almostEqual(normdot,0, TOL)){ + return dNAN; + } + if(normdot < 0){ + return 0.0; + } + } + return dNAN; + } + + std::vector distances; distances.reserve(10); + + // coincident points + if(_almostEqual(dotA, dotE)){ + distances.emplace_back(crossA-crossE); + } + else if(_almostEqual(dotA, dotF)){ + distances.emplace_back(crossA-crossF); + } + else if(dotA > EFmin && dotA < EFmax){ + auto d = pointDistance(A,E,F,reverse); + if(!isnan(d) && _almostEqual(d, 0)) + { // A currently touches EF, but AB is moving away from EF + auto dB = pointDistance(B,E,F,reverse,true); + if(dB < 0 || _almostEqual(dB*overlap,0)){ + d = dNAN; + } + } + if(isnan(d)){ + distances.emplace_back(d); + } + } + + if(_almostEqual(dotB, dotE)){ + distances.emplace_back(crossB-crossE); + } + else if(_almostEqual(dotB, dotF)){ + distances.emplace_back(crossB-crossF); + } + else if(dotB > EFmin && dotB < EFmax){ + auto d = pointDistance(B,E,F,reverse); + + if(!isnan(d) && _almostEqual(d, 0)) + { // crossA>crossB A currently touches EF, but AB is moving away from EF + double dA = pointDistance(A,E,F,reverse,true); + if(dA < 0 || _almostEqual(dA*overlap,0)){ + d = dNAN; + } + } + if(!isnan(d)){ + distances.emplace_back(d); + } + } + + if(dotE > ABmin && dotE < ABmax){ + auto d = pointDistance(E,A,B,direction); + if(!isnan(d) && _almostEqual(d, 0)) + { // crossF ABmin && dotF < ABmax){ + auto d = pointDistance(F,A,B,direction); + if(!isnan(d) && _almostEqual(d, 0)) + { // && crossE 0 || _almostEqual(d, 0)){ + distance = d; + } + } + } + } + return distance; + } + + static double polygonProjectionDistance(const Cntr& A, + const Cntr& B, + Vector direction) + { + auto Boffsetx = B.offsetx; + auto Boffsety = B.offsety; + auto Aoffsetx = A.offsetx; + auto Aoffsety = A.offsety; + + // close the loop for polygons + /*if(A[0] != A[A.length-1]){ + A.push(A[0]); + } + + if(B[0] != B[B.length-1]){ + B.push(B[0]); + }*/ + + auto& edgeA = A; + auto& edgeB = B; + + double distance = dNAN, d; +// Vector p, s1, s2; + + for(size_t i = 0; i < edgeB.size(); i++) { + // the shortest/most negative projection of B onto A + double minprojection = dNAN; + Vector minp; + for(size_t j = 0; j < edgeA.size() - 1; j++){ + Vector p = {x(edgeB[i]) + Boffsetx, y(edgeB[i]) + Boffsety }; + Vector s1 = {x(edgeA[j]) + Aoffsetx, y(edgeA[j]) + Aoffsety }; + Vector s2 = {x(edgeA[j+1]) + Aoffsetx, y(edgeA[j+1]) + Aoffsety }; + + if(abs((s2.y-s1.y) * direction.x - + (s2.x-s1.x) * direction.y) < TOL) continue; + + // project point, ignore edge boundaries + d = pointDistance(p, s1, s2, direction); + + if(!isnan(d) && (isnan(minprojection) || d < minprojection)) { + minprojection = d; + minp = p; + } + } + + if(!isnan(minprojection) && (isnan(distance) || + minprojection > distance)){ + distance = minprojection; + } + } + + return distance; + } + + static std::pair searchStartPoint( + const Cntr& AA, const Cntr& BB, bool inside, const std::vector& NFP = {}) + { + // clone arrays + auto A = AA; + auto B = BB; + +// // close the loop for polygons +// if(A[0] != A[A.size()-1]){ +// A.push(A[0]); +// } + +// if(B[0] != B[B.size()-1]){ +// B.push(B[0]); +// } + + // returns true if point already exists in the given nfp + auto inNfp = [](const Vector& p, const std::vector& nfp){ + if(nfp.empty()){ + return false; + } + + for(size_t i=0; i < nfp.size(); i++){ + for(size_t j = 0; j< nfp[i].size(); j++){ + if(_almostEqual(p.x, nfp[i][j].x) && + _almostEqual(p.y, nfp[i][j].y)){ + return true; + } + } + } + + return false; + }; + + for(size_t i = 0; i < A.size() - 1; i++){ + if(!A[i].marked) { + A[i].marked = true; + for(size_t j = 0; j < B.size(); j++){ + B.offsetx = A[i].x - B[j].x; + B.offsety = A[i].y - B[j].y; + + int Binside = 0; + for(size_t k = 0; k < B.size(); k++){ + int inpoly = pointInPolygon({B[k].x + B.offsetx, B[k].y + B.offsety}, A); + if(inpoly != 0){ + Binside = inpoly; + break; + } + } + + if(Binside == 0){ // A and B are the same + return {false, {}}; + } + + auto startPoint = std::make_pair(true, Vector(B.offsetx, B.offsety)); + if(((Binside && inside) || (!Binside && !inside)) && + !intersect(A,B) && !inNfp(startPoint.second, NFP)){ + return startPoint; + } + + // slide B along vector + auto vx = A[i+1].x - A[i].x; + auto vy = A[i+1].y - A[i].y; + + double d1 = polygonProjectionDistance(A,B,{vx, vy}); + double d2 = polygonProjectionDistance(B,A,{-vx, -vy}); + + double d = dNAN; + + // todo: clean this up + if(isnan(d1) && isnan(d2)){ + // nothin + } + else if(isnan(d1)){ + d = d2; + } + else if(isnan(d2)){ + d = d1; + } + else{ + d = min(d1,d2); + } + + // only slide until no longer negative + // todo: clean this up + if(!isnan(d) && !_almostEqual(d,0) && d > 0){ + + } + else{ + continue; + } + + auto vd2 = vx*vx + vy*vy; + + if(d*d < vd2 && !_almostEqual(d*d, vd2)){ + auto vd = sqrt(vx*vx + vy*vy); + vx *= d/vd; + vy *= d/vd; + } + + B.offsetx += vx; + B.offsety += vy; + + for(size_t k = 0; k < B.size(); k++){ + int inpoly = pointInPolygon({B[k].x + B.offsetx, B[k].y + B.offsety}, A); + if(inpoly != 0){ + Binside = inpoly; + break; + } + } + startPoint = std::make_pair(true, Vector{B.offsetx, B.offsety}); + if(((Binside && inside) || (!Binside && !inside)) && + !intersect(A,B) && !inNfp(startPoint.second, NFP)){ + return startPoint; + } + } + } + } + + return {false, Vector(0, 0)}; + } + + static std::vector noFitPolygon(Cntr A, + Cntr B, + bool inside, + bool searchEdges) + { + if(A.size() < 3 || B.size() < 3) { + throw GeometryException(GeomErr::NFP); + return {}; + } + + A.offsetx = 0; + A.offsety = 0; + + unsigned i = 0, j = 0; + + auto minA = y(A[0]); + long minAindex = 0; + + auto maxB = y(B[0]); + long maxBindex = 0; + + for(i = 1; i < A.size(); i++){ + A[i].marked = false; + if(y(A[i]) < minA){ + minA = y(A[i]); + minAindex = i; + } + } + + for(i = 1; i < B.size(); i++){ + B[i].marked = false; + if(y(B[i]) > maxB){ + maxB = y(B[i]); + maxBindex = i; + } + } + + std::pair startpoint; + + if(!inside){ + // shift B such that the bottom-most point of B is at the top-most + // point of A. This guarantees an initial placement with no + // intersections + startpoint = { true, + { x(A[minAindex]) - x(B[maxBindex]), + y(A[minAindex]) - y(B[maxBindex]) } + }; + } + else { + // no reliable heuristic for inside + startpoint = searchStartPoint(A, B, true); + } + + std::vector NFPlist; + + struct Touch { + int type; + long A, B; + Touch(int t, long a, long b): type(t), A(a), B(b) {} + }; + + while(startpoint.first) { + + B.offsetx = startpoint.second.x; + B.offsety = startpoint.second.y; + + // maintain a list of touching points/edges + std::vector touching; + + Cntr NFP; + NFP.emplace_back(x(B[0]) + B.offsetx, y(B[0]) + B.offsety); + + auto referencex = x(B[0]) + B.offsetx; + auto referencey = y(B[0]) + B.offsety; + auto startx = referencex; + auto starty = referencey; + unsigned counter = 0; + + // sanity check, prevent infinite loop + while(counter < 10*(A.size() + B.size())){ + touching.clear(); + + // find touching vertices/edges + for(i = 0; i < A.size(); i++){ + auto nexti = (i == A.size() - 1) ? 0 : i + 1; + for(j = 0; j < B.size(); j++){ + + auto nextj = (j == B.size() - 1) ? 0 : j + 1; + + if( _almostEqual(A[i].x, B[j].x+B.offsetx) && + _almostEqual(A[i].y, B[j].y+B.offsety)) + { + touching.emplace_back(0, i, j); + } + else if( _onSegment( + A[i], A[nexti], + { B[j].x+B.offsetx, B[j].y + B.offsety}) ) + { + touching.emplace_back(1, nexti, j); + } + else if( _onSegment( + {B[j].x+B.offsetx, B[j].y + B.offsety}, + {B[nextj].x+B.offsetx, B[nextj].y + B.offsety}, + A[i]) ) + { + touching.emplace_back(2, i, nextj); + } + } + } + + struct V { + Coord x, y; + Vector *start, *end; + operator bool() { + return start != nullptr && end != nullptr; + } + operator Vector() const { return {x, y}; } + }; + + // generate translation vectors from touching vertices/edges + std::vector vectors; + for(i=0; i < touching.size(); i++){ + auto& vertexA = A[touching[i].A]; + vertexA.marked = true; + + // adjacent A vertices + auto prevAindex = touching[i].A - 1; + auto nextAindex = touching[i].A + 1; + + prevAindex = (prevAindex < 0) ? A.size() - 1 : prevAindex; // loop + nextAindex = (nextAindex >= A.size()) ? 0 : nextAindex; // loop + + auto& prevA = A[prevAindex]; + auto& nextA = A[nextAindex]; + + // adjacent B vertices + auto& vertexB = B[touching[i].B]; + + auto prevBindex = touching[i].B-1; + auto nextBindex = touching[i].B+1; + + prevBindex = (prevBindex < 0) ? B.size() - 1 : prevBindex; // loop + nextBindex = (nextBindex >= B.size()) ? 0 : nextBindex; // loop + + auto& prevB = B[prevBindex]; + auto& nextB = B[nextBindex]; + + if(touching[i].type == 0){ + + V vA1 = { + prevA.x - vertexA.x, + prevA.y - vertexA.y, + &vertexA, + &prevA + }; + + V vA2 = { + nextA.x - vertexA.x, + nextA.y - vertexA.y, + &vertexA, + &nextA + }; + + // B vectors need to be inverted + V vB1 = { + vertexB.x - prevB.x, + vertexB.y - prevB.y, + &prevB, + &vertexB + }; + + V vB2 = { + vertexB.x - nextB.x, + vertexB.y - nextB.y, + &nextB, + &vertexB + }; + + vectors.emplace_back(vA1); + vectors.emplace_back(vA2); + vectors.emplace_back(vB1); + vectors.emplace_back(vB2); + } + else if(touching[i].type == 1){ + vectors.emplace_back(V{ + vertexA.x-(vertexB.x+B.offsetx), + vertexA.y-(vertexB.y+B.offsety), + &prevA, + &vertexA + }); + + vectors.emplace_back(V{ + prevA.x-(vertexB.x+B.offsetx), + prevA.y-(vertexB.y+B.offsety), + &vertexA, + &prevA + }); + } + else if(touching[i].type == 2){ + vectors.emplace_back(V{ + vertexA.x-(vertexB.x+B.offsetx), + vertexA.y-(vertexB.y+B.offsety), + &prevB, + &vertexB + }); + + vectors.emplace_back(V{ + vertexA.x-(prevB.x+B.offsetx), + vertexA.y-(prevB.y+B.offsety), + &vertexB, + &prevB + }); + } + } + + // TODO: there should be a faster way to reject vectors that + // will cause immediate intersection. For now just check them all + + V translate = {0, 0, nullptr, nullptr}; + V prevvector = {0, 0, nullptr, nullptr}; + double maxd = 0; + + for(i = 0; i < vectors.size(); i++) { + if(vectors[i].x == 0 && vectors[i].y == 0){ + continue; + } + + // if this vector points us back to where we came from, ignore it. + // ie cross product = 0, dot product < 0 + if(prevvector && vectors[i].y * prevvector.y + vectors[i].x * prevvector.x < 0){ + + // compare magnitude with unit vectors + double vectorlength = sqrt(vectors[i].x*vectors[i].x+vectors[i].y*vectors[i].y); + Vector unitv = {Coord(vectors[i].x/vectorlength), + Coord(vectors[i].y/vectorlength)}; + + double prevlength = sqrt(prevvector.x*prevvector.x+prevvector.y*prevvector.y); + Vector prevunit = { prevvector.x/prevlength, prevvector.y/prevlength}; + + // we need to scale down to unit vectors to normalize vector length. Could also just do a tan here + if(abs(unitv.y * prevunit.x - unitv.x * prevunit.y) < 0.0001){ + continue; + } + } + + double d = polygonSlideDistance(A, B, vectors[i], true); + double vecd2 = vectors[i].x*vectors[i].x + vectors[i].y*vectors[i].y; + + if(isnan(d) || d*d > vecd2){ + double vecd = sqrt(vectors[i].x*vectors[i].x + vectors[i].y*vectors[i].y); + d = vecd; + } + + if(!isnan(d) && d > maxd){ + maxd = d; + translate = vectors[i]; + } + } + + if(!translate || _almostEqual(maxd, 0)) + { + // didn't close the loop, something went wrong here + NFP.clear(); + break; + } + + translate.start->marked = true; + translate.end->marked = true; + + prevvector = translate; + + // trim + double vlength2 = translate.x*translate.x + translate.y*translate.y; + if(maxd*maxd < vlength2 && !_almostEqual(maxd*maxd, vlength2)){ + double scale = sqrt((maxd*maxd)/vlength2); + translate.x *= scale; + translate.y *= scale; + } + + referencex += translate.x; + referencey += translate.y; + + if(_almostEqual(referencex, startx) && + _almostEqual(referencey, starty)) { + // we've made a full loop + break; + } + + // if A and B start on a touching horizontal line, + // the end point may not be the start point + bool looped = false; + if(NFP.size() > 0) { + for(i = 0; i < NFP.size() - 1; i++) { + if(_almostEqual(referencex, NFP[i].x) && + _almostEqual(referencey, NFP[i].y)){ + looped = true; + } + } + } + + if(looped){ + // we've made a full loop + break; + } + + NFP.emplace_back(referencex, referencey); + + B.offsetx += translate.x; + B.offsety += translate.y; + + counter++; + } + + if(NFP.size() > 0){ + NFPlist.emplace_back(NFP); + } + + if(!searchEdges){ + // only get outer NFP or first inner NFP + break; + } + + startpoint = + searchStartPoint(A, B, inside, NFPlist); + + } + + return NFPlist; + } +}; + +template const double _alg::TOL = std::pow(10, -9); + +//template +//nfp::NfpResult nfpSimpleSimple(const S& stat, const S& orb) { +//// using Cntr = TContour; +// using Point = TPoint; +//// using Coord = TCoord; +//// using Shapes = nfp::Shapes; + +// namespace sl = shapelike; + +// noFitPolygon(sl::getContour(stat), sl::getContour(orb), true, true); +// return {S(), Point()}; +//} + +} +} + +#endif // NFP_SVGNEST_HPP diff --git a/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp b/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp new file mode 100644 index 000000000..7ceb2d24d --- /dev/null +++ b/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp @@ -0,0 +1,77 @@ +#ifndef NFP_SVGNEST_GLUE_HPP +#define NFP_SVGNEST_GLUE_HPP + +#include "nfp_svgnest.hpp" + +#include + +namespace libnest2d { + +namespace __svgnest { + +//template<> struct _Tol { +// static const BP2D_CONSTEXPR TCoord Value = 1000000; +//}; + +} + +namespace nfp { + +using NfpR = NfpResult; + +template<> struct NfpImpl { + NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { +// return nfpConvexOnly(sh, cother); + namespace sl = shapelike; + using alg = __svgnest::_alg; + + 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 { + NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { +// return nfpConvexOnly(sh, cother); + namespace sl = shapelike; + using alg = __svgnest::_alg; + + 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 { + NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { + namespace sl = shapelike; + using alg = __svgnest::_alg; + + 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 { +// static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::BOTH_CONCAVE; + static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY; +}; + +}} + +#endif // NFP_SVGNEST_GLUE_HPP diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index cc4bfff0f..dcb0da9e5 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -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 @@ -291,6 +293,7 @@ protected: std::vector areacache_; SpatIndex rtree_; double norm_; + Pile pile_cache_; public: _ArrBase(const TBin& bin, Distance dist, @@ -317,23 +320,26 @@ public: std::function progressind): _ArrBase(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(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(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(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);