mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	Merge branch 'tm_rotcalipers'
This commit is contained in:
		
						commit
						1e7b5c5a81
					
				
					 25 changed files with 1329 additions and 898 deletions
				
			
		|  | @ -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) | ||||
|  |  | |||
|  | @ -47,6 +47,17 @@ using NfpPlacer = _NfpPlacer<Box>; | |||
| // This supports only box shaped bins
 | ||||
| using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>; | ||||
| 
 | ||||
| #ifdef LIBNEST2D_STATIC | ||||
| 
 | ||||
| extern template class Nester<NfpPlacer, FirstFitSelection>; | ||||
| extern template class Nester<BottomLeftPlacer, FirstFitSelection>; | ||||
| extern template PackGroup Nester<NfpPlacer, FirstFitSelection>::execute( | ||||
|         std::vector<Item>::iterator, std::vector<Item>::iterator); | ||||
| extern template PackGroup Nester<BottomLeftPlacer, FirstFitSelection>::execute( | ||||
|         std::vector<Item>::iterator, std::vector<Item>::iterator); | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| template<class Placer = NfpPlacer, | ||||
|          class Selector = FirstFitSelection, | ||||
|          class Iterator = std::vector<Item>::iterator> | ||||
|  | @ -60,19 +71,6 @@ PackGroup nest(Iterator from, Iterator to, | |||
|     return nester.execute(from, to); | ||||
| } | ||||
| 
 | ||||
| template<class Placer = NfpPlacer, | ||||
|          class Selector = FirstFitSelection, | ||||
|          class Container = std::vector<Item>> | ||||
| PackGroup nest(Container&& cont, | ||||
|                const typename Placer::BinType& bin, | ||||
|                Coord dist = 0, | ||||
|                const typename Placer::Config& pconf = {}, | ||||
|                const typename Selector::Config& sconf = {}) | ||||
| { | ||||
|     return nest<Placer, Selector>(cont.begin(), cont.end(), | ||||
|                                   bin, dist, pconf, sconf); | ||||
| } | ||||
| 
 | ||||
| template<class Placer = NfpPlacer, | ||||
|          class Selector = FirstFitSelection, | ||||
|          class Iterator = std::vector<Item>::iterator> | ||||
|  | @ -90,6 +88,42 @@ PackGroup nest(Iterator from, Iterator to, | |||
|     return nester.execute(from, to); | ||||
| } | ||||
| 
 | ||||
| #ifdef LIBNEST2D_STATIC | ||||
| 
 | ||||
| extern template class Nester<NfpPlacer, FirstFitSelection>; | ||||
| extern template class Nester<BottomLeftPlacer, FirstFitSelection>; | ||||
| 
 | ||||
| extern template PackGroup nest(std::vector<Item>::iterator from,  | ||||
|                                std::vector<Item>::iterator to, | ||||
|                                const Box& bin, | ||||
|                                Coord dist = 0, | ||||
|                                const NfpPlacer::Config& pconf, | ||||
|                                const FirstFitSelection::Config& sconf); | ||||
| 
 | ||||
| extern template PackGroup nest(std::vector<Item>::iterator from,  | ||||
|                                std::vector<Item>::iterator to, | ||||
|                                const Box& bin, | ||||
|                                ProgressFunction prg, | ||||
|                                StopCondition scond, | ||||
|                                Coord dist = 0, | ||||
|                                const NfpPlacer::Config& pconf, | ||||
|                                const FirstFitSelection::Config& sconf); | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| template<class Placer = NfpPlacer, | ||||
|          class Selector = FirstFitSelection, | ||||
|          class Container = std::vector<Item>> | ||||
| PackGroup nest(Container&& cont, | ||||
|                const typename Placer::BinType& bin, | ||||
|                Coord dist = 0, | ||||
|                const typename Placer::Config& pconf = {}, | ||||
|                const typename Selector::Config& sconf = {}) | ||||
| { | ||||
|     return nest<Placer, Selector>(cont.begin(), cont.end(), | ||||
|                                   bin, dist, pconf, sconf); | ||||
| } | ||||
| 
 | ||||
| template<class Placer = NfpPlacer, | ||||
|          class Selector = FirstFitSelection, | ||||
|          class Container = std::vector<Item>> | ||||
|  | @ -105,71 +139,6 @@ PackGroup nest(Container&& cont, | |||
|                                   bin, prg, scond, dist, pconf, sconf); | ||||
| } | ||||
| 
 | ||||
| #ifdef LIBNEST2D_STATIC | ||||
| extern template | ||||
| PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>&>( | ||||
|     std::vector<Item>& cont, | ||||
|     const Box& bin, | ||||
|     Coord dist, | ||||
|     const NfpPlacer::Config& pcfg, | ||||
|     const FirstFitSelection::Config& scfg | ||||
| ); | ||||
| 
 | ||||
| extern template | ||||
| PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>&>( | ||||
|     std::vector<Item>& cont, | ||||
|     const Box& bin, | ||||
|     ProgressFunction prg, | ||||
|     StopCondition scond, | ||||
|     Coord dist, | ||||
|     const NfpPlacer::Config& pcfg, | ||||
|     const FirstFitSelection::Config& scfg | ||||
| ); | ||||
| 
 | ||||
| extern template | ||||
| PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>>( | ||||
|     std::vector<Item>&& cont, | ||||
|     const Box& bin, | ||||
|     Coord dist, | ||||
|     const NfpPlacer::Config& pcfg, | ||||
|     const FirstFitSelection::Config& scfg | ||||
| ); | ||||
| 
 | ||||
| extern template | ||||
| PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>>( | ||||
|     std::vector<Item>&& cont, | ||||
|     const Box& bin, | ||||
|     ProgressFunction prg, | ||||
|     StopCondition scond, | ||||
|     Coord dist, | ||||
|     const NfpPlacer::Config& pcfg, | ||||
|     const FirstFitSelection::Config& scfg | ||||
| ); | ||||
| 
 | ||||
| extern template | ||||
| PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>::iterator>( | ||||
|     std::vector<Item>::iterator from, | ||||
|     std::vector<Item>::iterator to, | ||||
|     const Box& bin, | ||||
|     Coord dist, | ||||
|     const NfpPlacer::Config& pcfg, | ||||
|     const FirstFitSelection::Config& scfg | ||||
| ); | ||||
| 
 | ||||
| extern template | ||||
| PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>::iterator>( | ||||
|     std::vector<Item>::iterator from, | ||||
|     std::vector<Item>::iterator to, | ||||
|     const Box& bin, | ||||
|     ProgressFunction prg, | ||||
|     StopCondition scond, | ||||
|     Coord dist, | ||||
|     const NfpPlacer::Config& pcfg, | ||||
|     const FirstFitSelection::Config& scfg | ||||
| ); | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif // LIBNEST2D_H
 | ||||
|  |  | |||
|  | @ -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_link_libraries(clipperBackend INTERFACE Boost::boost ) | |||
| 
 | ||||
| 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) | ||||
| 
 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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<PointImpl> { | ||||
|     using Type = ClipperLib::cInt; | ||||
| }; | ||||
| 
 | ||||
| // Type of point used by Clipper
 | ||||
| template<> struct PointType<PolygonImpl> { | ||||
|     using Type = PointImpl; | ||||
| }; | ||||
| 
 | ||||
| template<> struct PointType<PathImpl> { | ||||
|     using Type = PointImpl; | ||||
| }; | ||||
| 
 | ||||
| template<> struct PointType<PointImpl> { | ||||
|     using Type = PointImpl; | ||||
| }; | ||||
| 
 | ||||
| template<> struct CountourType<PolygonImpl> { | ||||
|     using Type = PathImpl; | ||||
| }; | ||||
| 
 | ||||
| template<> struct ShapeTag<PolygonImpl> { using Type = PolygonTag; }; | ||||
| template<> struct ShapeTag<PathImpl> { using Type = PathTag; }; | ||||
| template<> struct ShapeTag<PointImpl> { using Type = PointTag; }; | ||||
| template<> struct ShapeTag<PathImpl>    { using Type = PathTag; }; | ||||
| template<> struct ShapeTag<PointImpl>   { using Type = PointTag; }; | ||||
| 
 | ||||
