mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer
This commit is contained in:
		
						commit
						9d59d65fad
					
				
					 25 changed files with 2000 additions and 2035 deletions
				
			
		|  | @ -19,7 +19,7 @@ AlwaysBreakAfterDefinitionReturnType: None | |||
| AlwaysBreakAfterReturnType: None | ||||
| AlwaysBreakBeforeMultilineStrings: false | ||||
| AlwaysBreakTemplateDeclarations: false | ||||
| BinPackArguments: false | ||||
| BinPackArguments: true | ||||
| BinPackParameters: false | ||||
| BraceWrapping:    | ||||
|   AfterClass:      true | ||||
|  |  | |||
|  | @ -30,9 +30,7 @@ using Circle = _Circle<PointImpl>; | |||
| 
 | ||||
| using Item = _Item<PolygonImpl>; | ||||
| using Rectangle = _Rectangle<PolygonImpl>; | ||||
| 
 | ||||
| using PackGroup = _PackGroup<PolygonImpl>; | ||||
| using IndexedPackGroup = _IndexedPackGroup<PolygonImpl>; | ||||
| 
 | ||||
| using FillerSelection = selections::_FillerSelection<PolygonImpl>; | ||||
| using FirstFitSelection = selections::_FirstFitSelection<PolygonImpl>; | ||||
|  | @ -61,20 +59,20 @@ extern template PackGroup Nester<BottomLeftPlacer, FirstFitSelection>::execute( | |||
| template<class Placer = NfpPlacer, | ||||
|          class Selector = FirstFitSelection, | ||||
|          class Iterator = std::vector<Item>::iterator> | ||||
| PackGroup nest(Iterator from, Iterator to, | ||||
| void nest(Iterator from, Iterator to, | ||||
|                const typename Placer::BinType& bin, | ||||
|                Coord dist = 0, | ||||
|                const typename Placer::Config& pconf = {}, | ||||
|                const typename Selector::Config& sconf = {}) | ||||
| { | ||||
|     Nester<Placer, Selector> nester(bin, dist, pconf, sconf); | ||||
|     return nester.execute(from, to); | ||||
|     _Nester<Placer, Selector> nester(bin, dist, pconf, sconf); | ||||
|     nester.execute(from, to); | ||||
| } | ||||
| 
 | ||||
| template<class Placer = NfpPlacer, | ||||
|          class Selector = FirstFitSelection, | ||||
|          class Iterator = std::vector<Item>::iterator> | ||||
| PackGroup nest(Iterator from, Iterator to, | ||||
| void nest(Iterator from, Iterator to, | ||||
|                const typename Placer::BinType& bin, | ||||
|                ProgressFunction prg, | ||||
|                StopCondition scond = []() { return false; }, | ||||
|  | @ -82,10 +80,10 @@ PackGroup nest(Iterator from, Iterator to, | |||
|                const typename Placer::Config& pconf = {}, | ||||
|                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(scond) nester.stopCondition(scond); | ||||
|     return nester.execute(from, to); | ||||
|     nester.execute(from, to); | ||||
| } | ||||
| 
 | ||||
| #ifdef LIBNEST2D_STATIC | ||||
|  | @ -93,14 +91,14 @@ PackGroup nest(Iterator from, Iterator to, | |||
| extern template class Nester<NfpPlacer, 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, | ||||
|                                const Box& bin, | ||||
|                                Coord dist = 0, | ||||
|                                const NfpPlacer::Config& pconf, | ||||
|                                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, | ||||
|                                const Box& bin, | ||||
|                                ProgressFunction prg, | ||||
|  | @ -114,20 +112,19 @@ extern template PackGroup nest(std::vector<Item>::iterator from, | |||
| template<class Placer = NfpPlacer, | ||||
|          class Selector = FirstFitSelection, | ||||
|          class Container = std::vector<Item>> | ||||
| PackGroup nest(Container&& cont, | ||||
| void nest(Container&& cont, | ||||
|                const typename Placer::BinType& bin, | ||||
|                Coord dist = 0, | ||||
|                const typename Placer::Config& pconf = {}, | ||||
|                const typename Selector::Config& sconf = {}) | ||||
| { | ||||
|     return nest<Placer, Selector>(cont.begin(), cont.end(), | ||||
|                                   bin, dist, pconf, sconf); | ||||
|     nest<Placer, Selector>(cont.begin(), cont.end(), bin, dist, pconf, sconf); | ||||
| } | ||||
| 
 | ||||
| template<class Placer = NfpPlacer, | ||||
|          class Selector = FirstFitSelection, | ||||
|          class Container = std::vector<Item>> | ||||
| PackGroup nest(Container&& cont, | ||||
| void nest(Container&& cont, | ||||
|                const typename Placer::BinType& bin, | ||||
|                ProgressFunction prg, | ||||
|                StopCondition scond = []() { return false; }, | ||||
|  | @ -135,8 +132,8 @@ PackGroup nest(Container&& cont, | |||
|                const typename Placer::Config& pconf = {}, | ||||
|                const typename Selector::Config& sconf = {}) | ||||
| { | ||||
|     return nest<Placer, Selector>(cont.begin(), cont.end(), | ||||
|                                   bin, prg, scond, dist, pconf, sconf); | ||||
|     nest<Placer, Selector>(cont.begin(), cont.end(), bin, prg, scond, dist, | ||||
|                            pconf, sconf); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -41,25 +41,25 @@ template<> struct HolesContainer<PolygonImpl> { using Type = ClipperLib::Paths; | |||
| namespace pointlike { | ||||
| 
 | ||||
| // 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; | ||||
| } | ||||
| 
 | ||||
| // 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; | ||||
| } | ||||
| 
 | ||||
| // 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; | ||||
| } | ||||
| 
 | ||||
| // 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; | ||||
| } | ||||
|  | @ -71,7 +71,8 @@ template<> inline TCoord<PointImpl>& y(PointImpl& p) | |||
| 
 | ||||
| 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 | ||||
| 
 | ||||
|  | @ -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
 | ||||
| template<> inline std::string toString(const PolygonImpl& sh) | ||||
| { | ||||
|  | @ -259,10 +268,12 @@ inline TMultiShape<PolygonImpl> clipper_execute( | |||
|         poly.Contour.swap(pptr->Contour); | ||||
| 
 | ||||
|         assert(!pptr->IsHole()); | ||||
| 
 | ||||
|         if(pptr->IsOpen()) { | ||||
|          | ||||
|         if(!poly.Contour.empty() ) { | ||||
|             auto front_p = poly.Contour.front(); | ||||
|             poly.Contour.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 h : pptr->Childs) { processHole(h, poly); } | ||||
|  | @ -274,10 +285,12 @@ inline TMultiShape<PolygonImpl> clipper_execute( | |||
|         poly.Holes.emplace_back(std::move(pptr->Contour)); | ||||
| 
 | ||||
|         assert(pptr->IsHole()); | ||||
| 
 | ||||
|         if(pptr->IsOpen()) { | ||||
|             auto front_p = poly.Holes.back().front(); | ||||
|             poly.Holes.back().emplace_back(front_p); | ||||
|          | ||||
|         if(!poly.Contour.empty() ) { | ||||
|             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); | ||||
|         } | ||||
| 
 | ||||
|         for(auto c : pptr->Childs) processPoly(c); | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -12,6 +12,8 @@ | |||
| 
 | ||||
| namespace libnest2d { | ||||
| 
 | ||||
| static const constexpr int BIN_ID_UNSET = -1; | ||||
| 
 | ||||
| /**
 | ||||
|  * \brief An item to be placed on a bin. | ||||
|  * | ||||
|  | @ -34,22 +36,22 @@ class _Item { | |||
|     RawShape sh_; | ||||
| 
 | ||||
|     // Transformation data
 | ||||
|     Vertex translation_; | ||||
|     Radians rotation_; | ||||
|     Coord offset_distance_; | ||||
|     Vertex translation_{0, 0}; | ||||
|     Radians rotation_{0.0}; | ||||
|     Coord inflation_{0}; | ||||
| 
 | ||||
|     // Info about whether the transformations will have to take place
 | ||||
|     // This is needed because if floating point is used, it is hard to say
 | ||||
|     // that a zero angle is not a rotation because of testing for equality.
 | ||||
|     bool has_rotation_ = false, has_translation_ = false, has_offset_ = false; | ||||
|     bool has_rotation_ = false, has_translation_ = false, has_inflation_ = false; | ||||
| 
 | ||||
|     // For caching the calculations as they can get pretty expensive.
 | ||||
|     mutable RawShape tr_cache_; | ||||
|     mutable bool tr_cache_valid_ = false; | ||||
|     mutable double area_cache_ = 0; | ||||
|     mutable bool area_cache_valid_ = false; | ||||
|     mutable RawShape offset_cache_; | ||||
|     mutable bool offset_cache_valid_ = false; | ||||
|     mutable RawShape inflate_cache_; | ||||
|     mutable bool inflate_cache_valid_ = false; | ||||
| 
 | ||||
|     enum class Convexity: char { | ||||
|         UNCHECKED, | ||||
|  | @ -65,6 +67,9 @@ class _Item { | |||
|         Box bb; bool valid; | ||||
|         BBCache(): valid(false) {} | ||||
|     } bb_cache_; | ||||
|      | ||||
|     int binid_{BIN_ID_UNSET}, priority_{0}; | ||||
|     bool fixed_{false}; | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|  | @ -121,8 +126,16 @@ public: | |||
| 
 | ||||
|     inline _Item(TContour<RawShape>&& contour, | ||||
|                  THolesContainer<RawShape>&& holes): | ||||
|         sh_(sl::create<RawShape>(std::move(contour), | ||||
|                                         std::move(holes))) {} | ||||
|         sh_(sl::create<RawShape>(std::move(contour), 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 | ||||
|  | @ -200,7 +213,7 @@ public: | |||
|         double ret ; | ||||
|         if(area_cache_valid_) ret = area_cache_; | ||||
|         else { | ||||
|             ret = sl::area(offsettedShape()); | ||||
|             ret = sl::area(infaltedShape()); | ||||
|             area_cache_ = ret; | ||||
|             area_cache_valid_ = true; | ||||
|         } | ||||
|  | @ -271,17 +284,21 @@ public: | |||
|     { | ||||
|         rotation(rotation() + rads); | ||||
|     } | ||||
| 
 | ||||
|     inline void addOffset(Coord distance) BP2D_NOEXCEPT | ||||
|      | ||||
|     inline void inflation(Coord distance) BP2D_NOEXCEPT | ||||
|     { | ||||
|         offset_distance_ = distance; | ||||
|         has_offset_ = true; | ||||
|         inflation_ = distance; | ||||
|         has_inflation_ = true; | ||||
|         invalidateCache(); | ||||
|     } | ||||
| 
 | ||||
|     inline void removeOffset() BP2D_NOEXCEPT { | ||||
|         has_offset_ = false; | ||||
|         invalidateCache(); | ||||
|      | ||||
|     inline Coord inflation() const BP2D_NOEXCEPT { | ||||
|         return inflation_; | ||||
|     } | ||||
|      | ||||
|     inline void inflate(Coord distance) BP2D_NOEXCEPT | ||||
|     { | ||||
|         inflation(inflation() + distance); | ||||
|     } | ||||
| 
 | ||||
|     inline Radians rotation() const BP2D_NOEXCEPT | ||||
|  | @ -315,7 +332,7 @@ public: | |||
|     { | ||||
|         if(tr_cache_valid_) return tr_cache_; | ||||
| 
 | ||||
|         RawShape cpy = offsettedShape(); | ||||
|         RawShape cpy = infaltedShape(); | ||||
|         if(has_rotation_) sl::rotate(cpy, rotation_); | ||||
|         if(has_translation_) sl::translate(cpy, translation_); | ||||
|         tr_cache_ = cpy; tr_cache_valid_ = true; | ||||
|  | @ -336,17 +353,17 @@ public: | |||
| 
 | ||||
|     inline void resetTransformation() BP2D_NOEXCEPT | ||||
|     { | ||||
|         has_translation_ = false; has_rotation_ = false; has_offset_ = false; | ||||
|         has_translation_ = false; has_rotation_ = false; has_inflation_ = false; | ||||
|         invalidateCache(); | ||||
|     } | ||||
| 
 | ||||
|     inline Box boundingBox() const { | ||||
|         if(!bb_cache_.valid) { | ||||
|             if(!has_rotation_) | ||||
|                 bb_cache_.bb = sl::boundingBox(offsettedShape()); | ||||
|                 bb_cache_.bb = sl::boundingBox(infaltedShape()); | ||||
|             else { | ||||
|                 // TODO make sure this works
 | ||||
|                 auto rotsh = offsettedShape(); | ||||
|                 auto rotsh = infaltedShape(); | ||||
|                 sl::rotate(rotsh, rotation_); | ||||
|                 bb_cache_.bb = sl::boundingBox(rotsh); | ||||
|             } | ||||
|  | @ -395,14 +412,14 @@ public: | |||
| 
 | ||||
| private: | ||||
| 
 | ||||
|     inline const RawShape& offsettedShape() const { | ||||
|         if(has_offset_ ) { | ||||
|             if(offset_cache_valid_) return offset_cache_; | ||||
|     inline const RawShape& infaltedShape() const { | ||||
|         if(has_inflation_ ) { | ||||
|             if(inflate_cache_valid_) return inflate_cache_; | ||||
| 
 | ||||
|             offset_cache_ = sh_; | ||||
|             sl::offset(offset_cache_, offset_distance_); | ||||
|             offset_cache_valid_ = true; | ||||
|             return offset_cache_; | ||||
|             inflate_cache_ = sh_; | ||||
|             sl::offset(inflate_cache_, inflation_); | ||||
|             inflate_cache_valid_ = true; | ||||
|             return inflate_cache_; | ||||
|         } | ||||
|         return sh_; | ||||
|     } | ||||
|  | @ -412,7 +429,7 @@ private: | |||
|         tr_cache_valid_ = false; | ||||
|         lmb_valid_ = false; rmt_valid_ = false; | ||||
|         area_cache_valid_ = false; | ||||
|         offset_cache_valid_ = false; | ||||
|         inflate_cache_valid_ = false; | ||||
|         bb_cache_.valid = false; | ||||
|         convexity_ = Convexity::UNCHECKED; | ||||
|     } | ||||
|  | @ -492,24 +509,6 @@ template<class RawShape> using _ItemGroup = std::vector<_ItemRef<RawShape>>; | |||
| template<class 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> | ||||
| struct ConstItemRange { | ||||
|     Iterator from; | ||||
|  | @ -738,54 +737,45 @@ public: | |||
|         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(); } | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * The Arranger is the front-end class for the libnest2d library. It takes the | ||||
|  * input items and outputs the items with the proper transformations to be | ||||
|  * inside the provided bin. | ||||
|  * The _Nester is the front-end class for the libnest2d library. It takes the | ||||
|  * input items and changes their transformations to be inside the provided bin. | ||||
|  */ | ||||
| template<class PlacementStrategy, class SelectionStrategy > | ||||
| class Nester { | ||||
| class _Nester { | ||||
|     using TSel = SelectionStrategyLike<SelectionStrategy>; | ||||
|     TSel selector_; | ||||
| public: | ||||
|     using Item = typename PlacementStrategy::Item; | ||||
|     using ShapeType = typename Item::ShapeType; | ||||
|     using ItemRef = std::reference_wrapper<Item>; | ||||
|     using TPlacer = PlacementStrategyLike<PlacementStrategy>; | ||||
|     using BinType = typename TPlacer::BinType; | ||||
|     using PlacementConfig = typename TPlacer::Config; | ||||
|     using SelectionConfig = typename TSel::Config; | ||||
| 
 | ||||
|     using Unit = TCoord<TPoint<typename Item::ShapeType>>; | ||||
| 
 | ||||
|     using IndexedPackGroup = _IndexedPackGroup<typename Item::ShapeType>; | ||||
|     using Coord = TCoord<TPoint<typename Item::ShapeType>>; | ||||
|     using PackGroup = _PackGroup<typename Item::ShapeType>; | ||||
|     using ResultType = PackGroup; | ||||
|     using ResultTypeIndexed = IndexedPackGroup; | ||||
| 
 | ||||
| private: | ||||
|     BinType bin_; | ||||
|     PlacementConfig pconfig_; | ||||
|     Unit min_obj_distance_; | ||||
|     Coord min_obj_distance_; | ||||
| 
 | ||||
|     using SItem =  typename SelectionStrategy::Item; | ||||
|     using TPItem = remove_cvref_t<Item>; | ||||
|     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: | ||||
| 
 | ||||
|  | @ -798,10 +788,8 @@ public: | |||
|     template<class TBinType = BinType, | ||||
|              class PConf = PlacementConfig, | ||||
|              class SConf = SelectionConfig> | ||||
|     Nester( TBinType&& bin, | ||||
|               Unit min_obj_distance = 0, | ||||
|               const PConf& pconfig = PConf(), | ||||
|               const SConf& sconfig = SConf()): | ||||
|     _Nester(TBinType&& bin, Coord min_obj_distance = 0, | ||||
|             const PConf& pconfig = PConf(), const SConf& sconfig = SConf()): | ||||
|         bin_(std::forward<TBinType>(bin)), | ||||
|         pconfig_(pconfig), | ||||
|         min_obj_distance_(min_obj_distance) | ||||
|  | @ -814,182 +802,59 @@ public: | |||
| 
 | ||||
|     void configure(const PlacementConfig& pconf) { pconfig_ = pconf; } | ||||
|     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; | ||||
|         selector_.configure(sconf); | ||||
|     } | ||||
|     void configure(const SelectionConfig& sconf, const PlacementConfig& pconf) { | ||||
|     void configure(const SelectionConfig& sconf, const PlacementConfig& pconf) | ||||
|     { | ||||
|         pconfig_ = pconf; | ||||
|         selector_.configure(sconf); | ||||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|      * \brief Arrange an input sequence and return a PackGroup object with | ||||
|      * the packed groups corresponding to the bins. | ||||
|      * \brief Arrange an input sequence of _Item-s. | ||||
|      * | ||||
|      * 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 selection algorithm. | ||||
|      */ | ||||
|     template<class TIterator> | ||||
|     inline PackGroup execute(TIterator from, TIterator to) | ||||
|     template<class It> | ||||
|     inline ItemIteratorOnly<It, void> execute(It from, It to) | ||||
|     { | ||||
|         return _execute(from, to); | ||||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|      * A version of the arrange method returning an IndexedPackGroup with | ||||
|      * 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.
 | ||||
|     template<class TIterator> | ||||
|     inline PackGroup operator() (TIterator from, TIterator 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>( | ||||
|             from, to, bin_, pconfig_); | ||||
|          | ||||
|         if(min_obj_distance_ > 0) std::for_each(from, to, [infl](Item& item) { | ||||
|             item.inflate(-infl); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /// Set a progress indicator function object for the selector.
 | ||||
|     inline Nester& progressIndicator(ProgressFunction func) | ||||
|     inline _Nester& progressIndicator(ProgressFunction func) | ||||
|     { | ||||
|         selector_.progressIndicator(func); return *this; | ||||
|     } | ||||
| 
 | ||||
|     /// 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 | ||||
|     { | ||||
|         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); | ||||
|     } | ||||
| 
 | ||||
|     inline Unit availableSpaceLeft(const Item& item) { | ||||
|     inline Coord availableSpaceLeft(const Item& item) { | ||||
|         return availableSpace(item, Dir::LEFT); | ||||
|     } | ||||
| 
 | ||||
|     inline Unit availableSpaceDown(const Item& item) { | ||||
|     inline Coord availableSpaceDown(const Item& item) { | ||||
|         return availableSpace(item, Dir::DOWN); | ||||
|     } | ||||
| 
 | ||||
|  | @ -83,7 +83,7 @@ protected: | |||
|         // Get initial position for item in the top right corner
 | ||||
|         setInitialPosition(item); | ||||
| 
 | ||||
|         Unit d = availableSpaceDown(item); | ||||
|         Coord d = availableSpaceDown(item); | ||||
|         auto eps = config_.epsilon; | ||||
|         bool can_move = d > eps; | ||||
|         bool can_be_packed = can_move; | ||||
|  | @ -179,7 +179,7 @@ protected: | |||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     Unit availableSpace(const Item& _item, const Dir dir) { | ||||
|     Coord availableSpace(const Item& _item, const Dir dir) { | ||||
| 
 | ||||
|         Item item (_item.transformedShape()); | ||||
| 
 | ||||
|  | @ -223,7 +223,7 @@ protected: | |||
|                                                    cmp); | ||||
| 
 | ||||
|         // 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
 | ||||
|         // in the way for the nearest intersection
 | ||||
|  |  | |||
|  | @ -581,8 +581,12 @@ public: | |||
| 
 | ||||
|     static inline double overfit(const Box& bb, const Box& bin) | ||||
|     { | ||||
|         auto wdiff = double(bb.width() - bin.width()); | ||||
|         auto hdiff = double(bb.height() - bin.height()); | ||||
|         auto Bw = bin.width(); | ||||
|         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; | ||||
|         if(wdiff > 0) diff += wdiff; | ||||
|         if(hdiff > 0) diff += hdiff; | ||||
|  | @ -801,7 +805,6 @@ private: | |||
|             // optimize
 | ||||
|             config_.object_function = prev_func; | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     struct Optimum { | ||||
|  | @ -816,29 +819,14 @@ private: | |||
| 
 | ||||
|     class Optimizer: public opt::TOptimizer<opt::Method::L_SUBPLEX> { | ||||
|     public: | ||||
|         Optimizer() { | ||||
|         Optimizer(float accuracy = 1.f) { | ||||
|             opt::StopCriteria stopcr; | ||||
|             stopcr.max_iterations = 200; | ||||
|             stopcr.max_iterations = unsigned(std::floor(1000 * accuracy)); | ||||
|             stopcr.relative_score_difference = 1e-20; | ||||
|             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>; | ||||
| 
 | ||||
|     template<class Range = ConstItemRange<typename Base::DefaultIter>> | ||||
|  | @ -935,7 +923,7 @@ private: | |||
|                     _objfunc = [norm, binbb, pbb, ins_check](const Item& item) | ||||
|                     { | ||||
|                         auto ibb = item.boundingBox(); | ||||
|                         auto fullbb = boundingBox(pbb, ibb); | ||||
|                         auto fullbb = sl::boundingBox(pbb, ibb); | ||||
| 
 | ||||
|                         double score = pl::distance(ibb.center(), | ||||
|                                                     binbb.center()); | ||||
|  | @ -1005,14 +993,15 @@ private: | |||
| 
 | ||||
|                     auto& rofn = rawobjfunc; | ||||
|                     auto& nfpoint = getNfpPoint; | ||||
|                     float accuracy = config_.accuracy; | ||||
| 
 | ||||
|                     __parallel::enumerate( | ||||
|                                 cache.corners().begin(), | ||||
|                                 cache.corners().end(), | ||||
|                                 [&results, &item, &rofn, &nfpoint, ch] | ||||
|                                 [&results, &item, &rofn, &nfpoint, ch, accuracy] | ||||
|                                 (double pos, size_t n) | ||||
|                     { | ||||
|                         Optimizer solver; | ||||
|                         Optimizer solver(accuracy); | ||||
| 
 | ||||
|                         Item itemcpy = item; | ||||
|                         auto contour_ofn = [&rofn, &nfpoint, ch, &itemcpy] | ||||
|  | @ -1059,10 +1048,10 @@ private: | |||
|                         __parallel::enumerate(cache.corners(hidx).begin(), | ||||
|                                       cache.corners(hidx).end(), | ||||
|                                       [&results, &item, &nfpoint, | ||||
|                                        &rofn, ch, hidx] | ||||
|                                        &rofn, ch, hidx, accuracy] | ||||
|                                       (double pos, size_t n) | ||||
|                         { | ||||
|                             Optimizer solver; | ||||
|                             Optimizer solver(accuracy); | ||||
| 
 | ||||
|                             Item itmcpy = item; | ||||
|                             auto hole_ofn = | ||||
|  |  | |||
|  | @ -18,7 +18,6 @@ public: | |||
|     using Segment = _Segment<Vertex>; | ||||
|     using BinType = TBin; | ||||
|     using Coord = TCoord<Vertex>; | ||||
|     using Unit = Coord; | ||||
|     using Config = Cfg; | ||||
|     using ItemGroup = _ItemGroup<RawShape>; | ||||
|     using DefaultIter = typename ItemGroup::const_iterator; | ||||
|  | @ -131,7 +130,6 @@ using typename Base::Vertex;      \ | |||
| using typename Base::Segment;     \ | ||||
| using typename Base::PackResult;  \ | ||||
| using typename Base::Coord;       \ | ||||
| using typename Base::Unit;        \ | ||||
| private: | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -711,7 +711,12 @@ public: | |||
|             addBin(); | ||||
|             packjob(placers[idx], remaining, idx); idx++; | ||||
|         } | ||||
| 
 | ||||
|          | ||||
|         int binid = 0; | ||||
|         for(auto &bin : packed_bins_) { | ||||
|             for(Item& itm : bin) itm.binId(binid); | ||||
|             binid++; | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -39,6 +39,20 @@ public: | |||
| 
 | ||||
|         std::vector<Placer> placers; | ||||
|         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
 | ||||
|         // placers as there are elements in packed bins and preload each item
 | ||||
|  | @ -48,11 +62,10 @@ public: | |||
|             placers.back().configure(pconfig); | ||||
|             placers.back().preload(ig); | ||||
|         } | ||||
| 
 | ||||
|         std::copy(first, last, std::back_inserter(store_)); | ||||
| 
 | ||||
|          | ||||
|         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); | ||||
|  | @ -76,7 +89,6 @@ public: | |||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         auto it = store_.begin(); | ||||
| 
 | ||||
|         while(it != store_.end() && !cancelled()) { | ||||
|  | @ -84,8 +96,10 @@ public: | |||
|             size_t j = 0; | ||||
|             while(!was_packed && !cancelled()) { | ||||
|                 for(; j < placers.size() && !was_packed && !cancelled(); j++) { | ||||
|                     if((was_packed = placers[j].pack(*it, rem(it, store_) ))) | ||||
|                             makeProgress(placers[j], j); | ||||
|                     if((was_packed = placers[j].pack(*it, rem(it, store_) ))) { | ||||
|                         it->get().binId(int(j)); | ||||
|                         makeProgress(placers[j], j); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if(!was_packed) { | ||||
|  |  | |||
|  | @ -22,8 +22,6 @@ public: | |||
| 
 | ||||
|     inline void stopCondition(StopCondition cond) { stopcond_ = cond; } | ||||
| 
 | ||||
|     inline void preload(const PackGroup& pckgrp) { packed_bins_ = pckgrp; } | ||||
| 
 | ||||
|     inline void clear() { packed_bins_.clear(); } | ||||
| 
 | ||||
| protected: | ||||
|  |  | |||
|  | @ -363,29 +363,43 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) | |||
|         {5, 5}, | ||||
|         {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()); | ||||
|      | ||||
|     ASSERT_EQ(groups.size(), 1u); | ||||
|     ASSERT_EQ(groups[0].size(), rects.size()); | ||||
|     _Nester<BottomLeftPlacer, DJDHeuristic> arrange(bin); | ||||
|      | ||||
|     arrange.execute(rects.begin(), rects.end()); | ||||
| 
 | ||||
|     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:
 | ||||
|      | ||||
|     for(auto result : groups) { | ||||
|         bool valid = true; | ||||
|         for(Item& r1 : result) { | ||||
|             for(Item& r2 : result) { | ||||
|                 if(&r1 != &r2 ) { | ||||
|                     valid = !Item::intersects(r1, r2) || Item::touches(r1, r2); | ||||
|                     valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); | ||||
|                     ASSERT_TRUE(valid); | ||||
|                 } | ||||
|     bool valid = true; | ||||
|     for(Item& r1 : rects) { | ||||
|         for(Item& r2 : rects) { | ||||
|             if(&r1 != &r2 ) { | ||||
|                 valid = !Item::intersects(r1, r2) || Item::touches(r1, r2); | ||||
|                 ASSERT_TRUE(valid); | ||||
|                 valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); | ||||
|                 ASSERT_TRUE(valid); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
| } | ||||
| 
 | ||||
| TEST(GeometryAlgorithms, ArrangeRectanglesLoose) | ||||
|  | @ -415,21 +429,36 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) | |||
|         {5, 5}, | ||||
|         {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; | ||||
|      | ||||
|     Nester<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250), | ||||
|                                                    min_obj_distance); | ||||
|     _Nester<BottomLeftPlacer, DJDHeuristic> arrange(bin, min_obj_distance); | ||||
|      | ||||
|     auto groups = arrange(rects.begin(), rects.end()); | ||||
|     arrange.execute(rects.begin(), rects.end()); | ||||
|      | ||||
|     ASSERT_EQ(groups.size(), 1u); | ||||
|     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(); | ||||
|                                       }); | ||||
|      | ||||
|     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:
 | ||||
|     auto result = groups[0]; | ||||
|     bool valid = true; | ||||
|     for(Item& r1 : result) { | ||||
|         for(Item& r2 : result) { | ||||
|     for(Item& r1 : rects) { | ||||
|         for(Item& r2 : rects) { | ||||
|             if(&r1 != &r2 ) { | ||||
|                 valid = !Item::intersects(r1, r2); | ||||
|                 valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); | ||||
|  | @ -541,27 +570,24 @@ TEST(GeometryAlgorithms, convexHull) { | |||
| 
 | ||||
| TEST(GeometryAlgorithms, NestTest) { | ||||
|     std::vector<Item> input = prusaParts(); | ||||
| 
 | ||||
|     libnest2d::nest(input, Box(250000000, 210000000), [](unsigned cnt) { | ||||
|         std::cout << "parts left: " << cnt << std::endl; | ||||
|     }); | ||||
| 
 | ||||
|     auto max_binid_it = std::max_element(input.begin(), input.end(), | ||||
|                                          [](const Item &i1, const Item &i2) { | ||||
|                                              return i1.binId() < i2.binId(); | ||||
|                                          }); | ||||
| 
 | ||||
|     size_t bins = max_binid_it == input.end() ? 0 : max_binid_it->binId() + 1; | ||||
|      | ||||
|     PackGroup result = libnest2d::nest(input, | ||||
|                                        Box(250000000, 210000000), | ||||
|                                        [](unsigned cnt) { | ||||
|                                            std::cout | ||||
|                                                << "parts left: " << cnt | ||||
|                                                << std::endl; | ||||
|                                        }); | ||||
|      | ||||
|     ASSERT_LE(result.size(), 2); | ||||
|      | ||||
|     size_t partsum = std::accumulate(result.begin(), | ||||
|                                      result.end(), | ||||
|                                      size_t(0), | ||||
|                                      [](size_t s, | ||||
|                                         const decltype( | ||||
|                                             result)::value_type &bin) { | ||||
|                                          return s += bin.size(); | ||||
|                                      }); | ||||
|      | ||||
|     ASSERT_EQ(input.size(), partsum); | ||||
|     ASSERT_EQ(bins, 2u); | ||||
| 
 | ||||
|     ASSERT_TRUE( | ||||
|         std::all_of(input.begin(), input.end(), [](const Item &itm) { | ||||
|             return itm.binId() != BIN_ID_UNSET; | ||||
|         })); | ||||
| } | ||||
| 
 | ||||
| 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 | ||||
|     Model.cpp | ||||
|     Model.hpp | ||||
|     ModelArrange.hpp | ||||
|     ModelArrange.cpp | ||||
|     Arrange.hpp | ||||
|     Arrange.cpp | ||||
|     MotionPlanner.cpp | ||||
|     MotionPlanner.hpp | ||||
|     MultiPoint.cpp | ||||
|  |  | |||
|  | @ -126,6 +126,7 @@ void GCodeAnalyzer::reset() | |||
|     _set_start_position(DEFAULT_START_POSITION); | ||||
|     _set_start_extrusion(DEFAULT_START_EXTRUSION); | ||||
|     _reset_axes_position(); | ||||
|     _reset_cached_position(); | ||||
| 
 | ||||
|     m_moves_map.clear(); | ||||
|     m_extruder_offsets.clear(); | ||||
|  | @ -262,6 +263,16 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi | |||
|                         _processM108orM135(line); | ||||
|                         break; | ||||
|                     } | ||||
|                 case 401: // Repetier: Store x, y and z position
 | ||||
|                     { | ||||
|                         _processM401(line); | ||||
|                         break; | ||||
|                     } | ||||
|                 case 402: // Repetier: Go to stored position
 | ||||
|                     { | ||||
|                         _processM402(line); | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 break; | ||||
|  | @ -433,12 +444,6 @@ void GCodeAnalyzer::_processM83(const GCodeReader::GCodeLine& line) | |||
|     _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) | ||||
| { | ||||
|     // These M-codes are used by MakerWare and Sailfish to change active tool.
 | ||||
|  | @ -447,7 +452,7 @@ void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) | |||
| 
 | ||||
|     size_t code = ::atoi(&(line.cmd()[1])); | ||||
|     if ((code == 108 && m_gcode_flavor == gcfSailfish) | ||||
|      || (code == 135 && m_gcode_flavor == gcfMakerWare)) { | ||||
|         || (code == 135 && m_gcode_flavor == gcfMakerWare)) { | ||||
| 
 | ||||
|         std::string cmd = line.raw(); | ||||
|         size_t T_pos = cmd.find("T"); | ||||
|  | @ -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) | ||||
| { | ||||
|     if (cmd.length() > 1) | ||||
|  | @ -668,6 +733,17 @@ const Vec3d& GCodeAnalyzer::_get_start_position() const | |||
|     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) | ||||
| { | ||||
|     m_state.start_extrusion = extrusion; | ||||
|  |  | |||
|  | @ -96,6 +96,7 @@ private: | |||
|         EPositioningType e_local_positioning_type; | ||||
|         Metadata data; | ||||
|         Vec3d start_position = Vec3d::Zero(); | ||||
|         float cached_position[5]; | ||||
|         float start_extrusion; | ||||
|         float position[Num_Axis]; | ||||
|         unsigned int cur_cp_color_id = 0; | ||||
|  | @ -170,6 +171,12 @@ private: | |||
|     // Set tool (MakerWare and Sailfish flavor)
 | ||||
|     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
 | ||||
|     void _processM600(const GCodeReader::GCodeLine& line); | ||||
| 
 | ||||
|  | @ -232,6 +239,11 @@ private: | |||
|     void _set_start_position(const Vec3d& position); | ||||
|     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); | ||||
|     float _get_start_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
 | ||||
| template<class T> | ||||
| using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, T>; | ||||
| template<class T, class O = T> | ||||
| using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, O>; | ||||
| 
 | ||||
| template<class T> | ||||
| using ScaledCoordOnly = enable_if_t<is_scaled_coord<T>::value, T>; | ||||
| template<class T, class O = T> | ||||
| using ScaledCoordOnly = enable_if_t<is_scaled_coord<T>::value, O>; | ||||
| 
 | ||||
| template<class T> | ||||
| using ArithmeticOnly = enable_if_t<std::is_arithmetic<T>::value, T>; | ||||
| 
 | ||||
| // 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>; | ||||
| template<class T, class O = T> | ||||
| using ArithmeticOnly = enable_if_t<std::is_arithmetic<T>::value, O>; | ||||
| 
 | ||||
| // Semantics are the following:
 | ||||
| // Upscaling (scaled()): only from floating point types (or Vec) to either
 | ||||
| //                       floating point or integer 'scaled coord' coordinates.
 | ||||
| // Downscaling (unscaled()): from arithmetic types (or Vec) to either
 | ||||
| //                           floating point only
 | ||||
| // Downscaling (unscaled()): from arithmetic (or Vec) to floating point only
 | ||||
| 
 | ||||
| // Conversion definition from unscaled to floating point scaled
 | ||||
| template<class Tout, | ||||
|          class Tin, | ||||
|          class = FloatingOnly<Tin>, | ||||
|          class = FloatingOnly<Tout>> | ||||
| inline SLIC3R_CONSTEXPR Tout scaled(const Tin &v) SLIC3R_NOEXCEPT | ||||
|          class = FloatingOnly<Tin>> | ||||
| inline constexpr FloatingOnly<Tout> scaled(const Tin &v) 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'.
 | ||||
| // TODO: is the rounding necessary ? Here it is to show that it can be different
 | ||||
| // but it does not have to be. Using std::round means loosing noexcept and
 | ||||
| // constexpr modifiers
 | ||||
| // TODO: is the rounding necessary? Here it is commented  out to show that
 | ||||
| // it can be different for integers but it does not have to be. Using
 | ||||
| // std::round means loosing noexcept and constexpr modifiers
 | ||||
| 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>(v / static_cast<Tin>(SCALING_FACTOR)); | ||||
|     return Tout(v / Tin(SCALING_FACTOR)); | ||||
| } | ||||
| 
 | ||||
| // Conversion for Eigen vectors (N dimensional points)
 | ||||
| template<class Tout = coord_t, class Tin, int N, class = FloatingOnly<Tin>> | ||||
| inline EigenVec<ArithmeticOnly<Tout>, N> scaled(const EigenVec<Tin, N> &v) | ||||
| template<class Tout = coord_t, | ||||
|          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>(); | ||||
| } | ||||
|  | @ -366,9 +365,9 @@ template<class Tout = double, | |||
|          class Tin, | ||||
|          class = ArithmeticOnly<Tin>, | ||||
|          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
 | ||||
|  | @ -377,9 +376,10 @@ template<class Tout = double, | |||
|          class Tin, | ||||
|          int N, | ||||
|          class = ArithmeticOnly<Tin>, | ||||
|          class = FloatingOnly<Tout>> | ||||
| inline SLIC3R_CONSTEXPR EigenVec<Tout, N> unscaled( | ||||
|     const EigenVec<Tin, N> &v) SLIC3R_NOEXCEPT | ||||
|          class = FloatingOnly<Tout>, | ||||
|          int...EigenArgs> | ||||
| inline constexpr Eigen::Matrix<Tout, N, EigenArgs...> | ||||
| unscaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v) noexcept | ||||
| { | ||||
|     return v.template cast<Tout>() * SCALING_FACTOR; | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| #include "Model.hpp" | ||||
| #include "Geometry.hpp" | ||||
| #include "MTUtils.hpp" | ||||
| 
 | ||||
| #include "Format/AMF.hpp" | ||||
| #include "Format/OBJ.hpp" | ||||
|  | @ -369,34 +370,44 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb | |||
| /*  arrange objects preserving their instance count
 | ||||
|     but altering their instance positions */ | ||||
| bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) | ||||
| { | ||||
|     // get the (transformed) size of each instance so that we take
 | ||||
|     // into account their different transformations when packing
 | ||||
|     Pointfs instance_sizes; | ||||
|     Pointfs instance_centers; | ||||
|     for (const ModelObject *o : this->objects) | ||||
|         for (size_t i = 0; i < o->instances.size(); ++ i) { | ||||
|             // an accurate snug bounding box around the transformed mesh.
 | ||||
|             BoundingBoxf3 bbox(o->instance_bounding_box(i, true)); | ||||
|             instance_sizes.emplace_back(to_2d(bbox.size())); | ||||
|             instance_centers.emplace_back(to_2d(bbox.center())); | ||||
| {     | ||||
|     size_t count = 0; | ||||
|     for (auto obj : objects) count += obj->instances.size(); | ||||
|      | ||||
|     arrangement::ArrangePolygons input; | ||||
|     ModelInstancePtrs instances; | ||||
|     input.reserve(count); | ||||
|     instances.reserve(count); | ||||
|     for (ModelObject *mo : objects) | ||||
|         for (ModelInstance *minst : mo->instances) { | ||||
|             input.emplace_back(minst->get_arrange_polygon()); | ||||
|             instances.emplace_back(minst); | ||||
|         } | ||||
| 
 | ||||
|     Pointfs positions; | ||||
|     if (! _arrange(instance_sizes, dist, bb, positions)) | ||||
|         return false; | ||||
| 
 | ||||
|     size_t idx = 0; | ||||
|     for (ModelObject *o : this->objects) { | ||||
|         for (ModelInstance *i : o->instances) { | ||||
|             Vec2d offset_xy = positions[idx] - instance_centers[idx]; | ||||
|             i->set_offset(Vec3d(offset_xy(0), offset_xy(1), i->get_offset(Z))); | ||||
|             ++idx; | ||||
|         } | ||||
|         o->invalidate_bounding_box(); | ||||
|      | ||||
|     arrangement::BedShapeHint bedhint; | ||||
|     coord_t bedwidth = 0; | ||||
|      | ||||
|     if (bb) { | ||||
|         bedwidth = scaled(bb->size().x()); | ||||
|         bedhint = arrangement::BedShapeHint( | ||||
|             BoundingBox(scaled(bb->min), scaled(bb->max))); | ||||
|     } | ||||
| 
 | ||||
|     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.
 | ||||
|  | @ -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
 | ||||
| } | ||||
| 
 | ||||
| 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
 | ||||
| // 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) | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ | |||
| #include "Slicing.hpp" | ||||
| #include "SLA/SLACommon.hpp" | ||||
| #include "TriangleMesh.hpp" | ||||
| #include "Arrange.hpp" | ||||
| 
 | ||||
| #include <map> | ||||
| #include <memory> | ||||
|  | @ -602,7 +603,7 @@ public: | |||
| 
 | ||||
|     const Vec3d& get_offset() const { return m_transformation.get_offset(); } | ||||
|     double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } | ||||
| 
 | ||||
|      | ||||
|     void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } | ||||
|     void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } | ||||
| 
 | ||||
|  | @ -621,7 +622,7 @@ public: | |||
|     const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } | ||||
|     double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } | ||||
| 	bool is_left_handed() const { return m_transformation.is_left_handed(); } | ||||
| 
 | ||||
|      | ||||
|     void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } | ||||
|     void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } | ||||
| 
 | ||||
|  | @ -639,6 +640,18 @@ public: | |||
|     const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } | ||||
| 
 | ||||
|     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: | ||||
|     friend class Print; | ||||
|  | @ -654,10 +667,10 @@ private: | |||
|     ModelObject* object; | ||||
| 
 | ||||
|     // 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.
 | ||||
|     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; | ||||
|     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) { | ||||
|         if (vol->is_wipe_tower) { | ||||
|             wti.is_wipe_tower = true; | ||||
|             wti.pos = Vec2d(m_config->opt_float("wipe_tower_x"), | ||||
|             wti.m_pos = Vec2d(m_config->opt_float("wipe_tower_x"), | ||||
|                             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(); | ||||
|             wti.bb_size = Vec2d(bb.size()(0), bb.size()(1)); | ||||
|             wti.m_bb_size = Vec2d(bb.size().x(), bb.size().y()); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     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) | ||||
| { | ||||
|     float z0 = 0.0f; | ||||
|  | @ -3600,7 +3588,7 @@ bool GLCanvas3D::_init_main_toolbar() | |||
| 
 | ||||
|     item.name = "arrange"; | ||||
|     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.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_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(); | ||||
| } | ||||
| 
 | ||||
| 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 Slic3r
 | ||||
|  |  | |||
|  | @ -4,7 +4,6 @@ | |||
| #include <stddef.h> | ||||
| #include <memory> | ||||
| 
 | ||||
| #include "libslic3r/ModelArrange.hpp" | ||||
| #include "3DScene.hpp" | ||||
| #include "GLToolbar.hpp" | ||||
| #include "Event.hpp" | ||||
|  | @ -626,9 +625,28 @@ public: | |||
| 
 | ||||
|     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(); } | ||||
| 
 | ||||
|     arr::WipeTowerInfo get_wipe_tower_info() const; | ||||
|     void arrange_wipe_tower(const arr::WipeTowerInfo& wti) const; | ||||
|      | ||||
|     class WipeTowerInfo { | ||||
|     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.
 | ||||
|     Linef3 mouse_ray(const Point& mouse_pos); | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| 
 | ||||
| #include <cstddef> | ||||
| #include <algorithm> | ||||
| #include <numeric> | ||||
| #include <vector> | ||||
| #include <string> | ||||
| #include <regex> | ||||
|  | @ -31,7 +32,6 @@ | |||
| #include "libslic3r/Format/3mf.hpp" | ||||
| #include "libslic3r/GCode/PreviewData.hpp" | ||||
| #include "libslic3r/Model.hpp" | ||||
| #include "libslic3r/ModelArrange.hpp" | ||||
| #include "libslic3r/Polygon.hpp" | ||||
| #include "libslic3r/Print.hpp" | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
|  | @ -1355,6 +1355,44 @@ struct Plater::priv | |||
|     BackgroundSlicingProcess    background_process; | ||||
|     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.
 | ||||
|     // 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 
 | ||||
|  | @ -1365,147 +1403,280 @@ struct Plater::priv | |||
|     // 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 displayed.
 | ||||
|     class Job: public wxEvtHandler { | ||||
|         int m_range = 100; | ||||
|     class Job : public wxEvtHandler | ||||
|     { | ||||
|         int               m_range = 100; | ||||
|         std::future<void> m_ftr; | ||||
|         priv *m_plater = nullptr; | ||||
|         std::atomic<bool> m_running {false}, m_canceled {false}; | ||||
|         bool m_finalized = false; | ||||
|          | ||||
|         void run() {  | ||||
|             m_running.store(true); process(); m_running.store(false);  | ||||
|              | ||||
|         priv *            m_plater = nullptr; | ||||
|         std::atomic<bool> m_running{false}, m_canceled{false}; | ||||
|         bool              m_finalized = false; | ||||
| 
 | ||||
|         void run() | ||||
|         { | ||||
|             m_running.store(true); | ||||
|             process(); | ||||
|             m_running.store(false); | ||||
| 
 | ||||
|             // ensure to call the last status to finalize the job
 | ||||
|             update_status(status_range(), ""); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|     protected: | ||||
|          | ||||
|         // status range for a particular job
 | ||||
|         virtual int status_range() const { return 100; } | ||||
|          | ||||
| 
 | ||||
|         // status update, to be used from the work thread (process() method)
 | ||||
|         void update_status(int st, const wxString& msg = "") {  | ||||
|             auto evt = new wxThreadEvent(); evt->SetInt(st); evt->SetString(msg); | ||||
|             wxQueueEvent(this, evt);  | ||||
|         void update_status(int st, const wxString &msg = "") | ||||
|         { | ||||
|             auto evt = new wxThreadEvent(); | ||||
|             evt->SetInt(st); | ||||
|             evt->SetString(msg); | ||||
|             wxQueueEvent(this, evt); | ||||
|         } | ||||
|          | ||||
|         priv& plater() { return *m_plater; } | ||||
|         bool was_canceled() const { return m_canceled.load(); } | ||||
|          | ||||
| 
 | ||||
|         priv &      plater() { return *m_plater; } | ||||
|         const priv &plater() const { return *m_plater; } | ||||
|         bool        was_canceled() const { return m_canceled.load(); } | ||||
| 
 | ||||
|         // Launched just before start(), a job can use it to prepare internals
 | ||||
|         virtual void prepare() {} | ||||
|          | ||||
|         // Launched when the job is finished. It refreshes the 3dscene by def.
 | ||||
|         virtual void finalize() { | ||||
| 
 | ||||
|         // Launched when the job is finished. It refreshes the 3Dscene by def.
 | ||||
|         virtual void finalize() | ||||
|         { | ||||
|             // Do a full refresh of scene tree, including regenerating
 | ||||
|             // all the GLVolumes. FIXME The update function shall just
 | ||||
|             // reload the modified matrices.
 | ||||
|             if(! was_canceled()) | ||||
|                 plater().update(true); | ||||
|             if (!was_canceled()) plater().update(true); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|     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(); | ||||
|                 if(! msg.empty()) plater().statusbar()->set_status_text(msg); | ||||
|                  | ||||
|                 if(m_finalized) return; | ||||
|                  | ||||
|                 if (!msg.empty()) | ||||
|                     plater().statusbar()->set_status_text(msg); | ||||
| 
 | ||||
|                 if (m_finalized) return; | ||||
| 
 | ||||
|                 plater().statusbar()->set_progress(evt.GetInt()); | ||||
|                 if(evt.GetInt() == status_range()) { | ||||
|                      | ||||
|                 if (evt.GetInt() == status_range()) { | ||||
|                     // set back the original range and cancel callback
 | ||||
|                     plater().statusbar()->set_range(m_range); | ||||
|                     plater().statusbar()->set_cancel_callback(); | ||||
|                     wxEndBusyCursor(); | ||||
|                      | ||||
| 
 | ||||
|                     finalize(); | ||||
|                      | ||||
| 
 | ||||
|                     // dont do finalization again for the same process
 | ||||
|                     m_finalized = true; | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|          | ||||
|         // TODO: use this when we all migrated to VS2019
 | ||||
|         // Job(const Job&) = delete;
 | ||||
|         // Job(Job&&) = default;
 | ||||
|         // Job& operator=(const Job&) = delete;
 | ||||
|         // 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()); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         Job(const Job &) = delete; | ||||
|         Job(Job &&)      = default; | ||||
|         Job &operator=(const Job &) = delete; | ||||
|         Job &operator=(Job &&) = default; | ||||
| 
 | ||||
|         virtual void process() = 0; | ||||
|          | ||||
|         void start() { // Start the job. No effect if the job is already running
 | ||||
|             if(! m_running.load()) { | ||||
|                  | ||||
|                 prepare();                 | ||||
|                  | ||||
| 
 | ||||
|         void start() | ||||
|         { // Start the job. No effect if the job is already running
 | ||||
|             if (!m_running.load()) { | ||||
|                 prepare(); | ||||
| 
 | ||||
|                 // Save the current status indicatior range and push the new one
 | ||||
|                 m_range = plater().statusbar()->get_range(); | ||||
|                 plater().statusbar()->set_range(status_range()); | ||||
|                  | ||||
| 
 | ||||
|                 // init cancellation flag and set the cancel callback
 | ||||
|                 m_canceled.store(false); | ||||
|                 plater().statusbar()->set_cancel_callback( [this](){  | ||||
|                     m_canceled.store(true); | ||||
|                 }); | ||||
|                  | ||||
|                 plater().statusbar()->set_cancel_callback( | ||||
|                     [this]() { m_canceled.store(true); }); | ||||
| 
 | ||||
|                 m_finalized = false; | ||||
|                  | ||||
| 
 | ||||
|                 // Changing cursor to busy
 | ||||
|                 wxBeginBusyCursor(); | ||||
|                  | ||||
|                 try {   // Execute the job
 | ||||
| 
 | ||||
|                 try { // Execute the job
 | ||||
|                     m_ftr = std::async(std::launch::async, &Job::run, this); | ||||
|                 } catch(std::exception& ) {  | ||||
|                     update_status(status_range(),  | ||||
|                     _(L("ERROR: not enough resources to execute a new job."))); | ||||
|                 } catch (std::exception &) { | ||||
|                     update_status(status_range(), | ||||
|                                   _(L("ERROR: not enough resources to " | ||||
|                                       "execute a new job."))); | ||||
|                 } | ||||
|                  | ||||
| 
 | ||||
|                 // The state changes will be undone when the process hits the
 | ||||
|                 // last status value, in the status update handler (see ctor)
 | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         // To wait for the running job and join the threads. False is returned
 | ||||
|         // if the timeout has been reached and the job is still running. Call
 | ||||
|         // cancel() before this fn if you want to explicitly end the job.
 | ||||
|         bool join(int timeout_ms = 0) const {  | ||||
|             if(!m_ftr.valid()) return true; | ||||
|              | ||||
|             if(timeout_ms <= 0)  | ||||
| 
 | ||||
|         // To wait for the running job and join the threads. False is
 | ||||
|         // returned if the timeout has been reached and the job is still
 | ||||
|         // running. Call cancel() before this fn if you want to explicitly
 | ||||
|         // end the job.
 | ||||
|         bool join(int timeout_ms = 0) const | ||||
|         { | ||||
|             if (!m_ftr.valid()) return true; | ||||
| 
 | ||||
|             if (timeout_ms <= 0) | ||||
|                 m_ftr.wait(); | ||||
|             else if(m_ftr.wait_for(std::chrono::milliseconds(timeout_ms)) ==  | ||||
|                     std::future_status::timeout)  | ||||
|             else if (m_ftr.wait_for(std::chrono::milliseconds( | ||||
|                          timeout_ms)) == std::future_status::timeout) | ||||
|                 return false; | ||||
|              | ||||
| 
 | ||||
|             return true; | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         bool is_running() const { return m_running.load(); } | ||||
|         void cancel() { m_canceled.store(true); } | ||||
|     }; | ||||
|      | ||||
| 
 | ||||
|     enum class Jobs : size_t { | ||||
|         Arrange, | ||||
|         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
 | ||||
|     // run at a time. Also, the background process will be stopped if a job is
 | ||||
|     // started.
 | ||||
|  | @ -1515,49 +1686,19 @@ struct Plater::priv | |||
|          | ||||
|         priv * m_plater; | ||||
| 
 | ||||
|         class ArrangeJob : public Job | ||||
|         { | ||||
|             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}*/; | ||||
|         ArrangeJob arrange_job{m_plater}; | ||||
|         RotoptimizeJob rotoptimize_job{m_plater}; | ||||
| 
 | ||||
|         // To create a new job, just define a new subclass of Job, implement
 | ||||
|         // the process and the optional prepare() and finalize() methods
 | ||||
|         // Register the instance of the class in the m_jobs container
 | ||||
|         // if it cannot run concurrently with other jobs in this group 
 | ||||
| 
 | ||||
|         std::vector<std::reference_wrapper<Job>> m_jobs/*{arrange_job,
 | ||||
|                                                         rotoptimize_job}*/; | ||||
|         std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job, | ||||
|                                                         rotoptimize_job}; | ||||
| 
 | ||||
|     public: | ||||
|         ExclusiveJobGroup(priv *_plater) | ||||
|             : m_plater(_plater) | ||||
|             , arrange_job(m_plater) | ||||
|             , rotoptimize_job(m_plater) | ||||
|             , m_jobs({arrange_job, rotoptimize_job}) | ||||
|         {} | ||||
|         ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {} | ||||
| 
 | ||||
|         void start(Jobs jid) { | ||||
|             m_plater->background_process.stop(); | ||||
|  | @ -1618,6 +1759,9 @@ struct Plater::priv | |||
|     std::string get_config(const std::string &key) const; | ||||
|     BoundingBoxf 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_model_objects(const ModelObjectPtrs &model_objects); | ||||
|     wxString get_export_file(GUI::FileType file_type); | ||||
|  | @ -2239,9 +2383,9 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode | |||
|     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))); | ||||
| 
 | ||||
|     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); | ||||
| 
 | ||||
|     // it remains to move the wipe tower:
 | ||||
|  | @ -2480,71 +2624,82 @@ void Plater::priv::sla_optimize_rotation() { | |||
|     m_ui_jobs.start(Jobs::Rotoptimize); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { | ||||
|     // TODO: we should decide whether to allow arrange when the search is
 | ||||
|     // running we should probably disable explicit slicing and background
 | ||||
|     // processing
 | ||||
| arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const { | ||||
|      | ||||
|     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")); | ||||
| 
 | ||||
|     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
 | ||||
|     // on printer technology. I guess the following should work but it crashes.
 | ||||
|     double dist = 6; // PrintConfig::min_object_distance(config);
 | ||||
|     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); | ||||
| 
 | ||||
|     const auto *bed_shape_opt = 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))); | ||||
| 
 | ||||
|     update_status(0, arrangestr); | ||||
| 
 | ||||
|     arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); | ||||
| 
 | ||||
|      | ||||
|     coord_t min_d = scaled(dist); | ||||
|     auto count = unsigned(m_selected.size()); | ||||
|     arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); | ||||
|      | ||||
|     try { | ||||
|         arr::BedShapeHint hint; | ||||
| 
 | ||||
|         // TODO: from Sasha from GUI or
 | ||||
|         hint.type = arr::BedShapeType::WHO_KNOWS; | ||||
| 
 | ||||
|         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(); }); | ||||
|         arrangement::arrange(m_selected, m_unselected, min_d, bedshape, | ||||
|                              [this, count](unsigned st) { | ||||
|                                  if (st > | ||||
|                                      0) // will not finalize after last one
 | ||||
|                                      update_status(count - st, arrangestr); | ||||
|                              }, | ||||
|                              [this]() { return was_canceled(); }); | ||||
|     } catch (std::exception & /*e*/) { | ||||
|         GUI::show_error(plater().q, | ||||
|                         L("Could not arrange model objects! " | ||||
|                           "Some geometries may be invalid.")); | ||||
|                         _(L("Could not arrange model objects! " | ||||
|                             "Some geometries may be invalid."))); | ||||
|     } | ||||
| 
 | ||||
|     update_status(count, | ||||
|      | ||||
|     // finalize just here.
 | ||||
|     update_status(int(count), | ||||
|                   was_canceled() ? _(L("Arranging canceled.")) | ||||
|                                  : _(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(); | ||||
|     if (obj_idx < 0) { return; } | ||||
|  | @ -2561,15 +2716,6 @@ void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() | |||
|         }, | ||||
|         [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
 | ||||
|      | ||||
|  | @ -2590,14 +2736,9 @@ void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() | |||
|              | ||||
|             oi->set_rotation(rt); | ||||
|         } | ||||
|      | ||||
|         arr::WipeTowerInfo wti; // useless in SLA context
 | ||||
|         arr::find_new_position(plater().model, | ||||
|                                o->instances, | ||||
|                                coord_t(mindist / SCALING_FACTOR), | ||||
|                                bed, | ||||
|                                wti); | ||||
|      | ||||
|          | ||||
|         plater().find_new_position(o->instances, scaled(mindist)); | ||||
| 
 | ||||
|         // Correct the z offset of the object which was corrupted be
 | ||||
|         // the rotation
 | ||||
|         o->ensure_on_bed(); | ||||
|  | @ -3486,7 +3627,7 @@ void Plater::priv::set_bed_shape(const Pointfs& shape) | |||
| 
 | ||||
| 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 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 YuSanka
						YuSanka