From d4fe7b5a962b0aff416e8b8929535034fe9ba69b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 6 Jun 2019 14:27:07 +0200 Subject: [PATCH 1/3] Adding rotating calipers algorithm for minimum are bounding box rotation. Cleanup, fix build on windows and add test for rotcalipers. Try to fix compilation on windows With updates from libnest2d Another build fix. Clean up and add comments. adding rotcalipers test and some cleanup Trying to fix on OSX Fix rotcalipers array indexing Get rid of boost convex hull. Adding helper function 'remove_collinear_points' Importing new libnest2d upgrades. Disable using __int128 in NFP on OSX --- src/libnest2d/CMakeLists.txt | 16 +- src/libnest2d/include/libnest2d.h | 125 ++--- .../libnest2d/backends/clipper/CMakeLists.txt | 13 +- .../backends/clipper/clipper_polygon.hpp | 8 +- .../libnest2d/backends/clipper/geometries.hpp | 117 +--- src/libnest2d/include/libnest2d/common.hpp | 28 + .../include/libnest2d/geometry_traits.hpp | 408 ++++++++++---- .../include/libnest2d/geometry_traits_nfp.hpp | 527 +++--------------- src/libnest2d/include/libnest2d/libnest2d.hpp | 15 +- src/libnest2d/include/libnest2d/optimizer.hpp | 4 +- .../libnest2d/optimizers/nlopt/CMakeLists.txt | 6 +- .../optimizers/optimlib/CMakeLists.txt | 5 - .../libnest2d/placers/bottomleftplacer.hpp | 8 +- .../include/libnest2d/placers/nfpplacer.hpp | 13 +- .../include/libnest2d/utils/boost_alg.hpp | 37 +- .../include/libnest2d/utils/rotcalipers.hpp | 268 +++++++++ src/libnest2d/src/libnest2d.cpp | 23 + src/libnest2d/tests/CMakeLists.txt | 9 +- src/libnest2d/tests/test.cpp | 205 ++++++- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Int128.hpp | 6 + src/libslic3r/MinAreaBoundingBox.cpp | 142 +++++ src/libslic3r/MinAreaBoundingBox.hpp | 59 ++ src/libslic3r/ModelArrange.cpp | 31 +- src/slic3r/GUI/Plater.cpp | 53 +- 25 files changed, 1272 insertions(+), 856 deletions(-) delete mode 100644 src/libnest2d/include/libnest2d/optimizers/optimlib/CMakeLists.txt create mode 100644 src/libnest2d/include/libnest2d/utils/rotcalipers.hpp create mode 100644 src/libnest2d/src/libnest2d.cpp create mode 100644 src/libslic3r/MinAreaBoundingBox.cpp create mode 100644 src/libslic3r/MinAreaBoundingBox.hpp diff --git a/src/libnest2d/CMakeLists.txt b/src/libnest2d/CMakeLists.txt index 2508c984a..d50c78b17 100644 --- a/src/libnest2d/CMakeLists.txt +++ b/src/libnest2d/CMakeLists.txt @@ -48,6 +48,9 @@ set(LIBNEST2D_SRCFILES ${SRC_DIR}/libnest2d/optimizer.hpp ${SRC_DIR}/libnest2d/utils/metaloop.hpp ${SRC_DIR}/libnest2d/utils/rotfinder.hpp + ${SRC_DIR}/libnest2d/utils/rotcalipers.hpp + ${SRC_DIR}/libnest2d/utils/bigint.hpp + ${SRC_DIR}/libnest2d/utils/rational.hpp ${SRC_DIR}/libnest2d/placers/placer_boilerplate.hpp ${SRC_DIR}/libnest2d/placers/bottomleftplacer.hpp ${SRC_DIR}/libnest2d/placers/nfpplacer.hpp @@ -70,12 +73,10 @@ if(TBB_FOUND) # The Intel TBB library will use the std::exception_ptr feature of C++11. target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0) - target_link_libraries(libnest2d INTERFACE tbb) - # The following breaks compilation on Visual Studio in Debug mode. - #find_package(Threads REQUIRED) - #target_link_libraries(libnest2d INTERFACE ${TBB_LIBRARIES} ${CMAKE_DL_LIBS} - # Threads::Threads - # ) + find_package(Threads REQUIRED) + target_link_libraries(libnest2d INTERFACE ${TBB_LIBRARIES} ${CMAKE_DL_LIBS} + Threads::Threads + ) else() find_package(OpenMP QUIET) @@ -92,10 +93,11 @@ endif() add_subdirectory(${SRC_DIR}/libnest2d/backends/${LIBNEST2D_GEOMETRIES}) target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_GEOMETRIES}Backend) + add_subdirectory(${SRC_DIR}/libnest2d/optimizers/${LIBNEST2D_OPTIMIZER}) target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_OPTIMIZER}Optimizer) -#target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES}) +# target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES}) target_include_directories(libnest2d INTERFACE ${SRC_DIR}) if(NOT LIBNEST2D_HEADER_ONLY) diff --git a/src/libnest2d/include/libnest2d.h b/src/libnest2d/include/libnest2d.h index 4ad752421..a6eb36a4b 100644 --- a/src/libnest2d/include/libnest2d.h +++ b/src/libnest2d/include/libnest2d.h @@ -47,6 +47,17 @@ using NfpPlacer = _NfpPlacer; // This supports only box shaped bins using BottomLeftPlacer = placers::_BottomLeftPlacer; +#ifdef LIBNEST2D_STATIC + +extern template class Nester; +extern template class Nester; +extern template PackGroup Nester::execute( + std::vector::iterator, std::vector::iterator); +extern template PackGroup Nester::execute( + std::vector::iterator, std::vector::iterator); + +#endif + template::iterator> @@ -60,19 +71,6 @@ PackGroup nest(Iterator from, Iterator to, return nester.execute(from, to); } -template> -PackGroup nest(Container&& cont, - const typename Placer::BinType& bin, - Coord dist = 0, - const typename Placer::Config& pconf = {}, - const typename Selector::Config& sconf = {}) -{ - return nest(cont.begin(), cont.end(), - bin, dist, pconf, sconf); -} - template::iterator> @@ -90,6 +88,42 @@ PackGroup nest(Iterator from, Iterator to, return nester.execute(from, to); } +#ifdef LIBNEST2D_STATIC + +extern template class Nester; +extern template class Nester; + +extern template PackGroup nest(std::vector::iterator from, + std::vector::iterator to, + const Box& bin, + Coord dist = 0, + const NfpPlacer::Config& pconf, + const FirstFitSelection::Config& sconf); + +extern template PackGroup nest(std::vector::iterator from, + std::vector::iterator to, + const Box& bin, + ProgressFunction prg, + StopCondition scond, + Coord dist = 0, + const NfpPlacer::Config& pconf, + const FirstFitSelection::Config& sconf); + +#endif + +template> +PackGroup nest(Container&& cont, + const typename Placer::BinType& bin, + Coord dist = 0, + const typename Placer::Config& pconf = {}, + const typename Selector::Config& sconf = {}) +{ + return nest(cont.begin(), cont.end(), + bin, dist, pconf, sconf); +} + template> @@ -105,71 +139,6 @@ PackGroup nest(Container&& cont, bin, prg, scond, dist, pconf, sconf); } -#ifdef LIBNEST2D_STATIC -extern template -PackGroup nest&>( - std::vector& cont, - const Box& bin, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); - -extern template -PackGroup nest&>( - std::vector& cont, - const Box& bin, - ProgressFunction prg, - StopCondition scond, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); - -extern template -PackGroup nest>( - std::vector&& cont, - const Box& bin, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); - -extern template -PackGroup nest>( - std::vector&& cont, - const Box& bin, - ProgressFunction prg, - StopCondition scond, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); - -extern template -PackGroup nest::iterator>( - std::vector::iterator from, - std::vector::iterator to, - const Box& bin, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); - -extern template -PackGroup nest::iterator>( - std::vector::iterator from, - std::vector::iterator to, - const Box& bin, - ProgressFunction prg, - StopCondition scond, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); - -#endif - } #endif // LIBNEST2D_H diff --git a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt index e20cbc70d..e207ecde4 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt +++ b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt @@ -33,19 +33,18 @@ if(NOT TARGET clipper) # If there is a clipper target in the parent project we a # ${clipper_library_BINARY_DIR} # ) - add_library(ClipperBackend STATIC + add_library(clipperBackend STATIC ${clipper_library_SOURCE_DIR}/clipper.cpp ${clipper_library_SOURCE_DIR}/clipper.hpp) - target_include_directories(ClipperBackend INTERFACE - ${clipper_library_SOURCE_DIR}) + target_include_directories(clipperBackend INTERFACE ${clipper_library_SOURCE_DIR}) else() message(FATAL_ERROR "Can't find clipper library and no SVN client found to download. You can download the clipper sources and define a clipper target in your project, that will work for libnest2d.") endif() else() - add_library(ClipperBackend INTERFACE) - target_link_libraries(ClipperBackend INTERFACE Clipper::Clipper) + add_library(clipperBackend INTERFACE) + target_link_libraries(clipperBackend INTERFACE Clipper::Clipper) endif() else() # set(CLIPPER_INCLUDE_DIRS "" PARENT_SCOPE) @@ -69,6 +68,6 @@ target_include_directories(clipperBackend SYSTEM INTERFACE ${Boost_INCLUDE_DIRS} target_compile_definitions(clipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER) -# And finally plug the ClipperBackend into libnest2d -#target_link_libraries(libnest2d INTERFACE ClipperBackend) +# And finally plug the clipperBackend into libnest2d +# target_link_libraries(libnest2d INTERFACE clipperBackend) diff --git a/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp b/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp index e9fbfbd18..6511fbb72 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp @@ -12,13 +12,13 @@ struct Polygon { inline Polygon() = default; inline explicit Polygon(const Path& cont): Contour(cont) {} - inline explicit Polygon(const Paths& holes): - Holes(holes) {} +// inline explicit Polygon(const Paths& holes): +// Holes(holes) {} inline Polygon(const Path& cont, const Paths& holes): Contour(cont), Holes(holes) {} inline explicit Polygon(Path&& cont): Contour(std::move(cont)) {} - inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {} +// inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {} inline Polygon(Path&& cont, Paths&& holes): Contour(std::move(cont)), Holes(std::move(holes)) {} }; @@ -42,7 +42,7 @@ inline IntPoint& operator -=(IntPoint& p, const IntPoint& pa ) { return p; } -inline IntPoint operator -(IntPoint& p ) { +inline IntPoint operator -(const IntPoint& p ) { IntPoint ret = p; ret.X = -ret.X; ret.Y = -ret.Y; diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp index 232668f61..e9fad405b 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp @@ -20,43 +20,23 @@ using PathImpl = ClipperLib::Path; using HoleStore = ClipperLib::Paths; using PolygonImpl = ClipperLib::Polygon; -// Type of coordinate units used by Clipper -template<> struct CoordType { - using Type = ClipperLib::cInt; -}; - -// Type of point used by Clipper -template<> struct PointType { - using Type = PointImpl; -}; - -template<> struct PointType { - using Type = PointImpl; -}; - -template<> struct PointType { - using Type = PointImpl; -}; - -template<> struct CountourType { - using Type = PathImpl; -}; - template<> struct ShapeTag { using Type = PolygonTag; }; -template<> struct ShapeTag { using Type = PathTag; }; -template<> struct ShapeTag { using Type = PointTag; }; +template<> struct ShapeTag { using Type = PathTag; }; +template<> struct ShapeTag { using Type = PointTag; }; -template<> struct ShapeTag> { - using Type = MultiPolygonTag; -}; +// Type of coordinate units used by Clipper. Enough to specialize for point, +// the rest of the types will work (Path, Polygon) +template<> struct CoordType { using Type = ClipperLib::cInt; }; -template<> struct PointType> { - using Type = PointImpl; -}; +// Enough to specialize for path, it will work for multishape and Polygon +template<> struct PointType { using Type = PointImpl; }; -template<> struct HolesContainer { - using Type = ClipperLib::Paths; -}; +// This is crucial. CountourType refers to itself by default, so we don't have +// to secialize for clipper Path. ContourType::Type is PathImpl. +template<> struct ContourType { using Type = PathImpl; }; + +// The holes are contained in Clipper::Paths +template<> struct HolesContainer { using Type = ClipperLib::Paths; }; namespace pointlike { @@ -86,39 +66,11 @@ template<> inline TCoord& y(PointImpl& p) } +// Using the libnest2d default area implementation #define DISABLE_BOOST_AREA -namespace _smartarea { - -template -inline double area(const PolygonImpl& /*sh*/) { - return std::nan(""); -} - -template<> -inline double area(const PolygonImpl& sh) { - return std::accumulate(sh.Holes.begin(), sh.Holes.end(), - ClipperLib::Area(sh.Contour), - [](double a, const ClipperLib::Path& pt){ - return a + ClipperLib::Area(pt); - }); -} - -template<> -inline double area(const PolygonImpl& sh) { - return -area(sh); -} - -} - namespace shapelike { -// 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); -} - template<> inline void offset(PolygonImpl& sh, TCoord distance) { #define DISABLE_BOOST_OFFSET @@ -200,43 +152,16 @@ inline PolygonImpl create(const PathImpl& path, const HoleStore& holes) { PolygonImpl p; p.Contour = path; - - // Expecting that the coordinate system Y axis is positive in upwards - // direction - if(ClipperLib::Orientation(p.Contour)) { - // Not clockwise then reverse the b*tch - ClipperLib::ReversePath(p.Contour); - } - p.Holes = holes; - for(auto& h : p.Holes) { - if(!ClipperLib::Orientation(h)) { - ClipperLib::ReversePath(h); - } - } - + return p; } template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) { PolygonImpl p; p.Contour.swap(path); - - // Expecting that the coordinate system Y axis is positive in upwards - // direction - if(ClipperLib::Orientation(p.Contour)) { - // Not clockwise then reverse the b*tch - ClipperLib::ReversePath(p.Contour); - } - p.Holes.swap(holes); - - for(auto& h : p.Holes) { - if(!ClipperLib::Orientation(h)) { - ClipperLib::ReversePath(h); - } - } - + return p; } @@ -314,13 +239,13 @@ inline void rotate(PolygonImpl& sh, const Radians& rads) } // namespace shapelike #define DISABLE_BOOST_NFP_MERGE -inline std::vector clipper_execute( +inline TMultiShape clipper_execute( ClipperLib::Clipper& clipper, ClipperLib::ClipType clipType, ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd, ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd) { - shapelike::Shapes retv; + TMultiShape retv; ClipperLib::PolyTree result; clipper.Execute(clipType, result, subjFillType, clipFillType); @@ -370,8 +295,8 @@ inline std::vector clipper_execute( namespace nfp { -template<> inline std::vector -merge(const std::vector& shapes) +template<> inline TMultiShape +merge(const TMultiShape& shapes) { ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); @@ -394,6 +319,8 @@ merge(const std::vector& shapes) } +#define DISABLE_BOOST_CONVEX_HULL + //#define DISABLE_BOOST_SERIALIZE //#define DISABLE_BOOST_UNSERIALIZE diff --git a/src/libnest2d/include/libnest2d/common.hpp b/src/libnest2d/include/libnest2d/common.hpp index 6867f76f3..66e095ae2 100644 --- a/src/libnest2d/include/libnest2d/common.hpp +++ b/src/libnest2d/include/libnest2d/common.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L #define BP2D_NOEXCEPT @@ -197,6 +198,33 @@ public: } }; +struct ScalarTag {}; +struct BigIntTag {}; +struct RationalTag {}; + +template struct _NumTag { + using Type = + enable_if_t::value, ScalarTag>; +}; + +template using NumTag = typename _NumTag>::Type; + +/// A local version for abs that is garanteed to work with libnest2d types +template inline T abs(const T& v, ScalarTag) +{ + return std::abs(v); +} + +template inline T abs(const T& v) { return abs(v, NumTag()); } + +template inline T2 cast(const T1& v, ScalarTag, ScalarTag) +{ + return static_cast(v); +} + +template inline T2 cast(const T1& v) { + return cast(v, NumTag(), NumTag()); +} } #endif // LIBNEST2D_CONFIG_HPP diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 917f5280d..6c55d0e3f 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -7,45 +7,125 @@ #include #include #include -#include #include #include +#include -#include "common.hpp" +#include namespace libnest2d { +// Meta tags for different geometry concepts. +struct PointTag {}; +struct PolygonTag {}; +struct PathTag {}; +struct MultiPolygonTag {}; +struct BoxTag {}; +struct CircleTag {}; + +/// Meta-function to derive the tag of a shape type. +template struct ShapeTag { using Type = typename Shape::Tag; }; + +/// Tag will be used instead of `typename ShapeTag::Type` +template using Tag = typename ShapeTag>::Type; + +/// Meta function to derive the contour type for a polygon which could be itself +template struct ContourType { using Type = RawShape; }; + +/// TContour instead of `typename ContourType::type` +template +using TContour = typename ContourType>::Type; + +/// Getting the type of point structure used by a shape. +template struct PointType { + using Type = typename PointType>::Type; +}; + +/// TPoint as shorthand for `typename PointType::Type`. +template +using TPoint = typename PointType>::Type; + /// Getting the coordinate data type for a geometry class. -template struct CoordType { using Type = long; }; +template struct CoordType { + using Type = typename CoordType>::Type; +}; /// TCoord as shorthand for typename `CoordType::Type`. template using TCoord = typename CoordType>::Type; -/// Getting the type of point structure used by a shape. -template struct PointType { using Type = typename Sh::PointType; }; +/// Getting the computation type for a certain geometry type. +/// It is the coordinate type by default but it is advised that a type with +/// larger precision and (or) range is specified. +template::value> struct ComputeType {}; -/// TPoint as shorthand for `typename PointType::Type`. -template -using TPoint = typename PointType>::Type; +/// A compute type is introduced to hold the results of computations on +/// coordinates and points. It should be larger in range than the coordinate +/// type or the range of coordinates should be limited to not loose precision. +template struct ComputeType { + using Type = typename ComputeType>::Type; +}; +/// libnest2d will choose a default compute type for various coordinate types +/// if the backend has not specified anything. +template struct DoublePrecision { using Type = T; }; +template<> struct DoublePrecision { using Type = int16_t; }; +template<> struct DoublePrecision { using Type = int32_t; }; +template<> struct DoublePrecision { using Type = int64_t; }; +template<> struct DoublePrecision { using Type = double; }; +template<> struct DoublePrecision { using Type = long double; }; +template struct ComputeType { + using Type = typename DoublePrecision::Type; +}; -template struct CountourType { using Type = RawShape; }; - -template -using TContour = typename CountourType>::Type; - +/// TCompute shorthand for `typename ComputeType::Type` +template using TCompute = typename ComputeType>::Type; +/// A meta function to derive a container type for holes in a polygon template struct HolesContainer { using Type = std::vector>; }; +/// Shorthand for `typename HolesContainer::Type` template using THolesContainer = typename HolesContainer>::Type; +/* + * TContour, TPoint, TCoord and TCompute should be usable for any type for which + * it makes sense. For example, the point type could be derived from the contour, + * the polygon and (or) the multishape as well. The coordinate type also and + * including the point type. TCoord, TCoord, TCoord are + * all valid types and derives the coordinate type of template argument Polygon, + * Path and Point. This is also true for TCompute, but it can also take the + * coordinate type as argument. + */ -template -struct LastPointIsFirst { static const bool Value = true; }; +/* + * A Multi shape concept is also introduced. A multi shape is something that + * can contain the result of an operation where the input is one polygon and + * the result could be many polygons or path -> paths. The MultiShape should be + * a container type. If the backend does not specialize the MultiShape template, + * a default multi shape container will be used. + */ + +/// The default multi shape container. +template struct DefaultMultiShape: public std::vector { + using Tag = MultiPolygonTag; + template DefaultMultiShape(Args&&...args): + std::vector(std::forward(args)...) {} +}; + +/// The MultiShape Type trait which gets the container type for a geometry type. +template struct MultiShape { using Type = DefaultMultiShape; }; + +/// use TMultiShape instead of `typename MultiShape::Type` +template +using TMultiShape = typename MultiShape>::Type; + +// A specialization of ContourType to work with the default multishape type +template struct ContourType> { + using Type = typename ContourType::Type; +}; enum class Orientation { CLOCKWISE, @@ -59,6 +139,11 @@ struct OrientationType { static const Orientation Value = Orientation::CLOCKWISE; }; +template inline /*constexpr*/ bool is_clockwise() { + return OrientationType>::Value == Orientation::CLOCKWISE; +} + + /** * \brief A point pair base class for other point pairs (segment, box, ...). * \tparam RawPoint The actual point type to use. @@ -69,21 +154,6 @@ struct PointPair { RawPoint p2; }; -struct PointTag {}; -struct PolygonTag {}; -struct PathTag {}; -struct MultiPolygonTag {}; -struct BoxTag {}; -struct CircleTag {}; - -/// Meta-functions to derive the tags -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; */ @@ -114,11 +184,16 @@ public: inline RawPoint center() const BP2D_NOEXCEPT; - inline double area() const BP2D_NOEXCEPT { - return double(width()*height()); + template> + inline Unit area() const BP2D_NOEXCEPT { + return Unit(width())*height(); } }; +template struct PointType<_Box> { + using Type = typename _Box::PointType; +}; + template class _Circle { RawPoint center_; @@ -129,7 +204,6 @@ public: using PointType = RawPoint; _Circle() = default; - _Circle(const RawPoint& center, double r): center_(center), radius_(r) {} inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; } @@ -137,12 +211,16 @@ public: inline double radius() const BP2D_NOEXCEPT { return radius_; } inline void radius(double r) { radius_ = r; } - + inline double area() const BP2D_NOEXCEPT { - return 2.0*Pi*radius_*radius_; + return Pi_2 * radius_ * radius_; } }; +template struct PointType<_Circle> { + using Type = typename _Circle::PointType; +}; + /** * \brief An abstraction of a directed line segment with two points. */ @@ -185,7 +263,12 @@ public: inline Radians angleToXaxis() const; /// The length of the segment in the measure of the coordinate system. - inline double length(); + template> inline Unit sqlength() const; + +}; + +template struct PointType<_Segment> { + using Type = typename _Circle::PointType; }; // This struct serves almost as a namespace. The only difference is that is can @@ -216,33 +299,56 @@ inline TCoord& y(RawPoint& p) return p.y(); } -template -inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/) +template> +inline Unit squaredDistance(const RawPoint& p1, const RawPoint& p2) { - static_assert(always_false::value, - "PointLike::distance(point, point) unimplemented!"); - return 0; + auto x1 = Unit(x(p1)), y1 = Unit(y(p1)), x2 = Unit(x(p2)), y2 = Unit(y(p2)); + Unit a = (x2 - x1), b = (y2 - y1); + return a * a + b * b; } template -inline double distance(const RawPoint& /*p1*/, - const _Segment& /*s*/) +inline double distance(const RawPoint& p1, const RawPoint& p2) { - static_assert(always_false::value, - "PointLike::distance(point, segment) unimplemented!"); - return 0; + return std::sqrt(squaredDistance(p1, p2)); } -template -inline std::pair, bool> horizontalDistance( +// create perpendicular vector +template inline Pt perp(const Pt& p) +{ + return Pt(y(p), -x(p)); +} + +template> +inline Unit dotperp(const Pt& a, const Pt& b) +{ + return Unit(x(a)) * Unit(y(b)) - Unit(y(a)) * Unit(x(b)); +} + +// dot product +template> +inline Unit dot(const Pt& a, const Pt& b) +{ + return Unit(x(a)) * x(b) + Unit(y(a)) * y(b); +} + +// squared vector magnitude +template> +inline Unit magnsq(const Pt& p) +{ + return Unit(x(p)) * x(p) + Unit(y(p)) * y(p); +} + +template> +inline std::pair horizontalDistance( const RawPoint& p, const _Segment& s) { - using Unit = TCoord; - auto x = pointlike::x(p), y = pointlike::y(p); - auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); - auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); + namespace pl = pointlike; + auto x = Unit(pl::x(p)), y = Unit(pl::y(p)); + auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first())); + auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second())); - TCoord ret; + Unit ret; if( (y < y1 && y < y2) || (y > y1 && y > y2) ) return {0, false}; @@ -250,8 +356,7 @@ inline std::pair, bool> horizontalDistance( ret = std::min( x-x1, x -x2); else if( (y == y1 && y == y2) && (x < x1 && x < x2)) ret = -std::min(x1 - x, x2 - x); - else if(std::abs(y - y1) <= std::numeric_limits::epsilon() && - std::abs(y - y2) <= std::numeric_limits::epsilon()) + else if(y == y1 && y == y2) ret = 0; else ret = x - x1 + (x1 - x2)*(y1 - y)/(y1 - y2); @@ -259,16 +364,16 @@ inline std::pair, bool> horizontalDistance( return {ret, true}; } -template -inline std::pair, bool> verticalDistance( +template> +inline std::pair verticalDistance( const RawPoint& p, const _Segment& s) { - using Unit = TCoord; - auto x = pointlike::x(p), y = pointlike::y(p); - auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); - auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); + namespace pl = pointlike; + auto x = Unit(pl::x(p)), y = Unit(pl::y(p)); + auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first())); + auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second())); - TCoord ret; + Unit ret; if( (x < x1 && x < x2) || (x > x1 && x > x2) ) return {0, false}; @@ -276,8 +381,7 @@ inline std::pair, bool> verticalDistance( ret = std::min( y-y1, y -y2); else if( (x == x1 && x == x2) && (y < y1 && y < y2)) ret = -std::min(y1 - y, y2 - y); - else if(std::abs(x - x1) <= std::numeric_limits::epsilon() && - std::abs(x - x2) <= std::numeric_limits::epsilon()) + else if(x == x1 && x == x2) ret = 0; else ret = y - y1 + (y1 - y2)*(x1 - x)/(x1 - x2); @@ -333,9 +437,10 @@ inline Radians _Segment::angleToXaxis() const } template -inline double _Segment::length() +template +inline Unit _Segment::sqlength() const { - return pointlike::distance(first(), second()); + return pointlike::squaredDistance(first(), second()); } template @@ -346,8 +451,8 @@ inline RawPoint _Box::center() const BP2D_NOEXCEPT { using Coord = TCoord; RawPoint ret = { // No rounding here, we dont know if these are int coords - static_cast( (getX(minc) + getX(maxc))/2.0 ), - static_cast( (getY(minc) + getY(maxc))/2.0 ) + Coord( (getX(minc) + getX(maxc)) / Coord(2) ), + Coord( (getY(minc) + getY(maxc)) / Coord(2) ) }; return ret; @@ -362,9 +467,6 @@ enum class Formats { // used in friend declarations and can be aliased at class scope. namespace shapelike { -template -using Shapes = TMultiShape; - template inline RawShape create(const TContour& contour, const THolesContainer& holes) @@ -449,7 +551,7 @@ inline void reserve(RawPath& p, size_t vertex_capacity, const PathTag&) template inline void addVertex(RawShape& sh, const PathTag&, Args...args) { - return sh.emplace_back(std::forward(args)...); + sh.emplace_back(std::forward(args)...); } template @@ -504,13 +606,8 @@ inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/) "shapelike::unserialize() unimplemented!"); } -template -inline double area(const RawShape& /*sh*/, const PolygonTag&) -{ - static_assert(always_false::value, - "shapelike::area() unimplemented!"); - return 0; -} +template +inline Unit area(const Cntr& poly, const PathTag& ); template inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) @@ -556,14 +653,14 @@ inline bool touches( const TPoint& /*point*/, template inline _Box> boundingBox(const RawShape& /*sh*/, - const PolygonTag&) + const PathTag&) { static_assert(always_false::value, "shapelike::boundingBox(shape) unimplemented!"); } template -inline _Box> +inline _Box> boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) { static_assert(always_false::value, @@ -571,21 +668,10 @@ boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) } template -inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&) -{ - static_assert(always_false::value, - "shapelike::convexHull(shape) unimplemented!"); - return RawShape(); -} +inline RawShape convexHull(const RawShape& sh, const PathTag&); -template -inline typename RawShapes::value_type -convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&) -{ - static_assert(always_false::value, - "shapelike::convexHull(shapes) unimplemented!"); - return typename RawShapes::value_type(); -} +template +inline S convexHull(const RawShapes& sh, const MultiPolygonTag&); template inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/) @@ -745,13 +831,19 @@ inline void reserve(T& sh, size_t vertex_capacity) { template inline void addVertex(RawShape& sh, const PolygonTag&, Args...args) { - return addVertex(contour(sh), PathTag(), std::forward(args)...); + addVertex(contour(sh), PathTag(), std::forward(args)...); } template // Tag dispatcher inline void addVertex(RawShape& sh, Args...args) { - return addVertex(sh, Tag(), std::forward(args)...); + addVertex(sh, Tag(), std::forward(args)...); +} + +template +inline _Box> boundingBox(const RawShape& poly, const PolygonTag&) +{ + return boundingBox(contour(poly), PathTag()); } template @@ -786,7 +878,7 @@ inline _Box> boundingBox(const S& sh) template inline double area(const Box& box, const BoxTag& ) { - return box.area(); + return box.template area(); } template @@ -795,6 +887,35 @@ inline double area(const Circle& circ, const CircleTag& ) return circ.area(); } +template +inline Unit area(const Cntr& poly, const PathTag& ) +{ + namespace sl = shapelike; + if (sl::cend(poly) - sl::cbegin(poly) < 3) return 0.0; + + Unit a = 0; + for (auto i = sl::cbegin(poly), j = std::prev(sl::cend(poly)); + i < sl::cend(poly); ++i) + { + auto xj = Unit(getX(*j)), yj = Unit(getY(*j)); + auto xi = Unit(getX(*i)), yi = Unit(getY(*i)); + a += (xj + xi) * (yj - yi); + j = i; + } + a /= 2; + return is_clockwise() ? a : -a; +} + +template inline double area(const S& poly, const PolygonTag& ) +{ + auto hls = holes(poly); + return std::accumulate(hls.begin(), hls.end(), + area(contour(poly), PathTag()), + [](double a, const TContour &h){ + return a + area(h, PathTag()); + }); +} + template // Dispatching function inline double area(const RawShape& sh) { @@ -811,6 +932,12 @@ inline double area(const RawShapes& shapes, const MultiPolygonTag&) }); } +template +inline RawShape convexHull(const RawShape& sh, const PolygonTag&) +{ + return create(convexHull(contour(sh), PathTag())); +} + template inline auto convexHull(const RawShape& sh) -> decltype(convexHull(sh, Tag())) // TODO: C++14 could deduce @@ -818,11 +945,91 @@ inline auto convexHull(const RawShape& sh) return convexHull(sh, Tag()); } +template +inline RawShape convexHull(const RawShape& sh, const PathTag&) +{ + using Unit = TCompute; + using Point = TPoint; + namespace sl = shapelike; + + size_t edges = sl::cend(sh) - sl::cbegin(sh); + if(edges <= 3) return {}; + + bool closed = false; + std::vector U, L; + U.reserve(1 + edges / 2); L.reserve(1 + edges / 2); + + std::vector pts; pts.reserve(edges); + std::copy(sl::cbegin(sh), sl::cend(sh), std::back_inserter(pts)); + + auto fpt = pts.front(), lpt = pts.back(); + if(getX(fpt) == getX(lpt) && getY(fpt) == getY(lpt)) { + closed = true; pts.pop_back(); + } + + std::sort(pts.begin(), pts.end(), + [](const Point& v1, const Point& v2) + { + Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2); + return x1 == x2 ? y1 < y2 : x1 < x2; + }); + + auto dir = [](const Point& p, const Point& q, const Point& r) { + return (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) - + (Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p)); + }; + + auto ik = pts.begin(); + + while(ik != pts.end()) { + + while(U.size() > 1 && dir(U[U.size() - 2], U.back(), *ik) <= 0) + U.pop_back(); + while(L.size() > 1 && dir(L[L.size() - 2], L.back(), *ik) >= 0) + L.pop_back(); + + U.emplace_back(*ik); + L.emplace_back(*ik); + + ++ik; + } + + RawShape ret; reserve(ret, U.size() + L.size()); + if(is_clockwise()) { + for(auto it = U.begin(); it != std::prev(U.end()); ++it) + addVertex(ret, *it); + for(auto it = L.rbegin(); it != std::prev(L.rend()); ++it) + addVertex(ret, *it); + if(closed) addVertex(ret, *std::prev(L.rend())); + } else { + for(auto it = L.begin(); it != std::prev(L.end()); ++it) + addVertex(ret, *it); + for(auto it = U.rbegin(); it != std::prev(U.rend()); ++it) + addVertex(ret, *it); + if(closed) addVertex(ret, *std::prev(U.rend())); + } + + return ret; +} + +template +inline S convexHull(const RawShapes& sh, const MultiPolygonTag&) +{ + namespace sl = shapelike; + S cntr; + for(auto& poly : sh) + for(auto it = sl::cbegin(poly); it != sl::cend(poly); ++it) + addVertex(cntr, *it); + + return convexHull(cntr, Tag()); +} + template inline bool isInside(const TP& point, const TC& circ, const PointTag&, const CircleTag&) { - return pointlike::distance(point, circ.center()) < circ.radius(); + auto r = circ.radius(); + return pointlike::squaredDistance(point, circ.center()) < r * r; } template @@ -972,6 +1179,9 @@ template inline bool isConvex(const RawShape& sh) // dispatch using Segment = _Segment; \ using Polygons = TMultiShape +namespace sl = shapelike; +namespace pl = pointlike; + } #endif // GEOMETRY_TRAITS_HPP diff --git a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp index cb0580ef4..4a2c69bca 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp @@ -1,26 +1,22 @@ #ifndef GEOMETRIES_NOFITPOLYGON_HPP #define GEOMETRIES_NOFITPOLYGON_HPP -#include "geometry_traits.hpp" #include #include #include #include +#include + namespace libnest2d { namespace __nfp { // Do not specialize this... -template +template> inline bool _vsort(const TPoint& v1, const TPoint& v2) { - using Coord = TCoord>; - Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2); - auto diff = y1 - y2; - if(std::abs(diff) <= std::numeric_limits::epsilon()) - return x1 < x2; - - return diff < 0; + Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2); + return y1 == y2 ? x1 < x2 : y1 < y2; } template> @@ -202,7 +198,7 @@ inline TPoint referenceVertex(const RawShape& sh) * convex as well in this case. * */ -template +template inline NfpResult nfpConvexOnly(const RawShape& sh, const RawShape& other) { @@ -238,12 +234,62 @@ inline NfpResult nfpConvexOnly(const RawShape& sh, ++first; ++next; } } - - // Sort the edges by angle to X axis. - std::sort(edgelist.begin(), edgelist.end(), - [](const Edge& e1, const Edge& e2) + + std::sort(edgelist.begin(), edgelist.end(), + [](const Edge& e1, const Edge& e2) { - return e1.angleToXaxis() > e2.angleToXaxis(); + Vertex ax(1, 0); // Unit vector for the X axis + + // get cectors from the edges + Vertex p1 = e1.second() - e1.first(); + Vertex p2 = e2.second() - e2.first(); + + // Quadrant mapping array. The quadrant of a vector can be determined + // from the dot product of the vector and its perpendicular pair + // with the unit vector X axis. The products will carry the values + // lcos = dot(p, ax) = l * cos(phi) and + // lsin = -dotperp(p, ax) = l * sin(phi) where + // l is the length of vector p. From the signs of these values we can + // construct an index which has the sign of lcos as MSB and the + // sign of lsin as LSB. This index can be used to retrieve the actual + // quadrant where vector p resides using the following map: + // (+ is 0, - is 1) + // cos | sin | decimal | quadrant + // + | + | 0 | 0 + // + | - | 1 | 3 + // - | + | 2 | 1 + // - | - | 3 | 2 + std::array quadrants {0, 3, 1, 2 }; + + std::array q {0, 0}; // Quadrant indices for p1 and p2 + + using TDots = std::array, 2>; + TDots lcos { pl::dot(p1, ax), pl::dot(p2, ax) }; + TDots lsin { -pl::dotperp(p1, ax), -pl::dotperp(p2, ax) }; + + // Construct the quadrant indices for p1 and p2 + for(size_t i = 0; i < 2; ++i) + if(lcos[i] == 0) q[i] = lsin[i] > 0 ? 1 : 3; + else if(lsin[i] == 0) q[i] = lcos[i] > 0 ? 0 : 2; + else q[i] = quadrants[((lcos[i] < 0) << 1) + (lsin[i] < 0)]; + + if(q[0] == q[1]) { // only bother if p1 and p2 are in the same quadrant + auto lsq1 = pl::magnsq(p1); // squared magnitudes, avoid sqrt + auto lsq2 = pl::magnsq(p2); // squared magnitudes, avoid sqrt + + // We will actually compare l^2 * cos^2(phi) which saturates the + // cos function. But with the quadrant info we can get the sign back + int sign = q[0] == 1 || q[0] == 2 ? -1 : 1; + + // If Ratio is an actual rational type, there is no precision loss + auto pcos1 = Ratio(lcos[0]) / lsq1 * sign * lcos[0]; + auto pcos2 = Ratio(lcos[1]) / lsq2 * sign * lcos[1]; + + return q[0] < 2 ? pcos1 < pcos2 : pcos1 > pcos2; + } + + // If in different quadrants, compare the quadrant indices only. + return q[0] > q[1]; }); __nfp::buildPolygon(edgelist, rsh, top_nfp); @@ -253,456 +299,9 @@ inline NfpResult nfpConvexOnly(const RawShape& sh, template NfpResult nfpSimpleSimple(const RawShape& cstationary, - const RawShape& cother) + const RawShape& cother) { - - // Algorithms are from the original algorithm proposed in paper: - // https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf - - // ///////////////////////////////////////////////////////////////////////// - // Algorithm 1: Obtaining the minkowski sum - // ///////////////////////////////////////////////////////////////////////// - - // I guess this is not a full minkowski sum of the two input polygons by - // definition. This yields a subset that is compatible with the next 2 - // algorithms. - - using Result = NfpResult; - using Vertex = TPoint; - using Coord = TCoord; - using Edge = _Segment; - namespace sl = shapelike; - using std::signbit; - using std::sort; - using std::vector; - using std::ref; - using std::reference_wrapper; - - // TODO The original algorithms expects the stationary polygon in - // counter clockwise and the orbiter in clockwise order. - // So for preventing any further complication, I will make the input - // the way it should be, than make my way around the orientations. - - // Reverse the stationary contour to counter clockwise - auto stcont = sl::contour(cstationary); - { - std::reverse(sl::begin(stcont), sl::end(stcont)); - stcont.pop_back(); - auto it = std::min_element(sl::begin(stcont), sl::end(stcont), - [](const Vertex& v1, const Vertex& v2) { - return getY(v1) < getY(v2); - }); - std::rotate(sl::begin(stcont), it, sl::end(stcont)); - sl::addVertex(stcont, sl::front(stcont)); - } - RawShape stationary; - sl::contour(stationary) = stcont; - - // Reverse the orbiter contour to counter clockwise - auto orbcont = sl::contour(cother); - { - std::reverse(orbcont.begin(), orbcont.end()); - - // Step 1: Make the orbiter reverse oriented - - orbcont.pop_back(); - auto it = std::min_element(orbcont.begin(), orbcont.end(), - [](const Vertex& v1, const Vertex& v2) { - return getY(v1) < getY(v2); - }); - - std::rotate(orbcont.begin(), it, orbcont.end()); - orbcont.emplace_back(orbcont.front()); - - for(auto &v : orbcont) v = -v; - - } - - // Copy the orbiter (contour only), we will have to work on it - RawShape orbiter; - sl::contour(orbiter) = orbcont; - - // An edge with additional data for marking it - struct MarkedEdge { - Edge e; Radians turn_angle = 0; bool is_turning_point = false; - MarkedEdge() = default; - MarkedEdge(const Edge& ed, Radians ta, bool tp): - e(ed), turn_angle(ta), is_turning_point(tp) {} - - // debug - std::string label; - }; - - // Container for marked edges - using EdgeList = vector; - - EdgeList A, B; - - // This is how an edge list is created from the polygons - auto fillEdgeList = [](EdgeList& L, const RawShape& ppoly, int dir) { - auto& poly = sl::contour(ppoly); - - L.reserve(sl::contourVertexCount(poly)); - - if(dir > 0) { - auto it = poly.begin(); - auto nextit = std::next(it); - - double turn_angle = 0; - bool is_turn_point = false; - - while(nextit != poly.end()) { - L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); - it++; nextit++; - } - } else { - auto it = sl::rbegin(poly); - auto nextit = std::next(it); - - double turn_angle = 0; - bool is_turn_point = false; - - while(nextit != sl::rend(poly)) { - L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); - it++; nextit++; - } - } - - auto getTurnAngle = [](const Edge& e1, const Edge& e2) { - auto phi = e1.angleToXaxis(); - auto phi_prev = e2.angleToXaxis(); - auto turn_angle = phi-phi_prev; - if(turn_angle > Pi) turn_angle -= TwoPi; - if(turn_angle < -Pi) turn_angle += TwoPi; - return turn_angle; - }; - - auto eit = L.begin(); - auto enext = std::next(eit); - - eit->turn_angle = getTurnAngle(L.front().e, L.back().e); - - while(enext != L.end()) { - enext->turn_angle = getTurnAngle( enext->e, eit->e); - eit->is_turning_point = - signbit(enext->turn_angle) != signbit(eit->turn_angle); - ++eit; ++enext; - } - - L.back().is_turning_point = signbit(L.back().turn_angle) != - signbit(L.front().turn_angle); - - }; - - // Step 2: Fill the edgelists - fillEdgeList(A, stationary, 1); - fillEdgeList(B, orbiter, 1); - - int i = 1; - for(MarkedEdge& me : A) { - std::cout << "a" << i << ":\n\t" - << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t" - << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t" - << "Turning point: " << (me.is_turning_point ? "yes" : "no") - << std::endl; - - me.label = "a"; me.label += std::to_string(i); - i++; - } - - i = 1; - for(MarkedEdge& me : B) { - std::cout << "b" << i << ":\n\t" - << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t" - << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t" - << "Turning point: " << (me.is_turning_point ? "yes" : "no") - << std::endl; - me.label = "b"; me.label += std::to_string(i); - i++; - } - - // A reference to a marked edge that also knows its container - struct MarkedEdgeRef { - reference_wrapper eref; - reference_wrapper> container; - Coord dir = 1; // Direction modifier - - inline Radians angleX() const { return eref.get().e.angleToXaxis(); } - inline const Edge& edge() const { return eref.get().e; } - inline Edge& edge() { return eref.get().e; } - inline bool isTurningPoint() const { - return eref.get().is_turning_point; - } - inline bool isFrom(const vector& cont ) { - return &(container.get()) == &cont; - } - inline bool eq(const MarkedEdgeRef& mr) { - return &(eref.get()) == &(mr.eref.get()); - } - - MarkedEdgeRef(reference_wrapper er, - reference_wrapper> ec): - eref(er), container(ec), dir(1) {} - - MarkedEdgeRef(reference_wrapper er, - reference_wrapper> ec, - Coord d): - eref(er), container(ec), dir(d) {} - }; - - using EdgeRefList = vector; - - // Comparing two marked edges - auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) { - return e1.angleX() < e2.angleX(); - }; - - EdgeRefList Aref, Bref; // We create containers for the references - Aref.reserve(A.size()); Bref.reserve(B.size()); - - // Fill reference container for the stationary polygon - std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) { - Aref.emplace_back( ref(me), ref(Aref) ); - }); - - // Fill reference container for the orbiting polygon - std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) { - Bref.emplace_back( ref(me), ref(Bref) ); - }); - - auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure - (const EdgeRefList& Q, const EdgeRefList& R, bool positive) - { - - // Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)" - // Sort the containers of edge references and merge them. - // Q could be sorted only once and be reused here but we would still - // need to merge it with sorted(R). - - EdgeRefList merged; - EdgeRefList S, seq; - merged.reserve(Q.size() + R.size()); - - merged.insert(merged.end(), R.begin(), R.end()); - std::stable_sort(merged.begin(), merged.end(), sortfn); - merged.insert(merged.end(), Q.begin(), Q.end()); - std::stable_sort(merged.begin(), merged.end(), sortfn); - - // Step 2 "set i = 1, k = 1, direction = 1, s1 = q1" - // we don't use i, instead, q is an iterator into Q. k would be an index - // into the merged sequence but we use "it" as an iterator for that - - // here we obtain references for the containers for later comparisons - const auto& Rcont = R.begin()->container.get(); - const auto& Qcont = Q.begin()->container.get(); - - // Set the initial direction - Coord dir = 1; - - // roughly i = 1 (so q = Q.begin()) and s1 = q1 so S[0] = q; - if(positive) { - auto q = Q.begin(); - S.emplace_back(*q); - - // Roughly step 3 - - std::cout << "merged size: " << merged.size() << std::endl; - auto mit = merged.begin(); - for(bool finish = false; !finish && q != Q.end();) { - ++q; // "Set i = i + 1" - - while(!finish && mit != merged.end()) { - if(mit->isFrom(Rcont)) { - auto s = *mit; - s.dir = dir; - S.emplace_back(s); - } - - if(mit->eq(*q)) { - S.emplace_back(*q); - if(mit->isTurningPoint()) dir = -dir; - if(q == Q.begin()) finish = true; - break; - } - - mit += dir; - // __nfp::advance(mit, merged, dir > 0); - } - } - } else { - auto q = Q.rbegin(); - S.emplace_back(*q); - - // Roughly step 3 - - std::cout << "merged size: " << merged.size() << std::endl; - auto mit = merged.begin(); - for(bool finish = false; !finish && q != Q.rend();) { - ++q; // "Set i = i + 1" - - while(!finish && mit != merged.end()) { - if(mit->isFrom(Rcont)) { - auto s = *mit; - s.dir = dir; - S.emplace_back(s); - } - - if(mit->eq(*q)) { - S.emplace_back(*q); - S.back().dir = -1; - if(mit->isTurningPoint()) dir = -dir; - if(q == Q.rbegin()) finish = true; - break; - } - - mit += dir; - // __nfp::advance(mit, merged, dir > 0); - } - } - } - - - // Step 4: - - // "Let starting edge r1 be in position si in sequence" - // whaaat? I guess this means the following: - auto it = S.begin(); - while(!it->eq(*R.begin())) ++it; - - // "Set j = 1, next = 2, direction = 1, seq1 = si" - // we don't use j, seq is expanded dynamically. - dir = 1; - auto next = std::next(R.begin()); seq.emplace_back(*it); - - // Step 5: - // "If all si edges have been allocated to seqj" should mean that - // we loop until seq has equal size with S - auto send = it; //it == S.begin() ? it : std::prev(it); - while(it != S.end()) { - ++it; if(it == S.end()) it = S.begin(); - if(it == send) break; - - if(it->isFrom(Qcont)) { - seq.emplace_back(*it); // "If si is from Q, j = j + 1, seqj = si" - - // "If si is a turning point in Q, - // direction = - direction, next = next + direction" - if(it->isTurningPoint()) { - dir = -dir; - next += dir; -// __nfp::advance(next, R, dir > 0); - } - } - - if(it->eq(*next) /*&& dir == next->dir*/) { // "If si = direction.rnext" - // "j = j + 1, seqj = si, next = next + direction" - seq.emplace_back(*it); - next += dir; -// __nfp::advance(next, R, dir > 0); - } - } - - return seq; - }; - - std::vector seqlist; - seqlist.reserve(Bref.size()); - - EdgeRefList Bslope = Bref; // copy Bref, we will make a slope diagram - - // make the slope diagram of B - std::sort(Bslope.begin(), Bslope.end(), sortfn); - - auto slopeit = Bslope.begin(); // search for the first turning point - while(!slopeit->isTurningPoint() && slopeit != Bslope.end()) slopeit++; - - if(slopeit == Bslope.end()) { - // no turning point means convex polygon. - seqlist.emplace_back(mink(Aref, Bref, true)); - } else { - int dir = 1; - - auto firstturn = Bref.begin(); - while(!firstturn->eq(*slopeit)) ++firstturn; - - assert(firstturn != Bref.end()); - - EdgeRefList bgroup; bgroup.reserve(Bref.size()); - bgroup.emplace_back(*slopeit); - - auto b_it = std::next(firstturn); - while(b_it != firstturn) { - if(b_it == Bref.end()) b_it = Bref.begin(); - - while(!slopeit->eq(*b_it)) { - __nfp::advance(slopeit, Bslope, dir > 0); - } - - if(!slopeit->isTurningPoint()) { - bgroup.emplace_back(*slopeit); - } else { - if(!bgroup.empty()) { - if(dir > 0) bgroup.emplace_back(*slopeit); - for(auto& me : bgroup) { - std::cout << me.eref.get().label << ", "; - } - std::cout << std::endl; - seqlist.emplace_back(mink(Aref, bgroup, dir == 1 ? true : false)); - bgroup.clear(); - if(dir < 0) bgroup.emplace_back(*slopeit); - } else { - bgroup.emplace_back(*slopeit); - } - - dir *= -1; - } - ++b_it; - } - } - -// while(it != Bref.end()) // This is step 3 and step 4 in one loop -// if(it->isTurningPoint()) { -// R = {R.last, it++}; -// auto seq = mink(Q, R, orientation); - -// // TODO step 6 (should be 5 shouldn't it?): linking edges from A -// // I don't get this step - -// seqlist.insert(seqlist.end(), seq.begin(), seq.end()); -// orientation = !orientation; -// } else ++it; - -// if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true); - - // ///////////////////////////////////////////////////////////////////////// - // Algorithm 2: breaking Minkowski sums into track line trips - // ///////////////////////////////////////////////////////////////////////// - - - // ///////////////////////////////////////////////////////////////////////// - // Algorithm 3: finding the boundary of the NFP from track line trips - // ///////////////////////////////////////////////////////////////////////// - - - for(auto& seq : seqlist) { - std::cout << "seqlist size: " << seq.size() << std::endl; - for(auto& s : seq) { - std::cout << (s.dir > 0 ? "" : "-") << s.eref.get().label << ", "; - } - std::cout << std::endl; - } - - auto& seq = seqlist.front(); - RawShape rsh; - Vertex top_nfp; - std::vector edgelist; edgelist.reserve(seq.size()); - for(auto& s : seq) { - edgelist.emplace_back(s.eref.get().e); - } - - __nfp::buildPolygon(edgelist, rsh, top_nfp); - - return Result(rsh, top_nfp); + return {}; } // Specializable NFP implementation class. Specialize it if you have a faster diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index c7b252e5d..5d74aa3d9 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -8,13 +8,10 @@ #include #include -#include "geometry_traits.hpp" +#include namespace libnest2d { -namespace sl = shapelike; -namespace pl = pointlike; - /** * \brief An item to be placed on a bin. * @@ -422,13 +419,9 @@ private: static inline bool vsort(const Vertex& v1, const Vertex& v2) { - Coord &&x1 = getX(v1), &&x2 = getX(v2); - Coord &&y1 = getY(v1), &&y2 = getY(v2); - auto diff = y1 - y2; - if(std::abs(diff) <= std::numeric_limits::epsilon()) - return x1 < x2; - - return diff < 0; + TCompute x1 = getX(v1), x2 = getX(v2); + TCompute y1 = getY(v1), y2 = getY(v2); + return y1 == y2 ? x1 < x2 : y1 < y2; } }; diff --git a/src/libnest2d/include/libnest2d/optimizer.hpp b/src/libnest2d/include/libnest2d/optimizer.hpp index 962a47392..e4c149f22 100644 --- a/src/libnest2d/include/libnest2d/optimizer.hpp +++ b/src/libnest2d/include/libnest2d/optimizer.hpp @@ -4,7 +4,8 @@ #include #include #include -#include "common.hpp" + +#include namespace libnest2d { namespace opt { @@ -60,6 +61,7 @@ enum class Method { L_SIMPLEX, L_SUBPLEX, G_GENETIC, + G_PARTICLE_SWARM //... }; diff --git a/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt b/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt index 4e16d4fc5..6f51718d8 100644 --- a/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt +++ b/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt @@ -48,7 +48,7 @@ else() target_link_libraries(nloptOptimizer INTERFACE Nlopt::Nlopt) endif() -#target_sources( NloptOptimizer INTERFACE +#target_sources( nloptOptimizer INTERFACE #${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp #${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp #${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp @@ -57,5 +57,5 @@ endif() target_compile_definitions(nloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT) -# And finally plug the NloptOptimizer into libnest2d -#target_link_libraries(libnest2d INTERFACE NloptOptimizer) +# And finally plug the nloptOptimizer into libnest2d +#target_link_libraries(libnest2d INTERFACE nloptOptimizer) diff --git a/src/libnest2d/include/libnest2d/optimizers/optimlib/CMakeLists.txt b/src/libnest2d/include/libnest2d/optimizers/optimlib/CMakeLists.txt deleted file mode 100644 index efbbd9cfb..000000000 --- a/src/libnest2d/include/libnest2d/optimizers/optimlib/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -find_package(Armadillo REQUIRED) - -add_library(OptimlibOptimizer INTERFACE) -target_include_directories(OptimlibOptimizer INTERFACE ${ARMADILLO_INCLUDE_DIRS}) -target_link_libraries(OptimlibOptimizer INTERFACE ${ARMADILLO_LIBRARIES}) \ No newline at end of file diff --git a/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp b/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp index 7f10be7d7..28aaad5ce 100644 --- a/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp @@ -7,15 +7,15 @@ namespace libnest2d { namespace placers { -template struct Epsilon {}; +template struct DefaultEpsilon {}; template -struct Epsilon::value, T> > { +struct DefaultEpsilon::value, T> > { static const T Value = 1; }; template -struct Epsilon::value, T> > { +struct DefaultEpsilon::value, T> > { static const T Value = 1e-3; }; @@ -24,7 +24,7 @@ struct BLConfig { DECLARE_MAIN_TYPES(RawShape); Coord min_obj_distance = 0; - Coord epsilon = Epsilon::Value; + Coord epsilon = DefaultEpsilon::Value; bool allow_rotations = false; }; diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 91affe978..c1f15fe61 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -103,14 +103,14 @@ Key hash(const _Item& item) { while(deg > N) { ms++; deg -= N; } ls += int(deg); ret.push_back(char(ms)); ret.push_back(char(ls)); - circ += seg.length(); + circ += std::sqrt(seg.template sqlength()); } it = ctr.begin(); nx = std::next(it); while(nx != ctr.end()) { Segment seg(*it++, *nx++); - auto l = int(M * seg.length() / circ); + auto l = int(M * std::sqrt(seg.template sqlength()) / circ); int ms = 'A', ls = 'A'; while(l > N) { ms++; l -= N; } ls += l; @@ -249,6 +249,11 @@ template class EdgeCache { std::vector holes_; double accuracy_ = 1.0; + + static double length(const Edge &e) + { + return std::sqrt(e.template sqlength()); + } void createCache(const RawShape& sh) { { // For the contour @@ -260,7 +265,7 @@ template class EdgeCache { while(next != endit) { contour_.emap.emplace_back(*(first++), *(next++)); - contour_.full_distance += contour_.emap.back().length(); + contour_.full_distance += length(contour_.emap.back()); contour_.distances.emplace_back(contour_.full_distance); } } @@ -275,7 +280,7 @@ template class EdgeCache { while(next != endit) { hc.emap.emplace_back(*(first++), *(next++)); - hc.full_distance += hc.emap.back().length(); + hc.full_distance += length(hc.emap.back()); hc.distances.emplace_back(hc.full_distance); } diff --git a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp index baf1c6a10..16dee513b 100644 --- a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp +++ b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp @@ -311,19 +311,19 @@ struct range_value { namespace libnest2d { // Now the algorithms that boost can provide... -namespace pointlike { -template<> -inline double distance(const PointImpl& p1, const PointImpl& p2 ) -{ - return boost::geometry::distance(p1, p2); -} +//namespace pointlike { +//template<> +//inline double distance(const PointImpl& p1, const PointImpl& p2 ) +//{ +// return boost::geometry::distance(p1, p2); +//} -template<> -inline double distance(const PointImpl& p, const bp2d::Segment& seg ) -{ - return boost::geometry::distance(p, seg); -} -} +//template<> +//inline double distance(const PointImpl& p, const bp2d::Segment& seg ) +//{ +// return boost::geometry::distance(p, seg); +//} +//} namespace shapelike { // Tell libnest2d how to make string out of a ClipperPolygon object @@ -382,16 +382,9 @@ inline bool touches( const PointImpl& point, const PolygonImpl& shape) } #ifndef DISABLE_BOOST_BOUNDING_BOX -template<> -inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&) -{ - bp2d::Box b; - boost::geometry::envelope(sh, b); - return b; -} template<> -inline bp2d::Box boundingBox(const PathImpl& sh, const PolygonTag&) +inline bp2d::Box boundingBox(const PathImpl& sh, const PathTag&) { bp2d::Box b; boost::geometry::envelope(sh, b); @@ -410,9 +403,9 @@ inline bp2d::Box boundingBox(const bp2d::Shapes& shapes, #ifndef DISABLE_BOOST_CONVEX_HULL template<> -inline PolygonImpl convexHull(const PolygonImpl& sh, const PolygonTag&) +inline PathImpl convexHull(const PathImpl& sh, const PathTag&) { - PolygonImpl ret; + PathImpl ret; boost::geometry::convex_hull(sh, ret); return ret; } diff --git a/src/libnest2d/include/libnest2d/utils/rotcalipers.hpp b/src/libnest2d/include/libnest2d/utils/rotcalipers.hpp new file mode 100644 index 000000000..76f91ba0c --- /dev/null +++ b/src/libnest2d/include/libnest2d/utils/rotcalipers.hpp @@ -0,0 +1,268 @@ +#ifndef ROTCALIPERS_HPP +#define ROTCALIPERS_HPP + +#include +#include +#include +#include + +#include + +namespace libnest2d { + +template> class RotatedBox { + Pt axis_; + Unit bottom_ = Unit(0), right_ = Unit(0); +public: + + RotatedBox() = default; + RotatedBox(const Pt& axis, Unit b, Unit r): + axis_(axis), bottom_(b), right_(r) {} + + inline long double area() const { + long double asq = pl::magnsq(axis_); + return cast(bottom_) * cast(right_) / asq; + } + + inline long double width() const { + return abs(bottom_) / std::sqrt(pl::magnsq(axis_)); + } + + inline long double height() const { + return abs(right_) / std::sqrt(pl::magnsq(axis_)); + } + + inline Unit bottom_extent() const { return bottom_; } + inline Unit right_extent() const { return right_; } + inline const Pt& axis() const { return axis_; } + + inline Radians angleToX() const { + double ret = std::atan2(getY(axis_), getX(axis_)); + auto s = std::signbit(ret); + if(s) ret += Pi_2; + return -ret; + } +}; + +template , class Unit = TCompute> +Poly removeCollinearPoints(const Poly& sh, Unit eps = Unit(0)) +{ + Poly ret; sl::reserve(ret, sl::contourVertexCount(sh)); + + Pt eprev = *sl::cbegin(sh) - *std::prev(sl::cend(sh)); + + auto it = sl::cbegin(sh); + auto itx = std::next(it); + if(itx != sl::cend(sh)) while (it != sl::cend(sh)) + { + Pt enext = *itx - *it; + + auto dp = pl::dotperp(eprev, enext); + if(abs(dp) > eps) sl::addVertex(ret, *it); + + eprev = enext; + if (++itx == sl::cend(sh)) itx = sl::cbegin(sh); + ++it; + } + + return ret; +} + +// The area of the bounding rectangle with the axis dir and support vertices +template, class R = TCompute> +inline R rectarea(const Pt& w, // the axis + const Pt& vb, const Pt& vr, + const Pt& vt, const Pt& vl) +{ + Unit a = pl::dot(w, vr - vl); + Unit b = pl::dot(-pl::perp(w), vt - vb); + R m = R(a) / pl::magnsq(w); + m = m * b; + return m; +}; + +template, + class R = TCompute, + class It = typename std::vector::const_iterator> +inline R rectarea(const Pt& w, const std::array& rect) +{ + return rectarea(w, *rect[0], *rect[1], *rect[2], *rect[3]); +} + +// This function is only applicable to counter-clockwise oriented convex +// polygons where only two points can be collinear witch each other. +template , + class Ratio = TCompute> +RotatedBox, Unit> minAreaBoundingBox(const RawShape& sh) +{ + using Point = TPoint; + using Iterator = typename TContour::const_iterator; + using pointlike::dot; using pointlike::magnsq; using pointlike::perp; + + // Get the first and the last vertex iterator + auto first = sl::cbegin(sh); + auto last = std::prev(sl::cend(sh)); + + // Check conditions and return undefined box if input is not sane. + if(last == first) return {}; + if(getX(*first) == getX(*last) && getY(*first) == getY(*last)) --last; + if(last - first < 2) return {}; + + RawShape shcpy; // empty at this point + { + Point p = *first, q = *std::next(first), r = *last; + + // Determine orientation from first 3 vertex (should be consistent) + Unit d = (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) - + (Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p)); + + if(d > 0) { + // The polygon is clockwise. A flip is needed (for now) + sl::reserve(shcpy, last - first); + auto it = last; while(it != first) sl::addVertex(shcpy, *it--); + sl::addVertex(shcpy, *first); + first = sl::cbegin(shcpy); last = std::prev(sl::cend(shcpy)); + } + } + + // Cyclic iterator increment + auto inc = [&first, &last](Iterator& it) { + if(it == last) it = first; else ++it; + }; + + // Cyclic previous iterator + auto prev = [&first, &last](Iterator it) { + return it == first ? last : std::prev(it); + }; + + // Cyclic next iterator + auto next = [&first, &last](Iterator it) { + return it == last ? first : std::next(it); + }; + + // Establish initial (axis aligned) rectangle support verices by determining + // polygon extremes: + + auto it = first; + Iterator minX = it, maxX = it, minY = it, maxY = it; + + do { // Linear walk through the vertices and save the extreme positions + + Point v = *it, d = v - *minX; + if(getX(d) < 0 || (getX(d) == 0 && getY(d) < 0)) minX = it; + + d = v - *maxX; + if(getX(d) > 0 || (getX(d) == 0 && getY(d) > 0)) maxX = it; + + d = v - *minY; + if(getY(d) < 0 || (getY(d) == 0 && getX(d) > 0)) minY = it; + + d = v - *maxY; + if(getY(d) > 0 || (getY(d) == 0 && getX(d) < 0)) maxY = it; + + } while(++it != std::next(last)); + + // Update the vertices defining the bounding rectangle. The rectangle with + // the smallest rotation is selected and the supporting vertices are + // returned in the 'rect' argument. + auto update = [&next, &inc] + (const Point& w, std::array& rect) + { + Iterator B = rect[0], Bn = next(B); + Iterator R = rect[1], Rn = next(R); + Iterator T = rect[2], Tn = next(T); + Iterator L = rect[3], Ln = next(L); + + Point b = *Bn - *B, r = *Rn - *R, t = *Tn - *T, l = *Ln - *L; + Point pw = perp(w); + using Pt = Point; + + Unit dotwpb = dot( w, b), dotwpr = dot(-pw, r); + Unit dotwpt = dot(-w, t), dotwpl = dot( pw, l); + Unit dw = magnsq(w); + + std::array angles; + angles[0] = (Ratio(dotwpb) / magnsq(b)) * dotwpb; + angles[1] = (Ratio(dotwpr) / magnsq(r)) * dotwpr; + angles[2] = (Ratio(dotwpt) / magnsq(t)) * dotwpt; + angles[3] = (Ratio(dotwpl) / magnsq(l)) * dotwpl; + + using AngleIndex = std::pair; + std::vector A; A.reserve(4); + + for (size_t i = 3, j = 0; j < 4; i = j++) { + if(rect[i] != rect[j] && angles[i] < dw) { + auto iv = std::make_pair(angles[i], i); + auto it = std::lower_bound(A.begin(), A.end(), iv, + [](const AngleIndex& ai, + const AngleIndex& aj) + { + return ai.first > aj.first; + }); + + A.insert(it, iv); + } + } + + // The polygon is supposed to be a rectangle. + if(A.empty()) return false; + + auto amin = A.front().first; + auto imin = A.front().second; + for(auto& a : A) if(a.first == amin) inc(rect[a.second]); + + std::rotate(rect.begin(), rect.begin() + imin, rect.end()); + + return true; + }; + + Point w(1, 0); + Point w_min = w; + Ratio minarea((Unit(getX(*maxX)) - getX(*minX)) * + (Unit(getY(*maxY)) - getY(*minY))); + + std::array rect = {minY, maxX, maxY, minX}; + std::array minrect = rect; + + // An edge might be examined twice in which case the algorithm terminates. + size_t c = 0, count = last - first + 1; + std::vector edgemask(count, false); + + while(c++ < count) + { + // Update the support vertices, if cannot be updated, break the cycle. + if(! update(w, rect)) break; + + size_t eidx = size_t(rect[0] - first); + + if(edgemask[eidx]) break; + edgemask[eidx] = true; + + // get the unnormalized direction vector + w = *rect[0] - *prev(rect[0]); + + // get the area of the rotated rectangle + Ratio rarea = rectarea(w, rect); + + // Update min area and the direction of the min bounding box; + if(rarea <= minarea) { w_min = w; minarea = rarea; minrect = rect; } + } + + Unit a = dot(w_min, *minrect[1] - *minrect[3]); + Unit b = dot(-perp(w_min), *minrect[2] - *minrect[0]); + RotatedBox bb(w_min, a, b); + + return bb; +} + +template Radians minAreaBoundingBoxRotation(const RawShape& sh) +{ + return minAreaBoundingBox(sh).angleToX(); +} + + +} + +#endif // ROTCALIPERS_HPP diff --git a/src/libnest2d/src/libnest2d.cpp b/src/libnest2d/src/libnest2d.cpp new file mode 100644 index 000000000..021458787 --- /dev/null +++ b/src/libnest2d/src/libnest2d.cpp @@ -0,0 +1,23 @@ +#include + +namespace libnest2d { + +template class Nester; +template class Nester; + +template PackGroup nest(std::vector::iterator from, + std::vector::iterator to, + const Box& bin, + Coord dist = 0, + const NfpPlacer::Config& pconf, + const FirstFitSelection::Config& sconf); + +template PackGroup nest(std::vector::iterator from, + std::vector::iterator to, + const Box& bin, + ProgressFunction prg, + StopCondition scond, + Coord dist = 0, + const NfpPlacer::Config& pconf, + const FirstFitSelection::Config& sconf); +} diff --git a/src/libnest2d/tests/CMakeLists.txt b/src/libnest2d/tests/CMakeLists.txt index fc3cd309d..1b7d8e3ae 100644 --- a/src/libnest2d/tests/CMakeLists.txt +++ b/src/libnest2d/tests/CMakeLists.txt @@ -49,7 +49,12 @@ add_executable(tests_clipper_nlopt target_link_libraries(tests_clipper_nlopt libnest2d ${GTEST_LIBS_TO_LINK} ) -target_include_directories(tests_clipper_nlopt PRIVATE BEFORE - ${GTEST_INCLUDE_DIRS}) +target_include_directories(tests_clipper_nlopt PRIVATE BEFORE ${GTEST_INCLUDE_DIRS}) + +if(NOT LIBNEST2D_HEADER_ONLY) + target_link_libraries(tests_clipper_nlopt ${LIBNAME}) +else() + target_link_libraries(tests_clipper_nlopt libnest2d) +endif() add_test(libnest2d_tests tests_clipper_nlopt) diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index 3b0eae161..5741e87b4 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -3,11 +3,43 @@ #include #include "printer_parts.h" -#include +//#include #include "../tools/svgtools.hpp" +#include + +#include "boost/multiprecision/integer.hpp" +#include "boost/rational.hpp" + +//#include "../tools/Int128.hpp" + +//#include "gte/Mathematics/GteMinimumAreaBox2.h" + //#include "../tools/libnfpglue.hpp" //#include "../tools/nfp_svgnest_glue.hpp" +namespace libnest2d { +#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__) +using LargeInt = __int128; +#else +using LargeInt = boost::multiprecision::int128_t; +template<> struct _NumTag { using Type = ScalarTag; }; +#endif +template struct _NumTag> { using Type = RationalTag; }; + +namespace nfp { + +template +struct NfpImpl +{ + NfpResult operator()(const S &sh, const S &other) + { + return nfpConvexOnly>(sh, other); + } +}; + +} +} + std::vector& prusaParts() { static std::vector ret; @@ -31,8 +63,8 @@ TEST(BasicFunctionality, Angles) ASSERT_DOUBLE_EQ(rad, Pi); ASSERT_DOUBLE_EQ(deg, 180); ASSERT_DOUBLE_EQ(deg2, 180); - ASSERT_DOUBLE_EQ(rad, (Radians) deg); - ASSERT_DOUBLE_EQ( (Degrees) rad, deg); + ASSERT_DOUBLE_EQ(rad, Radians(deg)); + ASSERT_DOUBLE_EQ( Degrees(rad), deg); ASSERT_TRUE(rad == deg); @@ -151,12 +183,12 @@ TEST(GeometryAlgorithms, Distance) { Segment seg(p1, p3); - ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755); +// ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755); auto result = pointlike::horizontalDistance(p2, seg); - auto check = [](Coord val, Coord expected) { - if(std::is_floating_point::value) + auto check = [](TCompute val, TCompute expected) { + if(std::is_floating_point>::value) ASSERT_DOUBLE_EQ(static_cast(val), static_cast(expected)); else @@ -415,7 +447,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) namespace { using namespace libnest2d; -template +template void exportSVG(std::vector>& result, const Bin& bin, int idx = 0) { @@ -500,6 +532,41 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) { } } +TEST(GeometryAlgorithms, convexHull) { + using namespace libnest2d; + + ClipperLib::Path poly = PRINTER_PART_POLYGONS[0]; + + auto chull = sl::convexHull(poly); + + ASSERT_EQ(chull.size(), poly.size()); +} + + +TEST(GeometryAlgorithms, NestTest) { + std::vector input = prusaParts(); + + PackGroup result = libnest2d::nest(input, + Box(250000000, 210000000), + [](unsigned cnt) { + std::cout + << "parts left: " << cnt + << std::endl; + }); + + ASSERT_LE(result.size(), 2); + + int partsum = std::accumulate(result.begin(), + result.end(), + 0, + [](int s, + const decltype(result)::value_type &bin) { + return s += bin.size(); + }); + + ASSERT_EQ(input.size(), partsum); +} + namespace { struct ItemPair { @@ -713,7 +780,7 @@ void testNfp(const std::vector& testdata) { auto& exportfun = exportSVG; - auto onetest = [&](Item& orbiter, Item& stationary, unsigned testidx){ + auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){ testcase++; orbiter.translate({210*SCALE, 0}); @@ -820,7 +887,7 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) { rect2.translate({10, 0}); rect3.translate({25, 0}); - shapelike::Shapes pile; + TMultiShape pile; pile.push_back(rect1.transformedShape()); pile.push_back(rect2.transformedShape()); @@ -833,6 +900,126 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) { ASSERT_EQ(shapelike::area(result.front()), ref.area()); } +namespace { + +long double refMinAreaBox(const PolygonImpl& p) { + + auto it = sl::cbegin(p), itx = std::next(it); + + long double min_area = std::numeric_limits::max(); + + + auto update_min = [&min_area, &it, &itx, &p]() { + Segment s(*it, *itx); + + PolygonImpl rotated = p; + sl::rotate(rotated, -s.angleToXaxis()); + auto bb = sl::boundingBox(rotated); + auto area = cast(sl::area(bb)); + if(min_area > area) min_area = area; + }; + + while(itx != sl::cend(p)) { + update_min(); + ++it; ++itx; + } + + it = std::prev(sl::cend(p)); itx = sl::cbegin(p); + update_min(); + + return min_area; +} + +template struct BoostGCD { + T operator()(const T &a, const T &b) { return boost::gcd(a, b); } +}; + +using Unit = int64_t; +using Ratio = boost::rational;// Rational; + +//double gteMinAreaBox(const PolygonImpl& p) { + +// using GteCoord = ClipperLib::cInt; +// using GtePoint = gte::Vector2; + +// gte::MinimumAreaBox2 mb; + +// std::vector points; +// points.reserve(p.Contour.size()); + +// for(auto& pt : p.Contour) points.emplace_back(GtePoint{GteCoord(pt.X), GteCoord(pt.Y)}); + +// mb(int(points.size()), points.data(), 0, nullptr, true); + +// auto min_area = double(mb.GetArea()); + +// return min_area; +//} + +} + +TEST(RotatingCalipers, MinAreaBBCClk) { +// PolygonImpl poly({{-50, 30}, {-50, -50}, {50, -50}, {50, 50}, {-40, 50}}); + +// PolygonImpl poly({{-50, 0}, {50, 0}, {0, 100}}); + + auto u = [](ClipperLib::cInt n) { return n*1000000; }; + PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}}); + + + long double arearef = refMinAreaBox(poly); + long double area = minAreaBoundingBox(poly).area(); +// double gtearea = gteMinAreaBox(poly); + + ASSERT_LE(std::abs(area - arearef), 500e6 ); +// ASSERT_LE(std::abs(gtearea - arearef), 500 ); +// ASSERT_DOUBLE_EQ(gtearea, arearef); +} + +TEST(RotatingCalipers, AllPrusaMinBB) { + size_t idx = 0; + long double err_epsilon = 500e6l; + + for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) { +// ClipperLib::Path rinput = PRINTER_PART_POLYGONS[idx]; +// rinput.pop_back(); +// std::reverse(rinput.begin(), rinput.end()); + +// PolygonImpl poly(removeCollinearPoints(rinput, 1000000)); + PolygonImpl poly(rinput); + + long double arearef = refMinAreaBox(poly); + auto bb = minAreaBoundingBox(rinput); + long double area = cast(bb.area()); +// double area = gteMinAreaBox(poly); + + bool succ = std::abs(arearef - area) < err_epsilon; + std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " + << arearef << " actual: " << area << std::endl; + + ASSERT_TRUE(succ); + } + + for(ClipperLib::Path rinput : STEGOSAUR_POLYGONS) { + rinput.pop_back(); + std::reverse(rinput.begin(), rinput.end()); + + PolygonImpl poly(removeCollinearPoints(rinput, 1000000)); + + + long double arearef = refMinAreaBox(poly); + auto bb = minAreaBoundingBox(poly); + long double area = cast(bb.area()); +// double area = gteMinAreaBox(poly); + + bool succ = std::abs(arearef - area) < err_epsilon; + std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " + << arearef << " actual: " << area << std::endl; + + ASSERT_TRUE(succ); + } +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 833b1ae62..e06a5f52a 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -163,6 +163,8 @@ add_library(libslic3r STATIC MTUtils.hpp Zipper.hpp Zipper.cpp + MinAreaBoundingBox.hpp + MinAreaBoundingBox.cpp miniz_extension.hpp miniz_extension.cpp SLA/SLABoilerPlate.hpp diff --git a/src/libslic3r/Int128.hpp b/src/libslic3r/Int128.hpp index 56dc5f461..8dc9e012d 100644 --- a/src/libslic3r/Int128.hpp +++ b/src/libslic3r/Int128.hpp @@ -37,6 +37,8 @@ * * *******************************************************************************/ +#ifndef SLIC3R_INT128_HPP +#define SLIC3R_INT128_HPP // #define SLIC3R_DEBUG // Make assert active if SLIC3R_DEBUG @@ -48,6 +50,8 @@ #endif #include +#include +#include #if ! defined(_MSC_VER) && defined(__SIZEOF_INT128__) #define HAS_INTRINSIC_128_TYPE @@ -293,3 +297,5 @@ public: return sign_determinant_2x2(p1, q1, p2, q2) * invert; } }; + +#endif // SLIC3R_INT128_HPP diff --git a/src/libslic3r/MinAreaBoundingBox.cpp b/src/libslic3r/MinAreaBoundingBox.cpp new file mode 100644 index 000000000..6fc1b3327 --- /dev/null +++ b/src/libslic3r/MinAreaBoundingBox.cpp @@ -0,0 +1,142 @@ +#include "MinAreaBoundingBox.hpp" + +#include +#include + +#include + +#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__) +#include +#endif + +#include +#include + +namespace libnest2d { + +template<> struct PointType { using Type = Slic3r::Point; }; +template<> struct CoordType { using Type = coord_t; }; +template<> struct ShapeTag { using Type = PolygonTag; }; +template<> struct ShapeTag { using Type = PolygonTag; }; +template<> struct ShapeTag { using Type = PathTag; }; +template<> struct ShapeTag { using Type = PointTag; }; +template<> struct ContourType { using Type = Slic3r::Points; }; +template<> struct ContourType { using Type = Slic3r::Points; }; + +namespace pointlike { + +template<> inline coord_t x(const Slic3r::Point& p) { return p.x(); } +template<> inline coord_t y(const Slic3r::Point& p) { return p.y(); } +template<> inline coord_t& x(Slic3r::Point& p) { return p.x(); } +template<> inline coord_t& y(Slic3r::Point& p) { return p.y(); } + +} // pointlike + +namespace shapelike { +template<> inline Slic3r::Points& contour(Slic3r::ExPolygon& sh) { return sh.contour.points; } +template<> inline const Slic3r::Points& contour(const Slic3r::ExPolygon& sh) { return sh.contour.points; } +template<> inline Slic3r::Points& contour(Slic3r::Polygon& sh) { return sh.points; } +template<> inline const Slic3r::Points& contour(const Slic3r::Polygon& sh) { return sh.points; } + +template<> Slic3r::Points::iterator begin(Slic3r::Points& pts, const PathTag&) { return pts.begin();} +template<> Slic3r::Points::const_iterator cbegin(const Slic3r::Points& pts, const PathTag&) { return pts.begin(); } +template<> Slic3r::Points::iterator end(Slic3r::Points& pts, const PathTag&) { return pts.end();} +template<> Slic3r::Points::const_iterator cend(const Slic3r::Points& pts, const PathTag&) { return pts.cend(); } + +template<> inline Slic3r::ExPolygon create(Slic3r::Points&& contour) +{ + Slic3r::ExPolygon expoly; expoly.contour.points.swap(contour); + return expoly; +} + +template<> inline Slic3r::Polygon create(Slic3r::Points&& contour) +{ + Slic3r::Polygon poly; poly.points.swap(contour); + return poly; +} + +} // shapelike +} // libnest2d + +namespace Slic3r { + +// Used as compute type. +using Unit = int64_t; + +#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__) +using Rational = boost::rational; +#else +using Rational = boost::rational<__int128>; +#endif + +MinAreaBoundigBox::MinAreaBoundigBox(const Polygon &p, PolygonLevel pc) +{ + const Polygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p); + + libnest2d::RotatedBox box = + libnest2d::minAreaBoundingBox(chull); + + m_right = box.right_extent(); + m_bottom = box.bottom_extent(); + m_axis = box.axis(); +} + +MinAreaBoundigBox::MinAreaBoundigBox(const ExPolygon &p, PolygonLevel pc) +{ + const ExPolygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p); + + libnest2d::RotatedBox box = + libnest2d::minAreaBoundingBox(chull); + + m_right = box.right_extent(); + m_bottom = box.bottom_extent(); + m_axis = box.axis(); +} + +MinAreaBoundigBox::MinAreaBoundigBox(const Points &pts, PolygonLevel pc) +{ + const Points& chull = pc == pcConvex ? pts : libnest2d::sl::convexHull(pts); + + libnest2d::RotatedBox box = + libnest2d::minAreaBoundingBox(chull); + + m_right = box.right_extent(); + m_bottom = box.bottom_extent(); + m_axis = box.axis(); +} + +double MinAreaBoundigBox::angle_to_X() const +{ + double ret = std::atan2(m_axis.y(), m_axis.x()); + auto s = std::signbit(ret); + if(s) ret += 2 * PI; + return -ret; +} + +long double MinAreaBoundigBox::width() const +{ + return std::abs(m_bottom) / std::sqrt(libnest2d::pl::magnsq(m_axis)); +} + +long double MinAreaBoundigBox::height() const +{ + return std::abs(m_right) / std::sqrt(libnest2d::pl::magnsq(m_axis)); +} + +long double MinAreaBoundigBox::area() const +{ + long double asq = libnest2d::pl::magnsq(m_axis); + return m_bottom * m_right / asq; +} + +void remove_collinear_points(Polygon &p) +{ + p = libnest2d::removeCollinearPoints(p, Unit(0)); +} + +void remove_collinear_points(ExPolygon &p) +{ + p = libnest2d::removeCollinearPoints(p, Unit(0)); +} + +} diff --git a/src/libslic3r/MinAreaBoundingBox.hpp b/src/libslic3r/MinAreaBoundingBox.hpp new file mode 100644 index 000000000..30d0e9799 --- /dev/null +++ b/src/libslic3r/MinAreaBoundingBox.hpp @@ -0,0 +1,59 @@ +#ifndef MINAREABOUNDINGBOX_HPP +#define MINAREABOUNDINGBOX_HPP + +#include "libslic3r/Point.hpp" + +namespace Slic3r { + +class Polygon; +class ExPolygon; + +void remove_collinear_points(Polygon& p); +void remove_collinear_points(ExPolygon& p); + +/// A class that holds a rotated bounding box. If instantiated with a polygon +/// type it will hold the minimum area bounding box for the given polygon. +/// If the input polygon is convex, the complexity is linear to the number of +/// points. Otherwise a convex hull of O(n*log(n)) has to be performed. +class MinAreaBoundigBox { + Point m_axis; + long double m_bottom = 0.0l, m_right = 0.0l; +public: + + // Polygons can be convex or simple (convex or concave with possible holes) + enum PolygonLevel { + pcConvex, pcSimple + }; + + // Constructors with various types of geometry data used in Slic3r. + // If the convexity is known apriory, pcConvex can be used to skip + // convex hull calculation. It is very important that the input polygons + // do NOT have any collinear points (except for the first and the last + // vertex being the same -- meaning a closed polygon for boost) + // To make sure this constraint is satisfied, you can call + // remove_collinear_points on the input polygon before handing over here) + explicit MinAreaBoundigBox(const Polygon&, PolygonLevel = pcSimple); + explicit MinAreaBoundigBox(const ExPolygon&, PolygonLevel = pcSimple); + explicit MinAreaBoundigBox(const Points&, PolygonLevel = pcSimple); + + // Returns the angle in radians needed for the box to be aligned with the + // X axis. Rotate the polygon by this angle and it will be aligned. + double angle_to_X() const; + + // The box width + long double width() const; + + // The box height + long double height() const; + + // The box area + long double area() const; + + // The axis of the rotated box. If the angle_to_X is not sufficiently + // precise, use this unnormalized direction vector. + const Point& axis() const { return m_axis; } +}; + +} + +#endif // MINAREABOUNDINGBOX_HPP diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index b088a1f17..b323285cc 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -9,6 +9,31 @@ #include #include +#include +#include + +namespace libnest2d { +#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__) +using LargeInt = __int128; +#else +using LargeInt = boost::multiprecision::int128_t; +template<> struct _NumTag { using Type = ScalarTag; }; +#endif +template struct _NumTag> { using Type = RationalTag; }; + +namespace nfp { + +template +struct NfpImpl +{ + NfpResult operator()(const S &sh, const S &other) + { + return nfpConvexOnly>(sh, other); + } +}; + +} +} namespace Slic3r { @@ -130,7 +155,7 @@ Box boundingBox(const Box& pilebb, const Box& ibb ) { // at the same time, it has to provide reasonable results. std::tuple objfunc(const PointImpl& bincenter, - const shapelike::Shapes& merged_pile, + const TMultiShape& merged_pile, const Box& pilebb, const ItemGroup& items, const Item &item, @@ -301,7 +326,7 @@ protected: using Packer = Nester; using PConfig = typename Packer::PlacementConfig; using Distance = TCoord; - using Pile = sl::Shapes; + using Pile = TMultiShape; Packer m_pck; PConfig m_pconf; // Placement configuration @@ -589,7 +614,7 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo& // Invalid geometries would throw exceptions when arranging if(item.vertexCount() > 3) { - item.rotation(float(Geometry::rotation_diff_z(rotation0, objinst->get_rotation()))), + item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation())); item.translation({ ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR), ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 44f77b3f7..193abba06 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -37,7 +37,12 @@ #include "libslic3r/SLA/SLARotfinder.hpp" #include "libslic3r/Utils.hpp" -#include "libnest2d/optimizers/nlopt/genetic.hpp" +//#include "libslic3r/ClipperUtils.hpp" + +// #include "libnest2d/optimizers/nlopt/genetic.hpp" +// #include "libnest2d/backends/clipper/geometries.hpp" +// #include "libnest2d/utils/rotcalipers.hpp" +#include "libslic3r/MinAreaBoundingBox.hpp" #include "GUI.hpp" #include "GUI_App.hpp" @@ -2261,46 +2266,18 @@ void Plater::priv::sla_optimize_rotation() { for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); double mindist = 6.0; // FIXME - double offs = mindist / 2.0 - EPSILON; - + if(rotoptimizing) // wasn't canceled for(ModelInstance * oi : o->instances) { oi->set_rotation({r[X], r[Y], r[Z]}); - - auto trchull = o->convex_hull_2d(oi->get_transformation().get_matrix()); - - namespace opt = libnest2d::opt; - opt::StopCriteria stopcr; - stopcr.relative_score_difference = 0.01; - stopcr.max_iterations = 10000; - stopcr.stop_score = 0.0; - opt::GeneticOptimizer solver(stopcr); - Polygon pbed(bed); - - auto bin = pbed.bounding_box(); - double binw = bin.size()(X) * SCALING_FACTOR - offs; - double binh = bin.size()(Y) * SCALING_FACTOR - offs; - - auto result = solver.optimize_min([&trchull, binw, binh](double rot){ - auto chull = trchull; - chull.rotate(rot); - - auto bb = chull.bounding_box(); - double bbw = bb.size()(X) * SCALING_FACTOR; - double bbh = bb.size()(Y) * SCALING_FACTOR; - - auto wdiff = bbw - binw; - auto hdiff = bbh - binh; - double diff = 0; - if(wdiff < 0 && hdiff < 0) diff = wdiff + hdiff; - if(wdiff > 0) diff += wdiff; - if(hdiff > 0) diff += hdiff; - - return diff; - }, opt::initvals(0.0), opt::bound(-PI/2, PI/2)); - - double r = std::get<0>(result.optimum); - + + Polygon trchull = o->convex_hull_2d(oi->get_transformation().get_matrix()); + MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); + double r = rotbb.angle_to_X(); + + // The box should be landscape + if(rotbb.width() < rotbb.height()) r += PI / 2; + Vec3d rt = oi->get_rotation(); rt(Z) += r; oi->set_rotation(rt); } From d809b4894a910ebcefcd89d8776697a7c38c86f4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 6 Jun 2019 12:21:38 +0200 Subject: [PATCH 2/3] Small addition to qhull dep handling. --- src/qhull/CMakeLists.txt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/qhull/CMakeLists.txt b/src/qhull/CMakeLists.txt index 262214a5c..734bb8295 100644 --- a/src/qhull/CMakeLists.txt +++ b/src/qhull/CMakeLists.txt @@ -10,7 +10,7 @@ # see bug report: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=925540 -# find_package(Qhull 7.2 QUIET) +find_package(Qhull 7.2 QUIET) add_library(qhull INTERFACE) @@ -139,9 +139,7 @@ endif(UNIX) ################################################## # LIBDIR is defined in the main xs CMake file: -target_include_directories(${qhull_STATIC} PRIVATE ${LIBDIR}/qhull/src) - +target_include_directories(${qhull_STATIC} BEFORE PUBLIC ${LIBDIR}/qhull/src) target_link_libraries(qhull INTERFACE ${qhull_STATIC}) -target_include_directories(qhull INTERFACE ${LIBDIR}/qhull/src) endif() From 670a5632263114efeb18b0e7f8a43a8daa3ac301 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 19 Jun 2019 12:04:11 +0200 Subject: [PATCH 3/3] Fix for issue #2536 --- src/libslic3r/ModelArrange.cpp | 50 ++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index b323285cc..fc03d0a43 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -318,7 +318,7 @@ class AutoArranger {}; // management and spatial index structures for acceleration. template class _ArrBase { -protected: +public: // Useful type shortcuts... using Placer = TPacker; @@ -327,6 +327,8 @@ protected: using PConfig = typename Packer::PlacementConfig; using Distance = TCoord; using Pile = TMultiShape; + +protected: Packer m_pck; PConfig m_pconf; // Placement configuration @@ -564,7 +566,10 @@ public: // 2D shape from top view. using ShapeData2D = std::vector>; -ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo& wti) { +ShapeData2D projectModelFromTop(const Slic3r::Model &model, + const WipeTowerInfo &wti, + double tolerance) +{ ShapeData2D ret; // Count all the items on the bin (all the object's instances) @@ -586,21 +591,32 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo& // Object instances should carry the same scaling and // x, y rotation that is why we use the first instance. { - ModelInstance *finst = objptr->instances.front(); - Vec3d rotation = finst->get_rotation(); - rotation.z() = 0.; - Transform3d trafo_instance = Geometry::assemble_transform(Vec3d::Zero(), rotation, finst->get_scaling_factor(), finst->get_mirror()); + ModelInstance *finst = objptr->instances.front(); + Vec3d rotation = finst->get_rotation(); + rotation.z() = 0.; + Transform3d trafo_instance = Geometry::assemble_transform( + Vec3d::Zero(), + rotation, + finst->get_scaling_factor(), + finst->get_mirror()); Polygon p = objptr->convex_hull_2d(trafo_instance); - assert(! p.points.empty()); - - // this may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (p.points.empty()) - continue; + + assert(!p.points.empty()); + // this may happen for malformed models, see: + // https://github.com/prusa3d/PrusaSlicer/issues/2209 + if (p.points.empty()) continue; + + if(tolerance > EPSILON) { + Polygons pp { p }; + pp = p.simplify(double(scaled(tolerance))); + if (!pp.empty()) p = pp.front(); + } + p.reverse(); assert(!p.is_counter_clockwise()); - p.append(p.first_point()); clpath = Slic3rMultiPoint_to_ClipperPath(p); + auto firstp = clpath.front(); clpath.emplace_back(firstp); } Vec3d rotation0 = objptr->instances.front()->get_rotation(); @@ -780,9 +796,9 @@ bool arrange(Model &model, // The model with the geometries std::function stopcondition) { bool ret = true; - + // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model, wti); + auto shapemap = arr::projectModelFromTop(model, wti, 0.1); // Copy the references for the shapes only as the arranger expects a // sequence of objects convertible to Item or ClipperPolygon @@ -807,7 +823,7 @@ bool arrange(Model &model, // The model with the geometries static_cast(bbb.min(0)), static_cast(bbb.min(1)) }, - { + { static_cast(bbb.max(0)), static_cast(bbb.max(1)) }); @@ -881,9 +897,9 @@ void find_new_position(const Model &model, coord_t min_obj_distance, const Polyline &bed, WipeTowerInfo& wti) -{ +{ // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model, wti); + auto shapemap = arr::projectModelFromTop(model, wti, 0.1); // Copy the references for the shapes only as the arranger expects a // sequence of objects convertible to Item or ClipperPolygon