| template<> struct ShapeTag<TMultiShape<PolygonImpl>> { | ||||
|     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<PointImpl> { using Type = ClipperLib::cInt; }; | ||||
| 
 | ||||
| template<> struct PointType<TMultiShape<PolygonImpl>> { | ||||
|     using Type = PointImpl; | ||||
| }; | ||||
| // Enough to specialize for path, it will work for multishape and Polygon
 | ||||
| template<> struct PointType<PathImpl> { using Type = PointImpl; }; | ||||
| 
 | ||||
| template<> struct HolesContainer<PolygonImpl> { | ||||
|     using Type = ClipperLib::Paths; | ||||
| }; | ||||
| // This is crucial. CountourType refers to itself by default, so we don't have
 | ||||
| // to secialize for clipper Path. ContourType<PathImpl>::Type is PathImpl.
 | ||||
| template<> struct ContourType<PolygonImpl> { using Type = PathImpl; }; | ||||
| 
 | ||||
| // The holes are contained in Clipper::Paths
 | ||||
| template<> struct HolesContainer<PolygonImpl> { using Type = ClipperLib::Paths; }; | ||||
| 
 | ||||
| namespace pointlike { | ||||
| 
 | ||||
|  | @ -86,39 +66,11 @@ template<> inline TCoord<PointImpl>& y(PointImpl& p) | |||
| 
 | ||||
| } | ||||
| 
 | ||||
| // Using the libnest2d default area implementation
 | ||||
| #define DISABLE_BOOST_AREA | ||||
| 
 | ||||
| namespace _smartarea { | ||||
| 
 | ||||
| template<Orientation o> | ||||
| inline double area(const PolygonImpl& /*sh*/) { | ||||
|     return std::nan(""); | ||||
| } | ||||
| 
 | ||||
| template<> | ||||
| inline double area<Orientation::COUNTER_CLOCKWISE>(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<Orientation::CLOCKWISE>(const PolygonImpl& sh) { | ||||
|     return -area<Orientation::COUNTER_CLOCKWISE>(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<OrientationType<PolygonImpl>::Value>(sh); | ||||
| } | ||||
| 
 | ||||
| template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> 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<PolygonImpl> clipper_execute( | ||||
| inline TMultiShape<PolygonImpl> clipper_execute( | ||||
|         ClipperLib::Clipper& clipper, | ||||
|         ClipperLib::ClipType clipType, | ||||
|         ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd, | ||||
|         ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd) | ||||
| { | ||||
|     shapelike::Shapes<PolygonImpl> retv; | ||||
|     TMultiShape<PolygonImpl> retv; | ||||
| 
 | ||||
|     ClipperLib::PolyTree result; | ||||
|     clipper.Execute(clipType, result, subjFillType, clipFillType); | ||||
|  | @ -370,8 +295,8 @@ inline std::vector<PolygonImpl> clipper_execute( | |||
| 
 | ||||
| namespace nfp { | ||||
| 
 | ||||
| template<> inline std::vector<PolygonImpl> | ||||
| merge(const std::vector<PolygonImpl>& shapes) | ||||
| template<> inline TMultiShape<PolygonImpl> | ||||
| merge(const TMultiShape<PolygonImpl>& shapes) | ||||
| { | ||||
|     ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); | ||||
| 
 | ||||
|  | @ -394,6 +319,8 @@ merge(const std::vector<PolygonImpl>& shapes) | |||
| 
 | ||||
| } | ||||
| 
 | ||||
| #define DISABLE_BOOST_CONVEX_HULL | ||||
| 
 | ||||
| //#define DISABLE_BOOST_SERIALIZE
 | ||||
| //#define DISABLE_BOOST_UNSERIALIZE
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| #include <string> | ||||
| #include <cmath> | ||||
| #include <type_traits> | ||||
| #include <limits> | ||||
| 
 | ||||
| #if defined(_MSC_VER) &&  _MSC_VER <= 1800 || __cplusplus < 201103L | ||||
|     #define BP2D_NOEXCEPT | ||||
|  | @ -197,6 +198,33 @@ public: | |||
|     } | ||||
| }; | ||||
| 
 | ||||
| struct ScalarTag {}; | ||||
| struct BigIntTag {}; | ||||
| struct RationalTag {}; | ||||
| 
 | ||||
| template<class T> struct _NumTag {  | ||||
|     using Type =  | ||||
|         enable_if_t<std::is_arithmetic<T>::value, ScalarTag>;  | ||||
| }; | ||||
| 
 | ||||
| template<class T> using NumTag = typename _NumTag<remove_cvref_t<T>>::Type; | ||||
| 
 | ||||
| /// A local version for abs that is garanteed to work with libnest2d types
 | ||||
| template <class T> inline T abs(const T& v, ScalarTag)  | ||||
| {  | ||||
|     return std::abs(v);  | ||||
| } | ||||
| 
 | ||||
| template<class T> inline T abs(const T& v) { return abs(v, NumTag<T>()); } | ||||
| 
 | ||||
| template<class T2, class T1> inline T2 cast(const T1& v, ScalarTag, ScalarTag)  | ||||
| { | ||||
|     return static_cast<T2>(v);     | ||||
| } | ||||
| 
 | ||||
| template<class T2, class T1> inline T2 cast(const T1& v) {  | ||||
|     return cast<T2, T1>(v, NumTag<T1>(), NumTag<T2>()); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| #endif // LIBNEST2D_CONFIG_HPP
 | ||||
|  |  | |||
|  | @ -7,45 +7,125 @@ | |||
| #include <array> | ||||
| #include <vector> | ||||
| #include <numeric> | ||||
| #include <limits> | ||||
| #include <iterator> | ||||
| #include <cmath> | ||||
| #include <cstdint> | ||||
| 
 | ||||
| #include "common.hpp" | ||||
| #include <libnest2d/common.hpp> | ||||
| 
 | ||||
| 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<class Shape> struct ShapeTag { using Type = typename Shape::Tag; }; | ||||
| 
 | ||||
| /// Tag<S> will be used instead of `typename ShapeTag<S>::Type`
 | ||||
| template<class S> using Tag = typename ShapeTag<remove_cvref_t<S>>::Type; | ||||
| 
 | ||||
| /// Meta function to derive the contour type for a polygon which could be itself
 | ||||
| template<class RawShape> struct ContourType { using Type = RawShape; }; | ||||
| 
 | ||||
| /// TContour<RawShape> instead of `typename ContourType<RawShape>::type`
 | ||||
| template<class RawShape> | ||||
| using TContour = typename ContourType<remove_cvref_t<RawShape>>::Type; | ||||
| 
 | ||||
| /// Getting the type of point structure used by a shape.
 | ||||
| template<class Sh> struct PointType {  | ||||
|     using Type = typename PointType<TContour<Sh>>::Type;  | ||||
| }; | ||||
| 
 | ||||
| /// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
 | ||||
| template<class Shape> | ||||
| using TPoint = typename PointType<remove_cvref_t<Shape>>::Type; | ||||
| 
 | ||||
| /// Getting the coordinate data type for a geometry class.
 | ||||
| template<class GeomClass> struct CoordType { using Type = long; }; | ||||
| template<class GeomClass> struct CoordType {  | ||||
|     using Type = typename CoordType<TPoint<GeomClass>>::Type;  | ||||
| }; | ||||
| 
 | ||||
| /// TCoord<GeomType> as shorthand for typename `CoordType<GeomType>::Type`.
 | ||||
| template<class GeomType> | ||||
| using TCoord = typename CoordType<remove_cvref_t<GeomType>>::Type; | ||||
| 
 | ||||
| 
 | ||||
| /// Getting the type of point structure used by a shape.
 | ||||
| template<class Sh> 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<class T, bool = std::is_arithmetic<T>::value> struct ComputeType {}; | ||||
| 
 | ||||
| /// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
 | ||||
| template<class Shape> | ||||
| using TPoint = typename PointType<remove_cvref_t<Shape>>::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<class GeomClass> struct ComputeType<GeomClass, false> { | ||||
|     using Type = typename ComputeType<TCoord<GeomClass>>::Type; | ||||
| }; | ||||
| 
 | ||||
| /// libnest2d will choose a default compute type for various coordinate types
 | ||||
| /// if the backend has not specified anything.
 | ||||
| template<class T> struct DoublePrecision { using Type = T; }; | ||||
| template<> struct DoublePrecision<int8_t> { using Type = int16_t; }; | ||||
| template<> struct DoublePrecision<int16_t> { using Type = int32_t; }; | ||||
| template<> struct DoublePrecision<int32_t> { using Type = int64_t; }; | ||||
| template<> struct DoublePrecision<float> { using Type = double; }; | ||||
| template<> struct DoublePrecision<double> { using Type = long double; }; | ||||
| template<class I> struct ComputeType<I, true> { | ||||
|     using Type = typename DoublePrecision<I>::Type; | ||||
| }; | ||||
| 
 | ||||
| template<class RawShape> struct CountourType { using Type = RawShape; }; | ||||
| 
 | ||||
| template<class RawShape> | ||||
| using TContour = typename CountourType<remove_cvref_t<RawShape>>::Type; | ||||
| 
 | ||||
| /// TCompute<T> shorthand for `typename ComputeType<T>::Type`
 | ||||
| template<class T> using TCompute = typename ComputeType<remove_cvref_t<T>>::Type; | ||||
| 
 | ||||
| /// A meta function to derive a container type for holes in a polygon
 | ||||
| template<class RawShape> | ||||
| struct HolesContainer { using Type = std::vector<TContour<RawShape>>;  }; | ||||
| 
 | ||||
| /// Shorthand for `typename HolesContainer<RawShape>::Type`
 | ||||
| template<class RawShape> | ||||
| using THolesContainer = typename HolesContainer<remove_cvref_t<RawShape>>::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<Polygon>, TCoord<Path>, TCoord<Point> 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<class RawShape> | ||||
| 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<class S> struct DefaultMultiShape: public std::vector<S> { | ||||
|     using Tag = MultiPolygonTag; | ||||
|     template<class...Args> DefaultMultiShape(Args&&...args): | ||||
|         std::vector<S>(std::forward<Args>(args)...) {} | ||||
| }; | ||||
| 
 | ||||
| /// The MultiShape Type trait which gets the container type for a geometry type.
 | ||||
| template<class S> struct MultiShape { using Type = DefaultMultiShape<S>; }; | ||||
| 
 | ||||
| /// use TMultiShape<S> instead of `typename MultiShape<S>::Type`
 | ||||
| template<class S>  | ||||
| using TMultiShape = typename MultiShape<remove_cvref_t<S>>::Type; | ||||
| 
 | ||||
| // A specialization of ContourType to work with the default multishape type
 | ||||
| template<class S> struct ContourType<DefaultMultiShape<S>> { | ||||
|     using Type = typename ContourType<S>::Type; | ||||
| }; | ||||
| 
 | ||||
| enum class Orientation { | ||||
|     CLOCKWISE, | ||||
|  | @ -59,6 +139,11 @@ struct OrientationType { | |||
|     static const Orientation Value = Orientation::CLOCKWISE; | ||||
| }; | ||||
| 
 | ||||
| template<class T> inline /*constexpr*/ bool is_clockwise() {  | ||||
|     return OrientationType<TContour<T>>::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<class Shape> struct ShapeTag { using Type = typename Shape::Tag; }; | ||||
| template<class S> using Tag = typename ShapeTag<remove_cvref_t<S>>::Type; | ||||
| 
 | ||||
| template<class S> struct MultiShape { using Type = std::vector<S>; }; | ||||
| template<class S> | ||||
| using TMultiShape =typename MultiShape<remove_cvref_t<S>>::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<class Unit = TCompute<RawPoint>>  | ||||
|     inline Unit area() const BP2D_NOEXCEPT { | ||||
|         return Unit(width())*height(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| template<class S> struct PointType<_Box<S>> {  | ||||
|     using Type = typename _Box<S>::PointType;  | ||||
| }; | ||||
| 
 | ||||
| template<class RawPoint> | ||||
| 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<class S> struct PointType<_Circle<S>> { | ||||
|     using Type = typename _Circle<S>::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<class Unit = TCompute<RawPoint>> inline Unit sqlength() const; | ||||
|      | ||||
| }; | ||||
| 
 | ||||
| template<class S> struct PointType<_Segment<S>> {  | ||||
|     using Type = typename _Circle<S>::PointType;  | ||||
| }; | ||||
| 
 | ||||
| // This struct serves almost as a namespace. The only difference is that is can
 | ||||
|  | @ -216,33 +299,56 @@ inline TCoord<RawPoint>& y(RawPoint& p) | |||
|     return p.y(); | ||||
| } | ||||
| 
 | ||||
| template<class RawPoint> | ||||
| inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/) | ||||
| template<class RawPoint, class Unit = TCompute<RawPoint>> | ||||
| inline Unit squaredDistance(const RawPoint& p1, const RawPoint& p2) | ||||
| { | ||||
|     static_assert(always_false<RawPoint>::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<class RawPoint> | ||||
| inline double distance(const RawPoint& /*p1*/, | ||||
|                        const _Segment<RawPoint>& /*s*/) | ||||
| inline double distance(const RawPoint& p1, const RawPoint& p2) | ||||
| { | ||||
|     static_assert(always_false<RawPoint>::value, | ||||
|                   "PointLike::distance(point, segment) unimplemented!"); | ||||
|     return 0; | ||||
|     return std::sqrt(squaredDistance<RawPoint, double>(p1, p2)); | ||||
| } | ||||
| 
 | ||||
| template<class RawPoint> | ||||
| inline std::pair<TCoord<RawPoint>, bool> horizontalDistance( | ||||
| // create perpendicular vector
 | ||||
| template<class Pt> inline Pt perp(const Pt& p)  | ||||
| {  | ||||
|     return Pt(y(p), -x(p)); | ||||
| } | ||||
| 
 | ||||
| template<class Pt, class Unit = TCompute<Pt>>  | ||||
| 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<class Pt, class Unit = TCompute<Pt>>  | ||||
| inline Unit dot(const Pt& a, const Pt& b)  | ||||
| { | ||||
|     return Unit(x(a)) * x(b) + Unit(y(a)) * y(b); | ||||
| } | ||||
| 
 | ||||
| // squared vector magnitude
 | ||||
| template<class Pt, class Unit = TCompute<Pt>>  | ||||
| inline Unit magnsq(const Pt& p)  | ||||
| { | ||||
|     return  Unit(x(p)) * x(p) + Unit(y(p)) * y(p); | ||||
| } | ||||
| 
 | ||||
| template<class RawPoint, class Unit = TCompute<RawPoint>> | ||||
| inline std::pair<Unit, bool> horizontalDistance( | ||||
|         const RawPoint& p, const _Segment<RawPoint>& s) | ||||
| { | ||||
|     using Unit = TCoord<RawPoint>; | ||||
|     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<RawPoint> ret; | ||||
|     Unit ret; | ||||
| 
 | ||||
|     if( (y < y1 && y < y2) || (y > y1 && y > y2) ) | ||||
|         return {0, false}; | ||||
|  | @ -250,8 +356,7 @@ inline std::pair<TCoord<RawPoint>, 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<Unit>::epsilon() && | ||||
|             std::abs(y - y2) <= std::numeric_limits<Unit>::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<TCoord<RawPoint>, bool> horizontalDistance( | |||
|     return {ret, true}; | ||||
| } | ||||
| 
 | ||||
| template<class RawPoint> | ||||
| inline std::pair<TCoord<RawPoint>, bool> verticalDistance( | ||||
| template<class RawPoint, class Unit = TCompute<RawPoint>> | ||||
| inline std::pair<Unit, bool> verticalDistance( | ||||
|         const RawPoint& p, const _Segment<RawPoint>& s) | ||||
| { | ||||
|     using Unit = TCoord<RawPoint>; | ||||
|     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<RawPoint> ret; | ||||
|     Unit ret; | ||||
| 
 | ||||
|     if( (x < x1 && x < x2) || (x > x1 && x > x2) ) | ||||
|         return {0, false}; | ||||
|  | @ -276,8 +381,7 @@ inline std::pair<TCoord<RawPoint>, 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<Unit>::epsilon() && | ||||
|             std::abs(x - x2) <= std::numeric_limits<Unit>::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<RawPoint>::angleToXaxis() const | |||
| } | ||||
| 
 | ||||
| template<class RawPoint> | ||||
| inline double _Segment<RawPoint>::length() | ||||
| template<class Unit> | ||||
| inline Unit _Segment<RawPoint>::sqlength() const | ||||
| { | ||||
|     return pointlike::distance(first(), second()); | ||||
|     return pointlike::squaredDistance<RawPoint, Unit>(first(), second()); | ||||
| } | ||||
| 
 | ||||
| template<class RawPoint> | ||||
|  | @ -346,8 +451,8 @@ inline RawPoint _Box<RawPoint>::center() const BP2D_NOEXCEPT { | |||
|     using Coord = TCoord<RawPoint>; | ||||
| 
 | ||||
|     RawPoint ret =  { // No rounding here, we dont know if these are int coords
 | ||||
|         static_cast<Coord>( (getX(minc) + getX(maxc))/2.0 ), | ||||
|         static_cast<Coord>( (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<class RawShape> | ||||
| using Shapes = TMultiShape<RawShape>; | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline RawShape create(const TContour<RawShape>& contour, | ||||
|                        const THolesContainer<RawShape>& holes) | ||||
|  | @ -449,7 +551,7 @@ inline void reserve(RawPath& p, size_t vertex_capacity, const PathTag&) | |||
| template<class RawShape, class...Args> | ||||
| inline void addVertex(RawShape& sh, const PathTag&, Args...args) | ||||
| { | ||||
|     return sh.emplace_back(std::forward<Args>(args)...); | ||||
|     sh.emplace_back(std::forward<Args>(args)...); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape, class Fn> | ||||
|  | @ -504,13 +606,8 @@ inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/) | |||
|                   "shapelike::unserialize() unimplemented!"); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline double area(const RawShape& /*sh*/, const PolygonTag&) | ||||
| { | ||||
|     static_assert(always_false<RawShape>::value, | ||||
|                   "shapelike::area() unimplemented!"); | ||||
|     return 0; | ||||
| } | ||||
| template<class Cntr, class Unit = double> | ||||
| inline Unit area(const Cntr& poly, const PathTag& ); | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) | ||||
|  | @ -556,14 +653,14 @@ inline bool touches( const TPoint<RawShape>& /*point*/, | |||
| 
 | ||||
| template<class RawShape> | ||||
| inline _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/, | ||||
|                                           const PolygonTag&) | ||||
|                                           const PathTag&) | ||||
| { | ||||
|     static_assert(always_false<RawShape>::value, | ||||
|                   "shapelike::boundingBox(shape) unimplemented!"); | ||||
| } | ||||
| 
 | ||||
| template<class RawShapes> | ||||
| inline _Box<TPoint<typename RawShapes::value_type>> | ||||
| inline _Box<TPoint<RawShapes>> | ||||
| boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) | ||||
| { | ||||
|     static_assert(always_false<RawShapes>::value, | ||||
|  | @ -571,21 +668,10 @@ boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) | |||
| } | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&) | ||||
| { | ||||
|     static_assert(always_false<RawShape>::value, | ||||
|                   "shapelike::convexHull(shape) unimplemented!"); | ||||
|     return RawShape(); | ||||
| } | ||||
| inline RawShape convexHull(const RawShape& sh, const PathTag&); | ||||
| 
 | ||||
| template<class RawShapes> | ||||
| inline typename RawShapes::value_type | ||||
| convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&) | ||||
| { | ||||
|     static_assert(always_false<RawShapes>::value, | ||||
|                   "shapelike::convexHull(shapes) unimplemented!"); | ||||
|     return typename RawShapes::value_type(); | ||||
| } | ||||
| template<class RawShapes, class S = typename RawShapes::value_type> | ||||
| inline S convexHull(const RawShapes& sh, const MultiPolygonTag&); | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/) | ||||
|  | @ -745,13 +831,19 @@ inline void reserve(T& sh, size_t vertex_capacity) { | |||
| template<class RawShape, class...Args> | ||||
| inline void addVertex(RawShape& sh, const PolygonTag&, Args...args) | ||||
| { | ||||
|     return addVertex(contour(sh), PathTag(), std::forward<Args>(args)...); | ||||
|     addVertex(contour(sh), PathTag(), std::forward<Args>(args)...); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape, class...Args> // Tag dispatcher
 | ||||
| inline void addVertex(RawShape& sh, Args...args) | ||||
| { | ||||
|     return addVertex(sh, Tag<RawShape>(), std::forward<Args>(args)...); | ||||
|     addVertex(sh, Tag<RawShape>(), std::forward<Args>(args)...); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline _Box<TPoint<RawShape>> boundingBox(const RawShape& poly, const PolygonTag&) | ||||
| { | ||||
|     return boundingBox(contour(poly), PathTag()); | ||||
| } | ||||
| 
 | ||||
| template<class Box> | ||||
|  | @ -786,7 +878,7 @@ inline _Box<TPoint<S>> boundingBox(const S& sh) | |||
| template<class Box> | ||||
| inline double area(const Box& box, const BoxTag& ) | ||||
| { | ||||
|     return box.area(); | ||||
|     return box.template area<double>(); | ||||
| } | ||||
| 
 | ||||
| template<class Circle> | ||||
|  | @ -795,6 +887,35 @@ inline double area(const Circle& circ, const CircleTag& ) | |||
|     return circ.area(); | ||||
| } | ||||
| 
 | ||||
| template<class Cntr, class Unit> | ||||
| 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<Cntr>() ? a : -a; | ||||
| } | ||||
| 
 | ||||
| template<class S> 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<S> &h){ | ||||
|         return a + area(h, PathTag());     | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape> // Dispatching function
 | ||||
| inline double area(const RawShape& sh) | ||||
| { | ||||
|  | @ -811,6 +932,12 @@ inline double area(const RawShapes& shapes, const MultiPolygonTag&) | |||
|     }); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline RawShape convexHull(const RawShape& sh, const PolygonTag&) | ||||
| { | ||||
|     return create<RawShape>(convexHull(contour(sh), PathTag())); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline auto convexHull(const RawShape& sh) | ||||
|     -> decltype(convexHull(sh, Tag<RawShape>())) // TODO: C++14 could deduce
 | ||||
|  | @ -818,11 +945,91 @@ inline auto convexHull(const RawShape& sh) | |||
|     return convexHull(sh, Tag<RawShape>()); | ||||
| } | ||||
| 
 | ||||
| template<class RawShape> | ||||
| inline RawShape convexHull(const RawShape& sh, const PathTag&) | ||||
| { | ||||
|     using Unit = TCompute<RawShape>; | ||||
|     using Point = TPoint<RawShape>; | ||||
|     namespace sl = shapelike; | ||||
|      | ||||
|     size_t edges = sl::cend(sh) - sl::cbegin(sh); | ||||
|     if(edges <= 3) return {}; | ||||
|      | ||||
|     bool closed = false; | ||||
|     std::vector<Point> U, L; | ||||
|     U.reserve(1 + edges / 2); L.reserve(1 + edges / 2); | ||||
|      | ||||
|     std::vector<Point> 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<RawShape>()) { | ||||
|         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<class RawShapes, class S> | ||||
| 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<S>()); | ||||
| } | ||||
| 
 | ||||
| template<class TP, class TC> | ||||
| 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<class TP, class TB> | ||||
|  | @ -972,6 +1179,9 @@ template<class RawShape> inline bool isConvex(const RawShape& sh) // dispatch | |||
|     using Segment = _Segment<Point>; \ | ||||
|     using Polygons = TMultiShape<T> | ||||
| 
 | ||||
| namespace sl = shapelike; | ||||
| namespace pl = pointlike; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif // GEOMETRY_TRAITS_HPP
 | ||||
|  |  | |||
|  | @ -1,26 +1,22 @@ | |||
| #ifndef GEOMETRIES_NOFITPOLYGON_HPP | ||||
| #define GEOMETRIES_NOFITPOLYGON_HPP | ||||
| 
 | ||||
| #include "geometry_traits.hpp" | ||||
| #include <algorithm> | ||||
| #include <functional> | ||||
| #include <vector> | ||||
| #include <iterator> | ||||
| 
 | ||||
| #include <libnest2d/geometry_traits.hpp> | ||||
| 
 | ||||
| namespace libnest2d { | ||||
| 
 | ||||
| namespace __nfp { | ||||
| // Do not specialize this...
 | ||||
| template<class RawShape> | ||||
| template<class RawShape, class Unit = TCompute<RawShape>> | ||||
| inline bool _vsort(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2) | ||||
| { | ||||
|     using Coord = TCoord<TPoint<RawShape>>; | ||||
|     Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2); | ||||
|     auto diff = y1 - y2; | ||||
|     if(std::abs(diff) <= std::numeric_limits<Coord>::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<class EdgeList, class RawShape, class Vertex = TPoint<RawShape>> | ||||
|  | @ -202,7 +198,7 @@ inline TPoint<RawShape> referenceVertex(const RawShape& sh) | |||
|  * convex as well in this case. | ||||
|  * | ||||
|  */ | ||||
| template<class RawShape> | ||||
| template<class RawShape, class Ratio = double> | ||||
| inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh, | ||||
|                                          const RawShape& other) | ||||
| { | ||||
|  | @ -238,12 +234,62 @@ inline NfpResult<RawShape> 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<int, 4> quadrants {0, 3, 1, 2 }; | ||||
| 
 | ||||
|         std::array<int, 2> q {0, 0}; // Quadrant indices for p1 and p2
 | ||||
| 
 | ||||
|         using TDots = std::array<TCompute<Vertex>, 2>; | ||||
|         TDots lcos { pl::dot(p1, ax), pl::dot(p2, ax) }; | ||||
|         TDots lsin { -pl::dotperp(p1, ax), -pl::dotperp(p2, ax) }; | ||||
| 
 | ||||
|         // Construct the quadrant indices for p1 and p2
 | ||||
|         for(size_t i = 0; i < 2; ++i) | ||||
|             if(lcos[i] == 0) q[i] = lsin[i] > 0 ? 1 : 3; | ||||
|             else if(lsin[i] == 0) q[i] = lcos[i] > 0 ? 0 : 2; | ||||
|             else q[i] = quadrants[((lcos[i] < 0) << 1) + (lsin[i] < 0)]; | ||||
|              | ||||
|         if(q[0] == q[1]) { // only bother if p1 and p2 are in the same quadrant
 | ||||
|             auto lsq1 = pl::magnsq(p1);     // squared magnitudes, avoid sqrt
 | ||||
|             auto lsq2 = pl::magnsq(p2);     // squared magnitudes, avoid sqrt
 | ||||
| 
 | ||||
|             // We will actually compare l^2 * cos^2(phi) which saturates the
 | ||||
|             // cos function. But with the quadrant info we can get the sign back
 | ||||
|             int sign = q[0] == 1 || q[0] == 2 ? -1 : 1; | ||||
|              | ||||
|             // If Ratio is an actual rational type, there is no precision loss
 | ||||
|             auto pcos1 = Ratio(lcos[0]) / lsq1 * sign * lcos[0]; | ||||
|             auto pcos2 = Ratio(lcos[1]) / lsq2 * sign * lcos[1]; | ||||
|              | ||||
|             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<RawShape> nfpConvexOnly(const RawShape& sh, | |||
| 
 | ||||
| template<class RawShape> | ||||
| NfpResult<RawShape> 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<RawShape>; | ||||
|     using Vertex = TPoint<RawShape>; | ||||
|     using Coord = TCoord<Vertex>; | ||||
|     using Edge = _Segment<Vertex>; | ||||
|     namespace sl = shapelike; | ||||
|     using std::signbit; | ||||
|     using std::sort; | ||||
|     using std::vector; | ||||
|     using std::ref; | ||||
|     using std::reference_wrapper; | ||||
| 
 | ||||
|     // TODO The original algorithms expects the stationary polygon in
 | ||||
|     // counter clockwise and the orbiter in clockwise order.
 | ||||
|     // So for preventing any further complication, I will make the input
 | ||||
|     // the way it should be, than make my way around the orientations.
 | ||||
| 
 | ||||
|     // Reverse the stationary contour to counter clockwise
 | ||||
|     auto stcont = sl::contour(cstationary); | ||||
|     { | ||||
|         std::reverse(sl::begin(stcont), sl::end(stcont)); | ||||
|         stcont.pop_back(); | ||||
|         auto it = std::min_element(sl::begin(stcont), sl::end(stcont), | ||||
|                                [](const Vertex& v1, const Vertex& v2) { | ||||
|             return getY(v1) < getY(v2); | ||||
|         }); | ||||
|         std::rotate(sl::begin(stcont), it, sl::end(stcont)); | ||||
|         sl::addVertex(stcont, sl::front(stcont)); | ||||
|     } | ||||
|     RawShape stationary; | ||||
|     sl::contour(stationary) = stcont; | ||||
| 
 | ||||
|     // Reverse the orbiter contour to counter clockwise
 | ||||
|     auto orbcont = sl::contour(cother); | ||||
|     { | ||||
|         std::reverse(orbcont.begin(), orbcont.end()); | ||||
| 
 | ||||
|         // Step 1: Make the orbiter reverse oriented
 | ||||
| 
 | ||||
|         orbcont.pop_back(); | ||||
|         auto it = std::min_element(orbcont.begin(), orbcont.end(), | ||||
|                               [](const Vertex& v1, const Vertex& v2) { | ||||
|             return getY(v1) < getY(v2); | ||||
|         }); | ||||
| 
 | ||||
|         std::rotate(orbcont.begin(), it, orbcont.end()); | ||||
|         orbcont.emplace_back(orbcont.front()); | ||||
| 
 | ||||
|         for(auto &v : orbcont) v = -v; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     // Copy the orbiter (contour only), we will have to work on it
 | ||||
|     RawShape orbiter; | ||||
|     sl::contour(orbiter) = orbcont; | ||||
| 
 | ||||
|     // An edge with additional data for marking it
 | ||||
|     struct MarkedEdge { | ||||
|         Edge e; Radians turn_angle = 0; bool is_turning_point = false; | ||||
|         MarkedEdge() = default; | ||||
|         MarkedEdge(const Edge& ed, Radians ta, bool tp): | ||||
|             e(ed), turn_angle(ta), is_turning_point(tp) {} | ||||
| 
 | ||||
|         // debug
 | ||||
|         std::string label; | ||||
|     }; | ||||
| 
 | ||||
|     // Container for marked edges
 | ||||
|     using EdgeList = vector<MarkedEdge>; | ||||
| 
 | ||||
|     EdgeList A, B; | ||||
| 
 | ||||
|     // This is how an edge list is created from the polygons
 | ||||
|     auto fillEdgeList = [](EdgeList& L, const RawShape& ppoly, int dir) { | ||||
|         auto& poly = sl::contour(ppoly); | ||||
| 
 | ||||
|         L.reserve(sl::contourVertexCount(poly)); | ||||
| 
 | ||||
|         if(dir > 0) { | ||||
|             auto it = poly.begin(); | ||||
|             auto nextit = std::next(it); | ||||
| 
 | ||||
|             double turn_angle = 0; | ||||
|             bool is_turn_point = false; | ||||
| 
 | ||||
|             while(nextit != poly.end()) { | ||||
|                 L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); | ||||
|                 it++; nextit++; | ||||
|             } | ||||
|         } else { | ||||
|             auto it = sl::rbegin(poly); | ||||
|             auto nextit = std::next(it); | ||||
| 
 | ||||
|             double turn_angle = 0; | ||||
|             bool is_turn_point = false; | ||||
| 
 | ||||
|             while(nextit != sl::rend(poly)) { | ||||
|                 L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); | ||||
|                 it++; nextit++; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         auto getTurnAngle = [](const Edge& e1, const Edge& e2) { | ||||
|             auto phi = e1.angleToXaxis(); | ||||
|             auto phi_prev = e2.angleToXaxis(); | ||||
|             auto turn_angle = phi-phi_prev; | ||||
|             if(turn_angle > Pi) turn_angle -= TwoPi; | ||||
|             if(turn_angle < -Pi) turn_angle += TwoPi; | ||||
|             return turn_angle; | ||||
|         }; | ||||
| 
 | ||||
|         auto eit = L.begin(); | ||||
|         auto enext = std::next(eit); | ||||
| 
 | ||||
|         eit->turn_angle = getTurnAngle(L.front().e, L.back().e); | ||||
| 
 | ||||
|         while(enext != L.end()) { | ||||
|             enext->turn_angle = getTurnAngle( enext->e, eit->e); | ||||
|             eit->is_turning_point = | ||||
|                     signbit(enext->turn_angle) != signbit(eit->turn_angle); | ||||
|             ++eit; ++enext; | ||||
|         } | ||||
| 
 | ||||
|         L.back().is_turning_point = signbit(L.back().turn_angle) != | ||||
|                                     signbit(L.front().turn_angle); | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|     // Step 2: Fill the edgelists
 | ||||
|     fillEdgeList(A, stationary, 1); | ||||
|     fillEdgeList(B, orbiter, 1); | ||||
| 
 | ||||
|     int i = 1; | ||||
|     for(MarkedEdge& me : A) { | ||||
|         std::cout << "a" << i << ":\n\t" | ||||
|                   << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t" | ||||
|                   << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t" | ||||
|                   << "Turning point: " << (me.is_turning_point ? "yes" : "no") | ||||
|                   << std::endl; | ||||
| 
 | ||||
|         me.label = "a"; me.label += std::to_string(i); | ||||
|         i++; | ||||
|     } | ||||
| 
 | ||||
|     i = 1; | ||||
|     for(MarkedEdge& me : B) { | ||||
|         std::cout << "b" << i << ":\n\t" | ||||
|                   << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t" | ||||
|                   << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t" | ||||
|                   << "Turning point: " << (me.is_turning_point ? "yes" : "no") | ||||
|                   << std::endl; | ||||
|         me.label = "b"; me.label += std::to_string(i); | ||||
|         i++; | ||||
|     } | ||||
| 
 | ||||
|     // A reference to a marked edge that also knows its container
 | ||||
|     struct MarkedEdgeRef { | ||||
|         reference_wrapper<MarkedEdge> eref; | ||||
|         reference_wrapper<vector<MarkedEdgeRef>> container; | ||||
|         Coord dir = 1;  // Direction modifier
 | ||||
| 
 | ||||
|         inline Radians angleX() const { return eref.get().e.angleToXaxis(); } | ||||
|         inline const Edge& edge() const { return eref.get().e; } | ||||
|         inline Edge& edge() { return eref.get().e; } | ||||
|         inline bool isTurningPoint() const { | ||||
|             return eref.get().is_turning_point; | ||||
|         } | ||||
|         inline bool isFrom(const vector<MarkedEdgeRef>& cont ) { | ||||
|             return &(container.get()) == &cont; | ||||
|         } | ||||
|         inline bool eq(const MarkedEdgeRef& mr) { | ||||
|             return &(eref.get()) == &(mr.eref.get()); | ||||
|         } | ||||
| 
 | ||||
|         MarkedEdgeRef(reference_wrapper<MarkedEdge> er, | ||||
|                       reference_wrapper<vector<MarkedEdgeRef>> ec): | ||||
|             eref(er), container(ec), dir(1) {} | ||||
| 
 | ||||
|         MarkedEdgeRef(reference_wrapper<MarkedEdge> er, | ||||
|                       reference_wrapper<vector<MarkedEdgeRef>> ec, | ||||
|                       Coord d): | ||||
|             eref(er), container(ec), dir(d) {} | ||||
|     }; | ||||
| 
 | ||||
|     using EdgeRefList = vector<MarkedEdgeRef>; | ||||
| 
 | ||||
|     // Comparing two marked edges
 | ||||
|     auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) { | ||||
|         return e1.angleX() < e2.angleX(); | ||||
|     }; | ||||
| 
 | ||||
|     EdgeRefList Aref, Bref;     // We create containers for the references
 | ||||
|     Aref.reserve(A.size()); Bref.reserve(B.size()); | ||||
| 
 | ||||
|     // Fill reference container for the stationary polygon
 | ||||
|     std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) { | ||||
|         Aref.emplace_back( ref(me), ref(Aref) ); | ||||
|     }); | ||||
| 
 | ||||
|     // Fill reference container for the orbiting polygon
 | ||||
|     std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) { | ||||
|         Bref.emplace_back( ref(me), ref(Bref) ); | ||||
|     }); | ||||
| 
 | ||||
|     auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure
 | ||||
|             (const EdgeRefList& Q, const EdgeRefList& R, bool positive) | ||||
|     { | ||||
| 
 | ||||
|         // Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)"
 | ||||
|         // Sort the containers of edge references and merge them.
 | ||||
|         // Q could be sorted only once and be reused here but we would still
 | ||||
|         // need to merge it with sorted(R).
 | ||||
| 
 | ||||
|         EdgeRefList merged; | ||||
|         EdgeRefList S, seq; | ||||
|         merged.reserve(Q.size() + R.size()); | ||||
| 
 | ||||
|         merged.insert(merged.end(), R.begin(), R.end()); | ||||
|         std::stable_sort(merged.begin(), merged.end(), sortfn); | ||||
|         merged.insert(merged.end(), Q.begin(), Q.end()); | ||||
|         std::stable_sort(merged.begin(), merged.end(), sortfn); | ||||
| 
 | ||||
|         // Step 2 "set i = 1, k = 1, direction = 1, s1 = q1"
 | ||||
|         // we don't use i, instead, q is an iterator into Q. k would be an index
 | ||||
|         // into the merged sequence but we use "it" as an iterator for that
 | ||||
| 
 | ||||
|         // here we obtain references for the containers for later comparisons
 | ||||
|         const auto& Rcont = R.begin()->container.get(); | ||||
|         const auto& Qcont = Q.begin()->container.get(); | ||||
| 
 | ||||
|         // Set the initial direction
 | ||||
|         Coord dir = 1; | ||||
| 
 | ||||
|         // roughly i = 1 (so q = Q.begin()) and s1 = q1 so S[0] = q;
 | ||||
|         if(positive) { | ||||
|             auto q = Q.begin(); | ||||
|             S.emplace_back(*q); | ||||
| 
 | ||||
|             // Roughly step 3
 | ||||
| 
 | ||||
|             std::cout << "merged size: " << merged.size() << std::endl; | ||||
|             auto mit = merged.begin(); | ||||
|             for(bool finish = false; !finish && q != Q.end();) { | ||||
|                 ++q; // "Set i = i + 1"
 | ||||
| 
 | ||||
|                 while(!finish && mit != merged.end()) { | ||||
|                     if(mit->isFrom(Rcont)) { | ||||
|                         auto s = *mit; | ||||
|                         s.dir = dir; | ||||
|                         S.emplace_back(s); | ||||
|                     } | ||||
| 
 | ||||
|                     if(mit->eq(*q)) { | ||||
|                         S.emplace_back(*q); | ||||
|                         if(mit->isTurningPoint()) dir = -dir; | ||||
|                         if(q == Q.begin()) finish = true; | ||||
|                         break; | ||||
|                     } | ||||
| 
 | ||||
|                     mit += dir; | ||||
|     //                __nfp::advance(mit, merged, dir > 0);
 | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             auto q = Q.rbegin(); | ||||
|             S.emplace_back(*q); | ||||
| 
 | ||||
|             // Roughly step 3
 | ||||
| 
 | ||||
|             std::cout << "merged size: " << merged.size() << std::endl; | ||||
|             auto mit = merged.begin(); | ||||
|             for(bool finish = false; !finish && q != Q.rend();) { | ||||
|                 ++q; // "Set i = i + 1"
 | ||||
| 
 | ||||
|                 while(!finish && mit != merged.end()) { | ||||
|                     if(mit->isFrom(Rcont)) { | ||||
|                         auto s = *mit; | ||||
|                         s.dir = dir; | ||||
|                         S.emplace_back(s); | ||||
|                     } | ||||
| 
 | ||||
|                     if(mit->eq(*q)) { | ||||
|                         S.emplace_back(*q); | ||||
|                         S.back().dir = -1; | ||||
|                         if(mit->isTurningPoint()) dir = -dir; | ||||
|                         if(q == Q.rbegin()) finish = true; | ||||
|                         break; | ||||
|                     } | ||||
| 
 | ||||
|                     mit += dir; | ||||
|             //                __nfp::advance(mit, merged, dir > 0);
 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         // Step 4:
 | ||||
| 
 | ||||
|         // "Let starting edge r1 be in position si in sequence"
 | ||||
|         // whaaat? I guess this means the following:
 | ||||
|         auto it = S.begin(); | ||||
|         while(!it->eq(*R.begin())) ++it; | ||||
| 
 | ||||
|         // "Set j = 1, next = 2, direction = 1, seq1 = si"
 | ||||
|         // we don't use j, seq is expanded dynamically.
 | ||||
|         dir = 1; | ||||
|         auto next = std::next(R.begin()); seq.emplace_back(*it); | ||||
| 
 | ||||
|         // Step 5:
 | ||||
|         // "If all si edges have been allocated to seqj" should mean that
 | ||||
|         // we loop until seq has equal size with S
 | ||||
|         auto send = it; //it == S.begin() ? it : std::prev(it);
 | ||||
|         while(it != S.end()) { | ||||
|             ++it; if(it == S.end()) it = S.begin(); | ||||
|             if(it == send) break; | ||||
| 
 | ||||
|             if(it->isFrom(Qcont)) { | ||||
|                 seq.emplace_back(*it); // "If si is from Q, j = j + 1, seqj = si"
 | ||||
| 
 | ||||
|                 // "If si is a turning point in Q,
 | ||||
|                 // direction = - direction, next = next + direction"
 | ||||
|                 if(it->isTurningPoint()) { | ||||
|                     dir = -dir; | ||||
|                     next += dir; | ||||
| //                    __nfp::advance(next, R, dir > 0);
 | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if(it->eq(*next) /*&& dir == next->dir*/) { // "If si = direction.rnext"
 | ||||
|                 // "j = j + 1, seqj = si, next = next + direction"
 | ||||
|                 seq.emplace_back(*it); | ||||
|                 next += dir; | ||||
| //                __nfp::advance(next, R, dir > 0);
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return seq; | ||||
|     }; | ||||
| 
 | ||||
|     std::vector<EdgeRefList> seqlist; | ||||
|     seqlist.reserve(Bref.size()); | ||||
| 
 | ||||
|     EdgeRefList Bslope = Bref;  // copy Bref, we will make a slope diagram
 | ||||
| 
 | ||||
|     // make the slope diagram of B
 | ||||
|     std::sort(Bslope.begin(), Bslope.end(), sortfn); | ||||
| 
 | ||||
|     auto slopeit = Bslope.begin(); // search for the first turning point
 | ||||
|     while(!slopeit->isTurningPoint() && slopeit != Bslope.end()) slopeit++; | ||||
| 
 | ||||
|     if(slopeit == Bslope.end()) { | ||||
|         // no turning point means convex polygon.
 | ||||
|         seqlist.emplace_back(mink(Aref, Bref, true)); | ||||
|     } else { | ||||
|         int dir = 1; | ||||
| 
 | ||||
|         auto firstturn = Bref.begin(); | ||||
|         while(!firstturn->eq(*slopeit)) ++firstturn; | ||||
| 
 | ||||
|         assert(firstturn != Bref.end()); | ||||
| 
 | ||||
|         EdgeRefList bgroup; bgroup.reserve(Bref.size()); | ||||
|         bgroup.emplace_back(*slopeit); | ||||
| 
 | ||||
|         auto b_it = std::next(firstturn); | ||||
|         while(b_it != firstturn) { | ||||
|             if(b_it == Bref.end()) b_it = Bref.begin(); | ||||
| 
 | ||||
|             while(!slopeit->eq(*b_it)) { | ||||
|                 __nfp::advance(slopeit, Bslope, dir > 0); | ||||
|             } | ||||
| 
 | ||||
|             if(!slopeit->isTurningPoint()) { | ||||
|                 bgroup.emplace_back(*slopeit); | ||||
|             } else { | ||||
|                 if(!bgroup.empty()) { | ||||
|                     if(dir > 0) bgroup.emplace_back(*slopeit); | ||||
|                     for(auto& me : bgroup) { | ||||
|                         std::cout << me.eref.get().label << ", "; | ||||
|                     } | ||||
|                     std::cout << std::endl; | ||||
|                     seqlist.emplace_back(mink(Aref, bgroup, dir == 1 ? true : false)); | ||||
|                     bgroup.clear(); | ||||
|                     if(dir < 0) bgroup.emplace_back(*slopeit); | ||||
|                 } else { | ||||
|                     bgroup.emplace_back(*slopeit); | ||||
|                 } | ||||
| 
 | ||||
|                 dir *= -1; | ||||
|             } | ||||
|             ++b_it; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| //    while(it != Bref.end()) // This is step 3 and step 4 in one loop
 | ||||
| //        if(it->isTurningPoint()) {
 | ||||
| //            R = {R.last, it++};
 | ||||
| //            auto seq = mink(Q, R, orientation);
 | ||||
| 
 | ||||
| //            // TODO step 6 (should be 5 shouldn't it?): linking edges from A
 | ||||
| //            // I don't get this step
 | ||||
| 
 | ||||
| //            seqlist.insert(seqlist.end(), seq.begin(), seq.end());
 | ||||
| //            orientation = !orientation;
 | ||||
| //        } else ++it;
 | ||||
| 
 | ||||
| //    if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true);
 | ||||
| 
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|     // Algorithm 2: breaking Minkowski sums into track line trips
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| 
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|     // Algorithm 3: finding the boundary of the NFP from track line trips
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| 
 | ||||
|     for(auto& seq : seqlist) { | ||||
|         std::cout << "seqlist size: " << seq.size() << std::endl; | ||||
|         for(auto& s : seq) { | ||||
|             std::cout << (s.dir > 0 ? "" : "-") << s.eref.get().label << ", "; | ||||
|         } | ||||
|         std::cout << std::endl; | ||||
|     } | ||||
| 
 | ||||
|     auto& seq = seqlist.front(); | ||||
|     RawShape rsh; | ||||
|     Vertex top_nfp; | ||||
|     std::vector<Edge> edgelist; edgelist.reserve(seq.size()); | ||||
|     for(auto& s : seq) { | ||||
|         edgelist.emplace_back(s.eref.get().e); | ||||
|     } | ||||
| 
 | ||||
|     __nfp::buildPolygon(edgelist, rsh, top_nfp); | ||||
| 
 | ||||
|     return Result(rsh, top_nfp); | ||||
|     return {}; | ||||
| } | ||||
| 
 | ||||
| // Specializable NFP implementation class. Specialize it if you have a faster
 | ||||
|  |  | |||
|  | @ -8,13 +8,10 @@ | |||
| #include <algorithm> | ||||
| #include <functional> | ||||
| 
 | ||||
| #include "geometry_traits.hpp" | ||||
| #include <libnest2d/geometry_traits.hpp> | ||||
| 
 | ||||
| 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<Coord>::epsilon()) | ||||
|             return x1 < x2; | ||||
| 
 | ||||
|         return diff < 0; | ||||
|         TCompute<Vertex> x1 = getX(v1), x2 = getX(v2); | ||||
|         TCompute<Vertex> y1 = getY(v1), y2 = getY(v2); | ||||
|         return y1 == y2 ? x1 < x2 : y1 < y2; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,7 +4,8 @@ | |||
| #include <tuple> | ||||
| #include <functional> | ||||
| #include <limits> | ||||
| #include "common.hpp" | ||||
| 
 | ||||
| #include <libnest2d/common.hpp> | ||||
| 
 | ||||
| namespace libnest2d { namespace opt { | ||||
| 
 | ||||
|  | @ -60,6 +61,7 @@ enum class Method { | |||
|     L_SIMPLEX, | ||||
|     L_SUBPLEX, | ||||
|     G_GENETIC, | ||||
|     G_PARTICLE_SWARM | ||||
|     //...
 | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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}) | ||||
|  | @ -7,15 +7,15 @@ | |||
| 
 | ||||
| namespace libnest2d { namespace placers { | ||||
| 
 | ||||
| template<class T, class = T> struct Epsilon {}; | ||||
| template<class T, class = T> struct DefaultEpsilon {}; | ||||
| 
 | ||||
| template<class T> | ||||
| struct Epsilon<T, enable_if_t<std::is_integral<T>::value, T> > { | ||||
| struct DefaultEpsilon<T, enable_if_t<std::is_integral<T>::value, T> > { | ||||
|     static const T Value = 1; | ||||
| }; | ||||
| 
 | ||||
| template<class T> | ||||
| struct Epsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > { | ||||
| struct DefaultEpsilon<T, enable_if_t<std::is_floating_point<T>::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<Coord>::Value; | ||||
|     Coord epsilon = DefaultEpsilon<Coord>::Value; | ||||
|     bool allow_rotations = false; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -103,14 +103,14 @@ Key hash(const _Item<S>& 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<double>()); | ||||
|     } | ||||
| 
 | ||||
|     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<double>()) / circ); | ||||
|         int ms = 'A', ls = 'A'; | ||||
|         while(l > N) { ms++; l -= N; } | ||||
|         ls += l; | ||||
|  | @ -249,6 +249,11 @@ template<class RawShape> class EdgeCache { | |||
|     std::vector<ContourCache> holes_; | ||||
| 
 | ||||
|     double accuracy_ = 1.0; | ||||
|      | ||||
|     static double length(const Edge &e)  | ||||
|     {  | ||||
|         return std::sqrt(e.template sqlength<double>()); | ||||
|     } | ||||
| 
 | ||||
|     void createCache(const RawShape& sh) { | ||||
|         {   // For the contour
 | ||||
|  | @ -260,7 +265,7 @@ template<class RawShape> 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 RawShape> 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); | ||||
|             } | ||||
| 
 | ||||
|  |  | |||
|  | @ -311,19 +311,19 @@ struct range_value<bp2d::Shapes> { | |||
| 
 | ||||
| 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<bp2d::Shapes>(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; | ||||
| } | ||||
|  |  | |||
							
								
								
									
										268
									
								
								src/libnest2d/include/libnest2d/utils/rotcalipers.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								src/libnest2d/include/libnest2d/utils/rotcalipers.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,268 @@ | |||
| #ifndef ROTCALIPERS_HPP | ||||
| #define ROTCALIPERS_HPP | ||||
| 
 | ||||
| #include <numeric> | ||||
| #include <functional> | ||||
| #include <array> | ||||
| #include <cmath> | ||||
| 
 | ||||
| #include <libnest2d/geometry_traits.hpp> | ||||
| 
 | ||||
| namespace libnest2d { | ||||
| 
 | ||||
| template<class Pt, class Unit = TCompute<Pt>> class RotatedBox { | ||||
|     Pt axis_; | ||||
|     Unit bottom_ = Unit(0), right_ = Unit(0); | ||||
| public: | ||||
|      | ||||
|     RotatedBox() = default; | ||||
|     RotatedBox(const Pt& axis, Unit b, Unit r): | ||||
|         axis_(axis), bottom_(b), right_(r) {} | ||||
|      | ||||
|     inline long double area() const {  | ||||
|         long double asq = pl::magnsq<Pt, long double>(axis_); | ||||
|         return cast<long double>(bottom_) * cast<long double>(right_) / asq; | ||||
|     } | ||||
|      | ||||
|     inline long double width() const {  | ||||
|         return abs(bottom_) / std::sqrt(pl::magnsq<Pt, long double>(axis_)); | ||||
|     } | ||||
|      | ||||
|     inline long double height() const {  | ||||
|         return abs(right_) / std::sqrt(pl::magnsq<Pt, long double>(axis_)); | ||||
|     } | ||||
|      | ||||
|     inline Unit bottom_extent() const { return bottom_; } | ||||
|     inline Unit right_extent() const { return right_;  } | ||||
|     inline const Pt& axis() const { return axis_; } | ||||
|      | ||||
|     inline Radians angleToX() const { | ||||
|         double ret = std::atan2(getY(axis_), getX(axis_)); | ||||
|         auto s = std::signbit(ret); | ||||
|         if(s) ret += Pi_2; | ||||
|         return -ret; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| template <class Poly, class Pt = TPoint<Poly>, class Unit = TCompute<Pt>>  | ||||
| Poly removeCollinearPoints(const Poly& sh, Unit eps = Unit(0)) | ||||
| { | ||||
|     Poly ret; sl::reserve(ret, sl::contourVertexCount(sh)); | ||||
|      | ||||
|     Pt eprev = *sl::cbegin(sh) - *std::prev(sl::cend(sh)); | ||||
|      | ||||
|     auto it  = sl::cbegin(sh); | ||||
|     auto itx = std::next(it); | ||||
|     if(itx != sl::cend(sh)) while (it != sl::cend(sh)) | ||||
|     { | ||||
|         Pt enext = *itx - *it; | ||||
| 
 | ||||
|         auto dp = pl::dotperp<Pt, Unit>(eprev, enext); | ||||
|         if(abs(dp) > eps) sl::addVertex(ret, *it); | ||||
|          | ||||
|         eprev = enext; | ||||
|         if (++itx == sl::cend(sh)) itx = sl::cbegin(sh); | ||||
|         ++it; | ||||
|     } | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| // The area of the bounding rectangle with the axis dir and support vertices
 | ||||
| template<class Pt, class Unit = TCompute<Pt>, class R = TCompute<Pt>>  | ||||
| inline R rectarea(const Pt& w, // the axis
 | ||||
|                   const Pt& vb, const Pt& vr,  | ||||
|                   const Pt& vt, const Pt& vl)  | ||||
| { | ||||
|     Unit a = pl::dot<Pt, Unit>(w, vr - vl);  | ||||
|     Unit b = pl::dot<Pt, Unit>(-pl::perp(w), vt - vb); | ||||
|     R m = R(a) / pl::magnsq<Pt, Unit>(w); | ||||
|     m = m * b; | ||||
|     return m; | ||||
| }; | ||||
| 
 | ||||
| template<class Pt,  | ||||
|          class Unit = TCompute<Pt>, | ||||
|          class R = TCompute<Pt>, | ||||
|          class It = typename std::vector<Pt>::const_iterator> | ||||
| inline R rectarea(const Pt& w, const std::array<It, 4>& rect) | ||||
| { | ||||
|     return rectarea<Pt, Unit, R>(w, *rect[0], *rect[1], *rect[2], *rect[3]); | ||||
| } | ||||
| 
 | ||||
| // This function is only applicable to counter-clockwise oriented convex
 | ||||
| // polygons where only two points can be collinear witch each other.
 | ||||
| template <class RawShape,  | ||||
|           class Unit = TCompute<RawShape>,  | ||||
|           class Ratio = TCompute<RawShape>>  | ||||
| RotatedBox<TPoint<RawShape>, Unit> minAreaBoundingBox(const RawShape& sh)  | ||||
| { | ||||
|     using Point = TPoint<RawShape>; | ||||
|     using Iterator = typename TContour<RawShape>::const_iterator; | ||||
|     using pointlike::dot; using pointlike::magnsq; using pointlike::perp; | ||||
| 
 | ||||
|     // Get the first and the last vertex iterator
 | ||||
|     auto first = sl::cbegin(sh); | ||||
|     auto last = std::prev(sl::cend(sh)); | ||||
|      | ||||
|     // Check conditions and return undefined box if input is not sane.
 | ||||
|     if(last == first) return {}; | ||||
|     if(getX(*first) == getX(*last) && getY(*first) == getY(*last)) --last; | ||||
|     if(last - first < 2) return {}; | ||||
|      | ||||
|     RawShape shcpy; // empty at this point
 | ||||
|     {    | ||||
|         Point p = *first, q = *std::next(first), r = *last; | ||||
|          | ||||
|         // Determine orientation from first 3 vertex (should be consistent)
 | ||||
|         Unit d = (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) - | ||||
|                  (Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p)); | ||||
|          | ||||
|         if(d > 0) {  | ||||
|             // The polygon is clockwise. A flip is needed (for now)
 | ||||
|             sl::reserve(shcpy, last - first); | ||||
|             auto it = last; while(it != first) sl::addVertex(shcpy, *it--); | ||||
|             sl::addVertex(shcpy, *first); | ||||
|             first = sl::cbegin(shcpy); last = std::prev(sl::cend(shcpy)); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // Cyclic iterator increment
 | ||||
|     auto inc = [&first, &last](Iterator& it) { | ||||
|        if(it == last) it = first; else ++it; | ||||
|     }; | ||||
|      | ||||
|     // Cyclic previous iterator
 | ||||
|     auto prev = [&first, &last](Iterator it) {  | ||||
|         return it == first ? last : std::prev(it);  | ||||
|     }; | ||||
|      | ||||
|     // Cyclic next iterator
 | ||||
|     auto next = [&first, &last](Iterator it) { | ||||
|         return it == last ? first : std::next(it);     | ||||
|     }; | ||||
|      | ||||
|     // Establish initial (axis aligned) rectangle support verices by determining 
 | ||||
|     // polygon extremes:
 | ||||
|      | ||||
|     auto it = first; | ||||
|     Iterator minX = it, maxX = it, minY = it, maxY = it; | ||||
|      | ||||
|     do { // Linear walk through the vertices and save the extreme positions
 | ||||
|          | ||||
|         Point v = *it, d = v - *minX; | ||||
|         if(getX(d) < 0 || (getX(d) == 0 && getY(d) < 0)) minX = it; | ||||
|          | ||||
|         d = v - *maxX; | ||||
|         if(getX(d) > 0 || (getX(d) == 0 && getY(d) > 0)) maxX = it; | ||||
|          | ||||
|         d = v - *minY; | ||||
|         if(getY(d) < 0 || (getY(d) == 0 && getX(d) > 0)) minY = it; | ||||
|          | ||||
|         d = v - *maxY; | ||||
|         if(getY(d) > 0 || (getY(d) == 0 && getX(d) < 0)) maxY = it; | ||||
|          | ||||
|     } while(++it != std::next(last)); | ||||
|      | ||||
|     // Update the vertices defining the bounding rectangle. The rectangle with
 | ||||
|     // the smallest rotation is selected and the supporting vertices are 
 | ||||
|     // returned in the 'rect' argument.
 | ||||
|     auto update = [&next, &inc] | ||||
|             (const Point& w, std::array<Iterator, 4>& rect)  | ||||
|     { | ||||
|         Iterator B = rect[0], Bn = next(B); | ||||
|         Iterator R = rect[1], Rn = next(R); | ||||
|         Iterator T = rect[2], Tn = next(T); | ||||
|         Iterator L = rect[3], Ln = next(L); | ||||
|          | ||||
|         Point b = *Bn - *B, r = *Rn - *R, t = *Tn - *T, l = *Ln - *L; | ||||
|         Point pw = perp(w); | ||||
|         using Pt = Point; | ||||
|          | ||||
|         Unit dotwpb = dot<Pt, Unit>( w, b), dotwpr = dot<Pt, Unit>(-pw, r); | ||||
|         Unit dotwpt = dot<Pt, Unit>(-w, t), dotwpl = dot<Pt, Unit>( pw, l); | ||||
|         Unit dw     = magnsq<Pt, Unit>(w); | ||||
|          | ||||
|         std::array<Ratio, 4> angles; | ||||
|         angles[0] = (Ratio(dotwpb) / magnsq<Pt, Unit>(b)) * dotwpb; | ||||
|         angles[1] = (Ratio(dotwpr) / magnsq<Pt, Unit>(r)) * dotwpr; | ||||
|         angles[2] = (Ratio(dotwpt) / magnsq<Pt, Unit>(t)) * dotwpt; | ||||
|         angles[3] = (Ratio(dotwpl) / magnsq<Pt, Unit>(l)) * dotwpl; | ||||
|          | ||||
|         using AngleIndex = std::pair<Ratio, size_t>; | ||||
|         std::vector<AngleIndex> A; A.reserve(4); | ||||
| 
 | ||||
|         for (size_t i = 3, j = 0; j < 4; i = j++) { | ||||
|             if(rect[i] != rect[j] && angles[i] < dw) { | ||||
|                 auto iv = std::make_pair(angles[i], i); | ||||
|                 auto it = std::lower_bound(A.begin(), A.end(), iv, | ||||
|                                            [](const AngleIndex& ai,  | ||||
|                                               const AngleIndex& aj)  | ||||
|                 {  | ||||
|                     return ai.first > aj.first;  | ||||
|                 }); | ||||
|                  | ||||
|                 A.insert(it, iv); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         // The polygon is supposed to be a rectangle.
 | ||||
|         if(A.empty()) return false; | ||||
|         | ||||
|         auto amin = A.front().first; | ||||
|         auto imin = A.front().second; | ||||
|         for(auto& a : A) if(a.first == amin) inc(rect[a.second]); | ||||
|              | ||||
|         std::rotate(rect.begin(), rect.begin() + imin, rect.end()); | ||||
|          | ||||
|         return true; | ||||
|     }; | ||||
|      | ||||
|     Point w(1, 0); | ||||
|     Point w_min = w; | ||||
|     Ratio minarea((Unit(getX(*maxX)) - getX(*minX)) *  | ||||
|                   (Unit(getY(*maxY)) - getY(*minY))); | ||||
|      | ||||
|     std::array<Iterator, 4> rect = {minY, maxX, maxY, minX}; | ||||
|     std::array<Iterator, 4> minrect = rect; | ||||
|      | ||||
|     // An edge might be examined twice in which case the algorithm terminates.
 | ||||
|     size_t c = 0, count = last - first + 1; | ||||
|     std::vector<bool> edgemask(count, false); | ||||
|      | ||||
|     while(c++ < count)  | ||||
|     {    | ||||
|         // Update the support vertices, if cannot be updated, break the cycle.
 | ||||
|         if(! update(w, rect)) break; | ||||
|          | ||||
|         size_t eidx = size_t(rect[0] - first); | ||||
|          | ||||
|         if(edgemask[eidx]) break; | ||||
|         edgemask[eidx] = true; | ||||
|                  | ||||
|         // get the unnormalized direction vector
 | ||||
|         w = *rect[0] - *prev(rect[0]); | ||||
|          | ||||
|         // get the area of the rotated rectangle
 | ||||
|         Ratio rarea = rectarea<Point, Unit, Ratio>(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<Point, Unit>(w_min, *minrect[1] - *minrect[3]); | ||||
|     Unit b = dot<Point, Unit>(-perp(w_min), *minrect[2] - *minrect[0]); | ||||
|     RotatedBox<Point, Unit> bb(w_min, a, b); | ||||
|      | ||||
|     return bb; | ||||
| } | ||||
| 
 | ||||
| template <class RawShape> Radians minAreaBoundingBoxRotation(const RawShape& sh) | ||||
| { | ||||
|     return minAreaBoundingBox(sh).angleToX(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif // ROTCALIPERS_HPP
 | ||||
							
								
								
									
										23
									
								
								src/libnest2d/src/libnest2d.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/libnest2d/src/libnest2d.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| #include <libnest2d.h> | ||||
| 
 | ||||
| namespace libnest2d { | ||||
| 
 | ||||
| template class Nester<NfpPlacer, FirstFitSelection>; | ||||
| template class Nester<BottomLeftPlacer, FirstFitSelection>; | ||||
| 
 | ||||
| template PackGroup nest(std::vector<Item>::iterator from,  | ||||
|                         std::vector<Item>::iterator to, | ||||
|                         const Box& bin, | ||||
|                         Coord dist = 0, | ||||
|                         const NfpPlacer::Config& pconf, | ||||
|                         const FirstFitSelection::Config& sconf); | ||||
| 
 | ||||
| template PackGroup nest(std::vector<Item>::iterator from,  | ||||
|                         std::vector<Item>::iterator to, | ||||
|                         const Box& bin, | ||||
|                         ProgressFunction prg, | ||||
|                         StopCondition scond, | ||||
|                         Coord dist = 0, | ||||
|                         const NfpPlacer::Config& pconf, | ||||
|                         const FirstFitSelection::Config& sconf); | ||||
| } | ||||
|  | @ -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) | ||||
|  |  | |||
|  | @ -3,11 +3,43 @@ | |||
| 
 | ||||
| #include <libnest2d.h> | ||||
| #include "printer_parts.h" | ||||
| #include <libnest2d/geometry_traits_nfp.hpp> | ||||
| //#include <libnest2d/geometry_traits_nfp.hpp>
 | ||||
| #include "../tools/svgtools.hpp" | ||||
| #include <libnest2d/utils/rotcalipers.hpp> | ||||
| 
 | ||||
| #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<LargeInt> { using Type = ScalarTag; }; | ||||
| #endif | ||||
| template<class T> struct _NumTag<boost::rational<T>> { using Type = RationalTag; }; | ||||
| 
 | ||||
| namespace nfp { | ||||
| 
 | ||||
| template<class S> | ||||
| struct NfpImpl<S, NfpLevel::CONVEX_ONLY> | ||||
| { | ||||
|     NfpResult<S> operator()(const S &sh, const S &other) | ||||
|     { | ||||
|         return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| } | ||||
| 
 | ||||
| std::vector<libnest2d::Item>& prusaParts() { | ||||
|     static std::vector<libnest2d::Item> 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<Coord>::value) | ||||
|     auto check = [](TCompute<Coord> val, TCompute<Coord> expected) { | ||||
|         if(std::is_floating_point<TCompute<Coord>>::value) | ||||
|             ASSERT_DOUBLE_EQ(static_cast<double>(val), | ||||
|                              static_cast<double>(expected)); | ||||
|         else | ||||
|  | @ -415,7 +447,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) | |||
| namespace { | ||||
| using namespace libnest2d; | ||||
| 
 | ||||
| template<unsigned long SCALE = 1, class Bin> | ||||
| template<long long SCALE = 1, class Bin> | ||||
| void exportSVG(std::vector<std::reference_wrapper<Item>>& 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<Item> 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<ItemPair>& testdata) { | |||
| 
 | ||||
|     auto& exportfun = exportSVG<SCALE, Box>; | ||||
| 
 | ||||
|     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<PolygonImpl> pile; | ||||
|     TMultiShape<PolygonImpl> 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<long double>::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<long double>(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<class T> struct BoostGCD {  | ||||
|     T operator()(const T &a, const T &b) { return boost::gcd(a, b); } | ||||
| }; | ||||
| 
 | ||||
| using Unit = int64_t; | ||||
| using Ratio = boost::rational<boost::multiprecision::int128_t>;// Rational<boost::multiprecision::int256_t>;
 | ||||
| 
 | ||||
| //double gteMinAreaBox(const PolygonImpl& p) {    
 | ||||
|      | ||||
| //    using GteCoord = ClipperLib::cInt;
 | ||||
| //    using GtePoint = gte::Vector2<GteCoord>;
 | ||||
|   | ||||
| //    gte::MinimumAreaBox2<GteCoord, Ratio> mb;
 | ||||
|      | ||||
| //    std::vector<GtePoint> 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<PolygonImpl, Unit, Ratio>(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<PathImpl, PointImpl, Unit>(rinput, 1000000));
 | ||||
|         PolygonImpl poly(rinput); | ||||
|          | ||||
|         long double arearef = refMinAreaBox(poly); | ||||
|         auto bb = minAreaBoundingBox<PathImpl, Unit, Ratio>(rinput); | ||||
|         long double area = cast<long double>(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<PathImpl, PointImpl, Unit>(rinput, 1000000)); | ||||
|          | ||||
|          | ||||
|         long double arearef = refMinAreaBox(poly); | ||||
|         auto bb = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly); | ||||
|         long double area = cast<long double>(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(); | ||||
|  |  | |||
|  | @ -160,6 +160,8 @@ add_library(libslic3r STATIC | |||
|     MTUtils.hpp | ||||
|     Zipper.hpp | ||||
|     Zipper.cpp | ||||
|     MinAreaBoundingBox.hpp | ||||
|     MinAreaBoundingBox.cpp | ||||
|     miniz_extension.hpp | ||||
|     miniz_extension.cpp | ||||
|     SLA/SLABoilerPlate.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 <cassert> | ||||
| #include <cstdint> | ||||
| #include <cmath> | ||||
| 
 | ||||
| #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
 | ||||
|  |  | |||
							
								
								
									
										142
									
								
								src/libslic3r/MinAreaBoundingBox.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								src/libslic3r/MinAreaBoundingBox.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,142 @@ | |||
| #include "MinAreaBoundingBox.hpp" | ||||
| 
 | ||||
| #include <libslic3r/ExPolygon.hpp> | ||||
| #include <boost/rational.hpp> | ||||
| 
 | ||||
| #include <libslic3r/Int128.hpp> | ||||
| 
 | ||||
| #if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__) | ||||
| #include <boost/multiprecision/integer.hpp> | ||||
| #endif | ||||
| 
 | ||||
| #include <libnest2d/geometry_traits.hpp> | ||||
| #include <libnest2d/utils/rotcalipers.hpp> | ||||
| 
 | ||||
| namespace libnest2d { | ||||
| 
 | ||||
| template<> struct PointType<Slic3r::Points>      { using Type = Slic3r::Point; }; | ||||
| template<> struct CoordType<Slic3r::Point>       { using Type = coord_t; }; | ||||
| template<> struct ShapeTag<Slic3r::ExPolygon>    { using Type = PolygonTag; }; | ||||
| template<> struct ShapeTag<Slic3r::Polygon>      { using Type = PolygonTag; }; | ||||
| template<> struct ShapeTag<Slic3r::Points>       { using Type = PathTag; }; | ||||
| template<> struct ShapeTag<Slic3r::Point>        { using Type = PointTag; }; | ||||
| template<> struct ContourType<Slic3r::ExPolygon> { using Type = Slic3r::Points; }; | ||||
| template<> struct ContourType<Slic3r::Polygon>   { 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::ExPolygon>(Slic3r::Points&& contour) | ||||
| { | ||||
|     Slic3r::ExPolygon expoly; expoly.contour.points.swap(contour); | ||||
|     return expoly; | ||||
| } | ||||
| 
 | ||||
| template<> inline Slic3r::Polygon create<Slic3r::Polygon>(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<boost::multiprecision::int128_t>; | ||||
| #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<Point, Unit> box =  | ||||
|             libnest2d::minAreaBoundingBox<Polygon, Unit, Rational>(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<Point, Unit> box =  | ||||
|             libnest2d::minAreaBoundingBox<ExPolygon, Unit, Rational>(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<Point, Unit> box =  | ||||
|             libnest2d::minAreaBoundingBox<Points, Unit, Rational>(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<Point, long double>(m_axis)); | ||||
| } | ||||
| 
 | ||||
| long double MinAreaBoundigBox::height() const | ||||
| { | ||||
|     return std::abs(m_right) / std::sqrt(libnest2d::pl::magnsq<Point, long double>(m_axis)); | ||||
| } | ||||
| 
 | ||||
| long double MinAreaBoundigBox::area() const | ||||
| { | ||||
|     long double asq = libnest2d::pl::magnsq<Point, long double>(m_axis); | ||||
|     return m_bottom * m_right / asq;    | ||||
| } | ||||
| 
 | ||||
| void remove_collinear_points(Polygon &p) | ||||
| { | ||||
|     p = libnest2d::removeCollinearPoints<Polygon>(p, Unit(0)); | ||||
| } | ||||
| 
 | ||||
| void remove_collinear_points(ExPolygon &p) | ||||
| { | ||||
|     p = libnest2d::removeCollinearPoints<ExPolygon>(p, Unit(0)); | ||||
| } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										59
									
								
								src/libslic3r/MinAreaBoundingBox.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/libslic3r/MinAreaBoundingBox.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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
 | ||||
|  | @ -9,6 +9,31 @@ | |||
| #include <ClipperUtils.hpp> | ||||
| 
 | ||||
| #include <boost/geometry/index/rtree.hpp> | ||||
| #include <boost/multiprecision/integer.hpp> | ||||
| #include <boost/rational.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<LargeInt> { using Type = ScalarTag; }; | ||||
| #endif | ||||
| template<class T> struct _NumTag<boost::rational<T>> { using Type = RationalTag; }; | ||||
| 
 | ||||
| namespace nfp { | ||||
| 
 | ||||
| template<class S> | ||||
| struct NfpImpl<S, NfpLevel::CONVEX_ONLY> | ||||
| { | ||||
|     NfpResult<S> operator()(const S &sh, const S &other) | ||||
|     { | ||||
|         return nfpConvexOnly<S, boost::rational<LargeInt>>(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<double /*score*/, Box /*farthest point from bin center*/> | ||||
| objfunc(const PointImpl& bincenter, | ||||
|         const shapelike::Shapes<PolygonImpl>& merged_pile, | ||||
|         const TMultiShape<PolygonImpl>& merged_pile, | ||||
|         const Box& pilebb, | ||||
|         const ItemGroup& items, | ||||
|         const Item &item, | ||||
|  | @ -293,7 +318,7 @@ class AutoArranger {}; | |||
| // management and spatial index structures for acceleration.
 | ||||
| template<class TBin> | ||||
| class _ArrBase { | ||||
| protected: | ||||
| public: | ||||
| 
 | ||||
|     // Useful type shortcuts...
 | ||||
|     using Placer = TPacker<TBin>; | ||||
|  | @ -301,7 +326,9 @@ protected: | |||
|     using Packer = Nester<Placer, Selector>; | ||||
|     using PConfig = typename Packer::PlacementConfig; | ||||
|     using Distance = TCoord<PointImpl>; | ||||
|     using Pile = sl::Shapes<PolygonImpl>; | ||||
|     using Pile = TMultiShape<PolygonImpl>; | ||||
|      | ||||
| protected: | ||||
| 
 | ||||
|     Packer m_pck; | ||||
|     PConfig m_pconf;            // Placement configuration
 | ||||
|  | @ -539,7 +566,10 @@ public: | |||
| // 2D shape from top view.
 | ||||
| using ShapeData2D = std::vector<std::pair<Slic3r::ModelInstance*, Item>>; | ||||
| 
 | ||||
| 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)
 | ||||
|  | @ -561,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(); | ||||
|  | @ -589,7 +630,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) | ||||
|  | @ -755,9 +796,9 @@ bool arrange(Model &model,              // The model with the geometries | |||
|              std::function<bool ()> 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
 | ||||
|  | @ -782,7 +823,7 @@ bool arrange(Model &model,              // The model with the geometries | |||
|                          static_cast<libnest2d::Coord>(bbb.min(0)), | ||||
|                          static_cast<libnest2d::Coord>(bbb.min(1)) | ||||
|                      }, | ||||
|     { | ||||
|                      { | ||||
|                          static_cast<libnest2d::Coord>(bbb.max(0)), | ||||
|                          static_cast<libnest2d::Coord>(bbb.max(1)) | ||||
|                      }); | ||||
|  | @ -856,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
 | ||||
|  |  | |||
|  | @ -39,7 +39,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" | ||||
|  | @ -2468,8 +2473,9 @@ void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() | |||
|         }, | ||||
|         [this]() { return was_canceled(); }); | ||||
| 
 | ||||
|     const auto *bed_shape_opt = plater().config->opt<ConfigOptionPoints>( | ||||
|         "bed_shape"); | ||||
|     const auto *bed_shape_opt = | ||||
|         plater().config->opt<ConfigOptionPoints>("bed_shape"); | ||||
|      | ||||
|     assert(bed_shape_opt); | ||||
| 
 | ||||
|     auto &   bedpoints = bed_shape_opt->values; | ||||
|  | @ -2478,70 +2484,40 @@ void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() | |||
|     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 (!was_canceled()) // wasn't canceled
 | ||||
|         for (ModelInstance *oi : o->instances) { | ||||
|      | ||||
|     if (!was_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); | ||||
| 
 | ||||
|             Vec3d rt = oi->get_rotation(); | ||||
|             rt(Z) += r; | ||||
|             oi->set_rotation(rt); | ||||
| 
 | ||||
|             arr::WipeTowerInfo wti; // useless in SLA context
 | ||||
|             arr::find_new_position(plater().model, | ||||
|                                    o->instances, | ||||
|                                    coord_t(mindist / SCALING_FACTOR), | ||||
|                                    bed, | ||||
|                                    wti); | ||||
| 
 | ||||
|             // Correct the z offset of the object which was corrupted be
 | ||||
|             // the rotation
 | ||||
|             o->ensure_on_bed(); | ||||
|      | ||||
|             auto    trmatrix = oi->get_transformation().get_matrix(); | ||||
|             Polygon trchull  = o->convex_hull_2d(trmatrix); | ||||
|              | ||||
|             update_status(100, _(L("Orientation found."))); | ||||
|             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); | ||||
|         } | ||||
|     else { | ||||
|         update_status(100, _(L("Orientation search canceled."))); | ||||
|      | ||||
|         arr::WipeTowerInfo wti; // useless in SLA context
 | ||||
|         arr::find_new_position(plater().model, | ||||
|                                o->instances, | ||||
|                                coord_t(mindist / SCALING_FACTOR), | ||||
|                                bed, | ||||
|                                wti); | ||||
|      | ||||
|         // Correct the z offset of the object which was corrupted be
 | ||||
|         // the rotation
 | ||||
|         o->ensure_on_bed(); | ||||
|     } | ||||
| 
 | ||||
|     update_status(100, | ||||
|                   was_canceled() ? _(L("Orientation search canceled.")) | ||||
|                                  : _(L("Orientation found."))); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::split_object() | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 tamasmeszaros
						tamasmeszaros