mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 12:41:20 -06:00 
			
		
		
		
	Merge branch 'master' into ys_overrides
This commit is contained in:
		
						commit
						356166ad86
					
				
					 32 changed files with 2073 additions and 2074 deletions
				
			
		|  | @ -19,7 +19,7 @@ AlwaysBreakAfterDefinitionReturnType: None | ||||||
| AlwaysBreakAfterReturnType: None | AlwaysBreakAfterReturnType: None | ||||||
| AlwaysBreakBeforeMultilineStrings: false | AlwaysBreakBeforeMultilineStrings: false | ||||||
| AlwaysBreakTemplateDeclarations: false | AlwaysBreakTemplateDeclarations: false | ||||||
| BinPackArguments: false | BinPackArguments: true | ||||||
| BinPackParameters: false | BinPackParameters: false | ||||||
| BraceWrapping:    | BraceWrapping:    | ||||||
|   AfterClass:      true |   AfterClass:      true | ||||||
|  |  | ||||||
|  | @ -317,10 +317,10 @@ endif() | ||||||
| 
 | 
 | ||||||
| # Find eigen3 or use bundled version | # Find eigen3 or use bundled version | ||||||
| if (NOT SLIC3R_STATIC) | if (NOT SLIC3R_STATIC) | ||||||
|     find_package(Eigen3 3) |     find_package(Eigen3 3.3) | ||||||
| endif () | endif () | ||||||
| if (NOT Eigen3_FOUND) | if (NOT EIGEN3_FOUND) | ||||||
|     set(Eigen3_FOUND 1) |     set(EIGEN3_FOUND 1) | ||||||
|     set(EIGEN3_INCLUDE_DIR ${LIBDIR}/eigen/) |     set(EIGEN3_INCLUDE_DIR ${LIBDIR}/eigen/) | ||||||
| endif () | endif () | ||||||
| include_directories(BEFORE SYSTEM ${EIGEN3_INCLUDE_DIR}) | include_directories(BEFORE SYSTEM ${EIGEN3_INCLUDE_DIR}) | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								deps/deps-macos.cmake
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								deps/deps-macos.cmake
									
										
									
									
										vendored
									
									
								
							|  | @ -111,6 +111,6 @@ ExternalProject_Add(dep_wxwidgets | ||||||
|         --with-expat=builtin |         --with-expat=builtin | ||||||
|         --disable-debug |         --disable-debug | ||||||
|         --disable-debug_flag |         --disable-debug_flag | ||||||
|     BUILD_COMMAND make "-j${NPROC}" && make -C locale allmo |     BUILD_COMMAND make "-j${NPROC}" && PATH=/usr/local/opt/gettext/bin/:$ENV{PATH} make -C locale allmo | ||||||
|     INSTALL_COMMAND make install |     INSTALL_COMMAND make install | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | @ -1,7 +1,15 @@ | ||||||
| 
 | 
 | ||||||
| # Building PrusaSlicer on Mac OS | # Building PrusaSlicer on Mac OS | ||||||
| 
 | 
 | ||||||
| To build PrusaSlicer on Mac OS, you will need to install XCode, [CMake](https://cmake.org/) (available on Brew) and possibly git. | To build PrusaSlicer on Mac OS, you will need the following software: | ||||||
|  | 
 | ||||||
|  | - XCode | ||||||
|  | - CMake | ||||||
|  | - git | ||||||
|  | - gettext | ||||||
|  | 
 | ||||||
|  | XCode is available through Apple's App Store, the other three tools are available on | ||||||
|  | [brew](https://brew.sh/) (use `brew install cmake git gettext` to install them). | ||||||
| 
 | 
 | ||||||
| ### Dependencies | ### Dependencies | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -30,9 +30,7 @@ using Circle = _Circle<PointImpl>; | ||||||
| 
 | 
 | ||||||
| using Item = _Item<PolygonImpl>; | using Item = _Item<PolygonImpl>; | ||||||
| using Rectangle = _Rectangle<PolygonImpl>; | using Rectangle = _Rectangle<PolygonImpl>; | ||||||
| 
 |  | ||||||
| using PackGroup = _PackGroup<PolygonImpl>; | using PackGroup = _PackGroup<PolygonImpl>; | ||||||
| using IndexedPackGroup = _IndexedPackGroup<PolygonImpl>; |  | ||||||
| 
 | 
 | ||||||
| using FillerSelection = selections::_FillerSelection<PolygonImpl>; | using FillerSelection = selections::_FillerSelection<PolygonImpl>; | ||||||
| using FirstFitSelection = selections::_FirstFitSelection<PolygonImpl>; | using FirstFitSelection = selections::_FirstFitSelection<PolygonImpl>; | ||||||
|  | @ -61,20 +59,20 @@ extern template PackGroup Nester<BottomLeftPlacer, FirstFitSelection>::execute( | ||||||
| template<class Placer = NfpPlacer, | template<class Placer = NfpPlacer, | ||||||
|          class Selector = FirstFitSelection, |          class Selector = FirstFitSelection, | ||||||
|          class Iterator = std::vector<Item>::iterator> |          class Iterator = std::vector<Item>::iterator> | ||||||
| PackGroup nest(Iterator from, Iterator to, | void nest(Iterator from, Iterator to, | ||||||
|                const typename Placer::BinType& bin, |                const typename Placer::BinType& bin, | ||||||
|                Coord dist = 0, |                Coord dist = 0, | ||||||
|                const typename Placer::Config& pconf = {}, |                const typename Placer::Config& pconf = {}, | ||||||
|                const typename Selector::Config& sconf = {}) |                const typename Selector::Config& sconf = {}) | ||||||
| { | { | ||||||
|     Nester<Placer, Selector> nester(bin, dist, pconf, sconf); |     _Nester<Placer, Selector> nester(bin, dist, pconf, sconf); | ||||||
|     return nester.execute(from, to); |     nester.execute(from, to); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| template<class Placer = NfpPlacer, | template<class Placer = NfpPlacer, | ||||||
|          class Selector = FirstFitSelection, |          class Selector = FirstFitSelection, | ||||||
|          class Iterator = std::vector<Item>::iterator> |          class Iterator = std::vector<Item>::iterator> | ||||||
| PackGroup nest(Iterator from, Iterator to, | void nest(Iterator from, Iterator to, | ||||||
|                const typename Placer::BinType& bin, |                const typename Placer::BinType& bin, | ||||||
|                ProgressFunction prg, |                ProgressFunction prg, | ||||||
|                StopCondition scond = []() { return false; }, |                StopCondition scond = []() { return false; }, | ||||||
|  | @ -82,10 +80,10 @@ PackGroup nest(Iterator from, Iterator to, | ||||||
|                const typename Placer::Config& pconf = {}, |                const typename Placer::Config& pconf = {}, | ||||||
|                const typename Selector::Config& sconf = {}) |                const typename Selector::Config& sconf = {}) | ||||||
| { | { | ||||||
|     Nester<Placer, Selector> nester(bin, dist, pconf, sconf); |     _Nester<Placer, Selector> nester(bin, dist, pconf, sconf); | ||||||
|     if(prg) nester.progressIndicator(prg); |     if(prg) nester.progressIndicator(prg); | ||||||
|     if(scond) nester.stopCondition(scond); |     if(scond) nester.stopCondition(scond); | ||||||
|     return nester.execute(from, to); |     nester.execute(from, to); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #ifdef LIBNEST2D_STATIC | #ifdef LIBNEST2D_STATIC | ||||||
|  | @ -93,14 +91,14 @@ PackGroup nest(Iterator from, Iterator to, | ||||||
| extern template class Nester<NfpPlacer, FirstFitSelection>; | extern template class Nester<NfpPlacer, FirstFitSelection>; | ||||||
| extern template class Nester<BottomLeftPlacer, FirstFitSelection>; | extern template class Nester<BottomLeftPlacer, FirstFitSelection>; | ||||||
| 
 | 
 | ||||||
| extern template PackGroup nest(std::vector<Item>::iterator from,  | extern template void nest(std::vector<Item>::iterator from,  | ||||||
|                                std::vector<Item>::iterator to, |                                std::vector<Item>::iterator to, | ||||||
|                                const Box& bin, |                                const Box& bin, | ||||||
|                                Coord dist = 0, |                                Coord dist = 0, | ||||||
|                                const NfpPlacer::Config& pconf, |                                const NfpPlacer::Config& pconf, | ||||||
|                                const FirstFitSelection::Config& sconf); |                                const FirstFitSelection::Config& sconf); | ||||||
| 
 | 
 | ||||||
| extern template PackGroup nest(std::vector<Item>::iterator from,  | extern template void nest(std::vector<Item>::iterator from,  | ||||||
|                                std::vector<Item>::iterator to, |                                std::vector<Item>::iterator to, | ||||||
|                                const Box& bin, |                                const Box& bin, | ||||||
|                                ProgressFunction prg, |                                ProgressFunction prg, | ||||||
|  | @ -114,20 +112,19 @@ extern template PackGroup nest(std::vector<Item>::iterator from, | ||||||
| template<class Placer = NfpPlacer, | template<class Placer = NfpPlacer, | ||||||
|          class Selector = FirstFitSelection, |          class Selector = FirstFitSelection, | ||||||
|          class Container = std::vector<Item>> |          class Container = std::vector<Item>> | ||||||
| PackGroup nest(Container&& cont, | void nest(Container&& cont, | ||||||
|                const typename Placer::BinType& bin, |                const typename Placer::BinType& bin, | ||||||
|                Coord dist = 0, |                Coord dist = 0, | ||||||
|                const typename Placer::Config& pconf = {}, |                const typename Placer::Config& pconf = {}, | ||||||
|                const typename Selector::Config& sconf = {}) |                const typename Selector::Config& sconf = {}) | ||||||
| { | { | ||||||
|     return nest<Placer, Selector>(cont.begin(), cont.end(), |     nest<Placer, Selector>(cont.begin(), cont.end(), bin, dist, pconf, sconf); | ||||||
|                                   bin, dist, pconf, sconf); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| template<class Placer = NfpPlacer, | template<class Placer = NfpPlacer, | ||||||
|          class Selector = FirstFitSelection, |          class Selector = FirstFitSelection, | ||||||
|          class Container = std::vector<Item>> |          class Container = std::vector<Item>> | ||||||
| PackGroup nest(Container&& cont, | void nest(Container&& cont, | ||||||
|                const typename Placer::BinType& bin, |                const typename Placer::BinType& bin, | ||||||
|                ProgressFunction prg, |                ProgressFunction prg, | ||||||
|                StopCondition scond = []() { return false; }, |                StopCondition scond = []() { return false; }, | ||||||
|  | @ -135,8 +132,8 @@ PackGroup nest(Container&& cont, | ||||||
|                const typename Placer::Config& pconf = {}, |                const typename Placer::Config& pconf = {}, | ||||||
|                const typename Selector::Config& sconf = {}) |                const typename Selector::Config& sconf = {}) | ||||||
| { | { | ||||||
|     return nest<Placer, Selector>(cont.begin(), cont.end(), |     nest<Placer, Selector>(cont.begin(), cont.end(), bin, prg, scond, dist, | ||||||
|                                   bin, prg, scond, dist, pconf, sconf); |                            pconf, sconf); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -41,25 +41,25 @@ template<> struct HolesContainer<PolygonImpl> { using Type = ClipperLib::Paths; | ||||||
| namespace pointlike { | namespace pointlike { | ||||||
| 
 | 
 | ||||||
| // Tell libnest2d how to extract the X coord from a ClipperPoint object
 | // Tell libnest2d how to extract the X coord from a ClipperPoint object
 | ||||||
| template<> inline TCoord<PointImpl> x(const PointImpl& p) | template<> inline ClipperLib::cInt x(const PointImpl& p) | ||||||
| { | { | ||||||
|     return p.X; |     return p.X; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Tell libnest2d how to extract the Y coord from a ClipperPoint object
 | // Tell libnest2d how to extract the Y coord from a ClipperPoint object
 | ||||||
| template<> inline TCoord<PointImpl> y(const PointImpl& p) | template<> inline ClipperLib::cInt y(const PointImpl& p) | ||||||
| { | { | ||||||
|     return p.Y; |     return p.Y; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Tell libnest2d how to extract the X coord from a ClipperPoint object
 | // Tell libnest2d how to extract the X coord from a ClipperPoint object
 | ||||||
| template<> inline TCoord<PointImpl>& x(PointImpl& p) | template<> inline ClipperLib::cInt& x(PointImpl& p) | ||||||
| { | { | ||||||
|     return p.X; |     return p.X; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Tell libnest2d how to extract the Y coord from a ClipperPoint object
 | // Tell libnest2d how to extract the Y coord from a ClipperPoint object
 | ||||||
| template<> inline TCoord<PointImpl>& y(PointImpl& p) | template<> inline ClipperLib::cInt& y(PointImpl& p) | ||||||
| { | { | ||||||
|     return p.Y; |     return p.Y; | ||||||
| } | } | ||||||
|  | @ -71,7 +71,8 @@ template<> inline TCoord<PointImpl>& y(PointImpl& p) | ||||||
| 
 | 
 | ||||||
| namespace shapelike { | namespace shapelike { | ||||||
| 
 | 
 | ||||||
| template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance) | template<> | ||||||
|  | inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance, const PolygonTag&) | ||||||
| { | { | ||||||
|     #define DISABLE_BOOST_OFFSET |     #define DISABLE_BOOST_OFFSET | ||||||
| 
 | 
 | ||||||
|  | @ -123,6 +124,14 @@ template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | template<> | ||||||
|  | inline void offset(PathImpl& sh, TCoord<PointImpl> distance, const PathTag&) | ||||||
|  | { | ||||||
|  |     PolygonImpl p(std::move(sh)); | ||||||
|  |     offset(p, distance, PolygonTag()); | ||||||
|  |     sh = p.Contour; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Tell libnest2d how to make string out of a ClipperPolygon object
 | // Tell libnest2d how to make string out of a ClipperPolygon object
 | ||||||
| template<> inline std::string toString(const PolygonImpl& sh) | template<> inline std::string toString(const PolygonImpl& sh) | ||||||
| { | { | ||||||
|  | @ -260,8 +269,10 @@ inline TMultiShape<PolygonImpl> clipper_execute( | ||||||
| 
 | 
 | ||||||
|         assert(!pptr->IsHole()); |         assert(!pptr->IsHole()); | ||||||
|          |          | ||||||
|         if(pptr->IsOpen()) { |         if(!poly.Contour.empty() ) { | ||||||
|             auto front_p = poly.Contour.front(); |             auto front_p = poly.Contour.front(); | ||||||
|  |             auto &back_p  = poly.Contour.back(); | ||||||
|  |             if(front_p.X != back_p.X || front_p.Y != back_p.X)  | ||||||
|                 poly.Contour.emplace_back(front_p); |                 poly.Contour.emplace_back(front_p); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -275,9 +286,11 @@ inline TMultiShape<PolygonImpl> clipper_execute( | ||||||
| 
 | 
 | ||||||
|         assert(pptr->IsHole()); |         assert(pptr->IsHole()); | ||||||
|          |          | ||||||
|         if(pptr->IsOpen()) { |         if(!poly.Contour.empty() ) { | ||||||
|             auto front_p = poly.Holes.back().front(); |             auto front_p = poly.Contour.front(); | ||||||
|             poly.Holes.back().emplace_back(front_p); |             auto &back_p  = poly.Contour.back(); | ||||||
|  |             if(front_p.X != back_p.X || front_p.Y != back_p.X)  | ||||||
|  |                 poly.Contour.emplace_back(front_p); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         for(auto c : pptr->Childs) processPoly(c); |         for(auto c : pptr->Childs) processPoly(c); | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -12,6 +12,8 @@ | ||||||
| 
 | 
 | ||||||
| namespace libnest2d { | namespace libnest2d { | ||||||
| 
 | 
 | ||||||
|  | static const constexpr int BIN_ID_UNSET = -1; | ||||||
|  | 
 | ||||||
| /**
 | /**
 | ||||||
|  * \brief An item to be placed on a bin. |  * \brief An item to be placed on a bin. | ||||||
|  * |  * | ||||||
|  | @ -34,22 +36,22 @@ class _Item { | ||||||
|     RawShape sh_; |     RawShape sh_; | ||||||
| 
 | 
 | ||||||
|     // Transformation data
 |     // Transformation data
 | ||||||
|     Vertex translation_; |     Vertex translation_{0, 0}; | ||||||
|     Radians rotation_; |     Radians rotation_{0.0}; | ||||||
|     Coord offset_distance_; |     Coord inflation_{0}; | ||||||
| 
 | 
 | ||||||
|     // Info about whether the transformations will have to take place
 |     // Info about whether the transformations will have to take place
 | ||||||
|     // This is needed because if floating point is used, it is hard to say
 |     // This is needed because if floating point is used, it is hard to say
 | ||||||
|     // that a zero angle is not a rotation because of testing for equality.
 |     // that a zero angle is not a rotation because of testing for equality.
 | ||||||
|     bool has_rotation_ = false, has_translation_ = false, has_offset_ = false; |     bool has_rotation_ = false, has_translation_ = false, has_inflation_ = false; | ||||||
| 
 | 
 | ||||||
|     // For caching the calculations as they can get pretty expensive.
 |     // For caching the calculations as they can get pretty expensive.
 | ||||||
|     mutable RawShape tr_cache_; |     mutable RawShape tr_cache_; | ||||||
|     mutable bool tr_cache_valid_ = false; |     mutable bool tr_cache_valid_ = false; | ||||||
|     mutable double area_cache_ = 0; |     mutable double area_cache_ = 0; | ||||||
|     mutable bool area_cache_valid_ = false; |     mutable bool area_cache_valid_ = false; | ||||||
|     mutable RawShape offset_cache_; |     mutable RawShape inflate_cache_; | ||||||
|     mutable bool offset_cache_valid_ = false; |     mutable bool inflate_cache_valid_ = false; | ||||||
| 
 | 
 | ||||||
|     enum class Convexity: char { |     enum class Convexity: char { | ||||||
|         UNCHECKED, |         UNCHECKED, | ||||||
|  | @ -66,6 +68,9 @@ class _Item { | ||||||
|         BBCache(): valid(false) {} |         BBCache(): valid(false) {} | ||||||
|     } bb_cache_; |     } bb_cache_; | ||||||
|      |      | ||||||
|  |     int binid_{BIN_ID_UNSET}, priority_{0}; | ||||||
|  |     bool fixed_{false}; | ||||||
|  | 
 | ||||||
| public: | public: | ||||||
| 
 | 
 | ||||||
|     /// The type of the shape which was handed over as the template argument.
 |     /// The type of the shape which was handed over as the template argument.
 | ||||||
|  | @ -121,8 +126,16 @@ public: | ||||||
| 
 | 
 | ||||||
|     inline _Item(TContour<RawShape>&& contour, |     inline _Item(TContour<RawShape>&& contour, | ||||||
|                  THolesContainer<RawShape>&& holes): |                  THolesContainer<RawShape>&& holes): | ||||||
|         sh_(sl::create<RawShape>(std::move(contour), |         sh_(sl::create<RawShape>(std::move(contour), std::move(holes))) {} | ||||||
|                                         std::move(holes))) {} |      | ||||||
|  |     inline bool isFixed() const noexcept { return fixed_; } | ||||||
|  |     inline void markAsFixed(bool fixed = true) { fixed_ = fixed; } | ||||||
|  |      | ||||||
|  |     inline void binId(int idx) { binid_ = idx; } | ||||||
|  |     inline int binId() const noexcept { return binid_; } | ||||||
|  |      | ||||||
|  |     inline void priority(int p) { priority_ = p; } | ||||||
|  |     inline int priority() const noexcept { return priority_; } | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * @brief Convert the polygon to string representation. The format depends |      * @brief Convert the polygon to string representation. The format depends | ||||||
|  | @ -200,7 +213,7 @@ public: | ||||||
|         double ret ; |         double ret ; | ||||||
|         if(area_cache_valid_) ret = area_cache_; |         if(area_cache_valid_) ret = area_cache_; | ||||||
|         else { |         else { | ||||||
|             ret = sl::area(offsettedShape()); |             ret = sl::area(infaltedShape()); | ||||||
|             area_cache_ = ret; |             area_cache_ = ret; | ||||||
|             area_cache_valid_ = true; |             area_cache_valid_ = true; | ||||||
|         } |         } | ||||||
|  | @ -272,16 +285,20 @@ public: | ||||||
|         rotation(rotation() + rads); |         rotation(rotation() + rads); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     inline void addOffset(Coord distance) BP2D_NOEXCEPT |     inline void inflation(Coord distance) BP2D_NOEXCEPT | ||||||
|     { |     { | ||||||
|         offset_distance_ = distance; |         inflation_ = distance; | ||||||
|         has_offset_ = true; |         has_inflation_ = true; | ||||||
|         invalidateCache(); |         invalidateCache(); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     inline void removeOffset() BP2D_NOEXCEPT { |     inline Coord inflation() const BP2D_NOEXCEPT { | ||||||
|         has_offset_ = false; |         return inflation_; | ||||||
|         invalidateCache(); |     } | ||||||
|  |      | ||||||
|  |     inline void inflate(Coord distance) BP2D_NOEXCEPT | ||||||
|  |     { | ||||||
|  |         inflation(inflation() + distance); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     inline Radians rotation() const BP2D_NOEXCEPT |     inline Radians rotation() const BP2D_NOEXCEPT | ||||||
|  | @ -315,7 +332,7 @@ public: | ||||||
|     { |     { | ||||||
|         if(tr_cache_valid_) return tr_cache_; |         if(tr_cache_valid_) return tr_cache_; | ||||||
| 
 | 
 | ||||||
|         RawShape cpy = offsettedShape(); |         RawShape cpy = infaltedShape(); | ||||||
|         if(has_rotation_) sl::rotate(cpy, rotation_); |         if(has_rotation_) sl::rotate(cpy, rotation_); | ||||||
|         if(has_translation_) sl::translate(cpy, translation_); |         if(has_translation_) sl::translate(cpy, translation_); | ||||||
|         tr_cache_ = cpy; tr_cache_valid_ = true; |         tr_cache_ = cpy; tr_cache_valid_ = true; | ||||||
|  | @ -336,17 +353,17 @@ public: | ||||||
| 
 | 
 | ||||||
|     inline void resetTransformation() BP2D_NOEXCEPT |     inline void resetTransformation() BP2D_NOEXCEPT | ||||||
|     { |     { | ||||||
|         has_translation_ = false; has_rotation_ = false; has_offset_ = false; |         has_translation_ = false; has_rotation_ = false; has_inflation_ = false; | ||||||
|         invalidateCache(); |         invalidateCache(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     inline Box boundingBox() const { |     inline Box boundingBox() const { | ||||||
|         if(!bb_cache_.valid) { |         if(!bb_cache_.valid) { | ||||||
|             if(!has_rotation_) |             if(!has_rotation_) | ||||||
|                 bb_cache_.bb = sl::boundingBox(offsettedShape()); |                 bb_cache_.bb = sl::boundingBox(infaltedShape()); | ||||||
|             else { |             else { | ||||||
|                 // TODO make sure this works
 |                 // TODO make sure this works
 | ||||||
|                 auto rotsh = offsettedShape(); |                 auto rotsh = infaltedShape(); | ||||||
|                 sl::rotate(rotsh, rotation_); |                 sl::rotate(rotsh, rotation_); | ||||||
|                 bb_cache_.bb = sl::boundingBox(rotsh); |                 bb_cache_.bb = sl::boundingBox(rotsh); | ||||||
|             } |             } | ||||||
|  | @ -395,14 +412,14 @@ public: | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 
 | 
 | ||||||
|     inline const RawShape& offsettedShape() const { |     inline const RawShape& infaltedShape() const { | ||||||
|         if(has_offset_ ) { |         if(has_inflation_ ) { | ||||||
|             if(offset_cache_valid_) return offset_cache_; |             if(inflate_cache_valid_) return inflate_cache_; | ||||||
| 
 | 
 | ||||||
|             offset_cache_ = sh_; |             inflate_cache_ = sh_; | ||||||
|             sl::offset(offset_cache_, offset_distance_); |             sl::offset(inflate_cache_, inflation_); | ||||||
|             offset_cache_valid_ = true; |             inflate_cache_valid_ = true; | ||||||
|             return offset_cache_; |             return inflate_cache_; | ||||||
|         } |         } | ||||||
|         return sh_; |         return sh_; | ||||||
|     } |     } | ||||||
|  | @ -412,7 +429,7 @@ private: | ||||||
|         tr_cache_valid_ = false; |         tr_cache_valid_ = false; | ||||||
|         lmb_valid_ = false; rmt_valid_ = false; |         lmb_valid_ = false; rmt_valid_ = false; | ||||||
|         area_cache_valid_ = false; |         area_cache_valid_ = false; | ||||||
|         offset_cache_valid_ = false; |         inflate_cache_valid_ = false; | ||||||
|         bb_cache_.valid = false; |         bb_cache_.valid = false; | ||||||
|         convexity_ = Convexity::UNCHECKED; |         convexity_ = Convexity::UNCHECKED; | ||||||
|     } |     } | ||||||
|  | @ -492,24 +509,6 @@ template<class RawShape> using _ItemGroup = std::vector<_ItemRef<RawShape>>; | ||||||
| template<class RawShape> | template<class RawShape> | ||||||
| using _PackGroup = std::vector<std::vector<_ItemRef<RawShape>>>; | using _PackGroup = std::vector<std::vector<_ItemRef<RawShape>>>; | ||||||
| 
 | 
 | ||||||
| /**
 |  | ||||||
|  * \brief A list of packed (index, item) pair vectors. Each vector represents a |  | ||||||
|  * bin. |  | ||||||
|  * |  | ||||||
|  * The index is points to the position of the item in the original input |  | ||||||
|  * sequence. This way the caller can use the items as a transformation data |  | ||||||
|  * carrier and transform the original objects manually. |  | ||||||
|  */ |  | ||||||
| template<class RawShape> |  | ||||||
| using _IndexedPackGroup = std::vector< |  | ||||||
|                                std::vector< |  | ||||||
|                                    std::pair< |  | ||||||
|                                        unsigned, |  | ||||||
|                                        _ItemRef<RawShape> |  | ||||||
|                                    > |  | ||||||
|                                > |  | ||||||
|                           >; |  | ||||||
| 
 |  | ||||||
| template<class Iterator> | template<class Iterator> | ||||||
| struct ConstItemRange { | struct ConstItemRange { | ||||||
|     Iterator from; |     Iterator from; | ||||||
|  | @ -738,54 +737,45 @@ public: | ||||||
|         return impl_.getResult(); |         return impl_.getResult(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /**
 |  | ||||||
|      * @brief Loading a group of already packed bins. It is best to use a result |  | ||||||
|      * from a previous packing. The algorithm will consider this input as if the |  | ||||||
|      * objects are already packed and not move them. If any of these items are |  | ||||||
|      * outside the bin, it is up to the placer algorithm what will happen. |  | ||||||
|      * Packing additional items can fail for the bottom-left and nfp placers. |  | ||||||
|      * @param pckgrp A packgroup which is a vector of item vectors. Each item |  | ||||||
|      * vector corresponds to a packed bin. |  | ||||||
|      */ |  | ||||||
|     inline void preload(const PackGroup& pckgrp) { impl_.preload(pckgrp); } |  | ||||||
| 
 |  | ||||||
|     void clear() { impl_.clear(); } |     void clear() { impl_.clear(); } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * The Arranger is the front-end class for the libnest2d library. It takes the |  * The _Nester is the front-end class for the libnest2d library. It takes the | ||||||
|  * input items and outputs the items with the proper transformations to be |  * input items and changes their transformations to be inside the provided bin. | ||||||
|  * inside the provided bin. |  | ||||||
|  */ |  */ | ||||||
| template<class PlacementStrategy, class SelectionStrategy > | template<class PlacementStrategy, class SelectionStrategy > | ||||||
| class Nester { | class _Nester { | ||||||
|     using TSel = SelectionStrategyLike<SelectionStrategy>; |     using TSel = SelectionStrategyLike<SelectionStrategy>; | ||||||
|     TSel selector_; |     TSel selector_; | ||||||
| public: | public: | ||||||
|     using Item = typename PlacementStrategy::Item; |     using Item = typename PlacementStrategy::Item; | ||||||
|  |     using ShapeType = typename Item::ShapeType; | ||||||
|     using ItemRef = std::reference_wrapper<Item>; |     using ItemRef = std::reference_wrapper<Item>; | ||||||
|     using TPlacer = PlacementStrategyLike<PlacementStrategy>; |     using TPlacer = PlacementStrategyLike<PlacementStrategy>; | ||||||
|     using BinType = typename TPlacer::BinType; |     using BinType = typename TPlacer::BinType; | ||||||
|     using PlacementConfig = typename TPlacer::Config; |     using PlacementConfig = typename TPlacer::Config; | ||||||
|     using SelectionConfig = typename TSel::Config; |     using SelectionConfig = typename TSel::Config; | ||||||
| 
 |     using Coord = TCoord<TPoint<typename Item::ShapeType>>; | ||||||
|     using Unit = TCoord<TPoint<typename Item::ShapeType>>; |  | ||||||
| 
 |  | ||||||
|     using IndexedPackGroup = _IndexedPackGroup<typename Item::ShapeType>; |  | ||||||
|     using PackGroup = _PackGroup<typename Item::ShapeType>; |     using PackGroup = _PackGroup<typename Item::ShapeType>; | ||||||
|     using ResultType = PackGroup; |     using ResultType = PackGroup; | ||||||
|     using ResultTypeIndexed = IndexedPackGroup; |  | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     BinType bin_; |     BinType bin_; | ||||||
|     PlacementConfig pconfig_; |     PlacementConfig pconfig_; | ||||||
|     Unit min_obj_distance_; |     Coord min_obj_distance_; | ||||||
| 
 | 
 | ||||||
|     using SItem =  typename SelectionStrategy::Item; |     using SItem =  typename SelectionStrategy::Item; | ||||||
|     using TPItem = remove_cvref_t<Item>; |     using TPItem = remove_cvref_t<Item>; | ||||||
|     using TSItem = remove_cvref_t<SItem>; |     using TSItem = remove_cvref_t<SItem>; | ||||||
| 
 | 
 | ||||||
|     std::vector<TPItem> item_cache_; |     StopCondition stopfn_; | ||||||
|  | 
 | ||||||
|  |     template<class It> using TVal = remove_ref_t<typename It::value_type>; | ||||||
|  |      | ||||||
|  |     template<class It, class Out> | ||||||
|  |     using ItemIteratorOnly = | ||||||
|  |         enable_if_t<std::is_convertible<TVal<It>&, TPItem&>::value, Out>; | ||||||
| 
 | 
 | ||||||
| public: | public: | ||||||
| 
 | 
 | ||||||
|  | @ -798,10 +788,8 @@ public: | ||||||
|     template<class TBinType = BinType, |     template<class TBinType = BinType, | ||||||
|              class PConf = PlacementConfig, |              class PConf = PlacementConfig, | ||||||
|              class SConf = SelectionConfig> |              class SConf = SelectionConfig> | ||||||
|     Nester( TBinType&& bin, |     _Nester(TBinType&& bin, Coord min_obj_distance = 0, | ||||||
|               Unit min_obj_distance = 0, |             const PConf& pconfig = PConf(), const SConf& sconfig = SConf()): | ||||||
|               const PConf& pconfig = PConf(), |  | ||||||
|               const SConf& sconfig = SConf()): |  | ||||||
|         bin_(std::forward<TBinType>(bin)), |         bin_(std::forward<TBinType>(bin)), | ||||||
|         pconfig_(pconfig), |         pconfig_(pconfig), | ||||||
|         min_obj_distance_(min_obj_distance) |         min_obj_distance_(min_obj_distance) | ||||||
|  | @ -814,182 +802,59 @@ public: | ||||||
| 
 | 
 | ||||||
|     void configure(const PlacementConfig& pconf) { pconfig_ = pconf; } |     void configure(const PlacementConfig& pconf) { pconfig_ = pconf; } | ||||||
|     void configure(const SelectionConfig& sconf) { selector_.configure(sconf); } |     void configure(const SelectionConfig& sconf) { selector_.configure(sconf); } | ||||||
|     void configure(const PlacementConfig& pconf, const SelectionConfig& sconf) { |     void configure(const PlacementConfig& pconf, const SelectionConfig& sconf) | ||||||
|  |     { | ||||||
|         pconfig_ = pconf; |         pconfig_ = pconf; | ||||||
|         selector_.configure(sconf); |         selector_.configure(sconf); | ||||||
|     } |     } | ||||||
|     void configure(const SelectionConfig& sconf, const PlacementConfig& pconf) { |     void configure(const SelectionConfig& sconf, const PlacementConfig& pconf) | ||||||
|  |     { | ||||||
|         pconfig_ = pconf; |         pconfig_ = pconf; | ||||||
|         selector_.configure(sconf); |         selector_.configure(sconf); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * \brief Arrange an input sequence and return a PackGroup object with |      * \brief Arrange an input sequence of _Item-s. | ||||||
|      * the packed groups corresponding to the bins. |      * | ||||||
|  |      * To get the result, call the translation(), rotation() and binId() | ||||||
|  |      * methods of each item. If only the transformed polygon is needed, call | ||||||
|  |      * transformedShape() to get the properly transformed shapes. | ||||||
|      * |      * | ||||||
|      * The number of groups in the pack group is the number of bins opened by |      * The number of groups in the pack group is the number of bins opened by | ||||||
|      * the selection algorithm. |      * the selection algorithm. | ||||||
|      */ |      */ | ||||||
|     template<class TIterator> |     template<class It> | ||||||
|     inline PackGroup execute(TIterator from, TIterator to) |     inline ItemIteratorOnly<It, void> execute(It from, It to) | ||||||
|     { |     { | ||||||
|         return _execute(from, to); |         auto infl = static_cast<Coord>(std::ceil(min_obj_distance_/2.0)); | ||||||
|     } |         if(infl > 0) std::for_each(from, to, [this, infl](Item& item) { | ||||||
|  |             item.inflate(infl); | ||||||
|  |         }); | ||||||
|          |          | ||||||
|     /**
 |         selector_.template packItems<PlacementStrategy>( | ||||||
|      * A version of the arrange method returning an IndexedPackGroup with |             from, to, bin_, pconfig_); | ||||||
|      * the item indexes into the original input sequence. |  | ||||||
|      * |  | ||||||
|      * Takes a little longer to collect the indices. Scales linearly with the |  | ||||||
|      * input sequence size. |  | ||||||
|      */ |  | ||||||
|     template<class TIterator> |  | ||||||
|     inline IndexedPackGroup executeIndexed(TIterator from, TIterator to) |  | ||||||
|     { |  | ||||||
|         return _executeIndexed(from, to); |  | ||||||
|     } |  | ||||||
|          |          | ||||||
|     /// Shorthand to normal arrange method.
 |         if(min_obj_distance_ > 0) std::for_each(from, to, [infl](Item& item) { | ||||||
|     template<class TIterator> |             item.inflate(-infl); | ||||||
|     inline PackGroup operator() (TIterator from, TIterator to) |         }); | ||||||
|     { |  | ||||||
|         return _execute(from, to); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Set a progress indicator function object for the selector.
 |     /// Set a progress indicator function object for the selector.
 | ||||||
|     inline Nester& progressIndicator(ProgressFunction func) |     inline _Nester& progressIndicator(ProgressFunction func) | ||||||
|     { |     { | ||||||
|         selector_.progressIndicator(func); return *this; |         selector_.progressIndicator(func); return *this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Set a predicate to tell when to abort nesting.
 |     /// Set a predicate to tell when to abort nesting.
 | ||||||
|     inline Nester& stopCondition(StopCondition fn) |     inline _Nester& stopCondition(StopCondition fn) | ||||||
|     { |     { | ||||||
|         selector_.stopCondition(fn); return *this; |         stopfn_ = fn; selector_.stopCondition(fn); return *this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     inline const PackGroup& lastResult() const |     inline const PackGroup& lastResult() const | ||||||
|     { |     { | ||||||
|         return selector_.getResult(); |         return selector_.getResult(); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     inline void preload(const PackGroup& pgrp) |  | ||||||
|     { |  | ||||||
|         selector_.preload(pgrp); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     inline void preload(const IndexedPackGroup& ipgrp) |  | ||||||
|     { |  | ||||||
|         PackGroup pgrp; pgrp.reserve(ipgrp.size()); |  | ||||||
|         for(auto& ig : ipgrp) { |  | ||||||
|             pgrp.emplace_back(); pgrp.back().reserve(ig.size()); |  | ||||||
|             for(auto& r : ig) pgrp.back().emplace_back(r.second); |  | ||||||
|         } |  | ||||||
|         preload(pgrp); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
| 
 |  | ||||||
|     template<class TIterator, |  | ||||||
|              class IT = remove_cvref_t<typename TIterator::value_type>, |  | ||||||
| 
 |  | ||||||
|              // This function will be used only if the iterators are pointing to
 |  | ||||||
|              // a type compatible with the libnets2d::_Item template.
 |  | ||||||
|              // This way we can use references to input elements as they will
 |  | ||||||
|              // have to exist for the lifetime of this call.
 |  | ||||||
|              class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT> |  | ||||||
|              > |  | ||||||
|     inline const PackGroup& _execute(TIterator from, TIterator to, bool = false) |  | ||||||
|     { |  | ||||||
|         __execute(from, to); |  | ||||||
|         return lastResult(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     template<class TIterator, |  | ||||||
|              class IT = remove_cvref_t<typename TIterator::value_type>, |  | ||||||
|              class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT> |  | ||||||
|              > |  | ||||||
|     inline const PackGroup& _execute(TIterator from, TIterator to, int = false) |  | ||||||
|     { |  | ||||||
|         item_cache_ = {from, to}; |  | ||||||
| 
 |  | ||||||
|         __execute(item_cache_.begin(), item_cache_.end()); |  | ||||||
|         return lastResult(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     template<class TIterator, |  | ||||||
|              class IT = remove_cvref_t<typename TIterator::value_type>, |  | ||||||
| 
 |  | ||||||
|              // This function will be used only if the iterators are pointing to
 |  | ||||||
|              // a type compatible with the libnest2d::_Item template.
 |  | ||||||
|              // This way we can use references to input elements as they will
 |  | ||||||
|              // have to exist for the lifetime of this call.
 |  | ||||||
|              class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT> |  | ||||||
|              > |  | ||||||
|     inline IndexedPackGroup _executeIndexed(TIterator from, |  | ||||||
|                                             TIterator to, |  | ||||||
|                                             bool = false) |  | ||||||
|     { |  | ||||||
|         __execute(from, to); |  | ||||||
|         return createIndexedPackGroup(from, to, selector_); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     template<class TIterator, |  | ||||||
|              class IT = remove_cvref_t<typename TIterator::value_type>, |  | ||||||
|              class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT> |  | ||||||
|              > |  | ||||||
|     inline IndexedPackGroup _executeIndexed(TIterator from, |  | ||||||
|                                             TIterator to, |  | ||||||
|                                             int = false) |  | ||||||
|     { |  | ||||||
|         item_cache_ = {from, to}; |  | ||||||
|         __execute(item_cache_.begin(), item_cache_.end()); |  | ||||||
|         return createIndexedPackGroup(from, to, selector_); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     template<class TIterator> |  | ||||||
|     static IndexedPackGroup createIndexedPackGroup(TIterator from, |  | ||||||
|                                                    TIterator to, |  | ||||||
|                                                    TSel& selector) |  | ||||||
|     { |  | ||||||
|         IndexedPackGroup pg; |  | ||||||
|         pg.reserve(selector.getResult().size()); |  | ||||||
| 
 |  | ||||||
|         const PackGroup& pckgrp = selector.getResult(); |  | ||||||
| 
 |  | ||||||
|         for(size_t i = 0; i < pckgrp.size(); i++) { |  | ||||||
|             auto items = pckgrp[i]; |  | ||||||
|             pg.emplace_back(); |  | ||||||
|             pg[i].reserve(items.size()); |  | ||||||
| 
 |  | ||||||
|             for(Item& itemA : items) { |  | ||||||
|                 auto it = from; |  | ||||||
|                 unsigned idx = 0; |  | ||||||
|                 while(it != to) { |  | ||||||
|                     Item& itemB = *it; |  | ||||||
|                     if(&itemB == &itemA) break; |  | ||||||
|                     it++; idx++; |  | ||||||
|                 } |  | ||||||
|                 pg[i].emplace_back(idx, itemA); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return pg; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     template<class TIter> inline void __execute(TIter from, TIter to) |  | ||||||
|     { |  | ||||||
|         if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { |  | ||||||
|             item.addOffset(static_cast<Unit>(std::ceil(min_obj_distance_/2.0))); |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         selector_.template packItems<PlacementStrategy>( |  | ||||||
|                     from, to, bin_, pconfig_); |  | ||||||
| 
 |  | ||||||
|         if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) { |  | ||||||
|             item.removeOffset(); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -68,11 +68,11 @@ public: | ||||||
|         return toWallPoly(item, Dir::DOWN); |         return toWallPoly(item, Dir::DOWN); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     inline Unit availableSpaceLeft(const Item& item) { |     inline Coord availableSpaceLeft(const Item& item) { | ||||||
|         return availableSpace(item, Dir::LEFT); |         return availableSpace(item, Dir::LEFT); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     inline Unit availableSpaceDown(const Item& item) { |     inline Coord availableSpaceDown(const Item& item) { | ||||||
|         return availableSpace(item, Dir::DOWN); |         return availableSpace(item, Dir::DOWN); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -83,7 +83,7 @@ protected: | ||||||
|         // Get initial position for item in the top right corner
 |         // Get initial position for item in the top right corner
 | ||||||
|         setInitialPosition(item); |         setInitialPosition(item); | ||||||
| 
 | 
 | ||||||
|         Unit d = availableSpaceDown(item); |         Coord d = availableSpaceDown(item); | ||||||
|         auto eps = config_.epsilon; |         auto eps = config_.epsilon; | ||||||
|         bool can_move = d > eps; |         bool can_move = d > eps; | ||||||
|         bool can_be_packed = can_move; |         bool can_be_packed = can_move; | ||||||
|  | @ -179,7 +179,7 @@ protected: | ||||||
|         return ret; |         return ret; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Unit availableSpace(const Item& _item, const Dir dir) { |     Coord availableSpace(const Item& _item, const Dir dir) { | ||||||
| 
 | 
 | ||||||
|         Item item (_item.transformedShape()); |         Item item (_item.transformedShape()); | ||||||
| 
 | 
 | ||||||
|  | @ -223,7 +223,7 @@ protected: | ||||||
|                                                    cmp); |                                                    cmp); | ||||||
| 
 | 
 | ||||||
|         // Get the initial distance in floating point
 |         // Get the initial distance in floating point
 | ||||||
|         Unit m = getCoord(*minvertex_it); |         Coord m = getCoord(*minvertex_it); | ||||||
| 
 | 
 | ||||||
|         // Check available distance for every vertex of item to the objects
 |         // Check available distance for every vertex of item to the objects
 | ||||||
|         // in the way for the nearest intersection
 |         // in the way for the nearest intersection
 | ||||||
|  |  | ||||||
|  | @ -581,8 +581,12 @@ public: | ||||||
| 
 | 
 | ||||||
|     static inline double overfit(const Box& bb, const Box& bin) |     static inline double overfit(const Box& bb, const Box& bin) | ||||||
|     { |     { | ||||||
|         auto wdiff = double(bb.width() - bin.width()); |         auto Bw = bin.width(); | ||||||
|         auto hdiff = double(bb.height() - bin.height()); |         auto Bh = bin.height(); | ||||||
|  |         auto mBw = -Bw; | ||||||
|  |         auto mBh = -Bh; | ||||||
|  |         auto wdiff = double(bb.width()) + mBw; | ||||||
|  |         auto hdiff = double(bb.height()) + mBh; | ||||||
|         double diff = 0; |         double diff = 0; | ||||||
|         if(wdiff > 0) diff += wdiff; |         if(wdiff > 0) diff += wdiff; | ||||||
|         if(hdiff > 0) diff += hdiff; |         if(hdiff > 0) diff += hdiff; | ||||||
|  | @ -801,7 +805,6 @@ private: | ||||||
|             // optimize
 |             // optimize
 | ||||||
|             config_.object_function = prev_func; |             config_.object_function = prev_func; | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     struct Optimum { |     struct Optimum { | ||||||
|  | @ -816,29 +819,14 @@ private: | ||||||
| 
 | 
 | ||||||
|     class Optimizer: public opt::TOptimizer<opt::Method::L_SUBPLEX> { |     class Optimizer: public opt::TOptimizer<opt::Method::L_SUBPLEX> { | ||||||
|     public: |     public: | ||||||
|         Optimizer() { |         Optimizer(float accuracy = 1.f) { | ||||||
|             opt::StopCriteria stopcr; |             opt::StopCriteria stopcr; | ||||||
|             stopcr.max_iterations = 200; |             stopcr.max_iterations = unsigned(std::floor(1000 * accuracy)); | ||||||
|             stopcr.relative_score_difference = 1e-20; |             stopcr.relative_score_difference = 1e-20; | ||||||
|             this->stopcr_ = stopcr; |             this->stopcr_ = stopcr; | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     static Box boundingBox(const Box& pilebb, const Box& ibb ) { |  | ||||||
|         auto& pminc = pilebb.minCorner(); |  | ||||||
|         auto& pmaxc = pilebb.maxCorner(); |  | ||||||
|         auto& iminc = ibb.minCorner(); |  | ||||||
|         auto& imaxc = ibb.maxCorner(); |  | ||||||
|         Vertex minc, maxc; |  | ||||||
| 
 |  | ||||||
|         setX(minc, std::min(getX(pminc), getX(iminc))); |  | ||||||
|         setY(minc, std::min(getY(pminc), getY(iminc))); |  | ||||||
| 
 |  | ||||||
|         setX(maxc, std::max(getX(pmaxc), getX(imaxc))); |  | ||||||
|         setY(maxc, std::max(getY(pmaxc), getY(imaxc))); |  | ||||||
|         return Box(minc, maxc); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     using Edges = EdgeCache<RawShape>; |     using Edges = EdgeCache<RawShape>; | ||||||
| 
 | 
 | ||||||
|     template<class Range = ConstItemRange<typename Base::DefaultIter>> |     template<class Range = ConstItemRange<typename Base::DefaultIter>> | ||||||
|  | @ -935,7 +923,7 @@ private: | ||||||
|                     _objfunc = [norm, binbb, pbb, ins_check](const Item& item) |                     _objfunc = [norm, binbb, pbb, ins_check](const Item& item) | ||||||
|                     { |                     { | ||||||
|                         auto ibb = item.boundingBox(); |                         auto ibb = item.boundingBox(); | ||||||
|                         auto fullbb = boundingBox(pbb, ibb); |                         auto fullbb = sl::boundingBox(pbb, ibb); | ||||||
| 
 | 
 | ||||||
|                         double score = pl::distance(ibb.center(), |                         double score = pl::distance(ibb.center(), | ||||||
|                                                     binbb.center()); |                                                     binbb.center()); | ||||||
|  | @ -1005,14 +993,15 @@ private: | ||||||
| 
 | 
 | ||||||
|                     auto& rofn = rawobjfunc; |                     auto& rofn = rawobjfunc; | ||||||
|                     auto& nfpoint = getNfpPoint; |                     auto& nfpoint = getNfpPoint; | ||||||
|  |                     float accuracy = config_.accuracy; | ||||||
| 
 | 
 | ||||||
|                     __parallel::enumerate( |                     __parallel::enumerate( | ||||||
|                                 cache.corners().begin(), |                                 cache.corners().begin(), | ||||||
|                                 cache.corners().end(), |                                 cache.corners().end(), | ||||||
|                                 [&results, &item, &rofn, &nfpoint, ch] |                                 [&results, &item, &rofn, &nfpoint, ch, accuracy] | ||||||
|                                 (double pos, size_t n) |                                 (double pos, size_t n) | ||||||
|                     { |                     { | ||||||
|                         Optimizer solver; |                         Optimizer solver(accuracy); | ||||||
| 
 | 
 | ||||||
|                         Item itemcpy = item; |                         Item itemcpy = item; | ||||||
|                         auto contour_ofn = [&rofn, &nfpoint, ch, &itemcpy] |                         auto contour_ofn = [&rofn, &nfpoint, ch, &itemcpy] | ||||||
|  | @ -1059,10 +1048,10 @@ private: | ||||||
|                         __parallel::enumerate(cache.corners(hidx).begin(), |                         __parallel::enumerate(cache.corners(hidx).begin(), | ||||||
|                                       cache.corners(hidx).end(), |                                       cache.corners(hidx).end(), | ||||||
|                                       [&results, &item, &nfpoint, |                                       [&results, &item, &nfpoint, | ||||||
|                                        &rofn, ch, hidx] |                                        &rofn, ch, hidx, accuracy] | ||||||
|                                       (double pos, size_t n) |                                       (double pos, size_t n) | ||||||
|                         { |                         { | ||||||
|                             Optimizer solver; |                             Optimizer solver(accuracy); | ||||||
| 
 | 
 | ||||||
|                             Item itmcpy = item; |                             Item itmcpy = item; | ||||||
|                             auto hole_ofn = |                             auto hole_ofn = | ||||||
|  |  | ||||||
|  | @ -18,7 +18,6 @@ public: | ||||||
|     using Segment = _Segment<Vertex>; |     using Segment = _Segment<Vertex>; | ||||||
|     using BinType = TBin; |     using BinType = TBin; | ||||||
|     using Coord = TCoord<Vertex>; |     using Coord = TCoord<Vertex>; | ||||||
|     using Unit = Coord; |  | ||||||
|     using Config = Cfg; |     using Config = Cfg; | ||||||
|     using ItemGroup = _ItemGroup<RawShape>; |     using ItemGroup = _ItemGroup<RawShape>; | ||||||
|     using DefaultIter = typename ItemGroup::const_iterator; |     using DefaultIter = typename ItemGroup::const_iterator; | ||||||
|  | @ -131,7 +130,6 @@ using typename Base::Vertex;      \ | ||||||
| using typename Base::Segment;     \ | using typename Base::Segment;     \ | ||||||
| using typename Base::PackResult;  \ | using typename Base::PackResult;  \ | ||||||
| using typename Base::Coord;       \ | using typename Base::Coord;       \ | ||||||
| using typename Base::Unit;        \ |  | ||||||
| private: | private: | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -712,6 +712,11 @@ public: | ||||||
|             packjob(placers[idx], remaining, idx); idx++; |             packjob(placers[idx], remaining, idx); idx++; | ||||||
|         } |         } | ||||||
|          |          | ||||||
|  |         int binid = 0; | ||||||
|  |         for(auto &bin : packed_bins_) { | ||||||
|  |             for(Item& itm : bin) itm.binId(binid); | ||||||
|  |             binid++; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -40,6 +40,20 @@ public: | ||||||
|         std::vector<Placer> placers; |         std::vector<Placer> placers; | ||||||
|         placers.reserve(last-first); |         placers.reserve(last-first); | ||||||
|          |          | ||||||
|  |         std::for_each(first, last, [this](Item& itm) { | ||||||
|  |             if(itm.isFixed()) { | ||||||
|  |                 if (itm.binId() < 0) itm.binId(0); | ||||||
|  |                 auto binidx = size_t(itm.binId()); | ||||||
|  |                  | ||||||
|  |                 while(packed_bins_.size() <= binidx) | ||||||
|  |                     packed_bins_.emplace_back(); | ||||||
|  |                  | ||||||
|  |                 packed_bins_[binidx].emplace_back(itm); | ||||||
|  |             } else { | ||||||
|  |                 store_.emplace_back(itm); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|         // If the packed_items array is not empty we have to create as many
 |         // If the packed_items array is not empty we have to create as many
 | ||||||
|         // placers as there are elements in packed bins and preload each item
 |         // placers as there are elements in packed bins and preload each item
 | ||||||
|         // into the appropriate placer
 |         // into the appropriate placer
 | ||||||
|  | @ -49,10 +63,9 @@ public: | ||||||
|             placers.back().preload(ig); |             placers.back().preload(ig); | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         std::copy(first, last, std::back_inserter(store_)); |  | ||||||
| 
 |  | ||||||
|         auto sortfunc = [](Item& i1, Item& i2) { |         auto sortfunc = [](Item& i1, Item& i2) { | ||||||
|             return i1.area() > i2.area(); |             int p1 = i1.priority(), p2 = i2.priority(); | ||||||
|  |             return p1 == p2 ? i1.area() > i2.area() : p1 > p2; | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         std::sort(store_.begin(), store_.end(), sortfunc); |         std::sort(store_.begin(), store_.end(), sortfunc); | ||||||
|  | @ -76,7 +89,6 @@ public: | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|         auto it = store_.begin(); |         auto it = store_.begin(); | ||||||
| 
 | 
 | ||||||
|         while(it != store_.end() && !cancelled()) { |         while(it != store_.end() && !cancelled()) { | ||||||
|  | @ -84,9 +96,11 @@ public: | ||||||
|             size_t j = 0; |             size_t j = 0; | ||||||
|             while(!was_packed && !cancelled()) { |             while(!was_packed && !cancelled()) { | ||||||
|                 for(; j < placers.size() && !was_packed && !cancelled(); j++) { |                 for(; j < placers.size() && !was_packed && !cancelled(); j++) { | ||||||
|                     if((was_packed = placers[j].pack(*it, rem(it, store_) ))) |                     if((was_packed = placers[j].pack(*it, rem(it, store_) ))) { | ||||||
|  |                         it->get().binId(int(j)); | ||||||
|                         makeProgress(placers[j], j); |                         makeProgress(placers[j], j); | ||||||
|                     } |                     } | ||||||
|  |                 } | ||||||
| 
 | 
 | ||||||
|                 if(!was_packed) { |                 if(!was_packed) { | ||||||
|                     placers.emplace_back(bin); |                     placers.emplace_back(bin); | ||||||
|  |  | ||||||
|  | @ -22,8 +22,6 @@ public: | ||||||
| 
 | 
 | ||||||
|     inline void stopCondition(StopCondition cond) { stopcond_ = cond; } |     inline void stopCondition(StopCondition cond) { stopcond_ = cond; } | ||||||
| 
 | 
 | ||||||
|     inline void preload(const PackGroup& pckgrp) { packed_bins_ = pckgrp; } |  | ||||||
| 
 |  | ||||||
|     inline void clear() { packed_bins_.clear(); } |     inline void clear() { packed_bins_.clear(); } | ||||||
| 
 | 
 | ||||||
| protected: | protected: | ||||||
|  |  | ||||||
|  | @ -363,29 +363,43 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) | ||||||
|         {5, 5}, |         {5, 5}, | ||||||
|         {20, 20} }; |         {20, 20} }; | ||||||
|      |      | ||||||
|  |     Box bin(210, 250, {105, 125}); | ||||||
|      |      | ||||||
|     Nester<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250)); |     ASSERT_EQ(bin.width(), 210); | ||||||
|  |     ASSERT_EQ(bin.height(), 250); | ||||||
|  |     ASSERT_EQ(getX(bin.center()), 105); | ||||||
|  |     ASSERT_EQ(getY(bin.center()), 125); | ||||||
|      |      | ||||||
|     auto groups = arrange(rects.begin(), rects.end()); |     _Nester<BottomLeftPlacer, DJDHeuristic> arrange(bin); | ||||||
|      |      | ||||||
|     ASSERT_EQ(groups.size(), 1u); |     arrange.execute(rects.begin(), rects.end()); | ||||||
|     ASSERT_EQ(groups[0].size(), rects.size()); | 
 | ||||||
|  |     auto max_group = std::max_element(rects.begin(), rects.end(), | ||||||
|  |                                       [](const Item &i1, const Item &i2) { | ||||||
|  |                                           return i1.binId() < i2.binId(); | ||||||
|  |                                       }); | ||||||
|  | 
 | ||||||
|  |     int groups = max_group == rects.end() ? 0 : max_group->binId() + 1; | ||||||
|  | 
 | ||||||
|  |     ASSERT_EQ(groups, 1u); | ||||||
|  |     ASSERT_TRUE( | ||||||
|  |         std::all_of(rects.begin(), rects.end(), [](const Rectangle &itm) { | ||||||
|  |             return itm.binId() != BIN_ID_UNSET; | ||||||
|  |         })); | ||||||
| 
 | 
 | ||||||
|     // check for no intersections, no containment:
 |     // check for no intersections, no containment:
 | ||||||
|      |      | ||||||
|     for(auto result : groups) { |  | ||||||
|     bool valid = true; |     bool valid = true; | ||||||
|         for(Item& r1 : result) { |     for(Item& r1 : rects) { | ||||||
|             for(Item& r2 : result) { |         for(Item& r2 : rects) { | ||||||
|             if(&r1 != &r2 ) { |             if(&r1 != &r2 ) { | ||||||
|                 valid = !Item::intersects(r1, r2) || Item::touches(r1, r2); |                 valid = !Item::intersects(r1, r2) || Item::touches(r1, r2); | ||||||
|  |                 ASSERT_TRUE(valid); | ||||||
|                 valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); |                 valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); | ||||||
|                 ASSERT_TRUE(valid); |                 ASSERT_TRUE(valid); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     } |  | ||||||
|      |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| TEST(GeometryAlgorithms, ArrangeRectanglesLoose) | TEST(GeometryAlgorithms, ArrangeRectanglesLoose) | ||||||
|  | @ -415,21 +429,36 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) | ||||||
|         {5, 5}, |         {5, 5}, | ||||||
|         {20, 20} }; |         {20, 20} }; | ||||||
|      |      | ||||||
|  |     Box bin(210, 250, {105, 125}); | ||||||
|  |      | ||||||
|  |     ASSERT_EQ(bin.width(), 210); | ||||||
|  |     ASSERT_EQ(bin.height(), 250); | ||||||
|  |     ASSERT_EQ(getX(bin.center()), 105); | ||||||
|  |     ASSERT_EQ(getY(bin.center()), 125); | ||||||
|  |      | ||||||
|     Coord min_obj_distance = 5; |     Coord min_obj_distance = 5; | ||||||
|      |      | ||||||
|     Nester<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250), |     _Nester<BottomLeftPlacer, DJDHeuristic> arrange(bin, min_obj_distance); | ||||||
|                                                    min_obj_distance); |  | ||||||
|      |      | ||||||
|     auto groups = arrange(rects.begin(), rects.end()); |     arrange.execute(rects.begin(), rects.end()); | ||||||
|      |      | ||||||
|     ASSERT_EQ(groups.size(), 1u); |     auto max_group = std::max_element(rects.begin(), rects.end(), | ||||||
|     ASSERT_EQ(groups[0].size(), rects.size()); |                                       [](const Item &i1, const Item &i2) { | ||||||
|  |                                           return i1.binId() < i2.binId(); | ||||||
|  |                                       }); | ||||||
|  |      | ||||||
|  |     size_t groups = max_group == rects.end() ? 0 : max_group->binId() + 1; | ||||||
|  |      | ||||||
|  |     ASSERT_EQ(groups, 1u); | ||||||
|  |     ASSERT_TRUE( | ||||||
|  |         std::all_of(rects.begin(), rects.end(), [](const Rectangle &itm) { | ||||||
|  |             return itm.binId() != BIN_ID_UNSET; | ||||||
|  |         })); | ||||||
|      |      | ||||||
|     // check for no intersections, no containment:
 |     // check for no intersections, no containment:
 | ||||||
|     auto result = groups[0]; |  | ||||||
|     bool valid = true; |     bool valid = true; | ||||||
|     for(Item& r1 : result) { |     for(Item& r1 : rects) { | ||||||
|         for(Item& r2 : result) { |         for(Item& r2 : rects) { | ||||||
|             if(&r1 != &r2 ) { |             if(&r1 != &r2 ) { | ||||||
|                 valid = !Item::intersects(r1, r2); |                 valid = !Item::intersects(r1, r2); | ||||||
|                 valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); |                 valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); | ||||||
|  | @ -542,26 +571,23 @@ TEST(GeometryAlgorithms, convexHull) { | ||||||
| TEST(GeometryAlgorithms, NestTest) { | TEST(GeometryAlgorithms, NestTest) { | ||||||
|     std::vector<Item> input = prusaParts(); |     std::vector<Item> input = prusaParts(); | ||||||
| 
 | 
 | ||||||
|     PackGroup result = libnest2d::nest(input, |     libnest2d::nest(input, Box(250000000, 210000000), [](unsigned cnt) { | ||||||
|                                        Box(250000000, 210000000), |         std::cout << "parts left: " << cnt << std::endl; | ||||||
|                                        [](unsigned cnt) { |  | ||||||
|                                            std::cout |  | ||||||
|                                                << "parts left: " << cnt |  | ||||||
|                                                << std::endl; |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     ASSERT_LE(result.size(), 2); |     auto max_binid_it = std::max_element(input.begin(), input.end(), | ||||||
|      |                                          [](const Item &i1, const Item &i2) { | ||||||
|     size_t partsum = std::accumulate(result.begin(), |                                              return i1.binId() < i2.binId(); | ||||||
|                                      result.end(), |  | ||||||
|                                      size_t(0), |  | ||||||
|                                      [](size_t s, |  | ||||||
|                                         const decltype( |  | ||||||
|                                             result)::value_type &bin) { |  | ||||||
|                                          return s += bin.size(); |  | ||||||
|                                          }); |                                          }); | ||||||
| 
 | 
 | ||||||
|     ASSERT_EQ(input.size(), partsum); |     size_t bins = max_binid_it == input.end() ? 0 : max_binid_it->binId() + 1; | ||||||
|  |      | ||||||
|  |     ASSERT_EQ(bins, 2u); | ||||||
|  | 
 | ||||||
|  |     ASSERT_TRUE( | ||||||
|  |         std::all_of(input.begin(), input.end(), [](const Item &itm) { | ||||||
|  |             return itm.binId() != BIN_ID_UNSET; | ||||||
|  |         })); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| namespace { | namespace { | ||||||
|  |  | ||||||
							
								
								
									
										642
									
								
								src/libslic3r/Arrange.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										642
									
								
								src/libslic3r/Arrange.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,642 @@ | ||||||
|  | #include "Arrange.hpp" | ||||||
|  | #include "Geometry.hpp" | ||||||
|  | #include "SVG.hpp" | ||||||
|  | #include "MTUtils.hpp" | ||||||
|  | 
 | ||||||
|  | #include <libnest2d/backends/clipper/geometries.hpp> | ||||||
|  | #include <libnest2d/optimizers/nlopt/subplex.hpp> | ||||||
|  | #include <libnest2d/placers/nfpplacer.hpp> | ||||||
|  | #include <libnest2d/selections/firstfit.hpp> | ||||||
|  | 
 | ||||||
|  | #include <numeric> | ||||||
|  | #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 nfp
 | ||||||
|  | } // namespace libnest2d
 | ||||||
|  | 
 | ||||||
|  | namespace Slic3r { | ||||||
|  | 
 | ||||||
|  | template<class Tout = double, class = FloatingOnly<Tout>, int...EigenArgs> | ||||||
|  | inline SLIC3R_CONSTEXPR Eigen::Matrix<Tout, 2, EigenArgs...> unscaled( | ||||||
|  |     const ClipperLib::IntPoint &v) SLIC3R_NOEXCEPT | ||||||
|  | { | ||||||
|  |     return Eigen::Matrix<Tout, 2, EigenArgs...>{unscaled<Tout>(v.X), | ||||||
|  |                                                 unscaled<Tout>(v.Y)}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | namespace arrangement { | ||||||
|  | 
 | ||||||
|  | using namespace libnest2d; | ||||||
|  | namespace clppr = ClipperLib; | ||||||
|  | 
 | ||||||
|  | // Get the libnest2d types for clipper backend
 | ||||||
|  | using Item         = _Item<clppr::Polygon>; | ||||||
|  | using Box          = _Box<clppr::IntPoint>; | ||||||
|  | using Circle       = _Circle<clppr::IntPoint>; | ||||||
|  | using Segment      = _Segment<clppr::IntPoint>; | ||||||
|  | using MultiPolygon = TMultiShape<clppr::Polygon>; | ||||||
|  | 
 | ||||||
|  | // Summon the spatial indexing facilities from boost
 | ||||||
|  | namespace bgi = boost::geometry::index; | ||||||
|  | using SpatElement = std::pair<Box, unsigned>; | ||||||
|  | using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; | ||||||
|  | using ItemGroup = std::vector<std::reference_wrapper<Item>>; | ||||||
|  | 
 | ||||||
|  | // A coefficient used in separating bigger items and smaller items.
 | ||||||
|  | const double BIG_ITEM_TRESHOLD = 0.02; | ||||||
|  | 
 | ||||||
|  | // Fill in the placer algorithm configuration with values carefully chosen for
 | ||||||
|  | // Slic3r.
 | ||||||
|  | template<class PConf> | ||||||
|  | void fillConfig(PConf& pcfg) { | ||||||
|  | 
 | ||||||
|  |     // Align the arranged pile into the center of the bin
 | ||||||
|  |     pcfg.alignment = PConf::Alignment::CENTER; | ||||||
|  | 
 | ||||||
|  |     // Start placing the items from the center of the print bed
 | ||||||
|  |     pcfg.starting_point = PConf::Alignment::CENTER; | ||||||
|  | 
 | ||||||
|  |     // TODO cannot use rotations until multiple objects of same geometry can
 | ||||||
|  |     // handle different rotations.
 | ||||||
|  |     pcfg.rotations = { 0.0 }; | ||||||
|  | 
 | ||||||
|  |     // The accuracy of optimization.
 | ||||||
|  |     // Goes from 0.0 to 1.0 and scales performance as well
 | ||||||
|  |     pcfg.accuracy = 0.65f; | ||||||
|  |      | ||||||
|  |     // Allow parallel execution.
 | ||||||
|  |     pcfg.parallel = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Apply penalty to object function result. This is used only when alignment
 | ||||||
|  | // after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN)
 | ||||||
|  | double fixed_overfit(const std::tuple<double, Box>& result, const Box &binbb) | ||||||
|  | { | ||||||
|  |     double score = std::get<0>(result); | ||||||
|  |     Box pilebb  = std::get<1>(result); | ||||||
|  |     Box fullbb  = sl::boundingBox(pilebb, binbb); | ||||||
|  |     auto diff = double(fullbb.area()) - binbb.area(); | ||||||
|  |     if(diff > 0) score += diff; | ||||||
|  |      | ||||||
|  |     return score; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A class encapsulating the libnest2d Nester class and extending it with other
 | ||||||
|  | // management and spatial index structures for acceleration.
 | ||||||
|  | template<class TBin> | ||||||
|  | class AutoArranger { | ||||||
|  | public: | ||||||
|  |     // Useful type shortcuts...
 | ||||||
|  |     using Placer = typename placers::_NofitPolyPlacer<clppr::Polygon, TBin>; | ||||||
|  |     using Selector = selections::_FirstFitSelection<clppr::Polygon>; | ||||||
|  |     using Packer   = _Nester<Placer, Selector>; | ||||||
|  |     using PConfig  = typename Packer::PlacementConfig; | ||||||
|  |     using Distance = TCoord<PointImpl>; | ||||||
|  | 
 | ||||||
|  | protected: | ||||||
|  |     Packer    m_pck; | ||||||
|  |     PConfig   m_pconf; // Placement configuration
 | ||||||
|  |     TBin      m_bin; | ||||||
|  |     double    m_bin_area; | ||||||
|  |     SpatIndex m_rtree; // spatial index for the normal (bigger) objects
 | ||||||
|  |     SpatIndex m_smallsrtree;    // spatial index for only the smaller items
 | ||||||
|  |     double    m_norm;           // A coefficient to scale distances
 | ||||||
|  |     MultiPolygon m_merged_pile; // The already merged pile (vector of items)
 | ||||||
|  |     Box          m_pilebb;      // The bounding box of the merged pile.
 | ||||||
|  |     ItemGroup m_remaining; // Remaining items (m_items at the beginning)
 | ||||||
|  |     ItemGroup m_items;     // The items to be packed
 | ||||||
|  |      | ||||||
|  |     template<class T> ArithmeticOnly<T, double> norm(T val) | ||||||
|  |     { | ||||||
|  |         return double(val) / m_norm; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // This is "the" object function which is evaluated many times for each
 | ||||||
|  |     // vertex (decimated with the accuracy parameter) of each object.
 | ||||||
|  |     // Therefore it is upmost crucial for this function to be as efficient
 | ||||||
|  |     // as it possibly can be but at the same time, it has to provide
 | ||||||
|  |     // reasonable results.
 | ||||||
|  |     std::tuple<double /*score*/, Box /*farthest point from bin center*/> | ||||||
|  |     objfunc(const Item &item, const clppr::IntPoint &bincenter) | ||||||
|  |     { | ||||||
|  |         const double bin_area = m_bin_area; | ||||||
|  |         const SpatIndex& spatindex = m_rtree; | ||||||
|  |         const SpatIndex& smalls_spatindex = m_smallsrtree; | ||||||
|  |         const ItemGroup& remaining = m_remaining; | ||||||
|  |          | ||||||
|  |         // We will treat big items (compared to the print bed) differently
 | ||||||
|  |         auto isBig = [bin_area](double a) { | ||||||
|  |             return a/bin_area > BIG_ITEM_TRESHOLD ; | ||||||
|  |         }; | ||||||
|  |          | ||||||
|  |         // Candidate item bounding box
 | ||||||
|  |         auto ibb = item.boundingBox(); | ||||||
|  |          | ||||||
|  |         // Calculate the full bounding box of the pile with the candidate item
 | ||||||
|  |         auto fullbb = sl::boundingBox(m_pilebb, ibb); | ||||||
|  |          | ||||||
|  |         // The bounding box of the big items (they will accumulate in the center
 | ||||||
|  |         // of the pile
 | ||||||
|  |         Box bigbb; | ||||||
|  |         if(spatindex.empty()) bigbb = fullbb; | ||||||
|  |         else { | ||||||
|  |             auto boostbb = spatindex.bounds(); | ||||||
|  |             boost::geometry::convert(boostbb, bigbb); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // Will hold the resulting score
 | ||||||
|  |         double score = 0; | ||||||
|  |          | ||||||
|  |         // Density is the pack density: how big is the arranged pile
 | ||||||
|  |         double density = 0; | ||||||
|  |          | ||||||
|  |         // Distinction of cases for the arrangement scene
 | ||||||
|  |         enum e_cases { | ||||||
|  |             // This branch is for big items in a mixed (big and small) scene
 | ||||||
|  |             // OR for all items in a small-only scene.
 | ||||||
|  |             BIG_ITEM, | ||||||
|  |              | ||||||
|  |             // This branch is for the last big item in a mixed scene
 | ||||||
|  |             LAST_BIG_ITEM, | ||||||
|  |              | ||||||
|  |             // For small items in a mixed scene.
 | ||||||
|  |             SMALL_ITEM | ||||||
|  |         } compute_case; | ||||||
|  |          | ||||||
|  |         bool bigitems = isBig(item.area()) || spatindex.empty(); | ||||||
|  |         if(bigitems && !remaining.empty()) compute_case = BIG_ITEM; | ||||||
|  |         else if (bigitems && remaining.empty()) compute_case = LAST_BIG_ITEM; | ||||||
|  |         else compute_case = SMALL_ITEM; | ||||||
|  |          | ||||||
|  |         switch (compute_case) { | ||||||
|  |         case BIG_ITEM: { | ||||||
|  |             const clppr::IntPoint& minc = ibb.minCorner(); // bottom left corner
 | ||||||
|  |             const clppr::IntPoint& maxc = ibb.maxCorner(); // top right corner
 | ||||||
|  | 
 | ||||||
|  |             // top left and bottom right corners
 | ||||||
|  |             clppr::IntPoint top_left{getX(minc), getY(maxc)}; | ||||||
|  |             clppr::IntPoint bottom_right{getX(maxc), getY(minc)}; | ||||||
|  | 
 | ||||||
|  |             // Now the distance of the gravity center will be calculated to the
 | ||||||
|  |             // five anchor points and the smallest will be chosen.
 | ||||||
|  |             std::array<double, 5> dists; | ||||||
|  |             auto cc = fullbb.center(); // The gravity center
 | ||||||
|  |             dists[0] = pl::distance(minc, cc); | ||||||
|  |             dists[1] = pl::distance(maxc, cc); | ||||||
|  |             dists[2] = pl::distance(ibb.center(), cc); | ||||||
|  |             dists[3] = pl::distance(top_left, cc); | ||||||
|  |             dists[4] = pl::distance(bottom_right, cc); | ||||||
|  | 
 | ||||||
|  |             // The smalles distance from the arranged pile center:
 | ||||||
|  |             double dist = norm(*(std::min_element(dists.begin(), dists.end()))); | ||||||
|  |             double bindist = norm(pl::distance(ibb.center(), bincenter)); | ||||||
|  |             dist = 0.8 * dist + 0.2*bindist; | ||||||
|  | 
 | ||||||
|  |             // Prepare a variable for the alignment score.
 | ||||||
|  |             // This will indicate: how well is the candidate item
 | ||||||
|  |             // aligned with its neighbors. We will check the alignment
 | ||||||
|  |             // with all neighbors and return the score for the best
 | ||||||
|  |             // alignment. So it is enough for the candidate to be
 | ||||||
|  |             // aligned with only one item.
 | ||||||
|  |             auto alignment_score = 1.0; | ||||||
|  | 
 | ||||||
|  |             auto query = bgi::intersects(ibb); | ||||||
|  |             auto& index = isBig(item.area()) ? spatindex : smalls_spatindex; | ||||||
|  | 
 | ||||||
|  |             // Query the spatial index for the neighbors
 | ||||||
|  |             std::vector<SpatElement> result; | ||||||
|  |             result.reserve(index.size()); | ||||||
|  | 
 | ||||||
|  |             index.query(query, std::back_inserter(result)); | ||||||
|  | 
 | ||||||
|  |             // now get the score for the best alignment
 | ||||||
|  |             for(auto& e : result) {  | ||||||
|  |                 auto idx = e.second; | ||||||
|  |                 Item& p = m_items[idx]; | ||||||
|  |                 auto parea = p.area(); | ||||||
|  |                 if(std::abs(1.0 - parea/item.area()) < 1e-6) { | ||||||
|  |                     auto bb = sl::boundingBox(p.boundingBox(), ibb); | ||||||
|  |                     auto bbarea = bb.area(); | ||||||
|  |                     auto ascore = 1.0 - (item.area() + parea)/bbarea; | ||||||
|  | 
 | ||||||
|  |                     if(ascore < alignment_score) alignment_score = ascore; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             density = std::sqrt(norm(fullbb.width()) * norm(fullbb.height())); | ||||||
|  | 
 | ||||||
|  |             // The final mix of the score is the balance between the
 | ||||||
|  |             // distance from the full pile center, the pack density and
 | ||||||
|  |             // the alignment with the neighbors
 | ||||||
|  |             if (result.empty()) | ||||||
|  |                 score = 0.5 * dist + 0.5 * density; | ||||||
|  |             else | ||||||
|  |                 score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; | ||||||
|  |              | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         case LAST_BIG_ITEM: { | ||||||
|  |             auto mp = m_merged_pile; | ||||||
|  |             mp.emplace_back(item.transformedShape()); | ||||||
|  |             auto chull = sl::convexHull(mp); | ||||||
|  |      | ||||||
|  |             placers::EdgeCache<clppr::Polygon> ec(chull); | ||||||
|  |              | ||||||
|  |             double circ  = norm(ec.circumference()); | ||||||
|  |             double bcirc = 2.0 * norm(fullbb.width() + fullbb.height()); | ||||||
|  |             score = 0.5 * circ + 0.5 * bcirc; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         case SMALL_ITEM: { | ||||||
|  |             // Here there are the small items that should be placed around the
 | ||||||
|  |             // already processed bigger items.
 | ||||||
|  |             // No need to play around with the anchor points, the center will be
 | ||||||
|  |             // just fine for small items
 | ||||||
|  |             score = norm(pl::distance(ibb.center(), bigbb.center())); | ||||||
|  |             break; | ||||||
|  |         }             | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         return std::make_tuple(score, fullbb); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     std::function<double(const Item&)> get_objfn(); | ||||||
|  |      | ||||||
|  | public: | ||||||
|  |     AutoArranger(const TBin &                  bin, | ||||||
|  |                  Distance                      dist, | ||||||
|  |                  std::function<void(unsigned)> progressind, | ||||||
|  |                  std::function<bool(void)>     stopcond) | ||||||
|  |         : m_pck(bin, dist) | ||||||
|  |         , m_bin(bin) | ||||||
|  |         , m_bin_area(sl::area(bin)) | ||||||
|  |         , m_norm(std::sqrt(m_bin_area)) | ||||||
|  |     { | ||||||
|  |         fillConfig(m_pconf); | ||||||
|  | 
 | ||||||
|  |         // Set up a callback that is called just before arranging starts
 | ||||||
|  |         // This functionality is provided by the Nester class (m_pack).
 | ||||||
|  |         m_pconf.before_packing = | ||||||
|  |         [this](const MultiPolygon& merged_pile,            // merged pile
 | ||||||
|  |                const ItemGroup& items,             // packed items
 | ||||||
|  |                const ItemGroup& remaining)         // future items to be packed
 | ||||||
|  |         { | ||||||
|  |             m_items = items; | ||||||
|  |             m_merged_pile = merged_pile; | ||||||
|  |             m_remaining = remaining; | ||||||
|  | 
 | ||||||
|  |             m_pilebb = sl::boundingBox(merged_pile); | ||||||
|  | 
 | ||||||
|  |             m_rtree.clear(); | ||||||
|  |             m_smallsrtree.clear(); | ||||||
|  |              | ||||||
|  |             // We will treat big items (compared to the print bed) differently
 | ||||||
|  |             auto isBig = [this](double a) { | ||||||
|  |                 return a / m_bin_area > BIG_ITEM_TRESHOLD ; | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             for(unsigned idx = 0; idx < items.size(); ++idx) { | ||||||
|  |                 Item& itm = items[idx]; | ||||||
|  |                 if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx}); | ||||||
|  |                 m_smallsrtree.insert({itm.boundingBox(), idx}); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |          | ||||||
|  |         m_pconf.object_function = get_objfn(); | ||||||
|  |          | ||||||
|  |         if (progressind) m_pck.progressIndicator(progressind); | ||||||
|  |         if (stopcond) m_pck.stopCondition(stopcond); | ||||||
|  |          | ||||||
|  |         m_pck.configure(m_pconf); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     template<class...Args> inline void operator()(Args&&...args) { | ||||||
|  |         m_rtree.clear(); /*m_preload_idx.clear();*/ | ||||||
|  |         m_pck.execute(std::forward<Args>(args)...); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     inline void preload(std::vector<Item>& fixeditems) { | ||||||
|  |         m_pconf.alignment = PConfig::Alignment::DONT_ALIGN; | ||||||
|  |         auto bb = sl::boundingBox(m_bin); | ||||||
|  |         auto bbcenter = bb.center(); | ||||||
|  |         m_pconf.object_function = [this, bb, bbcenter](const Item &item) { | ||||||
|  |             return fixed_overfit(objfunc(item, bbcenter), bb); | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         // Build the rtree for queries to work
 | ||||||
|  |          | ||||||
|  |         for(unsigned idx = 0; idx < fixeditems.size(); ++idx) { | ||||||
|  |             Item& itm = fixeditems[idx]; | ||||||
|  |             itm.markAsFixed(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         m_pck.configure(m_pconf); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | template<> std::function<double(const Item&)> AutoArranger<Box>::get_objfn() | ||||||
|  | { | ||||||
|  |     auto bincenter = m_bin.center(); | ||||||
|  | 
 | ||||||
|  |     return [this, bincenter](const Item &itm) { | ||||||
|  |         auto result = objfunc(itm, bincenter); | ||||||
|  |          | ||||||
|  |         double score = std::get<0>(result); | ||||||
|  |         auto& fullbb = std::get<1>(result); | ||||||
|  |          | ||||||
|  |         double miss = Placer::overfit(fullbb, m_bin); | ||||||
|  |         miss = miss > 0? miss : 0; | ||||||
|  |         score += miss*miss; | ||||||
|  |          | ||||||
|  |         return score;     | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | template<> std::function<double(const Item&)> AutoArranger<Circle>::get_objfn() | ||||||
|  | { | ||||||
|  |     auto bincenter = m_bin.center(); | ||||||
|  |     return [this, bincenter](const Item &item) { | ||||||
|  |          | ||||||
|  |         auto result = objfunc(item, bincenter); | ||||||
|  |          | ||||||
|  |         double score = std::get<0>(result); | ||||||
|  |          | ||||||
|  |         auto isBig = [this](const Item& itm) { | ||||||
|  |             return itm.area() / m_bin_area > BIG_ITEM_TRESHOLD ; | ||||||
|  |         }; | ||||||
|  |          | ||||||
|  |         if(isBig(item)) { | ||||||
|  |             auto mp = m_merged_pile; | ||||||
|  |             mp.push_back(item.transformedShape()); | ||||||
|  |             auto chull = sl::convexHull(mp); | ||||||
|  |             double miss = Placer::overfit(chull, m_bin); | ||||||
|  |             if(miss < 0) miss = 0; | ||||||
|  |             score += miss*miss; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         return score; | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Specialization for a generalized polygon.
 | ||||||
|  | // Warning: this is unfinished business. It may or may not work.
 | ||||||
|  | template<> | ||||||
|  | std::function<double(const Item &)> AutoArranger<clppr::Polygon>::get_objfn() | ||||||
|  | { | ||||||
|  |     auto bincenter = sl::boundingBox(m_bin).center(); | ||||||
|  |     return [this, bincenter](const Item &item) { | ||||||
|  |         return std::get<0>(objfunc(item, bincenter)); | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | inline Circle to_lnCircle(const CircleBed& circ) { | ||||||
|  |     return Circle({circ.center()(0), circ.center()(1)}, circ.radius()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Get the type of bed geometry from a simple vector of points.
 | ||||||
|  | BedShapeHint::BedShapeHint(const Polyline &bed) { | ||||||
|  |     auto x = [](const Point& p) { return p(X); }; | ||||||
|  |     auto y = [](const Point& p) { return p(Y); }; | ||||||
|  | 
 | ||||||
|  |     auto width = [x](const BoundingBox& box) { | ||||||
|  |         return x(box.max) - x(box.min); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     auto height = [y](const BoundingBox& box) { | ||||||
|  |         return y(box.max) - y(box.min); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     auto area = [&width, &height](const BoundingBox& box) { | ||||||
|  |         double w = width(box); | ||||||
|  |         double h = height(box); | ||||||
|  |         return w * h; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     auto poly_area = [](Polyline p) { | ||||||
|  |         Polygon pp; pp.points.reserve(p.points.size() + 1); | ||||||
|  |         pp.points = std::move(p.points); | ||||||
|  |         pp.points.emplace_back(pp.points.front()); | ||||||
|  |         return std::abs(pp.area()); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     auto distance_to = [x, y](const Point& p1, const Point& p2) { | ||||||
|  |         double dx = x(p2) - x(p1); | ||||||
|  |         double dy = y(p2) - y(p1); | ||||||
|  |         return std::sqrt(dx*dx + dy*dy); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     auto bb = bed.bounding_box(); | ||||||
|  | 
 | ||||||
|  |     auto isCircle = [bb, distance_to](const Polyline& polygon) { | ||||||
|  |         auto center = bb.center(); | ||||||
|  |         std::vector<double> vertex_distances; | ||||||
|  |         double avg_dist = 0; | ||||||
|  |         for (auto pt: polygon.points) | ||||||
|  |         { | ||||||
|  |             double distance = distance_to(center, pt); | ||||||
|  |             vertex_distances.push_back(distance); | ||||||
|  |             avg_dist += distance; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         avg_dist /= vertex_distances.size(); | ||||||
|  | 
 | ||||||
|  |         CircleBed ret(center, avg_dist); | ||||||
|  |         for(auto el : vertex_distances) | ||||||
|  |         { | ||||||
|  |             if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { | ||||||
|  |                 ret = CircleBed(); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     auto parea = poly_area(bed); | ||||||
|  | 
 | ||||||
|  |     if( (1.0 - parea/area(bb)) < 1e-3 ) { | ||||||
|  |         m_type = BedShapes::bsBox; | ||||||
|  |         m_bed.box = bb; | ||||||
|  |     } | ||||||
|  |     else if(auto c = isCircle(bed)) { | ||||||
|  |         m_type = BedShapes::bsCircle; | ||||||
|  |         m_bed.circ = c; | ||||||
|  |     } else { | ||||||
|  |         m_type = BedShapes::bsIrregular; | ||||||
|  |         m_bed.polygon = bed; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | template<class BinT> // Arrange for arbitrary bin type
 | ||||||
|  | void _arrange( | ||||||
|  |     std::vector<Item> &           shapes, | ||||||
|  |     std::vector<Item> &           excludes, | ||||||
|  |     const BinT &                  bin, | ||||||
|  |     coord_t                       minobjd, | ||||||
|  |     std::function<void(unsigned)> prind, | ||||||
|  |     std::function<bool()>         stopfn) | ||||||
|  | { | ||||||
|  |     // Integer ceiling the min distance from the bed perimeters
 | ||||||
|  |     coord_t md = minobjd - 2 * scaled(0.1 + EPSILON); | ||||||
|  |     md = (md % 2) ? md / 2 + 1 : md / 2; | ||||||
|  |      | ||||||
|  |     auto corrected_bin = bin; | ||||||
|  |     sl::offset(corrected_bin, md); | ||||||
|  |      | ||||||
|  |     AutoArranger<BinT> arranger{corrected_bin, 0, prind, stopfn}; | ||||||
|  |      | ||||||
|  |     auto infl = coord_t(std::ceil(minobjd / 2.0)); | ||||||
|  |     for (Item& itm : shapes) itm.inflate(infl); | ||||||
|  |     for (Item& itm : excludes) itm.inflate(infl); | ||||||
|  |      | ||||||
|  |     auto it = excludes.begin(); | ||||||
|  |     while (it != excludes.end()) | ||||||
|  |         sl::isInside(it->transformedShape(), corrected_bin) ? | ||||||
|  |             ++it : it = excludes.erase(it); | ||||||
|  | 
 | ||||||
|  |     // If there is something on the plate
 | ||||||
|  |     if (!excludes.empty()) arranger.preload(excludes); | ||||||
|  | 
 | ||||||
|  |     std::vector<std::reference_wrapper<Item>> inp; | ||||||
|  |     inp.reserve(shapes.size() + excludes.size()); | ||||||
|  |     for (auto &itm : shapes  ) inp.emplace_back(itm); | ||||||
|  |     for (auto &itm : excludes) inp.emplace_back(itm); | ||||||
|  |      | ||||||
|  |     arranger(inp.begin(), inp.end()); | ||||||
|  |     for (Item &itm : inp) itm.inflate(-infl); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // The final client function for arrangement. A progress indicator and
 | ||||||
|  | // a stop predicate can be also be passed to control the process.
 | ||||||
|  | void arrange(ArrangePolygons &             arrangables, | ||||||
|  |              const ArrangePolygons &       excludes, | ||||||
|  |              coord_t                       min_obj_dist, | ||||||
|  |              const BedShapeHint &          bedhint, | ||||||
|  |              std::function<void(unsigned)> progressind, | ||||||
|  |              std::function<bool()>         stopcondition) | ||||||
|  | { | ||||||
|  |     namespace clppr = ClipperLib; | ||||||
|  |      | ||||||
|  |     std::vector<Item> items, fixeditems; | ||||||
|  |     items.reserve(arrangables.size()); | ||||||
|  |      | ||||||
|  |     // Create Item from Arrangeable
 | ||||||
|  |     auto process_arrangeable = | ||||||
|  |         [](const ArrangePolygon &arrpoly, std::vector<Item> &outp) | ||||||
|  |     { | ||||||
|  |         Polygon p        = arrpoly.poly.contour; | ||||||
|  |         const Vec2crd &  offs     = arrpoly.translation; | ||||||
|  |         double           rotation = arrpoly.rotation; | ||||||
|  | 
 | ||||||
|  |         if (p.is_counter_clockwise()) p.reverse(); | ||||||
|  | 
 | ||||||
|  |         clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); | ||||||
|  | 
 | ||||||
|  |         auto firstp = clpath.Contour.front(); | ||||||
|  |         clpath.Contour.emplace_back(firstp); | ||||||
|  | 
 | ||||||
|  |         outp.emplace_back(std::move(clpath)); | ||||||
|  |         outp.back().rotation(rotation); | ||||||
|  |         outp.back().translation({offs.x(), offs.y()}); | ||||||
|  |         outp.back().binId(arrpoly.bed_idx); | ||||||
|  |         outp.back().priority(arrpoly.priority); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     for (ArrangePolygon &arrangeable : arrangables) | ||||||
|  |         process_arrangeable(arrangeable, items); | ||||||
|  |      | ||||||
|  |     for (const ArrangePolygon &fixed: excludes) | ||||||
|  |         process_arrangeable(fixed, fixeditems); | ||||||
|  |      | ||||||
|  |     for (Item &itm : fixeditems) itm.inflate(scaled(-2. * EPSILON)); | ||||||
|  |      | ||||||
|  |     auto &cfn = stopcondition; | ||||||
|  |     auto &pri = progressind; | ||||||
|  |      | ||||||
|  |     switch (bedhint.get_type()) { | ||||||
|  |     case bsBox: { | ||||||
|  |         // Create the arranger for the box shaped bed
 | ||||||
|  |         BoundingBox bbb = bedhint.get_box(); | ||||||
|  |         Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; | ||||||
|  |          | ||||||
|  |         _arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     case bsCircle: { | ||||||
|  |         auto cc = to_lnCircle(bedhint.get_circle()); | ||||||
|  |          | ||||||
|  |         _arrange(items, fixeditems, cc, min_obj_dist, pri, cfn); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     case bsIrregular: { | ||||||
|  |         auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.get_irregular()); | ||||||
|  |         auto irrbed = sl::create<clppr::Polygon>(std::move(ctour)); | ||||||
|  |         BoundingBox polybb(bedhint.get_irregular()); | ||||||
|  |          | ||||||
|  |         _arrange(items, fixeditems, irrbed, min_obj_dist, pri, cfn); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     case bsInfinite: { | ||||||
|  |         const InfiniteBed& nobin = bedhint.get_infinite(); | ||||||
|  |         auto infbb = Box::infinite({nobin.center.x(), nobin.center.y()}); | ||||||
|  |          | ||||||
|  |         _arrange(items, fixeditems, infbb, min_obj_dist, pri, cfn); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     case bsUnknown: { | ||||||
|  |         // We know nothing about the bed, let it be infinite and zero centered
 | ||||||
|  |         _arrange(items, fixeditems, Box::infinite(), min_obj_dist, pri, cfn); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     }; | ||||||
|  |      | ||||||
|  |     for(size_t i = 0; i < items.size(); ++i) { | ||||||
|  |         clppr::IntPoint tr = items[i].translation(); | ||||||
|  |         arrangables[i].translation = {coord_t(tr.X), coord_t(tr.Y)}; | ||||||
|  |         arrangables[i].rotation    = items[i].rotation(); | ||||||
|  |         arrangables[i].bed_idx     = items[i].binId(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Arrange, without the fixed items (excludes)
 | ||||||
|  | void arrange(ArrangePolygons &             inp, | ||||||
|  |             coord_t                       min_d, | ||||||
|  |             const BedShapeHint &          bedhint, | ||||||
|  |             std::function<void(unsigned)> prfn, | ||||||
|  |             std::function<bool()>         stopfn) | ||||||
|  | { | ||||||
|  |     arrange(inp, {}, min_d, bedhint, prfn, stopfn); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace arr
 | ||||||
|  | } // namespace Slic3r
 | ||||||
							
								
								
									
										201
									
								
								src/libslic3r/Arrange.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								src/libslic3r/Arrange.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,201 @@ | ||||||
|  | #ifndef MODELARRANGE_HPP | ||||||
|  | #define MODELARRANGE_HPP | ||||||
|  | 
 | ||||||
|  | #include "ExPolygon.hpp" | ||||||
|  | #include "BoundingBox.hpp" | ||||||
|  | 
 | ||||||
|  | namespace Slic3r { | ||||||
|  | 
 | ||||||
|  | namespace arrangement { | ||||||
|  | 
 | ||||||
|  | /// A geometry abstraction for a circular print bed. Similarly to BoundingBox.
 | ||||||
|  | class CircleBed { | ||||||
|  |     Point center_; | ||||||
|  |     double radius_; | ||||||
|  | public: | ||||||
|  | 
 | ||||||
|  |     inline CircleBed(): center_(0, 0), radius_(std::nan("")) {} | ||||||
|  |     inline CircleBed(const Point& c, double r): center_(c), radius_(r) {} | ||||||
|  | 
 | ||||||
|  |     inline double radius() const { return radius_; } | ||||||
|  |     inline const Point& center() const { return center_; } | ||||||
|  |     inline operator bool() { return !std::isnan(radius_); } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /// Representing an unbounded bed.
 | ||||||
|  | struct InfiniteBed { Point center; }; | ||||||
|  | 
 | ||||||
|  | /// Types of print bed shapes.
 | ||||||
|  | enum BedShapes { | ||||||
|  |     bsBox, | ||||||
|  |     bsCircle, | ||||||
|  |     bsIrregular, | ||||||
|  |     bsInfinite, | ||||||
|  |     bsUnknown | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /// Info about the print bed for the arrange() function. This is a variant 
 | ||||||
|  | /// holding one of the four shapes a bed can be.
 | ||||||
|  | class BedShapeHint { | ||||||
|  |     BedShapes m_type = BedShapes::bsInfinite; | ||||||
|  |      | ||||||
|  |     union BedShape_u {  // TODO: use variant from cpp17?
 | ||||||
|  |         CircleBed   circ; | ||||||
|  |         BoundingBox box; | ||||||
|  |         Polyline    polygon; | ||||||
|  |         InfiniteBed infbed{}; | ||||||
|  |         ~BedShape_u() {} | ||||||
|  |         BedShape_u() {}; | ||||||
|  |     } m_bed; | ||||||
|  |      | ||||||
|  | public: | ||||||
|  | 
 | ||||||
|  |     BedShapeHint(){}; | ||||||
|  |      | ||||||
|  |     /// Get a bed shape hint for arrange() from a naked Polyline.
 | ||||||
|  |     explicit BedShapeHint(const Polyline &polyl); | ||||||
|  |     explicit BedShapeHint(const BoundingBox &bb) | ||||||
|  |     { | ||||||
|  |         m_type = bsBox; m_bed.box = bb; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     explicit BedShapeHint(const CircleBed &c) | ||||||
|  |     { | ||||||
|  |         m_type = bsCircle; m_bed.circ = c; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     explicit BedShapeHint(const InfiniteBed &ibed) | ||||||
|  |     { | ||||||
|  |         m_type = bsInfinite; m_bed.infbed = ibed; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     ~BedShapeHint() | ||||||
|  |     { | ||||||
|  |         if (m_type == BedShapes::bsIrregular) | ||||||
|  |             m_bed.polygon.Slic3r::Polyline::~Polyline(); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     BedShapeHint(const BedShapeHint &cpy) { *this = cpy; } | ||||||
|  |     BedShapeHint(BedShapeHint &&cpy) { *this = std::move(cpy); } | ||||||
|  | 
 | ||||||
|  |     BedShapeHint &operator=(const BedShapeHint &cpy) | ||||||
|  |     { | ||||||
|  |         m_type = cpy.m_type; | ||||||
|  |         switch(m_type) { | ||||||
|  |         case bsBox: m_bed.box = cpy.m_bed.box; break; | ||||||
|  |         case bsCircle: m_bed.circ = cpy.m_bed.circ; break; | ||||||
|  |         case bsIrregular: m_bed.polygon = cpy.m_bed.polygon; break; | ||||||
|  |         case bsInfinite: m_bed.infbed = cpy.m_bed.infbed; break; | ||||||
|  |         case bsUnknown: break; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         return *this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     BedShapeHint& operator=(BedShapeHint &&cpy) | ||||||
|  |     { | ||||||
|  |         m_type = cpy.m_type; | ||||||
|  |         switch(m_type) { | ||||||
|  |         case bsBox: m_bed.box = std::move(cpy.m_bed.box); break; | ||||||
|  |         case bsCircle: m_bed.circ = std::move(cpy.m_bed.circ); break; | ||||||
|  |         case bsIrregular: m_bed.polygon = std::move(cpy.m_bed.polygon); break; | ||||||
|  |         case bsInfinite: m_bed.infbed = std::move(cpy.m_bed.infbed); break; | ||||||
|  |         case bsUnknown: break; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         return *this; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     BedShapes get_type() const { return m_type; } | ||||||
|  | 
 | ||||||
|  |     const BoundingBox &get_box() const | ||||||
|  |     { | ||||||
|  |         assert(m_type == bsBox); return m_bed.box; | ||||||
|  |     } | ||||||
|  |     const CircleBed &get_circle() const | ||||||
|  |     { | ||||||
|  |         assert(m_type == bsCircle); return m_bed.circ; | ||||||
|  |     } | ||||||
|  |     const Polyline &get_irregular() const | ||||||
|  |     { | ||||||
|  |         assert(m_type == bsIrregular); return m_bed.polygon; | ||||||
|  |     } | ||||||
|  |     const InfiniteBed &get_infinite() const | ||||||
|  |     { | ||||||
|  |         assert(m_type == bsInfinite); return m_bed.infbed; | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /// A logical bed representing an object not being arranged. Either the arrange
 | ||||||
|  | /// has not yet successfully run on this ArrangePolygon or it could not fit the
 | ||||||
|  | /// object due to overly large size or invalid geometry.
 | ||||||
|  | static const constexpr int UNARRANGED = -1; | ||||||
|  | 
 | ||||||
|  | /// Input/Output structure for the arrange() function. The poly field will not
 | ||||||
|  | /// be modified during arrangement. Instead, the translation and rotation fields
 | ||||||
|  | /// will mark the needed transformation for the polygon to be in the arranged
 | ||||||
|  | /// position. These can also be set to an initial offset and rotation.
 | ||||||
|  | /// 
 | ||||||
|  | /// The bed_idx field will indicate the logical bed into which the
 | ||||||
|  | /// polygon belongs: UNARRANGED means no place for the polygon
 | ||||||
|  | /// (also the initial state before arrange), 0..N means the index of the bed.
 | ||||||
|  | /// Zero is the physical bed, larger than zero means a virtual bed.
 | ||||||
|  | struct ArrangePolygon {  | ||||||
|  |     ExPolygon poly;                 /// The 2D silhouette to be arranged
 | ||||||
|  |     Vec2crd   translation{0, 0};    /// The translation of the poly
 | ||||||
|  |     double    rotation{0.0};        /// The rotation of the poly in radians
 | ||||||
|  |     int       bed_idx{UNARRANGED};  /// To which logical bed does poly belong...
 | ||||||
|  |     int       priority{0}; | ||||||
|  |      | ||||||
|  |     /// Optional setter function which can store arbitrary data in its closure
 | ||||||
|  |     std::function<void(const ArrangePolygon&)> setter = nullptr; | ||||||
|  |      | ||||||
|  |     /// Helper function to call the setter with the arrange data arguments
 | ||||||
|  |     void apply() const { if (setter) setter(*this); } | ||||||
|  | 
 | ||||||
|  |     /// Test if arrange() was called previously and gave a successful result.
 | ||||||
|  |     bool is_arranged() const { return bed_idx != UNARRANGED; } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | using ArrangePolygons = std::vector<ArrangePolygon>; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * \brief Arranges the input polygons. | ||||||
|  |  * | ||||||
|  |  * WARNING: Currently, only convex polygons are supported by the libnest2d  | ||||||
|  |  * library which is used to do the arrangement. This might change in the future | ||||||
|  |  * this is why the interface contains a general polygon capable to have holes. | ||||||
|  |  * | ||||||
|  |  * \param items Input vector of ArrangePolygons. The transformation, rotation  | ||||||
|  |  * and bin_idx fields will be changed after the call finished and can be used | ||||||
|  |  * to apply the result on the input polygon. | ||||||
|  |  * | ||||||
|  |  * \param min_obj_distance The minimum distance which is allowed for any | ||||||
|  |  * pair of items on the print bed in any direction. | ||||||
|  |  * | ||||||
|  |  * \param bedhint Info about the shape and type of the bed. | ||||||
|  |  * | ||||||
|  |  * \param progressind Progress indicator callback called when | ||||||
|  |  * an object gets packed. The unsigned argument is the number of items | ||||||
|  |  * remaining to pack. | ||||||
|  |  * | ||||||
|  |  * \param stopcondition A predicate returning true if abort is needed. | ||||||
|  |  */ | ||||||
|  | void arrange(ArrangePolygons &             items, | ||||||
|  |              coord_t                       min_obj_distance, | ||||||
|  |              const BedShapeHint &          bedhint, | ||||||
|  |              std::function<void(unsigned)> progressind   = nullptr, | ||||||
|  |              std::function<bool(void)>     stopcondition = nullptr); | ||||||
|  | 
 | ||||||
|  | /// Same as the previous, only that it takes unmovable items as an
 | ||||||
|  | /// additional argument. Those will be considered as already arranged objects.
 | ||||||
|  | void arrange(ArrangePolygons &             items, | ||||||
|  |              const ArrangePolygons &       excludes, | ||||||
|  |              coord_t                       min_obj_distance, | ||||||
|  |              const BedShapeHint &          bedhint, | ||||||
|  |              std::function<void(unsigned)> progressind   = nullptr, | ||||||
|  |              std::function<bool(void)>     stopcondition = nullptr); | ||||||
|  | 
 | ||||||
|  | }   // arr
 | ||||||
|  | }   // Slic3r
 | ||||||
|  | #endif // MODELARRANGE_HPP
 | ||||||
|  | @ -106,8 +106,8 @@ add_library(libslic3r STATIC | ||||||
|     Line.hpp |     Line.hpp | ||||||
|     Model.cpp |     Model.cpp | ||||||
|     Model.hpp |     Model.hpp | ||||||
|     ModelArrange.hpp |     Arrange.hpp | ||||||
|     ModelArrange.cpp |     Arrange.cpp | ||||||
|     MotionPlanner.cpp |     MotionPlanner.cpp | ||||||
|     MotionPlanner.hpp |     MotionPlanner.hpp | ||||||
|     MultiPoint.cpp |     MultiPoint.cpp | ||||||
|  |  | ||||||
|  | @ -126,6 +126,7 @@ void GCodeAnalyzer::reset() | ||||||
|     _set_start_position(DEFAULT_START_POSITION); |     _set_start_position(DEFAULT_START_POSITION); | ||||||
|     _set_start_extrusion(DEFAULT_START_EXTRUSION); |     _set_start_extrusion(DEFAULT_START_EXTRUSION); | ||||||
|     _reset_axes_position(); |     _reset_axes_position(); | ||||||
|  |     _reset_cached_position(); | ||||||
| 
 | 
 | ||||||
|     m_moves_map.clear(); |     m_moves_map.clear(); | ||||||
|     m_extruder_offsets.clear(); |     m_extruder_offsets.clear(); | ||||||
|  | @ -262,6 +263,16 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi | ||||||
|                         _processM108orM135(line); |                         _processM108orM135(line); | ||||||
|                         break; |                         break; | ||||||
|                     } |                     } | ||||||
|  |                 case 401: // Repetier: Store x, y and z position
 | ||||||
|  |                     { | ||||||
|  |                         _processM401(line); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 case 402: // Repetier: Go to stored position
 | ||||||
|  |                     { | ||||||
|  |                         _processM402(line); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 break; |                 break; | ||||||
|  | @ -433,12 +444,6 @@ void GCodeAnalyzer::_processM83(const GCodeReader::GCodeLine& line) | ||||||
|     _set_e_local_positioning_type(Relative); |     _set_e_local_positioning_type(Relative); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GCodeAnalyzer::_processM600(const GCodeReader::GCodeLine& line) |  | ||||||
| { |  | ||||||
|     m_state.cur_cp_color_id++; |  | ||||||
|     _set_cp_color_id(m_state.cur_cp_color_id); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) | void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) | ||||||
| { | { | ||||||
|     // These M-codes are used by MakerWare and Sailfish to change active tool.
 |     // These M-codes are used by MakerWare and Sailfish to change active tool.
 | ||||||
|  | @ -458,6 +463,66 @@ void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void GCodeAnalyzer::_processM401(const GCodeReader::GCodeLine& line) | ||||||
|  | { | ||||||
|  |     if (m_gcode_flavor != gcfRepetier) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     for (unsigned char a = 0; a <= 3; ++a) | ||||||
|  |     { | ||||||
|  |         _set_cached_position(a, _get_axis_position((EAxis)a)); | ||||||
|  |     } | ||||||
|  |     _set_cached_position(4, _get_feedrate()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void GCodeAnalyzer::_processM402(const GCodeReader::GCodeLine& line) | ||||||
|  | { | ||||||
|  |     if (m_gcode_flavor != gcfRepetier) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     // see for reference:
 | ||||||
|  |     // https://github.com/repetier/Repetier-Firmware/blob/master/src/ArduinoAVR/Repetier/Printer.cpp
 | ||||||
|  |     // void Printer::GoToMemoryPosition(bool x, bool y, bool z, bool e, float feed)
 | ||||||
|  | 
 | ||||||
|  |     bool has_xyz = !(line.has_x() || line.has_y() || line.has_z()); | ||||||
|  | 
 | ||||||
|  |     float p = FLT_MAX; | ||||||
|  |     for (unsigned char a = X; a <= Z; ++a) | ||||||
|  |     { | ||||||
|  |         if (has_xyz || line.has(a)) | ||||||
|  |         { | ||||||
|  |             p = _get_cached_position(a); | ||||||
|  |             if (p != FLT_MAX) | ||||||
|  |                 _set_axis_position((EAxis)a, p); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     p = _get_cached_position(E); | ||||||
|  |     if (p != FLT_MAX) | ||||||
|  |         _set_axis_position(E, p); | ||||||
|  | 
 | ||||||
|  |     p = FLT_MAX; | ||||||
|  |     if (!line.has_value(4, p)) | ||||||
|  |         p = _get_cached_position(4); | ||||||
|  | 
 | ||||||
|  |     if (p != FLT_MAX) | ||||||
|  |         _set_feedrate(p); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void GCodeAnalyzer::_reset_cached_position() | ||||||
|  | { | ||||||
|  |     for (unsigned char a = 0; a <= 4; ++a) | ||||||
|  |     { | ||||||
|  |         m_state.cached_position[a] = FLT_MAX; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void GCodeAnalyzer::_processM600(const GCodeReader::GCodeLine& line) | ||||||
|  | { | ||||||
|  |     m_state.cur_cp_color_id++; | ||||||
|  |     _set_cp_color_id(m_state.cur_cp_color_id); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void GCodeAnalyzer::_processT(const std::string& cmd) | void GCodeAnalyzer::_processT(const std::string& cmd) | ||||||
| { | { | ||||||
|     if (cmd.length() > 1) |     if (cmd.length() > 1) | ||||||
|  | @ -668,6 +733,17 @@ const Vec3d& GCodeAnalyzer::_get_start_position() const | ||||||
|     return m_state.start_position; |     return m_state.start_position; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void GCodeAnalyzer::_set_cached_position(unsigned char axis, float position) | ||||||
|  | { | ||||||
|  |     if ((0 <= axis) || (axis <= 4)) | ||||||
|  |         m_state.cached_position[axis] = position; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | float GCodeAnalyzer::_get_cached_position(unsigned char axis) const | ||||||
|  | { | ||||||
|  |     return ((0 <= axis) || (axis <= 4)) ? m_state.cached_position[axis] : FLT_MAX; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void GCodeAnalyzer::_set_start_extrusion(float extrusion) | void GCodeAnalyzer::_set_start_extrusion(float extrusion) | ||||||
| { | { | ||||||
|     m_state.start_extrusion = extrusion; |     m_state.start_extrusion = extrusion; | ||||||
|  |  | ||||||
|  | @ -96,6 +96,7 @@ private: | ||||||
|         EPositioningType e_local_positioning_type; |         EPositioningType e_local_positioning_type; | ||||||
|         Metadata data; |         Metadata data; | ||||||
|         Vec3d start_position = Vec3d::Zero(); |         Vec3d start_position = Vec3d::Zero(); | ||||||
|  |         float cached_position[5]; | ||||||
|         float start_extrusion; |         float start_extrusion; | ||||||
|         float position[Num_Axis]; |         float position[Num_Axis]; | ||||||
|         unsigned int cur_cp_color_id = 0; |         unsigned int cur_cp_color_id = 0; | ||||||
|  | @ -170,6 +171,12 @@ private: | ||||||
|     // Set tool (MakerWare and Sailfish flavor)
 |     // Set tool (MakerWare and Sailfish flavor)
 | ||||||
|     void _processM108orM135(const GCodeReader::GCodeLine& line); |     void _processM108orM135(const GCodeReader::GCodeLine& line); | ||||||
| 
 | 
 | ||||||
|  |     // Repetier: Store x, y and z position
 | ||||||
|  |     void _processM401(const GCodeReader::GCodeLine& line); | ||||||
|  | 
 | ||||||
|  |     // Repetier: Go to stored position
 | ||||||
|  |     void _processM402(const GCodeReader::GCodeLine& line); | ||||||
|  | 
 | ||||||
|     // Set color change
 |     // Set color change
 | ||||||
|     void _processM600(const GCodeReader::GCodeLine& line); |     void _processM600(const GCodeReader::GCodeLine& line); | ||||||
| 
 | 
 | ||||||
|  | @ -232,6 +239,11 @@ private: | ||||||
|     void _set_start_position(const Vec3d& position); |     void _set_start_position(const Vec3d& position); | ||||||
|     const Vec3d& _get_start_position() const; |     const Vec3d& _get_start_position() const; | ||||||
| 
 | 
 | ||||||
|  |     void _set_cached_position(unsigned char axis, float position); | ||||||
|  |     float _get_cached_position(unsigned char axis) const; | ||||||
|  | 
 | ||||||
|  |     void _reset_cached_position(); | ||||||
|  | 
 | ||||||
|     void _set_start_extrusion(float extrusion); |     void _set_start_extrusion(float extrusion); | ||||||
|     float _get_start_extrusion() const; |     float _get_start_extrusion() const; | ||||||
|     float _get_delta_extrusion() const; |     float _get_delta_extrusion() const; | ||||||
|  |  | ||||||
|  | @ -314,49 +314,48 @@ template<class I> struct is_scaled_coord | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // Meta predicates for floating, 'scaled coord' and generic arithmetic types
 | // Meta predicates for floating, 'scaled coord' and generic arithmetic types
 | ||||||
| template<class T> | template<class T, class O = T> | ||||||
| using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, T>; | using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, O>; | ||||||
| 
 | 
 | ||||||
| template<class T> | template<class T, class O = T> | ||||||
| using ScaledCoordOnly = enable_if_t<is_scaled_coord<T>::value, T>; | using ScaledCoordOnly = enable_if_t<is_scaled_coord<T>::value, O>; | ||||||
| 
 | 
 | ||||||
| template<class T> | template<class T, class O = T> | ||||||
| using ArithmeticOnly = enable_if_t<std::is_arithmetic<T>::value, T>; | using ArithmeticOnly = enable_if_t<std::is_arithmetic<T>::value, O>; | ||||||
| 
 |  | ||||||
| // A shorter form for a generic Eigen vector which is widely used in PrusaSlicer 
 |  | ||||||
| template<class T, int N> |  | ||||||
| using EigenVec = Eigen::Matrix<T, N, 1, Eigen::DontAlign>; |  | ||||||
| 
 | 
 | ||||||
| // Semantics are the following:
 | // Semantics are the following:
 | ||||||
| // Upscaling (scaled()): only from floating point types (or Vec) to either
 | // Upscaling (scaled()): only from floating point types (or Vec) to either
 | ||||||
| //                       floating point or integer 'scaled coord' coordinates.
 | //                       floating point or integer 'scaled coord' coordinates.
 | ||||||
| // Downscaling (unscaled()): from arithmetic types (or Vec) to either
 | // Downscaling (unscaled()): from arithmetic (or Vec) to floating point only
 | ||||||
| //                           floating point only
 |  | ||||||
| 
 | 
 | ||||||
| // Conversion definition from unscaled to floating point scaled
 | // Conversion definition from unscaled to floating point scaled
 | ||||||
| template<class Tout, | template<class Tout, | ||||||
|          class Tin, |          class Tin, | ||||||
|          class = FloatingOnly<Tin>, |          class = FloatingOnly<Tin>> | ||||||
|          class = FloatingOnly<Tout>> | inline constexpr FloatingOnly<Tout> scaled(const Tin &v) noexcept | ||||||
| inline SLIC3R_CONSTEXPR Tout scaled(const Tin &v) SLIC3R_NOEXCEPT |  | ||||||
| { | { | ||||||
|     return static_cast<Tout>(v / static_cast<Tin>(SCALING_FACTOR)); |     return Tout(v / Tin(SCALING_FACTOR)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Conversion definition from unscaled to integer 'scaled coord'.
 | // Conversion definition from unscaled to integer 'scaled coord'.
 | ||||||
| // TODO: is the rounding necessary ? Here it is to show that it can be different
 | // TODO: is the rounding necessary? Here it is commented  out to show that
 | ||||||
| // but it does not have to be. Using std::round means loosing noexcept and
 | // it can be different for integers but it does not have to be. Using
 | ||||||
| // constexpr modifiers
 | // std::round means loosing noexcept and constexpr modifiers
 | ||||||
| template<class Tout = coord_t, class Tin, class = FloatingOnly<Tin>> | template<class Tout = coord_t, class Tin, class = FloatingOnly<Tin>> | ||||||
| inline SLIC3R_CONSTEXPR ScaledCoordOnly<Tout> scaled(const Tin &v) SLIC3R_NOEXCEPT | inline constexpr ScaledCoordOnly<Tout> scaled(const Tin &v) noexcept | ||||||
| { | { | ||||||
|     //return static_cast<Tout>(std::round(v / SCALING_FACTOR));
 |     //return static_cast<Tout>(std::round(v / SCALING_FACTOR));
 | ||||||
|     return static_cast<Tout>(v / static_cast<Tin>(SCALING_FACTOR)); |     return Tout(v / Tin(SCALING_FACTOR)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Conversion for Eigen vectors (N dimensional points)
 | // Conversion for Eigen vectors (N dimensional points)
 | ||||||
| template<class Tout = coord_t, class Tin, int N, class = FloatingOnly<Tin>> | template<class Tout = coord_t, | ||||||
| inline EigenVec<ArithmeticOnly<Tout>, N> scaled(const EigenVec<Tin, N> &v) |          class Tin, | ||||||
|  |          int N, | ||||||
|  |          class = FloatingOnly<Tin>, | ||||||
|  |          int...EigenArgs> | ||||||
|  | inline Eigen::Matrix<ArithmeticOnly<Tout>, N, EigenArgs...> | ||||||
|  | scaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v) | ||||||
| { | { | ||||||
|     return (v / SCALING_FACTOR).template cast<Tout>(); |     return (v / SCALING_FACTOR).template cast<Tout>(); | ||||||
| } | } | ||||||
|  | @ -366,9 +365,9 @@ template<class Tout = double, | ||||||
|          class Tin, |          class Tin, | ||||||
|          class = ArithmeticOnly<Tin>, |          class = ArithmeticOnly<Tin>, | ||||||
|          class = FloatingOnly<Tout>> |          class = FloatingOnly<Tout>> | ||||||
| inline SLIC3R_CONSTEXPR Tout unscaled(const Tin &v) SLIC3R_NOEXCEPT | inline constexpr Tout unscaled(const Tin &v) noexcept | ||||||
| { | { | ||||||
|     return static_cast<Tout>(v * static_cast<Tout>(SCALING_FACTOR)); |     return Tout(v * Tout(SCALING_FACTOR)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Unscaling for Eigen vectors. Input base type can be arithmetic, output base
 | // Unscaling for Eigen vectors. Input base type can be arithmetic, output base
 | ||||||
|  | @ -377,9 +376,10 @@ template<class Tout = double, | ||||||
|          class Tin, |          class Tin, | ||||||
|          int N, |          int N, | ||||||
|          class = ArithmeticOnly<Tin>, |          class = ArithmeticOnly<Tin>, | ||||||
|          class = FloatingOnly<Tout>> |          class = FloatingOnly<Tout>, | ||||||
| inline SLIC3R_CONSTEXPR EigenVec<Tout, N> unscaled( |          int...EigenArgs> | ||||||
|     const EigenVec<Tin, N> &v) SLIC3R_NOEXCEPT | inline constexpr Eigen::Matrix<Tout, N, EigenArgs...> | ||||||
|  | unscaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v) noexcept | ||||||
| { | { | ||||||
|     return v.template cast<Tout>() * SCALING_FACTOR; |     return v.template cast<Tout>() * SCALING_FACTOR; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| #include "Model.hpp" | #include "Model.hpp" | ||||||
| #include "Geometry.hpp" | #include "Geometry.hpp" | ||||||
|  | #include "MTUtils.hpp" | ||||||
| 
 | 
 | ||||||
| #include "Format/AMF.hpp" | #include "Format/AMF.hpp" | ||||||
| #include "Format/OBJ.hpp" | #include "Format/OBJ.hpp" | ||||||
|  | @ -370,33 +371,43 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb | ||||||
|     but altering their instance positions */ |     but altering their instance positions */ | ||||||
| bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) | bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) | ||||||
| {     | {     | ||||||
|     // get the (transformed) size of each instance so that we take
 |     size_t count = 0; | ||||||
|     // into account their different transformations when packing
 |     for (auto obj : objects) count += obj->instances.size(); | ||||||
|     Pointfs instance_sizes; |      | ||||||
|     Pointfs instance_centers; |     arrangement::ArrangePolygons input; | ||||||
|     for (const ModelObject *o : this->objects) |     ModelInstancePtrs instances; | ||||||
|         for (size_t i = 0; i < o->instances.size(); ++ i) { |     input.reserve(count); | ||||||
|             // an accurate snug bounding box around the transformed mesh.
 |     instances.reserve(count); | ||||||
|             BoundingBoxf3 bbox(o->instance_bounding_box(i, true)); |     for (ModelObject *mo : objects) | ||||||
|             instance_sizes.emplace_back(to_2d(bbox.size())); |         for (ModelInstance *minst : mo->instances) { | ||||||
|             instance_centers.emplace_back(to_2d(bbox.center())); |             input.emplace_back(minst->get_arrange_polygon()); | ||||||
|  |             instances.emplace_back(minst); | ||||||
|         } |         } | ||||||
|      |      | ||||||
|     Pointfs positions; |     arrangement::BedShapeHint bedhint; | ||||||
|     if (! _arrange(instance_sizes, dist, bb, positions)) |     coord_t bedwidth = 0; | ||||||
|         return false; |  | ||||||
|      |      | ||||||
|     size_t idx = 0; |     if (bb) { | ||||||
|     for (ModelObject *o : this->objects) { |         bedwidth = scaled(bb->size().x()); | ||||||
|         for (ModelInstance *i : o->instances) { |         bedhint = arrangement::BedShapeHint( | ||||||
|             Vec2d offset_xy = positions[idx] - instance_centers[idx]; |             BoundingBox(scaled(bb->min), scaled(bb->max))); | ||||||
|             i->set_offset(Vec3d(offset_xy(0), offset_xy(1), i->get_offset(Z))); |  | ||||||
|             ++idx; |  | ||||||
|         } |  | ||||||
|         o->invalidate_bounding_box(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return true; |     arrangement::arrange(input, scaled(dist), bedhint); | ||||||
|  |      | ||||||
|  |     bool ret = true; | ||||||
|  |     coord_t stride = bedwidth + bedwidth / 5; | ||||||
|  |      | ||||||
|  |     for(size_t i = 0; i < input.size(); ++i) { | ||||||
|  |         if (input[i].bed_idx != 0) ret = false; | ||||||
|  |         if (input[i].bed_idx >= 0) { | ||||||
|  |             input[i].translation += Vec2crd{input[i].bed_idx * stride, 0}; | ||||||
|  |             instances[i]->apply_arrange_result(input[i].translation, | ||||||
|  |                                                input[i].rotation); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Duplicate the entire model preserving instance relative positions.
 | // Duplicate the entire model preserving instance relative positions.
 | ||||||
|  | @ -1814,6 +1825,36 @@ void ModelInstance::transform_polygon(Polygon* polygon) const | ||||||
|     polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin
 |     polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const | ||||||
|  | { | ||||||
|  |     static const double SIMPLIFY_TOLERANCE_MM = 0.1; | ||||||
|  |      | ||||||
|  |     Vec3d rotation = get_rotation(); | ||||||
|  |     rotation.z()   = 0.; | ||||||
|  |     Transform3d trafo_instance = | ||||||
|  |         Geometry::assemble_transform(Vec3d::Zero(), rotation, | ||||||
|  |                                      get_scaling_factor(), get_mirror()); | ||||||
|  | 
 | ||||||
|  |     Polygon p = get_object()->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()) { | ||||||
|  |         Polygons pp{p}; | ||||||
|  |         pp = p.simplify(scaled<double>(SIMPLIFY_TOLERANCE_MM)); | ||||||
|  |         if (!pp.empty()) p = pp.front(); | ||||||
|  |     } | ||||||
|  |     | ||||||
|  |     arrangement::ArrangePolygon ret; | ||||||
|  |     ret.poly.contour = std::move(p); | ||||||
|  |     ret.translation  = Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))}; | ||||||
|  |     ret.rotation     = get_rotation(Z); | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Test whether the two models contain the same number of ModelObjects with the same set of IDs
 | // Test whether the two models contain the same number of ModelObjects with the same set of IDs
 | ||||||
| // ordered in the same order. In that case it is not necessary to kill the background processing.
 | // ordered in the same order. In that case it is not necessary to kill the background processing.
 | ||||||
| bool model_object_list_equal(const Model &model_old, const Model &model_new) | bool model_object_list_equal(const Model &model_old, const Model &model_new) | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ | ||||||
| #include "Slicing.hpp" | #include "Slicing.hpp" | ||||||
| #include "SLA/SLACommon.hpp" | #include "SLA/SLACommon.hpp" | ||||||
| #include "TriangleMesh.hpp" | #include "TriangleMesh.hpp" | ||||||
|  | #include "Arrange.hpp" | ||||||
| 
 | 
 | ||||||
| #include <map> | #include <map> | ||||||
| #include <memory> | #include <memory> | ||||||
|  | @ -640,6 +641,18 @@ public: | ||||||
| 
 | 
 | ||||||
|     bool is_printable() const { return print_volume_state == PVS_Inside; } |     bool is_printable() const { return print_volume_state == PVS_Inside; } | ||||||
|      |      | ||||||
|  |     // Getting the input polygon for arrange
 | ||||||
|  |     arrangement::ArrangePolygon get_arrange_polygon() const; | ||||||
|  |      | ||||||
|  |     // Apply the arrange result on the ModelInstance
 | ||||||
|  |     void apply_arrange_result(const Vec2crd& offs, double rotation) | ||||||
|  |     { | ||||||
|  |         // write the transformation data into the model instance
 | ||||||
|  |         set_rotation(Z, rotation); | ||||||
|  |         set_offset(X, unscale<double>(offs(X))); | ||||||
|  |         set_offset(Y, unscale<double>(offs(Y))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| protected: | protected: | ||||||
|     friend class Print; |     friend class Print; | ||||||
|     friend class SLAPrint; |     friend class SLAPrint; | ||||||
|  | @ -654,10 +667,10 @@ private: | ||||||
|     ModelObject* object; |     ModelObject* object; | ||||||
| 
 | 
 | ||||||
|     // Constructor, which assigns a new unique ID.
 |     // Constructor, which assigns a new unique ID.
 | ||||||
|     explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) { assert(this->id().valid()); } |     explicit ModelInstance(ModelObject *object) : print_volume_state(PVS_Inside), object(object) { assert(this->id().valid()); } | ||||||
|     // Constructor, which assigns a new unique ID.
 |     // Constructor, which assigns a new unique ID.
 | ||||||
|     explicit ModelInstance(ModelObject *object, const ModelInstance &other) : |     explicit ModelInstance(ModelObject *object, const ModelInstance &other) : | ||||||
|         m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) { assert(this->id().valid() && this->id() != other.id()); } |         m_transformation(other.m_transformation), print_volume_state(PVS_Inside), object(object) { assert(this->id().valid() && this->id() != other.id()); } | ||||||
| 
 | 
 | ||||||
|     explicit ModelInstance(ModelInstance &&rhs) = delete; |     explicit ModelInstance(ModelInstance &&rhs) = delete; | ||||||
|     ModelInstance& operator=(const ModelInstance &rhs) = delete; |     ModelInstance& operator=(const ModelInstance &rhs) = delete; | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,95 +0,0 @@ | ||||||
| #ifndef MODELARRANGE_HPP |  | ||||||
| #define MODELARRANGE_HPP |  | ||||||
| 
 |  | ||||||
| #include "Model.hpp" |  | ||||||
| 
 |  | ||||||
| namespace Slic3r { |  | ||||||
| 
 |  | ||||||
| class Model; |  | ||||||
| 
 |  | ||||||
| namespace arr { |  | ||||||
| 
 |  | ||||||
| class Circle { |  | ||||||
|     Point center_; |  | ||||||
|     double radius_; |  | ||||||
| public: |  | ||||||
| 
 |  | ||||||
|     inline Circle(): center_(0, 0), radius_(std::nan("")) {} |  | ||||||
|     inline Circle(const Point& c, double r): center_(c), radius_(r) {} |  | ||||||
| 
 |  | ||||||
|     inline double radius() const { return radius_; } |  | ||||||
|     inline const Point& center() const { return center_; } |  | ||||||
|     inline operator bool() { return !std::isnan(radius_); } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| enum class BedShapeType { |  | ||||||
|     BOX, |  | ||||||
|     CIRCLE, |  | ||||||
|     IRREGULAR, |  | ||||||
|     WHO_KNOWS |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| struct BedShapeHint { |  | ||||||
|     BedShapeType type; |  | ||||||
|     /*union*/ struct {  // I know but who cares...
 |  | ||||||
|         Circle circ; |  | ||||||
|         BoundingBox box; |  | ||||||
|         Polyline polygon; |  | ||||||
|     } shape; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| BedShapeHint bedShape(const Polyline& bed); |  | ||||||
| 
 |  | ||||||
| struct WipeTowerInfo { |  | ||||||
|     bool is_wipe_tower = false; |  | ||||||
|     Vec2d pos; |  | ||||||
|     Vec2d bb_size; |  | ||||||
|     double rotation; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * \brief Arranges the model objects on the screen. |  | ||||||
|  * |  | ||||||
|  * The arrangement considers multiple bins (aka. print beds) for placing all |  | ||||||
|  * the items provided in the model argument. If the items don't fit on one |  | ||||||
|  * print bed, the remaining will be placed onto newly created print beds. |  | ||||||
|  * The first_bin_only parameter, if set to true, disables this behavior and |  | ||||||
|  * makes sure that only one print bed is filled and the remaining items will be |  | ||||||
|  * untouched. When set to false, the items which could not fit onto the |  | ||||||
|  * print bed will be placed next to the print bed so the user should see a |  | ||||||
|  * pile of items on the print bed and some other piles outside the print |  | ||||||
|  * area that can be dragged later onto the print bed as a group. |  | ||||||
|  * |  | ||||||
|  * \param model The model object with the 3D content. |  | ||||||
|  * \param dist The minimum distance which is allowed for any pair of items |  | ||||||
|  * on the print bed  in any direction. |  | ||||||
|  * \param bb The bounding box of the print bed. It corresponds to the 'bin' |  | ||||||
|  * for bin packing. |  | ||||||
|  * \param first_bin_only This parameter controls whether to place the |  | ||||||
|  * remaining items which do not fit onto the print area next to the print |  | ||||||
|  * bed or leave them untouched (let the user arrange them by hand or remove |  | ||||||
|  * them). |  | ||||||
|  * \param progressind Progress indicator callback called when an object gets |  | ||||||
|  * packed. The unsigned argument is the number of items remaining to pack. |  | ||||||
|  * \param stopcondition A predicate returning true if abort is needed. |  | ||||||
|  */ |  | ||||||
| bool arrange(Model &model, |  | ||||||
|              WipeTowerInfo& wipe_tower_info, |  | ||||||
|              coord_t min_obj_distance, |  | ||||||
|              const Slic3r::Polyline& bed, |  | ||||||
|              BedShapeHint bedhint, |  | ||||||
|              bool first_bin_only, |  | ||||||
|              std::function<void(unsigned)> progressind, |  | ||||||
|              std::function<bool(void)> stopcondition); |  | ||||||
| 
 |  | ||||||
| /// This will find a suitable position for a new object instance and leave the
 |  | ||||||
| /// old items untouched.
 |  | ||||||
| void find_new_position(const Model& model, |  | ||||||
|                        ModelInstancePtrs instances_to_add, |  | ||||||
|                        coord_t min_obj_distance, |  | ||||||
|                        const Slic3r::Polyline& bed, |  | ||||||
|                        WipeTowerInfo& wti); |  | ||||||
| 
 |  | ||||||
| }   // arr
 |  | ||||||
| }   // Slic3r
 |  | ||||||
| #endif // MODELARRANGE_HPP
 |  | ||||||
|  | @ -3429,36 +3429,24 @@ void GLCanvas3D::update_ui_from_settings() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| arr::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const | GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const | ||||||
| { | { | ||||||
|     arr::WipeTowerInfo wti; |     WipeTowerInfo wti; | ||||||
|  |      | ||||||
|     for (const GLVolume* vol : m_volumes.volumes) { |     for (const GLVolume* vol : m_volumes.volumes) { | ||||||
|         if (vol->is_wipe_tower) { |         if (vol->is_wipe_tower) { | ||||||
|             wti.is_wipe_tower = true; |             wti.m_pos = Vec2d(m_config->opt_float("wipe_tower_x"), | ||||||
|             wti.pos = Vec2d(m_config->opt_float("wipe_tower_x"), |  | ||||||
|                             m_config->opt_float("wipe_tower_y")); |                             m_config->opt_float("wipe_tower_y")); | ||||||
|             wti.rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); |             wti.m_rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); | ||||||
|             const BoundingBoxf3& bb = vol->bounding_box(); |             const BoundingBoxf3& bb = vol->bounding_box(); | ||||||
|             wti.bb_size = Vec2d(bb.size()(0), bb.size()(1)); |             wti.m_bb_size = Vec2d(bb.size().x(), bb.size().y()); | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |      | ||||||
|     return wti; |     return wti; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| void GLCanvas3D::arrange_wipe_tower(const arr::WipeTowerInfo& wti) const |  | ||||||
| { |  | ||||||
|     if (wti.is_wipe_tower) { |  | ||||||
|         DynamicPrintConfig cfg; |  | ||||||
|         cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = wti.pos(0); |  | ||||||
|         cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = wti.pos(1); |  | ||||||
|         cfg.opt<ConfigOptionFloat>("wipe_tower_rotation_angle", true)->value = (180./M_PI) * wti.rotation; |  | ||||||
|         wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) | Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) | ||||||
| { | { | ||||||
|     float z0 = 0.0f; |     float z0 = 0.0f; | ||||||
|  | @ -3600,7 +3588,7 @@ bool GLCanvas3D::_init_main_toolbar() | ||||||
| 
 | 
 | ||||||
|     item.name = "arrange"; |     item.name = "arrange"; | ||||||
|     item.icon_filename = "arrange.svg"; |     item.icon_filename = "arrange.svg"; | ||||||
|     item.tooltip = _utf8(L("Arrange")) + " [A]"; |     item.tooltip = _utf8(L("Arrange")) + " [A]\n" + _utf8(L("Arrange selection")) + " [Shift+A]"; | ||||||
|     item.sprite_id = 3; |     item.sprite_id = 3; | ||||||
|     item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; |     item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; | ||||||
|     item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; |     item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; | ||||||
|  | @ -5745,5 +5733,14 @@ const SLAPrint* GLCanvas3D::sla_print() const | ||||||
|     return (m_process == nullptr) ? nullptr : m_process->sla_print(); |     return (m_process == nullptr) ? nullptr : m_process->sla_print(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const | ||||||
|  | { | ||||||
|  |     DynamicPrintConfig cfg; | ||||||
|  |     cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = m_pos(X); | ||||||
|  |     cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = m_pos(Y); | ||||||
|  |     cfg.opt<ConfigOptionFloat>("wipe_tower_rotation_angle", true)->value = (180./M_PI) * m_rotation; | ||||||
|  |     wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } // namespace GUI
 | } // namespace GUI
 | ||||||
| } // namespace Slic3r
 | } // namespace Slic3r
 | ||||||
|  |  | ||||||
|  | @ -4,7 +4,6 @@ | ||||||
| #include <stddef.h> | #include <stddef.h> | ||||||
| #include <memory> | #include <memory> | ||||||
| 
 | 
 | ||||||
| #include "libslic3r/ModelArrange.hpp" |  | ||||||
| #include "3DScene.hpp" | #include "3DScene.hpp" | ||||||
| #include "GLToolbar.hpp" | #include "GLToolbar.hpp" | ||||||
| #include "Event.hpp" | #include "Event.hpp" | ||||||
|  | @ -627,8 +626,27 @@ public: | ||||||
|     int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; } |     int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; } | ||||||
|     int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } |     int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } | ||||||
|      |      | ||||||
|     arr::WipeTowerInfo get_wipe_tower_info() const; |     class WipeTowerInfo { | ||||||
|     void arrange_wipe_tower(const arr::WipeTowerInfo& wti) const; |     protected: | ||||||
|  |         Vec2d m_pos = {std::nan(""), std::nan("")}; | ||||||
|  |         Vec2d m_bb_size = {0., 0.}; | ||||||
|  |         double m_rotation = 0.; | ||||||
|  |         friend class GLCanvas3D; | ||||||
|  |     public: | ||||||
|  |          | ||||||
|  |         inline operator bool() const | ||||||
|  |         { | ||||||
|  |             return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y()); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         inline const Vec2d& pos() const { return m_pos; } | ||||||
|  |         inline double rotation() const { return m_rotation; } | ||||||
|  |         inline const Vec2d bb_size() const { return m_bb_size; } | ||||||
|  |          | ||||||
|  |         void apply_wipe_tower() const; | ||||||
|  |     }; | ||||||
|  |      | ||||||
|  |     WipeTowerInfo get_wipe_tower_info() const; | ||||||
| 
 | 
 | ||||||
|     // Returns the view ray line, in world coordinate, at the given mouse position.
 |     // Returns the view ray line, in world coordinate, at the given mouse position.
 | ||||||
|     Linef3 mouse_ray(const Point& mouse_pos); |     Linef3 mouse_ray(const Point& mouse_pos); | ||||||
|  |  | ||||||
|  | @ -359,9 +359,7 @@ DynamicPrintConfig& ObjectList::get_item_config(const wxDataViewItem& item) cons | ||||||
|     assert(item); |     assert(item); | ||||||
|     const ItemType type = m_objects_model->GetItemType(item); |     const ItemType type = m_objects_model->GetItemType(item); | ||||||
| 
 | 
 | ||||||
|     const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : |     const int obj_idx = m_objects_model->GetObjectIdByItem(item); | ||||||
|                         m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); |  | ||||||
| 
 |  | ||||||
|     const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; |     const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; | ||||||
| 
 | 
 | ||||||
|     assert(obj_idx >= 0 || ((type & itVolume) && vol_idx >=0)); |     assert(obj_idx >= 0 || ((type & itVolume) && vol_idx >=0)); | ||||||
|  | @ -489,13 +487,15 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const | ||||||
| { | { | ||||||
|     const int obj_idx = m_objects_model->GetObjectIdByItem(item); |     const int obj_idx = m_objects_model->GetObjectIdByItem(item); | ||||||
|     if (obj_idx < 0) return; |     if (obj_idx < 0) return; | ||||||
|  |     const int volume_id = m_objects_model->GetVolumeIdByItem(item); | ||||||
| 
 | 
 | ||||||
|     if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { |     take_snapshot(wxString::Format(_(L("Rename %s")), volume_id < 0 ? _(L("Object")) : _(L("Sub-object")))); | ||||||
|  | 
 | ||||||
|  |     if (m_objects_model->GetItemType(item) & itObject) { | ||||||
|         (*m_objects)[obj_idx]->name = m_objects_model->GetName(item).ToUTF8().data(); |         (*m_objects)[obj_idx]->name = m_objects_model->GetName(item).ToUTF8().data(); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const int volume_id = m_objects_model->GetVolumeIdByItem(item); |  | ||||||
|     if (volume_id < 0) return; |     if (volume_id < 0) return; | ||||||
|     (*m_objects)[obj_idx]->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data(); |     (*m_objects)[obj_idx]->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data(); | ||||||
| } | } | ||||||
|  | @ -927,7 +927,7 @@ void ObjectList::OnDrop(wxDataViewEvent &event) | ||||||
| 
 | 
 | ||||||
|     if (m_dragged_data.type() == itInstance) |     if (m_dragged_data.type() == itInstance) | ||||||
|     { |     { | ||||||
|         take_snapshot(_(L("Instances to Separated Objects"))); |         Plater::TakeSnapshot snapshot(wxGetApp().plater(),_(L("Instances to Separated Objects"))); | ||||||
|         instances_to_separated_object(m_dragged_data.obj_idx(), m_dragged_data.inst_idxs()); |         instances_to_separated_object(m_dragged_data.obj_idx(), m_dragged_data.inst_idxs()); | ||||||
|         m_dragged_data.clear(); |         m_dragged_data.clear(); | ||||||
|         return; |         return; | ||||||
|  | @ -945,7 +945,7 @@ void ObjectList::OnDrop(wxDataViewEvent &event) | ||||||
| //     if (to_volume_id > from_volume_id) to_volume_id--;
 | //     if (to_volume_id > from_volume_id) to_volume_id--;
 | ||||||
| // #endif // __WXGTK__
 | // #endif // __WXGTK__
 | ||||||
| 
 | 
 | ||||||
|     take_snapshot(_(L("Remov Volume(s)"))); |     take_snapshot(_(L("Remove Volume(s)"))); | ||||||
| 
 | 
 | ||||||
|     auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes; |     auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes; | ||||||
|     auto delta = to_volume_id < from_volume_id ? -1 : 1; |     auto delta = to_volume_id < from_volume_id ? -1 : 1; | ||||||
|  | @ -1037,12 +1037,17 @@ void ObjectList::get_settings_choice(const wxString& category_name) | ||||||
| { | { | ||||||
|     wxArrayString names; |     wxArrayString names; | ||||||
|     wxArrayInt selections; |     wxArrayInt selections; | ||||||
|  |     wxDataViewItem item = GetSelection(); | ||||||
| 
 | 
 | ||||||
|     settings_menu_hierarchy settings_menu; |     settings_menu_hierarchy settings_menu; | ||||||
|     const bool is_part = m_objects_model->GetParent(GetSelection()) != wxDataViewItem(0); |     const bool is_part = m_objects_model->GetItemType(item) & (itVolume | itLayer); | ||||||
|     get_options_menu(settings_menu, is_part); |     get_options_menu(settings_menu, is_part); | ||||||
|     std::vector< std::pair<std::string, std::string> > *settings_list = nullptr; |     std::vector< std::pair<std::string, std::string> > *settings_list = nullptr; | ||||||
| 
 | 
 | ||||||
|  |     if (!m_config) | ||||||
|  |         m_config = &get_item_config(item); | ||||||
|  | 
 | ||||||
|  |     assert(m_config); | ||||||
|     auto opt_keys = m_config->keys(); |     auto opt_keys = m_config->keys(); | ||||||
| 
 | 
 | ||||||
|     for (auto& cat : settings_menu) |     for (auto& cat : settings_menu) | ||||||
|  | @ -1144,27 +1149,33 @@ void ObjectList::get_settings_choice(const wxString& category_name) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     // Add settings item for object/sub-object and show them 
 |     // Add settings item for object/sub-object and show them 
 | ||||||
|     show_settings(add_settings_item(GetSelection(), m_config));     |     if (!(m_objects_model->GetItemType(item) & (itObject | itVolume | itLayer))) | ||||||
|  |         item = m_objects_model->GetTopParent(item); | ||||||
|  |     show_settings(add_settings_item(item, m_config)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ObjectList::get_freq_settings_choice(const wxString& bundle_name) | void ObjectList::get_freq_settings_choice(const wxString& bundle_name) | ||||||
| { | { | ||||||
|     std::vector<std::string> options = get_options_for_bundle(bundle_name); |     std::vector<std::string> options = get_options_for_bundle(bundle_name); | ||||||
|  |     wxDataViewItem item = GetSelection(); | ||||||
| 
 | 
 | ||||||
|     /* Because of we couldn't edited layer_height for ItVolume from settings list,
 |     /* Because of we couldn't edited layer_height for ItVolume from settings list,
 | ||||||
|      * correct options according to the selected item type : |      * correct options according to the selected item type : | ||||||
|      * remove "layer_height" option |      * remove "layer_height" option | ||||||
|      */ |      */ | ||||||
|     if ((m_objects_model->GetItemType(GetSelection()) & itVolume) && bundle_name == _("Layers and Perimeters")) { |     if ((m_objects_model->GetItemType(item) & itVolume) && bundle_name == _("Layers and Perimeters")) { | ||||||
|         const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height"); |         const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height"); | ||||||
|         if (layer_height_it != options.end()) |         if (layer_height_it != options.end()) | ||||||
|             options.erase(layer_height_it); |             options.erase(layer_height_it); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (!m_config) | ||||||
|  |         m_config = &get_item_config(item); | ||||||
|  | 
 | ||||||
|     assert(m_config); |     assert(m_config); | ||||||
|     auto opt_keys = m_config->keys(); |     auto opt_keys = m_config->keys(); | ||||||
| 
 | 
 | ||||||
|     take_snapshot(wxString::Format(_(L("Add Settings Bundle for %s")), m_objects_model->GetItemType(GetSelection()) & itObject ? _(L("Object")) : _(L("Sub-object")))); |     take_snapshot(wxString::Format(_(L("Add Settings Bundle for %s")), m_objects_model->GetItemType(item) & (itVolume|itLayer) ? _(L("Sub-object")) : _(L("Object")))); | ||||||
| 
 | 
 | ||||||
|     const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; |     const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; | ||||||
|     for (auto& opt_key : options) |     for (auto& opt_key : options) | ||||||
|  | @ -1181,7 +1192,9 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Add settings item for object/sub-object and show them 
 |     // Add settings item for object/sub-object and show them 
 | ||||||
|     show_settings(add_settings_item(GetSelection(), m_config)); |     if (!(m_objects_model->GetItemType(item) & (itObject | itVolume | itLayer))) | ||||||
|  |         item = m_objects_model->GetTopParent(item); | ||||||
|  |     show_settings(add_settings_item(item, m_config)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ObjectList::show_settings(const wxDataViewItem settings_item) | void ObjectList::show_settings(const wxDataViewItem settings_item) | ||||||
|  | @ -3044,7 +3057,7 @@ void ObjectList::change_part_type() | ||||||
| 	if (new_type == type || new_type == ModelVolumeType::INVALID) | 	if (new_type == type || new_type == ModelVolumeType::INVALID) | ||||||
|         return; |         return; | ||||||
| 
 | 
 | ||||||
|     take_snapshot(_(L("Paste from Clipboard"))); |     take_snapshot(_(L("Change Part Type"))); | ||||||
| 
 | 
 | ||||||
|     const auto item = GetSelection(); |     const auto item = GetSelection(); | ||||||
|     volume->set_type(new_type); |     volume->set_type(new_type); | ||||||
|  | @ -3283,6 +3296,8 @@ void ObjectList::split_instances() | ||||||
|     if (obj_idx == -1) |     if (obj_idx == -1) | ||||||
|         return; |         return; | ||||||
| 
 | 
 | ||||||
|  |     Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Instances to Separated Objects"))); | ||||||
|  | 
 | ||||||
|     if (selection.is_single_full_object()) |     if (selection.is_single_full_object()) | ||||||
|     { |     { | ||||||
|         instances_to_separated_objects(obj_idx); |         instances_to_separated_objects(obj_idx); | ||||||
|  |  | ||||||
|  | @ -93,6 +93,7 @@ bool ObjectSettings::update_settings_list() | ||||||
|             btn->SetToolTip(_(L("Remove parameter"))); |             btn->SetToolTip(_(L("Remove parameter"))); | ||||||
| 
 | 
 | ||||||
| 			btn->Bind(wxEVT_BUTTON, [opt_key, config, this](wxEvent &event) { | 			btn->Bind(wxEVT_BUTTON, [opt_key, config, this](wxEvent &event) { | ||||||
|  |                 wxGetApp().plater()->take_snapshot(wxString::Format(_(L("Delete Option %s")), opt_key)); | ||||||
| 				config->erase(opt_key); | 				config->erase(opt_key); | ||||||
|                 wxGetApp().obj_list()->changed_object(); |                 wxGetApp().obj_list()->changed_object(); | ||||||
|                 wxTheApp->CallAfter([this]() { |                 wxTheApp->CallAfter([this]() { | ||||||
|  | @ -137,6 +138,13 @@ bool ObjectSettings::update_settings_list() | ||||||
|                 if (is_extruders_cat) |                 if (is_extruders_cat) | ||||||
|                     option.opt.max = wxGetApp().extruders_edited_cnt(); |                     option.opt.max = wxGetApp().extruders_edited_cnt(); | ||||||
|                 optgroup->append_single_option_line(option); |                 optgroup->append_single_option_line(option); | ||||||
|  | 
 | ||||||
|  |                 optgroup->get_field(opt)->m_on_change = [optgroup](const std::string& opt_id, const boost::any& value) { | ||||||
|  |                     // first of all take a snapshot and then change value in configuration
 | ||||||
|  |                     wxGetApp().plater()->take_snapshot(wxString::Format(_(L("Change Option %s")), opt_id)); | ||||||
|  |                     optgroup->on_change_OG(opt_id, value); | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|             } |             } | ||||||
|             optgroup->reload_config(); |             optgroup->reload_config(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| #include <cstddef> | #include <cstddef> | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
|  | #include <numeric> | ||||||
| #include <vector> | #include <vector> | ||||||
| #include <string> | #include <string> | ||||||
| #include <regex> | #include <regex> | ||||||
|  | @ -31,7 +32,6 @@ | ||||||
| #include "libslic3r/Format/3mf.hpp" | #include "libslic3r/Format/3mf.hpp" | ||||||
| #include "libslic3r/GCode/PreviewData.hpp" | #include "libslic3r/GCode/PreviewData.hpp" | ||||||
| #include "libslic3r/Model.hpp" | #include "libslic3r/Model.hpp" | ||||||
| #include "libslic3r/ModelArrange.hpp" |  | ||||||
| #include "libslic3r/Polygon.hpp" | #include "libslic3r/Polygon.hpp" | ||||||
| #include "libslic3r/Print.hpp" | #include "libslic3r/Print.hpp" | ||||||
| #include "libslic3r/PrintConfig.hpp" | #include "libslic3r/PrintConfig.hpp" | ||||||
|  | @ -1355,6 +1355,44 @@ struct Plater::priv | ||||||
|     BackgroundSlicingProcess    background_process; |     BackgroundSlicingProcess    background_process; | ||||||
|     bool suppressed_backround_processing_update { false }; |     bool suppressed_backround_processing_update { false }; | ||||||
|      |      | ||||||
|  |     // Cache the wti info
 | ||||||
|  |     class WipeTower: public GLCanvas3D::WipeTowerInfo { | ||||||
|  |         using ArrangePolygon = arrangement::ArrangePolygon; | ||||||
|  |         friend priv; | ||||||
|  |     public: | ||||||
|  |          | ||||||
|  |         void apply_arrange_result(const Vec2crd& tr, double rotation) | ||||||
|  |         { | ||||||
|  |             m_pos = unscaled(tr); m_rotation = rotation; | ||||||
|  |             apply_wipe_tower(); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         ArrangePolygon get_arrange_polygon() const | ||||||
|  |         { | ||||||
|  |             Polygon p({ | ||||||
|  |                 {coord_t(0), coord_t(0)}, | ||||||
|  |                 {scaled(m_bb_size(X)), coord_t(0)}, | ||||||
|  |                 {scaled(m_bb_size)}, | ||||||
|  |                 {coord_t(0), scaled(m_bb_size(Y))}, | ||||||
|  |                 {coord_t(0), coord_t(0)}, | ||||||
|  |                 }); | ||||||
|  |              | ||||||
|  |             ArrangePolygon ret; | ||||||
|  |             ret.poly.contour = std::move(p); | ||||||
|  |             ret.translation  = scaled(m_pos); | ||||||
|  |             ret.rotation     = m_rotation; | ||||||
|  |             return ret; | ||||||
|  |         } | ||||||
|  |     } wipetower; | ||||||
|  |      | ||||||
|  |     WipeTower& updated_wipe_tower() { | ||||||
|  |         auto wti = view3D->get_canvas3d()->get_wipe_tower_info(); | ||||||
|  |         wipetower.m_pos = wti.pos(); | ||||||
|  |         wipetower.m_rotation = wti.rotation(); | ||||||
|  |         wipetower.m_bb_size  = wti.bb_size(); | ||||||
|  |         return wipetower; | ||||||
|  |     } | ||||||
|  |      | ||||||
|     // A class to handle UI jobs like arranging and optimizing rotation.
 |     // A class to handle UI jobs like arranging and optimizing rotation.
 | ||||||
|     // These are not instant jobs, the user has to be informed about their
 |     // These are not instant jobs, the user has to be informed about their
 | ||||||
|     // state in the status progress indicator. On the other hand they are 
 |     // state in the status progress indicator. On the other hand they are 
 | ||||||
|  | @ -1365,59 +1403,65 @@ struct Plater::priv | ||||||
|     // objects would be frozen for the user. In case of arrange, an animation
 |     // objects would be frozen for the user. In case of arrange, an animation
 | ||||||
|     // could be shown, or with the optimize orientations, partial results
 |     // could be shown, or with the optimize orientations, partial results
 | ||||||
|     // could be displayed.
 |     // could be displayed.
 | ||||||
|     class Job: public wxEvtHandler { |     class Job : public wxEvtHandler | ||||||
|  |     { | ||||||
|         int               m_range = 100; |         int               m_range = 100; | ||||||
|         std::future<void> m_ftr; |         std::future<void> m_ftr; | ||||||
|         priv *m_plater = nullptr; |         priv *            m_plater = nullptr; | ||||||
|         std::atomic<bool> m_running {false}, m_canceled {false}; |         std::atomic<bool> m_running{false}, m_canceled{false}; | ||||||
|         bool              m_finalized = false; |         bool              m_finalized = false; | ||||||
| 
 | 
 | ||||||
|         void run() {  |         void run() | ||||||
|             m_running.store(true); process(); m_running.store(false);  |         { | ||||||
|  |             m_running.store(true); | ||||||
|  |             process(); | ||||||
|  |             m_running.store(false); | ||||||
| 
 | 
 | ||||||
|             // ensure to call the last status to finalize the job
 |             // ensure to call the last status to finalize the job
 | ||||||
|             update_status(status_range(), ""); |             update_status(status_range(), ""); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     protected: |     protected: | ||||||
|          |  | ||||||
|         // status range for a particular job
 |         // status range for a particular job
 | ||||||
|         virtual int status_range() const { return 100; } |         virtual int status_range() const { return 100; } | ||||||
| 
 | 
 | ||||||
|         // status update, to be used from the work thread (process() method)
 |         // status update, to be used from the work thread (process() method)
 | ||||||
|         void update_status(int st, const wxString& msg = "") {  |         void update_status(int st, const wxString &msg = "") | ||||||
|             auto evt = new wxThreadEvent(); evt->SetInt(st); evt->SetString(msg); |         { | ||||||
|  |             auto evt = new wxThreadEvent(); | ||||||
|  |             evt->SetInt(st); | ||||||
|  |             evt->SetString(msg); | ||||||
|             wxQueueEvent(this, evt); |             wxQueueEvent(this, evt); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         priv& plater() { return *m_plater; } |         priv &      plater() { return *m_plater; } | ||||||
|  |         const priv &plater() const { return *m_plater; } | ||||||
|         bool        was_canceled() const { return m_canceled.load(); } |         bool        was_canceled() const { return m_canceled.load(); } | ||||||
| 
 | 
 | ||||||
|         // Launched just before start(), a job can use it to prepare internals
 |         // Launched just before start(), a job can use it to prepare internals
 | ||||||
|         virtual void prepare() {} |         virtual void prepare() {} | ||||||
| 
 | 
 | ||||||
|         // Launched when the job is finished. It refreshes the 3dscene by def.
 |         // Launched when the job is finished. It refreshes the 3Dscene by def.
 | ||||||
|         virtual void finalize() { |         virtual void finalize() | ||||||
|  |         { | ||||||
|             // Do a full refresh of scene tree, including regenerating
 |             // Do a full refresh of scene tree, including regenerating
 | ||||||
|             // all the GLVolumes. FIXME The update function shall just
 |             // all the GLVolumes. FIXME The update function shall just
 | ||||||
|             // reload the modified matrices.
 |             // reload the modified matrices.
 | ||||||
|             if(! was_canceled()) |             if (!was_canceled()) plater().update(true); | ||||||
|                 plater().update(true); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     public: |     public: | ||||||
|          |         Job(priv *_plater) : m_plater(_plater) | ||||||
|         Job(priv *_plater): m_plater(_plater) |  | ||||||
|         { |         { | ||||||
|             Bind(wxEVT_THREAD, [this](const wxThreadEvent& evt){ |             Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { | ||||||
|                 auto msg = evt.GetString(); |                 auto msg = evt.GetString(); | ||||||
|                 if(! msg.empty()) plater().statusbar()->set_status_text(msg); |                 if (!msg.empty()) | ||||||
|  |                     plater().statusbar()->set_status_text(msg); | ||||||
| 
 | 
 | ||||||
|                 if(m_finalized) return; |                 if (m_finalized) return; | ||||||
| 
 | 
 | ||||||
|                 plater().statusbar()->set_progress(evt.GetInt()); |                 plater().statusbar()->set_progress(evt.GetInt()); | ||||||
|                 if(evt.GetInt() == status_range()) { |                 if (evt.GetInt() == status_range()) { | ||||||
|                      |  | ||||||
|                     // set back the original range and cancel callback
 |                     // set back the original range and cancel callback
 | ||||||
|                     plater().statusbar()->set_range(m_range); |                     plater().statusbar()->set_range(m_range); | ||||||
|                     plater().statusbar()->set_cancel_callback(); |                     plater().statusbar()->set_cancel_callback(); | ||||||
|  | @ -1431,28 +1475,16 @@ struct Plater::priv | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // TODO: use this when we all migrated to VS2019
 |         Job(const Job &) = delete; | ||||||
|         // Job(const Job&) = delete;
 |         Job(Job &&)      = default; | ||||||
|         // Job(Job&&) = default;
 |         Job &operator=(const Job &) = delete; | ||||||
|         // Job& operator=(const Job&) = delete;
 |         Job &operator=(Job &&) = default; | ||||||
|         // Job& operator=(Job&&) = default;
 |  | ||||||
|         Job(const Job&) = delete; |  | ||||||
|         Job& operator=(const Job&) = delete; |  | ||||||
|         Job(Job &&o) : |  | ||||||
|             m_range(o.m_range), |  | ||||||
|             m_ftr(std::move(o.m_ftr)), |  | ||||||
|             m_plater(o.m_plater), |  | ||||||
|             m_finalized(o.m_finalized) |  | ||||||
|         { |  | ||||||
|             m_running.store(o.m_running.load()); |  | ||||||
|             m_canceled.store(o.m_canceled.load()); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         virtual void process() = 0; |         virtual void process() = 0; | ||||||
| 
 | 
 | ||||||
|         void start() { // Start the job. No effect if the job is already running
 |         void start() | ||||||
|             if(! m_running.load()) { |         { // Start the job. No effect if the job is already running
 | ||||||
|                  |             if (!m_running.load()) { | ||||||
|                 prepare(); |                 prepare(); | ||||||
| 
 | 
 | ||||||
|                 // Save the current status indicatior range and push the new one
 |                 // Save the current status indicatior range and push the new one
 | ||||||
|  | @ -1461,9 +1493,8 @@ struct Plater::priv | ||||||
| 
 | 
 | ||||||
|                 // init cancellation flag and set the cancel callback
 |                 // init cancellation flag and set the cancel callback
 | ||||||
|                 m_canceled.store(false); |                 m_canceled.store(false); | ||||||
|                 plater().statusbar()->set_cancel_callback( [this](){  |                 plater().statusbar()->set_cancel_callback( | ||||||
|                     m_canceled.store(true); |                     [this]() { m_canceled.store(true); }); | ||||||
|                 }); |  | ||||||
| 
 | 
 | ||||||
|                 m_finalized = false; |                 m_finalized = false; | ||||||
| 
 | 
 | ||||||
|  | @ -1472,9 +1503,10 @@ struct Plater::priv | ||||||
| 
 | 
 | ||||||
|                 try { // Execute the job
 |                 try { // Execute the job
 | ||||||
|                     m_ftr = std::async(std::launch::async, &Job::run, this); |                     m_ftr = std::async(std::launch::async, &Job::run, this); | ||||||
|                 } catch(std::exception& ) {  |                 } catch (std::exception &) { | ||||||
|                     update_status(status_range(), |                     update_status(status_range(), | ||||||
|                     _(L("ERROR: not enough resources to execute a new job."))); |                                   _(L("ERROR: not enough resources to " | ||||||
|  |                                       "execute a new job."))); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 // The state changes will be undone when the process hits the
 |                 // The state changes will be undone when the process hits the
 | ||||||
|  | @ -1482,16 +1514,18 @@ struct Plater::priv | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // To wait for the running job and join the threads. False is returned
 |         // To wait for the running job and join the threads. False is
 | ||||||
|         // if the timeout has been reached and the job is still running. Call
 |         // returned if the timeout has been reached and the job is still
 | ||||||
|         // cancel() before this fn if you want to explicitly end the job.
 |         // running. Call cancel() before this fn if you want to explicitly
 | ||||||
|         bool join(int timeout_ms = 0) const {  |         // end the job.
 | ||||||
|             if(!m_ftr.valid()) return true; |         bool join(int timeout_ms = 0) const | ||||||
|  |         { | ||||||
|  |             if (!m_ftr.valid()) return true; | ||||||
| 
 | 
 | ||||||
|             if(timeout_ms <= 0)  |             if (timeout_ms <= 0) | ||||||
|                 m_ftr.wait(); |                 m_ftr.wait(); | ||||||
|             else if(m_ftr.wait_for(std::chrono::milliseconds(timeout_ms)) ==  |             else if (m_ftr.wait_for(std::chrono::milliseconds( | ||||||
|                     std::future_status::timeout)  |                          timeout_ms)) == std::future_status::timeout) | ||||||
|                 return false; |                 return false; | ||||||
| 
 | 
 | ||||||
|             return true; |             return true; | ||||||
|  | @ -1506,6 +1540,143 @@ struct Plater::priv | ||||||
|         Rotoptimize |         Rotoptimize | ||||||
|     }; |     }; | ||||||
|      |      | ||||||
|  |     class ArrangeJob : public Job | ||||||
|  |     { | ||||||
|  |         using ArrangePolygon = arrangement::ArrangePolygon; | ||||||
|  |         using ArrangePolygons = arrangement::ArrangePolygons; | ||||||
|  |          | ||||||
|  |         // The gap between logical beds in the x axis expressed in ratio of
 | ||||||
|  |         // the current bed width.
 | ||||||
|  |         static const constexpr double LOGICAL_BED_GAP = 1. / 5.; | ||||||
|  |          | ||||||
|  |         ArrangePolygons m_selected, m_unselected; | ||||||
|  |          | ||||||
|  |         // clear m_selected and m_unselected, reserve space for next usage
 | ||||||
|  |         void clear_input() { | ||||||
|  |             const Model &model = plater().model; | ||||||
|  |              | ||||||
|  |             size_t count = 0; // To know how much space to reserve
 | ||||||
|  |             for (auto obj : model.objects) count += obj->instances.size(); | ||||||
|  |             m_selected.clear(), m_unselected.clear(); | ||||||
|  |             m_selected.reserve(count + 1 /* for optional wti */); | ||||||
|  |             m_unselected.reserve(count + 1 /* for optional wti */); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // Stride between logical beds
 | ||||||
|  |         coord_t bed_stride() const { | ||||||
|  |             double bedwidth = plater().bed_shape_bb().size().x(); | ||||||
|  |             return scaled((1. + LOGICAL_BED_GAP) * bedwidth); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // Set up arrange polygon for a ModelInstance and Wipe tower
 | ||||||
|  |         template<class T> ArrangePolygon get_arrange_poly(T *obj) const { | ||||||
|  |             ArrangePolygon ap = obj->get_arrange_polygon(); | ||||||
|  |             ap.priority = 0; | ||||||
|  |             ap.bed_idx = ap.translation.x() / bed_stride(); | ||||||
|  |             ap.setter = [obj, this](const ArrangePolygon &p) { | ||||||
|  |                 if (p.is_arranged()) { | ||||||
|  |                     auto t = p.translation; t.x() += p.bed_idx * bed_stride(); | ||||||
|  |                     obj->apply_arrange_result(t, p.rotation); | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |             return ap; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // Prepare all objects on the bed regardless of the selection
 | ||||||
|  |         void prepare_all() { | ||||||
|  |             clear_input(); | ||||||
|  |              | ||||||
|  |             for (ModelObject *obj: plater().model.objects) | ||||||
|  |                 for (ModelInstance *mi : obj->instances) | ||||||
|  |                     m_selected.emplace_back(get_arrange_poly(mi)); | ||||||
|  |              | ||||||
|  |             auto& wti = plater().updated_wipe_tower(); | ||||||
|  |             if (wti) m_selected.emplace_back(get_arrange_poly(&wti)); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // Prepare the selected and unselected items separately. If nothing is
 | ||||||
|  |         // selected, behaves as if everything would be selected.
 | ||||||
|  |         void prepare_selected() { | ||||||
|  |             clear_input(); | ||||||
|  |              | ||||||
|  |             Model &model = plater().model; | ||||||
|  |             coord_t stride = bed_stride(); | ||||||
|  |              | ||||||
|  |             std::vector<const Selection::InstanceIdxsList *> | ||||||
|  |                 obj_sel(model.objects.size(), nullptr); | ||||||
|  |              | ||||||
|  |             for (auto &s : plater().get_selection().get_content()) | ||||||
|  |                 if (s.first < int(obj_sel.size())) obj_sel[s.first] = &s.second;  | ||||||
|  |              | ||||||
|  |             // Go through the objects and check if inside the selection
 | ||||||
|  |             for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { | ||||||
|  |                 const Selection::InstanceIdxsList * instlist = obj_sel[oidx]; | ||||||
|  |                 ModelObject *mo = model.objects[oidx]; | ||||||
|  |                  | ||||||
|  |                 std::vector<bool> inst_sel(mo->instances.size(), false); | ||||||
|  |                  | ||||||
|  |                 if (instlist) | ||||||
|  |                     for (auto inst_id : *instlist) inst_sel[inst_id] = true; | ||||||
|  |                  | ||||||
|  |                 for (size_t i = 0; i < inst_sel.size(); ++i) { | ||||||
|  |                     ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]); | ||||||
|  |                      | ||||||
|  |                     inst_sel[i] ? | ||||||
|  |                         m_selected.emplace_back(std::move(ap)) : | ||||||
|  |                         m_unselected.emplace_back(std::move(ap)); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             auto& wti = plater().updated_wipe_tower(); | ||||||
|  |             if (wti) { | ||||||
|  |                 ArrangePolygon &&ap = get_arrange_poly(&wti); | ||||||
|  |                  | ||||||
|  |                 plater().get_selection().is_wipe_tower() ? | ||||||
|  |                     m_selected.emplace_back(std::move(ap)) : | ||||||
|  |                     m_unselected.emplace_back(std::move(ap)); | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             // If the selection was empty arrange everything
 | ||||||
|  |             if (m_selected.empty()) m_selected.swap(m_unselected); | ||||||
|  |              | ||||||
|  |             // The strides have to be removed from the fixed items. For the
 | ||||||
|  |             // arrangeable (selected) items bed_idx is ignored and the
 | ||||||
|  |             // translation is irrelevant.
 | ||||||
|  |             for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |     protected: | ||||||
|  |          | ||||||
|  |         void prepare() override | ||||||
|  |         { | ||||||
|  |             wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all(); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |     public: | ||||||
|  |         using Job::Job; | ||||||
|  |          | ||||||
|  |         int status_range() const override { return int(m_selected.size()); } | ||||||
|  |          | ||||||
|  |         void process() override; | ||||||
|  |          | ||||||
|  |         void finalize() override { | ||||||
|  |             // Ignore the arrange result if aborted.
 | ||||||
|  |             if (was_canceled()) return; | ||||||
|  |              | ||||||
|  |             // Apply the arrange result to all selected objects
 | ||||||
|  |             for (ArrangePolygon &ap : m_selected) ap.apply(); | ||||||
|  | 
 | ||||||
|  |             plater().update(false /*dont force_full_scene_refresh*/); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |      | ||||||
|  |     class RotoptimizeJob : public Job | ||||||
|  |     { | ||||||
|  |     public: | ||||||
|  |         using Job::Job; | ||||||
|  |         void process() override; | ||||||
|  |     }; | ||||||
|  |      | ||||||
|     // Jobs defined inside the group class will be managed so that only one can
 |     // Jobs defined inside the group class will be managed so that only one can
 | ||||||
|     // run at a time. Also, the background process will be stopped if a job is
 |     // run at a time. Also, the background process will be stopped if a job is
 | ||||||
|     // started.
 |     // started.
 | ||||||
|  | @ -1515,49 +1686,19 @@ struct Plater::priv | ||||||
|          |          | ||||||
|         priv * m_plater; |         priv * m_plater; | ||||||
| 
 | 
 | ||||||
|         class ArrangeJob : public Job |         ArrangeJob arrange_job{m_plater}; | ||||||
|         { |         RotoptimizeJob rotoptimize_job{m_plater}; | ||||||
|             int count = 0; |  | ||||||
| 
 |  | ||||||
|         protected: |  | ||||||
|             void prepare() override |  | ||||||
|             { |  | ||||||
|                 count = 0; |  | ||||||
|                 for (auto obj : plater().model.objects) |  | ||||||
|                     count += int(obj->instances.size()); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|         public: |  | ||||||
|             //using Job::Job;
 |  | ||||||
|             ArrangeJob(priv * pltr): Job(pltr) {} |  | ||||||
|             int  status_range() const override { return count; } |  | ||||||
|             void set_count(int c) { count = c; } |  | ||||||
|             void process() override; |  | ||||||
|         } arrange_job/*{m_plater}*/; |  | ||||||
| 
 |  | ||||||
|         class RotoptimizeJob : public Job |  | ||||||
|         { |  | ||||||
|         public: |  | ||||||
|             //using Job::Job;
 |  | ||||||
|             RotoptimizeJob(priv * pltr): Job(pltr) {} |  | ||||||
|             void process() override; |  | ||||||
|         } rotoptimize_job/*{m_plater}*/; |  | ||||||
| 
 | 
 | ||||||
|         // To create a new job, just define a new subclass of Job, implement
 |         // To create a new job, just define a new subclass of Job, implement
 | ||||||
|         // the process and the optional prepare() and finalize() methods
 |         // the process and the optional prepare() and finalize() methods
 | ||||||
|         // Register the instance of the class in the m_jobs container
 |         // Register the instance of the class in the m_jobs container
 | ||||||
|         // if it cannot run concurrently with other jobs in this group 
 |         // if it cannot run concurrently with other jobs in this group 
 | ||||||
| 
 | 
 | ||||||
|         std::vector<std::reference_wrapper<Job>> m_jobs/*{arrange_job,
 |         std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job, | ||||||
|                                                         rotoptimize_job}*/; |                                                         rotoptimize_job}; | ||||||
| 
 | 
 | ||||||
|     public: |     public: | ||||||
|         ExclusiveJobGroup(priv *_plater) |         ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {} | ||||||
|             : m_plater(_plater) |  | ||||||
|             , arrange_job(m_plater) |  | ||||||
|             , rotoptimize_job(m_plater) |  | ||||||
|             , m_jobs({arrange_job, rotoptimize_job}) |  | ||||||
|         {} |  | ||||||
| 
 | 
 | ||||||
|         void start(Jobs jid) { |         void start(Jobs jid) { | ||||||
|             m_plater->background_process.stop(); |             m_plater->background_process.stop(); | ||||||
|  | @ -1618,6 +1759,9 @@ struct Plater::priv | ||||||
|     std::string get_config(const std::string &key) const; |     std::string get_config(const std::string &key) const; | ||||||
|     BoundingBoxf bed_shape_bb() const; |     BoundingBoxf bed_shape_bb() const; | ||||||
|     BoundingBox scaled_bed_shape_bb() const; |     BoundingBox scaled_bed_shape_bb() const; | ||||||
|  |     arrangement::BedShapeHint get_bed_shape_hint() const; | ||||||
|  |      | ||||||
|  |     void find_new_position(const ModelInstancePtrs  &instances, coord_t min_d); | ||||||
|     std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config); |     std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config); | ||||||
|     std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects); |     std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects); | ||||||
|     wxString get_export_file(GUI::FileType file_type); |     wxString get_export_file(GUI::FileType file_type); | ||||||
|  | @ -2240,7 +2384,7 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode | ||||||
|     Polyline bed; bed.points.reserve(bedpoints.size()); |     Polyline bed; bed.points.reserve(bedpoints.size()); | ||||||
|     for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); |     for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); | ||||||
|      |      | ||||||
|     arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); |     std::pair<bool, GLCanvas3D::WipeTowerInfo> wti = view3D->get_canvas3d()->get_wipe_tower_info(); | ||||||
|      |      | ||||||
|     arr::find_new_position(model, new_instances, min_obj_distance, bed, wti); |     arr::find_new_position(model, new_instances, min_obj_distance, bed, wti); | ||||||
| 
 | 
 | ||||||
|  | @ -2480,71 +2624,82 @@ void Plater::priv::sla_optimize_rotation() { | ||||||
|     m_ui_jobs.start(Jobs::Rotoptimize); |     m_ui_jobs.start(Jobs::Rotoptimize); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { | arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const { | ||||||
|     // TODO: we should decide whether to allow arrange when the search is
 |  | ||||||
|     // running we should probably disable explicit slicing and background
 |  | ||||||
|     // processing
 |  | ||||||
|      |      | ||||||
|  |     const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape"); | ||||||
|  |     assert(bed_shape_opt); | ||||||
|  |      | ||||||
|  |     if (!bed_shape_opt) return {}; | ||||||
|  |      | ||||||
|  |     auto &bedpoints = bed_shape_opt->values; | ||||||
|  |     Polyline bedpoly; bedpoly.points.reserve(bedpoints.size()); | ||||||
|  |     for (auto &v : bedpoints) bedpoly.append(scaled(v)); | ||||||
|  |      | ||||||
|  |     return arrangement::BedShapeHint(bedpoly); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Plater::priv::find_new_position(const ModelInstancePtrs &instances, | ||||||
|  |                                      coord_t min_d) | ||||||
|  | { | ||||||
|  |     arrangement::ArrangePolygons movable, fixed; | ||||||
|  |      | ||||||
|  |     for (const ModelObject *mo : model.objects) | ||||||
|  |         for (const ModelInstance *inst : mo->instances) { | ||||||
|  |             auto it = std::find(instances.begin(), instances.end(), inst); | ||||||
|  |             auto arrpoly = inst->get_arrange_polygon(); | ||||||
|  |              | ||||||
|  |             if (it == instances.end()) | ||||||
|  |                 fixed.emplace_back(std::move(arrpoly)); | ||||||
|  |             else | ||||||
|  |                 movable.emplace_back(std::move(arrpoly)); | ||||||
|  |         } | ||||||
|  |      | ||||||
|  |     if (updated_wipe_tower()) | ||||||
|  |         fixed.emplace_back(wipetower.get_arrange_polygon()); | ||||||
|  |      | ||||||
|  |     arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint()); | ||||||
|  |      | ||||||
|  |     for (size_t i = 0; i < instances.size(); ++i) | ||||||
|  |         if (movable[i].bed_idx == 0) | ||||||
|  |             instances[i]->apply_arrange_result(movable[i].translation, | ||||||
|  |                                                movable[i].rotation); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Plater::priv::ArrangeJob::process() { | ||||||
|     static const auto arrangestr = _(L("Arranging")); |     static const auto arrangestr = _(L("Arranging")); | ||||||
|      |      | ||||||
|     auto &config = plater().config; |  | ||||||
|     auto &view3D = plater().view3D; |  | ||||||
|     auto &model  = plater().model; |  | ||||||
| 
 |  | ||||||
|     // FIXME: I don't know how to obtain the minimum distance, it depends
 |     // FIXME: I don't know how to obtain the minimum distance, it depends
 | ||||||
|     // on printer technology. I guess the following should work but it crashes.
 |     // on printer technology. I guess the following should work but it crashes.
 | ||||||
|     double dist = 6; // PrintConfig::min_object_distance(config);
 |     double dist = 6; // PrintConfig::min_object_distance(config);
 | ||||||
|     if (plater().printer_technology == ptFFF) { |     if (plater().printer_technology == ptFFF) { | ||||||
|         dist = PrintConfig::min_object_distance(config); |         dist = PrintConfig::min_object_distance(plater().config); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     auto min_obj_distance = coord_t(dist / SCALING_FACTOR); |     coord_t min_d = scaled(dist); | ||||||
| 
 |     auto count = unsigned(m_selected.size()); | ||||||
|     const auto *bed_shape_opt = config->opt<ConfigOptionPoints>( |     arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); | ||||||
|         "bed_shape"); |  | ||||||
| 
 |  | ||||||
|     assert(bed_shape_opt); |  | ||||||
|     auto &   bedpoints = bed_shape_opt->values; |  | ||||||
|     Polyline bed; |  | ||||||
|     bed.points.reserve(bedpoints.size()); |  | ||||||
|     for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); |  | ||||||
| 
 |  | ||||||
|     update_status(0, arrangestr); |  | ||||||
| 
 |  | ||||||
|     arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); |  | ||||||
|      |      | ||||||
|     try { |     try { | ||||||
|         arr::BedShapeHint hint; |         arrangement::arrange(m_selected, m_unselected, min_d, bedshape, | ||||||
| 
 |                              [this, count](unsigned st) { | ||||||
|         // TODO: from Sasha from GUI or
 |                                  if (st > | ||||||
|         hint.type = arr::BedShapeType::WHO_KNOWS; |                                      0) // will not finalize after last one
 | ||||||
| 
 |                                      update_status(count - st, arrangestr); | ||||||
|         arr::arrange(model, |  | ||||||
|                      wti, |  | ||||||
|                      min_obj_distance, |  | ||||||
|                      bed, |  | ||||||
|                      hint, |  | ||||||
|                      false, // create many piles not just one pile
 |  | ||||||
|                      [this](unsigned st) { |  | ||||||
|                          if (st > 0) |  | ||||||
|                              update_status(count - int(st), arrangestr); |  | ||||||
|                              }, |                              }, | ||||||
|                              [this]() { return was_canceled(); }); |                              [this]() { return was_canceled(); }); | ||||||
|     } catch (std::exception & /*e*/) { |     } catch (std::exception & /*e*/) { | ||||||
|         GUI::show_error(plater().q, |         GUI::show_error(plater().q, | ||||||
|                         L("Could not arrange model objects! " |                         _(L("Could not arrange model objects! " | ||||||
|                           "Some geometries may be invalid.")); |                             "Some geometries may be invalid."))); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     update_status(count, |     // finalize just here.
 | ||||||
|  |     update_status(int(count), | ||||||
|                   was_canceled() ? _(L("Arranging canceled.")) |                   was_canceled() ? _(L("Arranging canceled.")) | ||||||
|                                  : _(L("Arranging done."))); |                                  : _(L("Arranging done."))); | ||||||
| 
 |  | ||||||
|     // it remains to move the wipe tower:
 |  | ||||||
|     view3D->get_canvas3d()->arrange_wipe_tower(wti); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() | void Plater::priv::RotoptimizeJob::process() | ||||||
| { | { | ||||||
|     int obj_idx = plater().get_selected_object_idx(); |     int obj_idx = plater().get_selected_object_idx(); | ||||||
|     if (obj_idx < 0) { return; } |     if (obj_idx < 0) { return; } | ||||||
|  | @ -2561,15 +2716,6 @@ void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() | ||||||
|         }, |         }, | ||||||
|         [this]() { return was_canceled(); }); |         [this]() { return was_canceled(); }); | ||||||
| 
 | 
 | ||||||
|     const auto *bed_shape_opt = |  | ||||||
|         plater().config->opt<ConfigOptionPoints>("bed_shape"); |  | ||||||
|      |  | ||||||
|     assert(bed_shape_opt); |  | ||||||
| 
 |  | ||||||
|     auto &   bedpoints = bed_shape_opt->values; |  | ||||||
|     Polyline bed; |  | ||||||
|     bed.points.reserve(bedpoints.size()); |  | ||||||
|     for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); |  | ||||||
| 
 | 
 | ||||||
|     double mindist = 6.0; // FIXME
 |     double mindist = 6.0; // FIXME
 | ||||||
|      |      | ||||||
|  | @ -2591,12 +2737,7 @@ void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() | ||||||
|             oi->set_rotation(rt); |             oi->set_rotation(rt); | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         arr::WipeTowerInfo wti; // useless in SLA context
 |         plater().find_new_position(o->instances, scaled(mindist)); | ||||||
|         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
 |         // Correct the z offset of the object which was corrupted be
 | ||||||
|         // the rotation
 |         // the rotation
 | ||||||
|  | @ -3486,7 +3627,7 @@ void Plater::priv::set_bed_shape(const Pointfs& shape) | ||||||
| 
 | 
 | ||||||
| bool Plater::priv::can_delete() const | bool Plater::priv::can_delete() const | ||||||
| { | { | ||||||
|     return !get_selection().is_empty() && !get_selection().is_wipe_tower(); |     return !get_selection().is_empty() && !get_selection().is_wipe_tower() && !m_ui_jobs.is_any_running(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool Plater::priv::can_delete_all() const | bool Plater::priv::can_delete_all() const | ||||||
|  |  | ||||||
|  | @ -552,6 +552,29 @@ void ObjectDataViewModelNode::msw_rescale() | ||||||
|         update_settings_digest_bitmaps(); |         update_settings_digest_bitmaps(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool ObjectDataViewModelNode::SetValue(const wxVariant& variant, unsigned col) | ||||||
|  | { | ||||||
|  |     switch (col) | ||||||
|  |     { | ||||||
|  |     case 0: { | ||||||
|  |         DataViewBitmapText data; | ||||||
|  |         data << variant; | ||||||
|  |         m_bmp = data.GetBitmap(); | ||||||
|  |         m_name = data.GetText(); | ||||||
|  |         return true; } | ||||||
|  |     case 1: { | ||||||
|  |         const wxString & val = variant.GetString(); | ||||||
|  |         m_extruder = val == "0" ? _(L("default")) : val; | ||||||
|  |         return true; } | ||||||
|  |     case 2: | ||||||
|  |         m_action_icon << variant; | ||||||
|  |         return true; | ||||||
|  |     default: | ||||||
|  |         printf("MyObjectTreeModel::SetValue: wrong column"); | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void ObjectDataViewModelNode::SetIdx(const int& idx) | void ObjectDataViewModelNode::SetIdx(const int& idx) | ||||||
| { | { | ||||||
|     m_idx = idx; |     m_idx = idx; | ||||||
|  |  | ||||||
|  | @ -301,27 +301,7 @@ public: | ||||||
| 		return m_children.GetCount(); | 		return m_children.GetCount(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	bool SetValue(const wxVariant &variant, unsigned int col) | 	bool            SetValue(const wxVariant &variant, unsigned int col); | ||||||
| 	{ |  | ||||||
| 		switch (col) |  | ||||||
| 		{ |  | ||||||
| 		case 0:{ |  | ||||||
|             DataViewBitmapText data; |  | ||||||
| 			data << variant; |  | ||||||
|             m_bmp = data.GetBitmap(); |  | ||||||
| 			m_name = data.GetText(); |  | ||||||
| 			return true;} |  | ||||||
| 		case 1: |  | ||||||
| 			m_extruder = variant.GetString(); |  | ||||||
| 			return true; |  | ||||||
| 		case 2: |  | ||||||
| 			m_action_icon << variant; |  | ||||||
| 			return true; |  | ||||||
| 		default: |  | ||||||
| 			printf("MyObjectTreeModel::SetValue: wrong column"); |  | ||||||
| 		} |  | ||||||
| 		return false; |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	void            SetBitmap(const wxBitmap &icon) { m_bmp = icon; } | 	void            SetBitmap(const wxBitmap &icon) { m_bmp = icon; } | ||||||
|     const wxBitmap& GetBitmap() const               { return m_bmp; } |     const wxBitmap& GetBitmap() const               { return m_bmp; } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 bubnikv
						bubnikv