mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	Getting rid of AppController.
This commit is contained in:
		
							parent
							
								
									33eade5300
								
							
						
					
					
						commit
						98a640ea06
					
				
					 13 changed files with 857 additions and 1941 deletions
				
			
		|  | @ -108,6 +108,7 @@ add_library(libslic3r STATIC | ||||||
|     Model.cpp |     Model.cpp | ||||||
|     Model.hpp |     Model.hpp | ||||||
|     ModelArrange.hpp |     ModelArrange.hpp | ||||||
|  |     ModelArrange.cpp | ||||||
|     MotionPlanner.cpp |     MotionPlanner.cpp | ||||||
|     MotionPlanner.hpp |     MotionPlanner.hpp | ||||||
|     MultiPoint.cpp |     MultiPoint.cpp | ||||||
|  |  | ||||||
							
								
								
									
										763
									
								
								src/libslic3r/ModelArrange.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										763
									
								
								src/libslic3r/ModelArrange.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,763 @@ | ||||||
|  | #include "ModelArrange.hpp" | ||||||
|  | #include "Model.hpp" | ||||||
|  | #include "SVG.hpp" | ||||||
|  | 
 | ||||||
|  | #include <libnest2d.h> | ||||||
|  | 
 | ||||||
|  | #include <numeric> | ||||||
|  | #include <ClipperUtils.hpp> | ||||||
|  | 
 | ||||||
|  | #include <boost/geometry/index/rtree.hpp> | ||||||
|  | 
 | ||||||
|  | namespace Slic3r { | ||||||
|  | 
 | ||||||
|  | namespace arr { | ||||||
|  | 
 | ||||||
|  | using namespace libnest2d; | ||||||
|  | 
 | ||||||
|  | std::string toString(const Model& model, bool holes = true) { | ||||||
|  |     std::stringstream  ss; | ||||||
|  | 
 | ||||||
|  |     ss << "{\n"; | ||||||
|  | 
 | ||||||
|  |     for(auto objptr : model.objects) { | ||||||
|  |         if(!objptr) continue; | ||||||
|  | 
 | ||||||
|  |         auto rmesh = objptr->raw_mesh(); | ||||||
|  | 
 | ||||||
|  |         for(auto objinst : objptr->instances) { | ||||||
|  |             if(!objinst) continue; | ||||||
|  | 
 | ||||||
|  |             Slic3r::TriangleMesh tmpmesh = rmesh; | ||||||
|  |             // CHECK_ME -> Is the following correct ?
 | ||||||
|  |             tmpmesh.scale(objinst->get_scaling_factor()); | ||||||
|  |             objinst->transform_mesh(&tmpmesh); | ||||||
|  |             ExPolygons expolys = tmpmesh.horizontal_projection(); | ||||||
|  |             for(auto& expoly_complex : expolys) { | ||||||
|  | 
 | ||||||
|  |                 auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR); | ||||||
|  |                 if(tmp.empty()) continue; | ||||||
|  |                 auto expoly = tmp.front(); | ||||||
|  |                 expoly.contour.make_clockwise(); | ||||||
|  |                 for(auto& h : expoly.holes) h.make_counter_clockwise(); | ||||||
|  | 
 | ||||||
|  |                 ss << "\t{\n"; | ||||||
|  |                 ss << "\t\t{\n"; | ||||||
|  | 
 | ||||||
|  |                 for(auto v : expoly.contour.points) ss << "\t\t\t{" | ||||||
|  |                                                     << v(0) << ", " | ||||||
|  |                                                     << v(1) << "},\n"; | ||||||
|  |                 { | ||||||
|  |                     auto v = expoly.contour.points.front(); | ||||||
|  |                     ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n"; | ||||||
|  |                 } | ||||||
|  |                 ss << "\t\t},\n"; | ||||||
|  | 
 | ||||||
|  |                 // Holes:
 | ||||||
|  |                 ss << "\t\t{\n"; | ||||||
|  |                 if(holes) for(auto h : expoly.holes) { | ||||||
|  |                     ss << "\t\t\t{\n"; | ||||||
|  |                     for(auto v : h.points) ss << "\t\t\t\t{" | ||||||
|  |                                            << v(0) << ", " | ||||||
|  |                                            << v(1) << "},\n"; | ||||||
|  |                     { | ||||||
|  |                         auto v = h.points.front(); | ||||||
|  |                         ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n"; | ||||||
|  |                     } | ||||||
|  |                     ss << "\t\t\t},\n"; | ||||||
|  |                 } | ||||||
|  |                 ss << "\t\t},\n"; | ||||||
|  | 
 | ||||||
|  |                 ss << "\t},\n"; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     ss << "}\n"; | ||||||
|  | 
 | ||||||
|  |     return ss.str(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void toSVG(SVG& svg, const Model& model) { | ||||||
|  |     for(auto objptr : model.objects) { | ||||||
|  |         if(!objptr) continue; | ||||||
|  | 
 | ||||||
|  |         auto rmesh = objptr->raw_mesh(); | ||||||
|  | 
 | ||||||
|  |         for(auto objinst : objptr->instances) { | ||||||
|  |             if(!objinst) continue; | ||||||
|  | 
 | ||||||
|  |             Slic3r::TriangleMesh tmpmesh = rmesh; | ||||||
|  |             tmpmesh.scale(objinst->get_scaling_factor()); | ||||||
|  |             objinst->transform_mesh(&tmpmesh); | ||||||
|  |             ExPolygons expolys = tmpmesh.horizontal_projection(); | ||||||
|  |             svg.draw(expolys); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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>>; | ||||||
|  | template<class TBin> | ||||||
|  | using TPacker = typename placers::_NofitPolyPlacer<PolygonImpl, TBin>; | ||||||
|  | 
 | ||||||
|  | const double BIG_ITEM_TRESHOLD = 0.02; | ||||||
|  | 
 | ||||||
|  | Box boundingBox(const Box& pilebb, const Box& ibb ) { | ||||||
|  |     auto& pminc = pilebb.minCorner(); | ||||||
|  |     auto& pmaxc = pilebb.maxCorner(); | ||||||
|  |     auto& iminc = ibb.minCorner(); | ||||||
|  |     auto& imaxc = ibb.maxCorner(); | ||||||
|  |     PointImpl 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); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::tuple<double /*score*/, Box /*farthest point from bin center*/> | ||||||
|  | objfunc(const PointImpl& bincenter, | ||||||
|  |         const shapelike::Shapes<PolygonImpl>& merged_pile, | ||||||
|  |         const Box& pilebb, | ||||||
|  |         const ItemGroup& items, | ||||||
|  |         const Item &item, | ||||||
|  |         double bin_area, | ||||||
|  |         double norm,            // A norming factor for physical dimensions
 | ||||||
|  |         // a spatial index to quickly get neighbors of the candidate item
 | ||||||
|  |         const SpatIndex& spatindex, | ||||||
|  |         const SpatIndex& smalls_spatindex, | ||||||
|  |         const ItemGroup& remaining | ||||||
|  |         ) | ||||||
|  | { | ||||||
|  |     using Coord = TCoord<PointImpl>; | ||||||
|  | 
 | ||||||
|  |     static const double ROUNDNESS_RATIO = 0.5; | ||||||
|  |     static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; | ||||||
|  | 
 | ||||||
|  |     // 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 = sl::boundingBox(item.transformedShape()); | ||||||
|  | 
 | ||||||
|  |     // Calculate the full bounding box of the pile with the candidate item
 | ||||||
|  |     auto fullbb = boundingBox(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; | ||||||
|  | 
 | ||||||
|  |     if(isBig(item.area()) || spatindex.empty()) { | ||||||
|  |         // This branch is for the bigger items..
 | ||||||
|  | 
 | ||||||
|  |         auto minc = ibb.minCorner(); // bottom left corner
 | ||||||
|  |         auto maxc = ibb.maxCorner(); // top right corner
 | ||||||
|  | 
 | ||||||
|  |         // top left and bottom right corners
 | ||||||
|  |         auto top_left = PointImpl{getX(minc), getY(maxc)}; | ||||||
|  |         auto bottom_right = PointImpl{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:
 | ||||||
|  |         auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; | ||||||
|  |         auto bindist = pl::distance(ibb.center(), bincenter) / norm; | ||||||
|  |         dist = 0.8*dist + 0.2*bindist; | ||||||
|  | 
 | ||||||
|  |         // Density is the pack density: how big is the arranged pile
 | ||||||
|  |         double density = 0; | ||||||
|  | 
 | ||||||
|  |         if(remaining.empty()) { | ||||||
|  | 
 | ||||||
|  |             auto mp = merged_pile; | ||||||
|  |             mp.emplace_back(item.transformedShape()); | ||||||
|  |             auto chull = sl::convexHull(mp); | ||||||
|  | 
 | ||||||
|  |             placers::EdgeCache<PolygonImpl> ec(chull); | ||||||
|  | 
 | ||||||
|  |             double circ = ec.circumference() / norm; | ||||||
|  |             double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; | ||||||
|  |             score = 0.5*circ + 0.5*bcirc; | ||||||
|  | 
 | ||||||
|  |         } else { | ||||||
|  |             // 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; | ||||||
|  | 
 | ||||||
|  |             density = std::sqrt((fullbb.width() / norm )* | ||||||
|  |                                 (fullbb.height() / norm)); | ||||||
|  |             auto querybb = item.boundingBox(); | ||||||
|  | 
 | ||||||
|  |             // Query the spatial index for the neighbors
 | ||||||
|  |             std::vector<SpatElement> result; | ||||||
|  |             result.reserve(spatindex.size()); | ||||||
|  |             if(isBig(item.area())) { | ||||||
|  |                 spatindex.query(bgi::intersects(querybb), | ||||||
|  |                                 std::back_inserter(result)); | ||||||
|  |             } else { | ||||||
|  |                 smalls_spatindex.query(bgi::intersects(querybb), | ||||||
|  |                                        std::back_inserter(result)); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             for(auto& e : result) { // now get the score for the best alignment
 | ||||||
|  |                 auto idx = e.second; | ||||||
|  |                 Item& p = items[idx]; | ||||||
|  |                 auto parea = p.area(); | ||||||
|  |                 if(std::abs(1.0 - parea/item.area()) < 1e-6) { | ||||||
|  |                     auto bb = boundingBox(p.boundingBox(), ibb); | ||||||
|  |                     auto bbarea = bb.area(); | ||||||
|  |                     auto ascore = 1.0 - (item.area() + parea)/bbarea; | ||||||
|  | 
 | ||||||
|  |                     if(ascore < alignment_score) alignment_score = ascore; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // 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; | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         // 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 = pl::distance(ibb.center(), bigbb.center()) / norm; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return std::make_tuple(score, fullbb); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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
 | ||||||
|  |     // arranger.useMinimumBoundigBoxRotation();
 | ||||||
|  |     pcfg.rotations = { 0.0 }; | ||||||
|  | 
 | ||||||
|  |     // The accuracy of optimization.
 | ||||||
|  |     // Goes from 0.0 to 1.0 and scales performance as well
 | ||||||
|  |     pcfg.accuracy = 0.65f; | ||||||
|  | 
 | ||||||
|  |     pcfg.parallel = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | template<class TBin> | ||||||
|  | class AutoArranger {}; | ||||||
|  | 
 | ||||||
|  | template<class TBin> | ||||||
|  | class _ArrBase { | ||||||
|  | protected: | ||||||
|  | 
 | ||||||
|  |     using Placer = TPacker<TBin>; | ||||||
|  |     using Selector = FirstFitSelection; | ||||||
|  |     using Packer = Nester<Placer, Selector>; | ||||||
|  |     using PConfig = typename Packer::PlacementConfig; | ||||||
|  |     using Distance = TCoord<PointImpl>; | ||||||
|  |     using Pile = sl::Shapes<PolygonImpl>; | ||||||
|  | 
 | ||||||
|  |     Packer m_pck; | ||||||
|  |     PConfig m_pconf; // Placement configuration
 | ||||||
|  |     double m_bin_area; | ||||||
|  |     SpatIndex m_rtree; | ||||||
|  |     SpatIndex m_smallsrtree; | ||||||
|  |     double m_norm; | ||||||
|  |     Pile m_merged_pile; | ||||||
|  |     Box m_pilebb; | ||||||
|  |     ItemGroup m_remaining; | ||||||
|  |     ItemGroup m_items; | ||||||
|  | public: | ||||||
|  | 
 | ||||||
|  |     _ArrBase(const TBin& bin, Distance dist, | ||||||
|  |              std::function<void(unsigned)> progressind, | ||||||
|  |              std::function<bool(void)> stopcond): | ||||||
|  |        m_pck(bin, dist), m_bin_area(sl::area(bin)), | ||||||
|  |        m_norm(std::sqrt(sl::area(bin))) | ||||||
|  |     { | ||||||
|  |         fillConfig(m_pconf); | ||||||
|  | 
 | ||||||
|  |         m_pconf.before_packing = | ||||||
|  |         [this](const Pile& 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_pck.progressIndicator(progressind); | ||||||
|  |         m_pck.stopCondition(stopcond); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     template<class...Args> inline IndexedPackGroup operator()(Args&&...args) { | ||||||
|  |         m_rtree.clear(); | ||||||
|  |         return m_pck.executeIndexed(std::forward<Args>(args)...); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | template<> | ||||||
|  | class AutoArranger<Box>: public _ArrBase<Box> { | ||||||
|  | public: | ||||||
|  | 
 | ||||||
|  |     AutoArranger(const Box& bin, Distance dist, | ||||||
|  |                  std::function<void(unsigned)> progressind, | ||||||
|  |                  std::function<bool(void)> stopcond): | ||||||
|  |         _ArrBase<Box>(bin, dist, progressind, stopcond) | ||||||
|  |     { | ||||||
|  | 
 | ||||||
|  |         m_pconf.object_function = [this, bin] (const Item &item) { | ||||||
|  | 
 | ||||||
|  |             auto result = objfunc(bin.center(), | ||||||
|  |                                   m_merged_pile, | ||||||
|  |                                   m_pilebb, | ||||||
|  |                                   m_items, | ||||||
|  |                                   item, | ||||||
|  |                                   m_bin_area, | ||||||
|  |                                   m_norm, | ||||||
|  |                                   m_rtree, | ||||||
|  |                                   m_smallsrtree, | ||||||
|  |                                   m_remaining); | ||||||
|  | 
 | ||||||
|  |             double score = std::get<0>(result); | ||||||
|  |             auto& fullbb = std::get<1>(result); | ||||||
|  | 
 | ||||||
|  |             double miss = Placer::overfit(fullbb, bin); | ||||||
|  |             miss = miss > 0? miss : 0; | ||||||
|  |             score += miss*miss; | ||||||
|  | 
 | ||||||
|  |             return score; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         m_pck.configure(m_pconf); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | using lnCircle = libnest2d::_Circle<libnest2d::PointImpl>; | ||||||
|  | 
 | ||||||
|  | inline lnCircle to_lnCircle(const Circle& circ) { | ||||||
|  |     return lnCircle({circ.center()(0), circ.center()(1)}, circ.radius()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | template<> | ||||||
|  | class AutoArranger<lnCircle>: public _ArrBase<lnCircle> { | ||||||
|  | public: | ||||||
|  | 
 | ||||||
|  |     AutoArranger(const lnCircle& bin, Distance dist, | ||||||
|  |                  std::function<void(unsigned)> progressind, | ||||||
|  |                  std::function<bool(void)> stopcond): | ||||||
|  |         _ArrBase<lnCircle>(bin, dist, progressind, stopcond) { | ||||||
|  | 
 | ||||||
|  |         m_pconf.object_function = [this, &bin] (const Item &item) { | ||||||
|  | 
 | ||||||
|  |             auto result = objfunc(bin.center(), | ||||||
|  |                                   m_merged_pile, | ||||||
|  |                                   m_pilebb, | ||||||
|  |                                   m_items, | ||||||
|  |                                   item, | ||||||
|  |                                   m_bin_area, | ||||||
|  |                                   m_norm, | ||||||
|  |                                   m_rtree, | ||||||
|  |                                   m_smallsrtree, | ||||||
|  |                                   m_remaining); | ||||||
|  | 
 | ||||||
|  |             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, bin); | ||||||
|  |                 if(miss < 0) miss = 0; | ||||||
|  |                 score += miss*miss; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return score; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         m_pck.configure(m_pconf); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | template<> | ||||||
|  | class AutoArranger<PolygonImpl>: public _ArrBase<PolygonImpl> { | ||||||
|  | public: | ||||||
|  |     AutoArranger(const PolygonImpl& bin, Distance dist, | ||||||
|  |                  std::function<void(unsigned)> progressind, | ||||||
|  |                  std::function<bool(void)> stopcond): | ||||||
|  |         _ArrBase<PolygonImpl>(bin, dist, progressind, stopcond) | ||||||
|  |     { | ||||||
|  |         m_pconf.object_function = [this, &bin] (const Item &item) { | ||||||
|  | 
 | ||||||
|  |             auto binbb = sl::boundingBox(bin); | ||||||
|  |             auto result = objfunc(binbb.center(), | ||||||
|  |                                   m_merged_pile, | ||||||
|  |                                   m_pilebb, | ||||||
|  |                                   m_items, | ||||||
|  |                                   item, | ||||||
|  |                                   m_bin_area, | ||||||
|  |                                   m_norm, | ||||||
|  |                                   m_rtree, | ||||||
|  |                                   m_smallsrtree, | ||||||
|  |                                   m_remaining); | ||||||
|  |             double score = std::get<0>(result); | ||||||
|  | 
 | ||||||
|  |             return score; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         m_pck.configure(m_pconf); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | template<> // Specialization with no bin
 | ||||||
|  | class AutoArranger<bool>: public _ArrBase<Box> { | ||||||
|  | public: | ||||||
|  | 
 | ||||||
|  |     AutoArranger(Distance dist, std::function<void(unsigned)> progressind, | ||||||
|  |                  std::function<bool(void)> stopcond): | ||||||
|  |         _ArrBase<Box>(Box(0, 0), dist, progressind, stopcond) | ||||||
|  |     { | ||||||
|  |         this->m_pconf.object_function = [this] (const Item &item) { | ||||||
|  | 
 | ||||||
|  |             auto result = objfunc({0, 0}, | ||||||
|  |                                   m_merged_pile, | ||||||
|  |                                   m_pilebb, | ||||||
|  |                                   m_items, | ||||||
|  |                                   item, | ||||||
|  |                                   0, | ||||||
|  |                                   m_norm, | ||||||
|  |                                   m_rtree, | ||||||
|  |                                   m_smallsrtree, | ||||||
|  |                                   m_remaining); | ||||||
|  |             return std::get<0>(result); | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         this->m_pck.configure(m_pconf); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // A container which stores a pointer to the 3D object and its projected
 | ||||||
|  | // 2D shape from top view.
 | ||||||
|  | using ShapeData2D = | ||||||
|  |     std::vector<std::pair<Slic3r::ModelInstance*, Item>>; | ||||||
|  | 
 | ||||||
|  | ShapeData2D projectModelFromTop(const Slic3r::Model &model) { | ||||||
|  |     ShapeData2D ret; | ||||||
|  | 
 | ||||||
|  |     auto s = std::accumulate(model.objects.begin(), model.objects.end(), size_t(0), | ||||||
|  |                     [](size_t s, ModelObject* o){ | ||||||
|  |         return s + o->instances.size(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     ret.reserve(s); | ||||||
|  | 
 | ||||||
|  |     for(auto objptr : model.objects) { | ||||||
|  |         if(objptr) { | ||||||
|  | 
 | ||||||
|  |             auto rmesh = objptr->raw_mesh(); | ||||||
|  | 
 | ||||||
|  |             for(auto objinst : objptr->instances) { | ||||||
|  |                 if(objinst) { | ||||||
|  |                     Slic3r::TriangleMesh tmpmesh = rmesh; | ||||||
|  |                     ClipperLib::PolygonImpl pn; | ||||||
|  | 
 | ||||||
|  |                     // CHECK_ME -> is the following correct ?
 | ||||||
|  |                     tmpmesh.scale(objinst->get_scaling_factor()); | ||||||
|  | 
 | ||||||
|  |                     // TODO export the exact 2D projection
 | ||||||
|  |                     auto p = tmpmesh.convex_hull(); | ||||||
|  | 
 | ||||||
|  |                     p.make_clockwise(); | ||||||
|  |                     p.append(p.first_point()); | ||||||
|  |                     pn.Contour = Slic3rMultiPoint_to_ClipperPath( p ); | ||||||
|  | 
 | ||||||
|  |                     // Efficient conversion to item.
 | ||||||
|  |                     Item item(std::move(pn)); | ||||||
|  | 
 | ||||||
|  |                     // Invalid geometries would throw exceptions when arranging
 | ||||||
|  |                     if(item.vertexCount() > 3) { | ||||||
|  |                         // CHECK_ME -> is the following correct or it should take in account all three rotations ?
 | ||||||
|  |                         item.rotation(objinst->get_rotation(Z)); | ||||||
|  |                         item.translation({ | ||||||
|  |                         ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR), | ||||||
|  |                         ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR) | ||||||
|  |                         }); | ||||||
|  |                         ret.emplace_back(objinst, item); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void applyResult( | ||||||
|  |         IndexedPackGroup::value_type& group, | ||||||
|  |         Coord batch_offset, | ||||||
|  |         ShapeData2D& shapemap) | ||||||
|  | { | ||||||
|  |     for(auto& r : group) { | ||||||
|  |         auto idx = r.first;     // get the original item index
 | ||||||
|  |         Item& item = r.second;  // get the item itself
 | ||||||
|  | 
 | ||||||
|  |         // Get the model instance from the shapemap using the index
 | ||||||
|  |         ModelInstance *inst_ptr = shapemap[idx].first; | ||||||
|  | 
 | ||||||
|  |         // Get the transformation data from the item object and scale it
 | ||||||
|  |         // appropriately
 | ||||||
|  |         auto off = item.translation(); | ||||||
|  |         Radians rot = item.rotation(); | ||||||
|  |         Vec3d foff(off.X*SCALING_FACTOR + batch_offset, | ||||||
|  |                    off.Y*SCALING_FACTOR, | ||||||
|  |                    inst_ptr->get_offset()(2)); | ||||||
|  | 
 | ||||||
|  |         // write the transformation data into the model instance
 | ||||||
|  |         inst_ptr->set_rotation(Z, rot); | ||||||
|  |         inst_ptr->set_offset(foff); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BedShapeHint bedShape(const Polyline &bed) { | ||||||
|  |     BedShapeHint ret; | ||||||
|  | 
 | ||||||
|  |     auto x = [](const Point& p) { return p(0); }; | ||||||
|  |     auto y = [](const Point& p) { return p(1); }; | ||||||
|  | 
 | ||||||
|  |     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(); | ||||||
|  | 
 | ||||||
|  |         Circle ret(center, avg_dist); | ||||||
|  |         for (auto el: vertex_distances) | ||||||
|  |         { | ||||||
|  |             if (abs(el - avg_dist) > 10 * SCALED_EPSILON) | ||||||
|  |                 ret = Circle(); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return ret; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     auto parea = poly_area(bed); | ||||||
|  | 
 | ||||||
|  |     if( (1.0 - parea/area(bb)) < 1e-3 ) { | ||||||
|  |         ret.type = BedShapeType::BOX; | ||||||
|  |         ret.shape.box = bb; | ||||||
|  |     } | ||||||
|  |     else if(auto c = isCircle(bed)) { | ||||||
|  |         ret.type = BedShapeType::CIRCLE; | ||||||
|  |         ret.shape.circ = c; | ||||||
|  |     } else { | ||||||
|  |         ret.type = BedShapeType::IRREGULAR; | ||||||
|  |         ret.shape.polygon = bed; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Determine the bed shape by hand
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool arrange(Model &model, | ||||||
|  |              coord_t min_obj_distance, | ||||||
|  |              const Polyline &bed, | ||||||
|  |              BedShapeHint bedhint, | ||||||
|  |              bool first_bin_only, | ||||||
|  |              std::function<void (unsigned)> progressind, | ||||||
|  |              std::function<bool ()> stopcondition) | ||||||
|  | { | ||||||
|  |     using ArrangeResult = _IndexedPackGroup<PolygonImpl>; | ||||||
|  | 
 | ||||||
|  |     bool ret = true; | ||||||
|  | 
 | ||||||
|  |     // Get the 2D projected shapes with their 3D model instance pointers
 | ||||||
|  |     auto shapemap = arr::projectModelFromTop(model); | ||||||
|  | 
 | ||||||
|  |     // Copy the references for the shapes only as the arranger expects a
 | ||||||
|  |     // sequence of objects convertible to Item or ClipperPolygon
 | ||||||
|  |     std::vector<std::reference_wrapper<Item>> shapes; | ||||||
|  |     shapes.reserve(shapemap.size()); | ||||||
|  |     std::for_each(shapemap.begin(), shapemap.end(), | ||||||
|  |                   [&shapes] (ShapeData2D::value_type& it) | ||||||
|  |     { | ||||||
|  |         shapes.push_back(std::ref(it.second)); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     IndexedPackGroup result; | ||||||
|  | 
 | ||||||
|  |     // If there is no hint about the shape, we will try to guess
 | ||||||
|  |     if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); | ||||||
|  | 
 | ||||||
|  |     BoundingBox bbb(bed); | ||||||
|  | 
 | ||||||
|  |     auto& cfn = stopcondition; | ||||||
|  | 
 | ||||||
|  |     auto binbb = Box({ | ||||||
|  |                          static_cast<libnest2d::Coord>(bbb.min(0)), | ||||||
|  |                          static_cast<libnest2d::Coord>(bbb.min(1)) | ||||||
|  |                      }, | ||||||
|  |     { | ||||||
|  |                          static_cast<libnest2d::Coord>(bbb.max(0)), | ||||||
|  |                          static_cast<libnest2d::Coord>(bbb.max(1)) | ||||||
|  |                      }); | ||||||
|  | 
 | ||||||
|  |     switch(bedhint.type) { | ||||||
|  |     case BedShapeType::BOX: { | ||||||
|  | 
 | ||||||
|  |         // Create the arranger for the box shaped bed
 | ||||||
|  |         AutoArranger<Box> arrange(binbb, min_obj_distance, progressind, cfn); | ||||||
|  | 
 | ||||||
|  |         // Arrange and return the items with their respective indices within the
 | ||||||
|  |         // input sequence.
 | ||||||
|  |         result = arrange(shapes.begin(), shapes.end()); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     case BedShapeType::CIRCLE: { | ||||||
|  | 
 | ||||||
|  |         auto c = bedhint.shape.circ; | ||||||
|  |         auto cc = to_lnCircle(c); | ||||||
|  | 
 | ||||||
|  |         AutoArranger<lnCircle> arrange(cc, min_obj_distance, progressind, cfn); | ||||||
|  |         result = arrange(shapes.begin(), shapes.end()); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     case BedShapeType::IRREGULAR: | ||||||
|  |     case BedShapeType::WHO_KNOWS: { | ||||||
|  | 
 | ||||||
|  |         using P = libnest2d::PolygonImpl; | ||||||
|  | 
 | ||||||
|  |         auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); | ||||||
|  |         P irrbed = sl::create<PolygonImpl>(std::move(ctour)); | ||||||
|  | 
 | ||||||
|  |         AutoArranger<P> arrange(irrbed, min_obj_distance, progressind, cfn); | ||||||
|  | 
 | ||||||
|  |         // Arrange and return the items with their respective indices within the
 | ||||||
|  |         // input sequence.
 | ||||||
|  |         result = arrange(shapes.begin(), shapes.end()); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     if(result.empty() || stopcondition()) return false; | ||||||
|  | 
 | ||||||
|  |     if(first_bin_only) { | ||||||
|  |         applyResult(result.front(), 0, shapemap); | ||||||
|  |     } else { | ||||||
|  | 
 | ||||||
|  |         const auto STRIDE_PADDING = 1.2; | ||||||
|  | 
 | ||||||
|  |         Coord stride = static_cast<Coord>(STRIDE_PADDING* | ||||||
|  |                                           binbb.width()*SCALING_FACTOR); | ||||||
|  |         Coord batch_offset = 0; | ||||||
|  | 
 | ||||||
|  |         for(auto& group : result) { | ||||||
|  |             applyResult(group, batch_offset, shapemap); | ||||||
|  | 
 | ||||||
|  |             // Only the first pack group can be placed onto the print bed. The
 | ||||||
|  |             // other objects which could not fit will be placed next to the
 | ||||||
|  |             // print bed
 | ||||||
|  |             batch_offset += stride; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for(auto objptr : model.objects) objptr->invalidate_bounding_box(); | ||||||
|  | 
 | ||||||
|  |     return ret && result.size() == 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | } | ||||||
|  | @ -2,549 +2,13 @@ | ||||||
| #define MODELARRANGE_HPP | #define MODELARRANGE_HPP | ||||||
| 
 | 
 | ||||||
| #include "Model.hpp" | #include "Model.hpp" | ||||||
| #include "SVG.hpp" |  | ||||||
| #include <libnest2d.h> |  | ||||||
| 
 |  | ||||||
| #include <numeric> |  | ||||||
| #include <ClipperUtils.hpp> |  | ||||||
| 
 |  | ||||||
| #include <boost/geometry/index/rtree.hpp> |  | ||||||
| 
 | 
 | ||||||
| namespace Slic3r { | namespace Slic3r { | ||||||
|  | 
 | ||||||
|  | class Model; | ||||||
|  | 
 | ||||||
| namespace arr { | namespace arr { | ||||||
| 
 | 
 | ||||||
| using namespace libnest2d; |  | ||||||
| 
 |  | ||||||
| std::string toString(const Model& model, bool holes = true) { |  | ||||||
|     std::stringstream  ss; |  | ||||||
| 
 |  | ||||||
|     ss << "{\n"; |  | ||||||
| 
 |  | ||||||
|     for(auto objptr : model.objects) { |  | ||||||
|         if(!objptr) continue; |  | ||||||
| 
 |  | ||||||
|         auto rmesh = objptr->raw_mesh(); |  | ||||||
| 
 |  | ||||||
|         for(auto objinst : objptr->instances) { |  | ||||||
|             if(!objinst) continue; |  | ||||||
| 
 |  | ||||||
|             Slic3r::TriangleMesh tmpmesh = rmesh; |  | ||||||
|             // CHECK_ME -> Is the following correct ?
 |  | ||||||
|             tmpmesh.scale(objinst->get_scaling_factor()); |  | ||||||
|             objinst->transform_mesh(&tmpmesh); |  | ||||||
|             ExPolygons expolys = tmpmesh.horizontal_projection(); |  | ||||||
|             for(auto& expoly_complex : expolys) { |  | ||||||
| 
 |  | ||||||
|                 auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR); |  | ||||||
|                 if(tmp.empty()) continue; |  | ||||||
|                 auto expoly = tmp.front(); |  | ||||||
|                 expoly.contour.make_clockwise(); |  | ||||||
|                 for(auto& h : expoly.holes) h.make_counter_clockwise(); |  | ||||||
| 
 |  | ||||||
|                 ss << "\t{\n"; |  | ||||||
|                 ss << "\t\t{\n"; |  | ||||||
| 
 |  | ||||||
|                 for(auto v : expoly.contour.points) ss << "\t\t\t{" |  | ||||||
|                                                     << v(0) << ", " |  | ||||||
|                                                     << v(1) << "},\n"; |  | ||||||
|                 { |  | ||||||
|                     auto v = expoly.contour.points.front(); |  | ||||||
|                     ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n"; |  | ||||||
|                 } |  | ||||||
|                 ss << "\t\t},\n"; |  | ||||||
| 
 |  | ||||||
|                 // Holes:
 |  | ||||||
|                 ss << "\t\t{\n"; |  | ||||||
|                 if(holes) for(auto h : expoly.holes) { |  | ||||||
|                     ss << "\t\t\t{\n"; |  | ||||||
|                     for(auto v : h.points) ss << "\t\t\t\t{" |  | ||||||
|                                            << v(0) << ", " |  | ||||||
|                                            << v(1) << "},\n"; |  | ||||||
|                     { |  | ||||||
|                         auto v = h.points.front(); |  | ||||||
|                         ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n"; |  | ||||||
|                     } |  | ||||||
|                     ss << "\t\t\t},\n"; |  | ||||||
|                 } |  | ||||||
|                 ss << "\t\t},\n"; |  | ||||||
| 
 |  | ||||||
|                 ss << "\t},\n"; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     ss << "}\n"; |  | ||||||
| 
 |  | ||||||
|     return ss.str(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void toSVG(SVG& svg, const Model& model) { |  | ||||||
|     for(auto objptr : model.objects) { |  | ||||||
|         if(!objptr) continue; |  | ||||||
| 
 |  | ||||||
|         auto rmesh = objptr->raw_mesh(); |  | ||||||
| 
 |  | ||||||
|         for(auto objinst : objptr->instances) { |  | ||||||
|             if(!objinst) continue; |  | ||||||
| 
 |  | ||||||
|             Slic3r::TriangleMesh tmpmesh = rmesh; |  | ||||||
|             tmpmesh.scale(objinst->get_scaling_factor()); |  | ||||||
|             objinst->transform_mesh(&tmpmesh); |  | ||||||
|             ExPolygons expolys = tmpmesh.horizontal_projection(); |  | ||||||
|             svg.draw(expolys); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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>>; |  | ||||||
| template<class TBin> |  | ||||||
| using TPacker = typename placers::_NofitPolyPlacer<PolygonImpl, TBin>; |  | ||||||
| 
 |  | ||||||
| const double BIG_ITEM_TRESHOLD = 0.02; |  | ||||||
| 
 |  | ||||||
| Box boundingBox(const Box& pilebb, const Box& ibb ) { |  | ||||||
|     auto& pminc = pilebb.minCorner(); |  | ||||||
|     auto& pmaxc = pilebb.maxCorner(); |  | ||||||
|     auto& iminc = ibb.minCorner(); |  | ||||||
|     auto& imaxc = ibb.maxCorner(); |  | ||||||
|     PointImpl 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); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::tuple<double /*score*/, Box /*farthest point from bin center*/> |  | ||||||
| objfunc(const PointImpl& bincenter, |  | ||||||
|         const shapelike::Shapes<PolygonImpl>& merged_pile, |  | ||||||
|         const Box& pilebb, |  | ||||||
|         const ItemGroup& items, |  | ||||||
|         const Item &item, |  | ||||||
|         double bin_area, |  | ||||||
|         double norm,            // A norming factor for physical dimensions
 |  | ||||||
|         // a spatial index to quickly get neighbors of the candidate item
 |  | ||||||
|         const SpatIndex& spatindex, |  | ||||||
|         const SpatIndex& smalls_spatindex, |  | ||||||
|         const ItemGroup& remaining |  | ||||||
|         ) |  | ||||||
| { |  | ||||||
|     using Coord = TCoord<PointImpl>; |  | ||||||
| 
 |  | ||||||
|     static const double ROUNDNESS_RATIO = 0.5; |  | ||||||
|     static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; |  | ||||||
| 
 |  | ||||||
|     // 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 = sl::boundingBox(item.transformedShape()); |  | ||||||
| 
 |  | ||||||
|     // Calculate the full bounding box of the pile with the candidate item
 |  | ||||||
|     auto fullbb = boundingBox(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; |  | ||||||
| 
 |  | ||||||
|     if(isBig(item.area()) || spatindex.empty()) { |  | ||||||
|         // This branch is for the bigger items..
 |  | ||||||
| 
 |  | ||||||
|         auto minc = ibb.minCorner(); // bottom left corner
 |  | ||||||
|         auto maxc = ibb.maxCorner(); // top right corner
 |  | ||||||
| 
 |  | ||||||
|         // top left and bottom right corners
 |  | ||||||
|         auto top_left = PointImpl{getX(minc), getY(maxc)}; |  | ||||||
|         auto bottom_right = PointImpl{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:
 |  | ||||||
|         auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; |  | ||||||
|         auto bindist = pl::distance(ibb.center(), bincenter) / norm; |  | ||||||
|         dist = 0.8*dist + 0.2*bindist; |  | ||||||
| 
 |  | ||||||
|         // Density is the pack density: how big is the arranged pile
 |  | ||||||
|         double density = 0; |  | ||||||
| 
 |  | ||||||
|         if(remaining.empty()) { |  | ||||||
| 
 |  | ||||||
|             auto mp = merged_pile; |  | ||||||
|             mp.emplace_back(item.transformedShape()); |  | ||||||
|             auto chull = sl::convexHull(mp); |  | ||||||
| 
 |  | ||||||
|             placers::EdgeCache<PolygonImpl> ec(chull); |  | ||||||
| 
 |  | ||||||
|             double circ = ec.circumference() / norm; |  | ||||||
|             double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; |  | ||||||
|             score = 0.5*circ + 0.5*bcirc; |  | ||||||
| 
 |  | ||||||
|         } else { |  | ||||||
|             // 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; |  | ||||||
| 
 |  | ||||||
|             density = std::sqrt((fullbb.width() / norm )* |  | ||||||
|                                 (fullbb.height() / norm)); |  | ||||||
|             auto querybb = item.boundingBox(); |  | ||||||
| 
 |  | ||||||
|             // Query the spatial index for the neighbors
 |  | ||||||
|             std::vector<SpatElement> result; |  | ||||||
|             result.reserve(spatindex.size()); |  | ||||||
|             if(isBig(item.area())) { |  | ||||||
|                 spatindex.query(bgi::intersects(querybb), |  | ||||||
|                                 std::back_inserter(result)); |  | ||||||
|             } else { |  | ||||||
|                 smalls_spatindex.query(bgi::intersects(querybb), |  | ||||||
|                                        std::back_inserter(result)); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             for(auto& e : result) { // now get the score for the best alignment
 |  | ||||||
|                 auto idx = e.second; |  | ||||||
|                 Item& p = items[idx]; |  | ||||||
|                 auto parea = p.area(); |  | ||||||
|                 if(std::abs(1.0 - parea/item.area()) < 1e-6) { |  | ||||||
|                     auto bb = boundingBox(p.boundingBox(), ibb); |  | ||||||
|                     auto bbarea = bb.area(); |  | ||||||
|                     auto ascore = 1.0 - (item.area() + parea)/bbarea; |  | ||||||
| 
 |  | ||||||
|                     if(ascore < alignment_score) alignment_score = ascore; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             // 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; |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         // 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 = pl::distance(ibb.center(), bigbb.center()) / norm; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return std::make_tuple(score, fullbb); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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
 |  | ||||||
|     // arranger.useMinimumBoundigBoxRotation();
 |  | ||||||
|     pcfg.rotations = { 0.0 }; |  | ||||||
| 
 |  | ||||||
|     // The accuracy of optimization.
 |  | ||||||
|     // Goes from 0.0 to 1.0 and scales performance as well
 |  | ||||||
|     pcfg.accuracy = 0.65f; |  | ||||||
| 
 |  | ||||||
|     pcfg.parallel = true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| template<class TBin> |  | ||||||
| class AutoArranger {}; |  | ||||||
| 
 |  | ||||||
| template<class TBin> |  | ||||||
| class _ArrBase { |  | ||||||
| protected: |  | ||||||
| 
 |  | ||||||
|     using Placer = TPacker<TBin>; |  | ||||||
|     using Selector = FirstFitSelection; |  | ||||||
|     using Packer = Nester<Placer, Selector>; |  | ||||||
|     using PConfig = typename Packer::PlacementConfig; |  | ||||||
|     using Distance = TCoord<PointImpl>; |  | ||||||
|     using Pile = sl::Shapes<PolygonImpl>; |  | ||||||
| 
 |  | ||||||
|     Packer m_pck; |  | ||||||
|     PConfig m_pconf; // Placement configuration
 |  | ||||||
|     double m_bin_area; |  | ||||||
|     SpatIndex m_rtree; |  | ||||||
|     SpatIndex m_smallsrtree; |  | ||||||
|     double m_norm; |  | ||||||
|     Pile m_merged_pile; |  | ||||||
|     Box m_pilebb; |  | ||||||
|     ItemGroup m_remaining; |  | ||||||
|     ItemGroup m_items; |  | ||||||
| public: |  | ||||||
| 
 |  | ||||||
|     _ArrBase(const TBin& bin, Distance dist, |  | ||||||
|              std::function<void(unsigned)> progressind, |  | ||||||
|              std::function<bool(void)> stopcond): |  | ||||||
|        m_pck(bin, dist), m_bin_area(sl::area(bin)), |  | ||||||
|        m_norm(std::sqrt(sl::area(bin))) |  | ||||||
|     { |  | ||||||
|         fillConfig(m_pconf); |  | ||||||
| 
 |  | ||||||
|         m_pconf.before_packing = |  | ||||||
|         [this](const Pile& 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_pck.progressIndicator(progressind); |  | ||||||
|         m_pck.stopCondition(stopcond); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     template<class...Args> inline IndexedPackGroup operator()(Args&&...args) { |  | ||||||
|         m_rtree.clear(); |  | ||||||
|         return m_pck.executeIndexed(std::forward<Args>(args)...); |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| template<> |  | ||||||
| class AutoArranger<Box>: public _ArrBase<Box> { |  | ||||||
| public: |  | ||||||
| 
 |  | ||||||
|     AutoArranger(const Box& bin, Distance dist, |  | ||||||
|                  std::function<void(unsigned)> progressind, |  | ||||||
|                  std::function<bool(void)> stopcond): |  | ||||||
|         _ArrBase<Box>(bin, dist, progressind, stopcond) |  | ||||||
|     { |  | ||||||
| 
 |  | ||||||
|         m_pconf.object_function = [this, bin] (const Item &item) { |  | ||||||
| 
 |  | ||||||
|             auto result = objfunc(bin.center(), |  | ||||||
|                                   m_merged_pile, |  | ||||||
|                                   m_pilebb, |  | ||||||
|                                   m_items, |  | ||||||
|                                   item, |  | ||||||
|                                   m_bin_area, |  | ||||||
|                                   m_norm, |  | ||||||
|                                   m_rtree, |  | ||||||
|                                   m_smallsrtree, |  | ||||||
|                                   m_remaining); |  | ||||||
| 
 |  | ||||||
|             double score = std::get<0>(result); |  | ||||||
|             auto& fullbb = std::get<1>(result); |  | ||||||
| 
 |  | ||||||
|             double miss = Placer::overfit(fullbb, bin); |  | ||||||
|             miss = miss > 0? miss : 0; |  | ||||||
|             score += miss*miss; |  | ||||||
| 
 |  | ||||||
|             return score; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         m_pck.configure(m_pconf); |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| using lnCircle = libnest2d::_Circle<libnest2d::PointImpl>; |  | ||||||
| 
 |  | ||||||
| template<> |  | ||||||
| class AutoArranger<lnCircle>: public _ArrBase<lnCircle> { |  | ||||||
| public: |  | ||||||
| 
 |  | ||||||
|     AutoArranger(const lnCircle& bin, Distance dist, |  | ||||||
|                  std::function<void(unsigned)> progressind, |  | ||||||
|                  std::function<bool(void)> stopcond): |  | ||||||
|         _ArrBase<lnCircle>(bin, dist, progressind, stopcond) { |  | ||||||
| 
 |  | ||||||
|         m_pconf.object_function = [this, &bin] (const Item &item) { |  | ||||||
| 
 |  | ||||||
|             auto result = objfunc(bin.center(), |  | ||||||
|                                   m_merged_pile, |  | ||||||
|                                   m_pilebb, |  | ||||||
|                                   m_items, |  | ||||||
|                                   item, |  | ||||||
|                                   m_bin_area, |  | ||||||
|                                   m_norm, |  | ||||||
|                                   m_rtree, |  | ||||||
|                                   m_smallsrtree, |  | ||||||
|                                   m_remaining); |  | ||||||
| 
 |  | ||||||
|             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, bin); |  | ||||||
|                 if(miss < 0) miss = 0; |  | ||||||
|                 score += miss*miss; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return score; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         m_pck.configure(m_pconf); |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| template<> |  | ||||||
| class AutoArranger<PolygonImpl>: public _ArrBase<PolygonImpl> { |  | ||||||
| public: |  | ||||||
|     AutoArranger(const PolygonImpl& bin, Distance dist, |  | ||||||
|                  std::function<void(unsigned)> progressind, |  | ||||||
|                  std::function<bool(void)> stopcond): |  | ||||||
|         _ArrBase<PolygonImpl>(bin, dist, progressind, stopcond) |  | ||||||
|     { |  | ||||||
|         m_pconf.object_function = [this, &bin] (const Item &item) { |  | ||||||
| 
 |  | ||||||
|             auto binbb = sl::boundingBox(bin); |  | ||||||
|             auto result = objfunc(binbb.center(), |  | ||||||
|                                   m_merged_pile, |  | ||||||
|                                   m_pilebb, |  | ||||||
|                                   m_items, |  | ||||||
|                                   item, |  | ||||||
|                                   m_bin_area, |  | ||||||
|                                   m_norm, |  | ||||||
|                                   m_rtree, |  | ||||||
|                                   m_smallsrtree, |  | ||||||
|                                   m_remaining); |  | ||||||
|             double score = std::get<0>(result); |  | ||||||
| 
 |  | ||||||
|             return score; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         m_pck.configure(m_pconf); |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| template<> // Specialization with no bin
 |  | ||||||
| class AutoArranger<bool>: public _ArrBase<Box> { |  | ||||||
| public: |  | ||||||
| 
 |  | ||||||
|     AutoArranger(Distance dist, std::function<void(unsigned)> progressind, |  | ||||||
|                  std::function<bool(void)> stopcond): |  | ||||||
|         _ArrBase<Box>(Box(0, 0), dist, progressind, stopcond) |  | ||||||
|     { |  | ||||||
|         this->m_pconf.object_function = [this] (const Item &item) { |  | ||||||
| 
 |  | ||||||
|             auto result = objfunc({0, 0}, |  | ||||||
|                                   m_merged_pile, |  | ||||||
|                                   m_pilebb, |  | ||||||
|                                   m_items, |  | ||||||
|                                   item, |  | ||||||
|                                   0, |  | ||||||
|                                   m_norm, |  | ||||||
|                                   m_rtree, |  | ||||||
|                                   m_smallsrtree, |  | ||||||
|                                   m_remaining); |  | ||||||
|             return std::get<0>(result); |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         this->m_pck.configure(m_pconf); |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // A container which stores a pointer to the 3D object and its projected
 |  | ||||||
| // 2D shape from top view.
 |  | ||||||
| using ShapeData2D = |  | ||||||
|     std::vector<std::pair<Slic3r::ModelInstance*, Item>>; |  | ||||||
| 
 |  | ||||||
| ShapeData2D projectModelFromTop(const Slic3r::Model &model) { |  | ||||||
|     ShapeData2D ret; |  | ||||||
| 
 |  | ||||||
|     auto s = std::accumulate(model.objects.begin(), model.objects.end(), size_t(0), |  | ||||||
|                     [](size_t s, ModelObject* o){ |  | ||||||
|         return s + o->instances.size(); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     ret.reserve(s); |  | ||||||
| 
 |  | ||||||
|     for(auto objptr : model.objects) { |  | ||||||
|         if(objptr) { |  | ||||||
| 
 |  | ||||||
|             auto rmesh = objptr->raw_mesh(); |  | ||||||
| 
 |  | ||||||
|             for(auto objinst : objptr->instances) { |  | ||||||
|                 if(objinst) { |  | ||||||
|                     Slic3r::TriangleMesh tmpmesh = rmesh; |  | ||||||
|                     ClipperLib::PolygonImpl pn; |  | ||||||
| 
 |  | ||||||
|                     // CHECK_ME -> is the following correct ?
 |  | ||||||
|                     tmpmesh.scale(objinst->get_scaling_factor()); |  | ||||||
| 
 |  | ||||||
|                     // TODO export the exact 2D projection
 |  | ||||||
|                     auto p = tmpmesh.convex_hull(); |  | ||||||
| 
 |  | ||||||
|                     p.make_clockwise(); |  | ||||||
|                     p.append(p.first_point()); |  | ||||||
|                     pn.Contour = Slic3rMultiPoint_to_ClipperPath( p ); |  | ||||||
| 
 |  | ||||||
|                     // Efficient conversion to item.
 |  | ||||||
|                     Item item(std::move(pn)); |  | ||||||
| 
 |  | ||||||
|                     // Invalid geometries would throw exceptions when arranging
 |  | ||||||
|                     if(item.vertexCount() > 3) { |  | ||||||
|                         // CHECK_ME -> is the following correct or it should take in account all three rotations ?
 |  | ||||||
|                         item.rotation(objinst->get_rotation(Z)); |  | ||||||
|                         item.translation({ |  | ||||||
|                         ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR), |  | ||||||
|                         ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR) |  | ||||||
|                         }); |  | ||||||
|                         ret.emplace_back(objinst, item); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return ret; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| class Circle { | class Circle { | ||||||
|     Point center_; |     Point center_; | ||||||
|     double radius_; |     double radius_; | ||||||
|  | @ -556,9 +20,9 @@ public: | ||||||
|     inline double radius() const { return radius_; } |     inline double radius() const { return radius_; } | ||||||
|     inline const Point& center() const { return center_; } |     inline const Point& center() const { return center_; } | ||||||
|     inline operator bool() { return !std::isnan(radius_); } |     inline operator bool() { return !std::isnan(radius_); } | ||||||
|     inline operator lnCircle() { | //    inline operator lnCircle() {
 | ||||||
|         return lnCircle({center_(0), center_(1)}, radius_); | //        return lnCircle({center_(0), center_(1)}, radius_);
 | ||||||
|     } | //    }
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| enum class BedShapeType { | enum class BedShapeType { | ||||||
|  | @ -577,109 +41,7 @@ struct BedShapeHint { | ||||||
|     } shape; |     } shape; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| BedShapeHint bedShape(const Polyline& bed) { | BedShapeHint bedShape(const Polyline& bed); | ||||||
|     BedShapeHint ret; |  | ||||||
| 
 |  | ||||||
|     auto x = [](const Point& p) { return p(0); }; |  | ||||||
|     auto y = [](const Point& p) { return p(1); }; |  | ||||||
| 
 |  | ||||||
|     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(); |  | ||||||
| 
 |  | ||||||
|         Circle ret(center, avg_dist); |  | ||||||
|         for (auto el: vertex_distances) |  | ||||||
|         { |  | ||||||
|             if (abs(el - avg_dist) > 10 * SCALED_EPSILON) |  | ||||||
|                 ret = Circle(); |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return ret; |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     auto parea = poly_area(bed); |  | ||||||
| 
 |  | ||||||
|     if( (1.0 - parea/area(bb)) < 1e-3 ) { |  | ||||||
|         ret.type = BedShapeType::BOX; |  | ||||||
|         ret.shape.box = bb; |  | ||||||
|     } |  | ||||||
|     else if(auto c = isCircle(bed)) { |  | ||||||
|         ret.type = BedShapeType::CIRCLE; |  | ||||||
|         ret.shape.circ = c; |  | ||||||
|     } else { |  | ||||||
|         ret.type = BedShapeType::IRREGULAR; |  | ||||||
|         ret.shape.polygon = bed; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Determine the bed shape by hand
 |  | ||||||
|     return ret; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void applyResult( |  | ||||||
|         IndexedPackGroup::value_type& group, |  | ||||||
|         Coord batch_offset, |  | ||||||
|         ShapeData2D& shapemap) |  | ||||||
| { |  | ||||||
|     for(auto& r : group) { |  | ||||||
|         auto idx = r.first;     // get the original item index
 |  | ||||||
|         Item& item = r.second;  // get the item itself
 |  | ||||||
| 
 |  | ||||||
|         // Get the model instance from the shapemap using the index
 |  | ||||||
|         ModelInstance *inst_ptr = shapemap[idx].first; |  | ||||||
| 
 |  | ||||||
|         // Get the transformation data from the item object and scale it
 |  | ||||||
|         // appropriately
 |  | ||||||
|         auto off = item.translation(); |  | ||||||
|         Radians rot = item.rotation(); |  | ||||||
|         Vec3d foff(off.X*SCALING_FACTOR + batch_offset, |  | ||||||
|                    off.Y*SCALING_FACTOR, |  | ||||||
|                    inst_ptr->get_offset()(2)); |  | ||||||
| 
 |  | ||||||
|         // write the transformation data into the model instance
 |  | ||||||
|         inst_ptr->set_rotation(Z, rot); |  | ||||||
|         inst_ptr->set_offset(foff); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * \brief Arranges the model objects on the screen. |  * \brief Arranges the model objects on the screen. | ||||||
|  | @ -707,112 +69,14 @@ void applyResult( | ||||||
|  * packed. The unsigned argument is the number of items remaining to pack. |  * packed. The unsigned argument is the number of items remaining to pack. | ||||||
|  * \param stopcondition A predicate returning true if abort is needed. |  * \param stopcondition A predicate returning true if abort is needed. | ||||||
|  */ |  */ | ||||||
| bool arrange(Model &model, coordf_t min_obj_distance, | bool arrange(Model &model, coord_t min_obj_distance, | ||||||
|              const Slic3r::Polyline& bed, |              const Slic3r::Polyline& bed, | ||||||
|              BedShapeHint bedhint, |              BedShapeHint bedhint, | ||||||
|              bool first_bin_only, |              bool first_bin_only, | ||||||
|              std::function<void(unsigned)> progressind, |              std::function<void(unsigned)> progressind, | ||||||
|              std::function<bool(void)> stopcondition) |              std::function<bool(void)> stopcondition); | ||||||
| { |  | ||||||
|     using ArrangeResult = _IndexedPackGroup<PolygonImpl>; |  | ||||||
| 
 |  | ||||||
|     bool ret = true; |  | ||||||
| 
 |  | ||||||
|     // Get the 2D projected shapes with their 3D model instance pointers
 |  | ||||||
|     auto shapemap = arr::projectModelFromTop(model); |  | ||||||
| 
 |  | ||||||
|     // Copy the references for the shapes only as the arranger expects a
 |  | ||||||
|     // sequence of objects convertible to Item or ClipperPolygon
 |  | ||||||
|     std::vector<std::reference_wrapper<Item>> shapes; |  | ||||||
|     shapes.reserve(shapemap.size()); |  | ||||||
|     std::for_each(shapemap.begin(), shapemap.end(), |  | ||||||
|                   [&shapes] (ShapeData2D::value_type& it) |  | ||||||
|     { |  | ||||||
|         shapes.push_back(std::ref(it.second)); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     IndexedPackGroup result; |  | ||||||
| 
 |  | ||||||
|     // If there is no hint about the shape, we will try to guess
 |  | ||||||
|     if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); |  | ||||||
| 
 |  | ||||||
|     BoundingBox bbb(bed); |  | ||||||
| 
 |  | ||||||
|     auto& cfn = stopcondition; |  | ||||||
| 
 |  | ||||||
|     auto binbb = Box({ |  | ||||||
|                          static_cast<libnest2d::Coord>(bbb.min(0)), |  | ||||||
|                          static_cast<libnest2d::Coord>(bbb.min(1)) |  | ||||||
|                      }, |  | ||||||
|                      { |  | ||||||
|                          static_cast<libnest2d::Coord>(bbb.max(0)), |  | ||||||
|                          static_cast<libnest2d::Coord>(bbb.max(1)) |  | ||||||
|                      }); |  | ||||||
| 
 |  | ||||||
|     switch(bedhint.type) { |  | ||||||
|     case BedShapeType::BOX: { |  | ||||||
| 
 |  | ||||||
|         // Create the arranger for the box shaped bed
 |  | ||||||
|         AutoArranger<Box> arrange(binbb, min_obj_distance, progressind, cfn); |  | ||||||
| 
 |  | ||||||
|         // Arrange and return the items with their respective indices within the
 |  | ||||||
|         // input sequence.
 |  | ||||||
|         result = arrange(shapes.begin(), shapes.end()); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     case BedShapeType::CIRCLE: { |  | ||||||
| 
 |  | ||||||
|         auto c = bedhint.shape.circ; |  | ||||||
|         auto cc = lnCircle(c); |  | ||||||
| 
 |  | ||||||
|         AutoArranger<lnCircle> arrange(cc, min_obj_distance, progressind, cfn); |  | ||||||
|         result = arrange(shapes.begin(), shapes.end()); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     case BedShapeType::IRREGULAR: |  | ||||||
|     case BedShapeType::WHO_KNOWS: { |  | ||||||
| 
 |  | ||||||
|         using P = libnest2d::PolygonImpl; |  | ||||||
| 
 |  | ||||||
|         auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); |  | ||||||
|         P irrbed = sl::create<PolygonImpl>(std::move(ctour)); |  | ||||||
| 
 |  | ||||||
|         AutoArranger<P> arrange(irrbed, min_obj_distance, progressind, cfn); |  | ||||||
| 
 |  | ||||||
|         // Arrange and return the items with their respective indices within the
 |  | ||||||
|         // input sequence.
 |  | ||||||
|         result = arrange(shapes.begin(), shapes.end()); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     if(result.empty() || stopcondition()) return false; |  | ||||||
| 
 |  | ||||||
|     if(first_bin_only) { |  | ||||||
|         applyResult(result.front(), 0, shapemap); |  | ||||||
|     } else { |  | ||||||
| 
 |  | ||||||
|         const auto STRIDE_PADDING = 1.2; |  | ||||||
| 
 |  | ||||||
|         Coord stride = static_cast<Coord>(STRIDE_PADDING* |  | ||||||
|                                           binbb.width()*SCALING_FACTOR); |  | ||||||
|         Coord batch_offset = 0; |  | ||||||
| 
 |  | ||||||
|         for(auto& group : result) { |  | ||||||
|             applyResult(group, batch_offset, shapemap); |  | ||||||
| 
 |  | ||||||
|             // Only the first pack group can be placed onto the print bed. The
 |  | ||||||
|             // other objects which could not fit will be placed next to the
 |  | ||||||
|             // print bed
 |  | ||||||
|             batch_offset += stride; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for(auto objptr : model.objects) objptr->invalidate_bounding_box(); |  | ||||||
| 
 |  | ||||||
|     return ret && result.size() == 1; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | 
 | ||||||
| } | } | ||||||
| #endif // MODELARRANGE_HPP
 | #endif // MODELARRANGE_HPP
 | ||||||
|  |  | ||||||
|  | @ -1,364 +0,0 @@ | ||||||
| #include "AppController.hpp" |  | ||||||
| 
 |  | ||||||
| #include <slic3r/GUI/GUI.hpp> |  | ||||||
| 
 |  | ||||||
| #include <future> |  | ||||||
| #include <chrono> |  | ||||||
| #include <sstream> |  | ||||||
| #include <cstdarg> |  | ||||||
| #include <thread> |  | ||||||
| #include <unordered_map> |  | ||||||
| 
 |  | ||||||
| #include <PrintConfig.hpp> |  | ||||||
| #include <Print.hpp> |  | ||||||
| #include <PrintExport.hpp> |  | ||||||
| #include <Geometry.hpp> |  | ||||||
| #include <Model.hpp> |  | ||||||
| #include <ModelArrange.hpp> |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| namespace Slic3r { |  | ||||||
| 
 |  | ||||||
| class AppControllerGui::PriData { |  | ||||||
| public: |  | ||||||
|     std::mutex m; |  | ||||||
|     std::thread::id ui_thread; |  | ||||||
| 
 |  | ||||||
|     inline explicit PriData(std::thread::id uit): ui_thread(uit) {} |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| AppControllerGui::AppControllerGui() |  | ||||||
|     :m_pri_data(new PriData(std::this_thread::get_id())) {} |  | ||||||
| 
 |  | ||||||
| AppControllerGui::~AppControllerGui() { |  | ||||||
|     m_pri_data.reset(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool AppControllerGui::is_main_thread() const |  | ||||||
| { |  | ||||||
|     return m_pri_data->ui_thread == std::this_thread::get_id(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // namespace GUI {
 |  | ||||||
| // PresetBundle* get_preset_bundle();
 |  | ||||||
| // }
 |  | ||||||
| 
 |  | ||||||
| static const PrintObjectStep STEP_SLICE                 = posSlice; |  | ||||||
| static const PrintObjectStep STEP_PERIMETERS            = posPerimeters; |  | ||||||
| static const PrintObjectStep STEP_PREPARE_INFILL        = posPrepareInfill; |  | ||||||
| static const PrintObjectStep STEP_INFILL                = posInfill; |  | ||||||
| static const PrintObjectStep STEP_SUPPORTMATERIAL       = posSupportMaterial; |  | ||||||
| static const PrintStep STEP_SKIRT                       = psSkirt; |  | ||||||
| static const PrintStep STEP_BRIM                        = psBrim; |  | ||||||
| static const PrintStep STEP_WIPE_TOWER                  = psWipeTower; |  | ||||||
| 
 |  | ||||||
| ProgresIndicatorPtr AppControllerGui::global_progress_indicator() { |  | ||||||
|     ProgresIndicatorPtr ret; |  | ||||||
| 
 |  | ||||||
|     m_pri_data->m.lock(); |  | ||||||
|     ret = m_global_progressind; |  | ||||||
|     m_pri_data->m.unlock(); |  | ||||||
| 
 |  | ||||||
|     return ret; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void AppControllerGui::global_progress_indicator(ProgresIndicatorPtr gpri) |  | ||||||
| { |  | ||||||
|     m_pri_data->m.lock(); |  | ||||||
|     m_global_progressind = gpri; |  | ||||||
|     m_pri_data->m.unlock(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| PrintController::PngExportData |  | ||||||
| PrintController::query_png_export_data(const DynamicPrintConfig& conf) |  | ||||||
| { |  | ||||||
|     PngExportData ret; |  | ||||||
| 
 |  | ||||||
|     auto c = GUI::get_appctl(); |  | ||||||
|     auto zippath = c->query_destination_path("Output zip file", "*.zip", |  | ||||||
|                                              "export-png", |  | ||||||
|                                              "out"); |  | ||||||
| 
 |  | ||||||
|     ret.zippath = zippath; |  | ||||||
| 
 |  | ||||||
|     ret.width_mm = conf.opt_float("display_width"); |  | ||||||
|     ret.height_mm = conf.opt_float("display_height"); |  | ||||||
| 
 |  | ||||||
|     ret.width_px = conf.opt_int("display_pixels_x"); |  | ||||||
|     ret.height_px = conf.opt_int("display_pixels_y"); |  | ||||||
| 
 |  | ||||||
|     auto opt_corr = conf.opt<ConfigOptionFloats>("printer_correction"); |  | ||||||
| 
 |  | ||||||
|     if(opt_corr) { |  | ||||||
|         ret.corr_x = opt_corr->values[0]; |  | ||||||
|         ret.corr_y = opt_corr->values[1]; |  | ||||||
|         ret.corr_z = opt_corr->values[2]; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     ret.exp_time_first_s = conf.opt_float("initial_exposure_time"); |  | ||||||
|     ret.exp_time_s = conf.opt_float("exposure_time"); |  | ||||||
| 
 |  | ||||||
|     return ret; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void PrintController::slice(ProgresIndicatorPtr pri) |  | ||||||
| { |  | ||||||
|     m_print->set_status_callback([pri](int st, const std::string& msg){ |  | ||||||
|         pri->update(unsigned(st), msg); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     m_print->process(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void PrintController::slice() |  | ||||||
| { |  | ||||||
|     auto ctl = GUI::get_appctl(); |  | ||||||
|     auto pri = ctl->global_progress_indicator(); |  | ||||||
|     if(!pri) pri = ctl->create_progress_indicator(100, L("Slicing")); |  | ||||||
|     slice(pri); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| template<> class LayerWriter<Zipper> { |  | ||||||
|     Zipper m_zip; |  | ||||||
| public: |  | ||||||
| 
 |  | ||||||
|     inline LayerWriter(const std::string& zipfile_path): m_zip(zipfile_path) {} |  | ||||||
| 
 |  | ||||||
|     inline void next_entry(const std::string& fname) { m_zip.next_entry(fname); } |  | ||||||
| 
 |  | ||||||
|     inline std::string get_name() const { return m_zip.get_name(); } |  | ||||||
| 
 |  | ||||||
|     template<class T> inline LayerWriter& operator<<(const T& arg) { |  | ||||||
|         m_zip.stream() << arg; return *this; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     inline void close() { m_zip.close(); } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| void PrintController::slice_to_png() |  | ||||||
| { |  | ||||||
| //    using Pointf3 = Vec3d;
 |  | ||||||
| 
 |  | ||||||
| //    auto ctl = GUI::get_appctl();
 |  | ||||||
| //    auto presetbundle = GUI::wxGetApp().preset_bundle;
 |  | ||||||
| 
 |  | ||||||
| //    assert(presetbundle);
 |  | ||||||
| 
 |  | ||||||
| //    // FIXME: this crashes in command line mode
 |  | ||||||
| //    auto pt = presetbundle->printers.get_selected_preset().printer_technology();
 |  | ||||||
| //    if(pt != ptSLA) {
 |  | ||||||
| //        ctl->report_issue(IssueType::ERR, L("Printer technology is not SLA!"),
 |  | ||||||
| //                     L("Error"));
 |  | ||||||
| //        return;
 |  | ||||||
| //    }
 |  | ||||||
| 
 |  | ||||||
| //    auto conf = presetbundle->full_config();
 |  | ||||||
| //    conf.validate();
 |  | ||||||
| 
 |  | ||||||
| //    auto exd = query_png_export_data(conf);
 |  | ||||||
| //    if(exd.zippath.empty()) return;
 |  | ||||||
| 
 |  | ||||||
| //    Print *print = m_print;
 |  | ||||||
| 
 |  | ||||||
| //    try {
 |  | ||||||
| //        print->apply_config(conf);
 |  | ||||||
| //        print->validate();
 |  | ||||||
| //    } catch(std::exception& e) {
 |  | ||||||
| //        ctl->report_issue(IssueType::ERR, e.what(), "Error");
 |  | ||||||
| //        return;
 |  | ||||||
| //    }
 |  | ||||||
| 
 |  | ||||||
| //    // TODO: copy the model and work with the copy only
 |  | ||||||
| //    bool correction = false;
 |  | ||||||
| //    if(exd.corr_x != 1.0 || exd.corr_y != 1.0 || exd.corr_z != 1.0) {
 |  | ||||||
| //        correction = true;
 |  | ||||||
| ////        print->invalidate_all_steps();
 |  | ||||||
| 
 |  | ||||||
| ////        for(auto po : print->objects) {
 |  | ||||||
| ////            po->model_object()->scale(
 |  | ||||||
| ////                        Pointf3(exd.corr_x, exd.corr_y, exd.corr_z)
 |  | ||||||
| ////                        );
 |  | ||||||
| ////            po->model_object()->invalidate_bounding_box();
 |  | ||||||
| ////            po->reload_model_instances();
 |  | ||||||
| ////            po->invalidate_all_steps();
 |  | ||||||
| ////        }
 |  | ||||||
| //    }
 |  | ||||||
| 
 |  | ||||||
| //    // Turn back the correction scaling on the model.
 |  | ||||||
| //    auto scale_back = [this, print, correction, exd]() {
 |  | ||||||
| //        if(correction) { // scale the model back
 |  | ||||||
| ////            print->invalidate_all_steps();
 |  | ||||||
| ////            for(auto po : print->objects) {
 |  | ||||||
| ////                po->model_object()->scale(
 |  | ||||||
| ////                    Pointf3(1.0/exd.corr_x, 1.0/exd.corr_y, 1.0/exd.corr_z)
 |  | ||||||
| ////                );
 |  | ||||||
| ////                po->model_object()->invalidate_bounding_box();
 |  | ||||||
| ////                po->reload_model_instances();
 |  | ||||||
| ////                po->invalidate_all_steps();
 |  | ||||||
| ////            }
 |  | ||||||
| //        }
 |  | ||||||
| //    };
 |  | ||||||
| 
 |  | ||||||
| //    auto print_bb = print->bounding_box();
 |  | ||||||
| //    Vec2d punsc = unscale(print_bb.size());
 |  | ||||||
| 
 |  | ||||||
| //    // If the print does not fit into the print area we should cry about it.
 |  | ||||||
| //    if(px(punsc) > exd.width_mm || py(punsc) > exd.height_mm) {
 |  | ||||||
| //        std::stringstream ss;
 |  | ||||||
| 
 |  | ||||||
| //        ss << L("Print will not fit and will be truncated!") << "\n"
 |  | ||||||
| //           << L("Width needed: ") << px(punsc) << " mm\n"
 |  | ||||||
| //           << L("Height needed: ") << py(punsc) << " mm\n";
 |  | ||||||
| 
 |  | ||||||
| //       if(!ctl->report_issue(IssueType::WARN_Q, ss.str(), L("Warning")))  {
 |  | ||||||
| //            scale_back();
 |  | ||||||
| //            return;
 |  | ||||||
| //       }
 |  | ||||||
| //    }
 |  | ||||||
| 
 |  | ||||||
| //    auto pri = ctl->create_progress_indicator(
 |  | ||||||
| //                200, L("Slicing to zipped png files..."));
 |  | ||||||
| 
 |  | ||||||
| //    pri->on_cancel([&print](){ print->cancel(); });
 |  | ||||||
| 
 |  | ||||||
| //    try {
 |  | ||||||
| //        pri->update(0, L("Slicing..."));
 |  | ||||||
| //        slice(pri);
 |  | ||||||
| //    } catch (std::exception& e) {
 |  | ||||||
| //        ctl->report_issue(IssueType::ERR, e.what(), L("Exception occurred"));
 |  | ||||||
| //        scale_back();
 |  | ||||||
| //        if(print->canceled()) print->restart();
 |  | ||||||
| //        return;
 |  | ||||||
| //    }
 |  | ||||||
| 
 |  | ||||||
| //    auto initstate = unsigned(pri->state());
 |  | ||||||
| //    print->set_status_callback([pri, initstate](int st, const std::string& msg)
 |  | ||||||
| //    {
 |  | ||||||
| //        pri->update(initstate + unsigned(st), msg);
 |  | ||||||
| //    });
 |  | ||||||
| 
 |  | ||||||
| //    try {
 |  | ||||||
| //        print_to<FilePrinterFormat::PNG, Zipper>( *print, exd.zippath,
 |  | ||||||
| //                    exd.width_mm, exd.height_mm,
 |  | ||||||
| //                    exd.width_px, exd.height_px,
 |  | ||||||
| //                    exd.exp_time_s, exd.exp_time_first_s);
 |  | ||||||
| 
 |  | ||||||
| //    } catch (std::exception& e) {
 |  | ||||||
| //        ctl->report_issue(IssueType::ERR, e.what(), L("Exception occurred"));
 |  | ||||||
| //    }
 |  | ||||||
| 
 |  | ||||||
| //    scale_back();
 |  | ||||||
| //    if(print->canceled()) print->restart();
 |  | ||||||
| //    print->set_status_default();
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const PrintConfig &PrintController::config() const |  | ||||||
| { |  | ||||||
|     return m_print->config(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void ProgressIndicator::message_fmt( |  | ||||||
|         const std::string &fmtstr, ...) { |  | ||||||
|     std::stringstream ss; |  | ||||||
|     va_list args; |  | ||||||
|     va_start(args, fmtstr); |  | ||||||
| 
 |  | ||||||
|     auto fmt = fmtstr.begin(); |  | ||||||
| 
 |  | ||||||
|     while (*fmt != '\0') { |  | ||||||
|         if (*fmt == 'd') { |  | ||||||
|             int i = va_arg(args, int); |  | ||||||
|             ss << i << '\n'; |  | ||||||
|         } else if (*fmt == 'c') { |  | ||||||
|             // note automatic conversion to integral type
 |  | ||||||
|             int c = va_arg(args, int); |  | ||||||
|             ss << static_cast<char>(c) << '\n'; |  | ||||||
|         } else if (*fmt == 'f') { |  | ||||||
|             double d = va_arg(args, double); |  | ||||||
|             ss << d << '\n'; |  | ||||||
|         } |  | ||||||
|         ++fmt; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     va_end(args); |  | ||||||
|     message(ss.str()); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void AppController::arrange_model() |  | ||||||
| { |  | ||||||
|     using Coord = libnest2d::TCoord<libnest2d::PointImpl>; |  | ||||||
| 
 |  | ||||||
|     auto ctl = GUI::get_appctl(); |  | ||||||
| 
 |  | ||||||
|     if(m_arranging.load()) return; |  | ||||||
| 
 |  | ||||||
|     // to prevent UI reentrancies
 |  | ||||||
|     m_arranging.store(true); |  | ||||||
| 
 |  | ||||||
|     unsigned count = 0; |  | ||||||
|     for(auto obj : m_model->objects) count += obj->instances.size(); |  | ||||||
| 
 |  | ||||||
|     auto pind = ctl->global_progress_indicator(); |  | ||||||
| 
 |  | ||||||
|     float pmax = 1.0; |  | ||||||
| 
 |  | ||||||
|     if(pind) { |  | ||||||
|         pmax = pind->max(); |  | ||||||
| 
 |  | ||||||
|         // Set the range of the progress to the object count
 |  | ||||||
|         pind->max(count); |  | ||||||
| 
 |  | ||||||
|         pind->on_cancel([this](){ |  | ||||||
|             m_arranging.store(false); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     auto dist = print_ctl()->config().min_object_distance(); |  | ||||||
| 
 |  | ||||||
|     // Create the arranger config
 |  | ||||||
|     auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR); |  | ||||||
| 
 |  | ||||||
|     auto& bedpoints = print_ctl()->config().bed_shape.values; |  | ||||||
|     Polyline bed; bed.points.reserve(bedpoints.size()); |  | ||||||
|     for(auto& v : bedpoints) |  | ||||||
|         bed.append(Point::new_scale(v(0), v(1))); |  | ||||||
| 
 |  | ||||||
|     if(pind) pind->update(0, L("Arranging objects...")); |  | ||||||
| 
 |  | ||||||
|     try { |  | ||||||
|         arr::BedShapeHint hint; |  | ||||||
|         // TODO: from Sasha from GUI
 |  | ||||||
|         hint.type = arr::BedShapeType::WHO_KNOWS; |  | ||||||
| 
 |  | ||||||
|         arr::arrange(*m_model, |  | ||||||
|                       min_obj_distance, |  | ||||||
|                       bed, |  | ||||||
|                       hint, |  | ||||||
|                       false, // create many piles not just one pile
 |  | ||||||
|                       [this, pind, &ctl, count](unsigned rem) { |  | ||||||
|             if(pind) |  | ||||||
|                 pind->update(count - rem, L("Arranging objects...")); |  | ||||||
| 
 |  | ||||||
|             ctl->process_events(); |  | ||||||
|         }, [this] () { return !m_arranging.load(); }); |  | ||||||
|     } catch(std::exception& e) { |  | ||||||
|         std::cerr << e.what() << std::endl; |  | ||||||
|         ctl->report_issue(IssueType::ERR, |  | ||||||
|                         L("Could not arrange model objects! " |  | ||||||
|                         "Some geometries may be invalid."), |  | ||||||
|                         L("Exception occurred")); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Restore previous max value
 |  | ||||||
|     if(pind) { |  | ||||||
|         pind->max(pmax); |  | ||||||
|         pind->update(0, m_arranging.load() ? L("Arranging done.") : |  | ||||||
|                                             L("Arranging canceled.")); |  | ||||||
| 
 |  | ||||||
|         pind->on_cancel(/*remove cancel function*/); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     m_arranging.store(false); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,414 +0,0 @@ | ||||||
| #ifndef APPCONTROLLER_HPP |  | ||||||
| #define APPCONTROLLER_HPP |  | ||||||
| 
 |  | ||||||
| #include <string> |  | ||||||
| #include <vector> |  | ||||||
| #include <memory> |  | ||||||
| #include <atomic> |  | ||||||
| #include <iostream> |  | ||||||
| 
 |  | ||||||
| #include "GUI/ProgressIndicator.hpp" |  | ||||||
| 
 |  | ||||||
| #include <PrintConfig.hpp> |  | ||||||
| 
 |  | ||||||
| namespace Slic3r { |  | ||||||
| 
 |  | ||||||
| class Model; |  | ||||||
| class Print; |  | ||||||
| class PrintObject; |  | ||||||
| class PrintConfig; |  | ||||||
| class ProgressStatusBar; |  | ||||||
| class DynamicPrintConfig; |  | ||||||
| 
 |  | ||||||
| /// A Progress indicator object smart pointer
 |  | ||||||
| using ProgresIndicatorPtr = std::shared_ptr<ProgressIndicator>; |  | ||||||
| 
 |  | ||||||
| using FilePath = std::string; |  | ||||||
| using FilePathList = std::vector<FilePath>; |  | ||||||
| 
 |  | ||||||
| /// Common runtime issue types
 |  | ||||||
| enum class IssueType { |  | ||||||
|     INFO, |  | ||||||
|     WARN, |  | ||||||
|     WARN_Q,     // Warning with a question to continue
 |  | ||||||
|     ERR, |  | ||||||
|     FATAL |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * @brief A boilerplate class for creating application logic. It should provide |  | ||||||
|  * features as issue reporting and progress indication, etc... |  | ||||||
|  * |  | ||||||
|  * The lower lever UI independent classes can be manipulated with a subclass |  | ||||||
|  * of this controller class. We can also catch any exceptions that lower level |  | ||||||
|  * methods could throw and display appropriate errors and warnings. |  | ||||||
|  * |  | ||||||
|  * Note that the outer and the inner interface of this class is free from any |  | ||||||
|  * UI toolkit dependencies. We can implement it with any UI framework or make it |  | ||||||
|  * a cli client. |  | ||||||
|  */ |  | ||||||
| class AppControllerBase { |  | ||||||
| public: |  | ||||||
| 
 |  | ||||||
|     using Ptr = std::shared_ptr<AppControllerBase>; |  | ||||||
| 
 |  | ||||||
|     inline virtual ~AppControllerBase() {} |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * @brief Query some paths from the user. |  | ||||||
|      * |  | ||||||
|      * It should display a file chooser dialog in case of a UI application. |  | ||||||
|      * @param title Title of a possible query dialog. |  | ||||||
|      * @param extensions Recognized file extensions. |  | ||||||
|      * @return Returns a list of paths chosen by the user. |  | ||||||
|      */ |  | ||||||
|     virtual FilePathList query_destination_paths( |  | ||||||
|             const std::string& title, |  | ||||||
|             const std::string& extensions, |  | ||||||
|             const std::string& functionid = "", |  | ||||||
|             const std::string& hint = "") const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * @brief Same as query_destination_paths but works for directories only. |  | ||||||
|      */ |  | ||||||
|     virtual FilePathList query_destination_dirs( |  | ||||||
|             const std::string& title, |  | ||||||
|             const std::string& functionid = "", |  | ||||||
|             const std::string& hint = "") const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * @brief Same as query_destination_paths but returns only one path. |  | ||||||
|      */ |  | ||||||
|     virtual FilePath query_destination_path( |  | ||||||
|             const std::string& title, |  | ||||||
|             const std::string& extensions, |  | ||||||
|             const std::string& functionid = "", |  | ||||||
|             const std::string& hint = "") const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * @brief Report an issue to the user be it fatal or recoverable. |  | ||||||
|      * |  | ||||||
|      * In a UI app this should display some message dialog. |  | ||||||
|      * |  | ||||||
|      * @param issuetype The type of the runtime issue. |  | ||||||
|      * @param description A somewhat longer description of the issue. |  | ||||||
|      * @param brief A very brief description. Can be used for message dialog |  | ||||||
|      * title. |  | ||||||
|      */ |  | ||||||
|     virtual bool report_issue(IssueType issuetype, |  | ||||||
|                               const std::string& description, |  | ||||||
|                               const std::string& brief) = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * @brief Return the global progress indicator for the current controller. |  | ||||||
|      * Can be empty as well. |  | ||||||
|      * |  | ||||||
|      * Only one thread should use the global indicator at a time. |  | ||||||
|      */ |  | ||||||
|     virtual ProgresIndicatorPtr global_progress_indicator() = 0; |  | ||||||
| 
 |  | ||||||
|     virtual void global_progress_indicator(ProgresIndicatorPtr gpri) = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * @brief A predicate telling the caller whether it is the thread that |  | ||||||
|      * created the AppConroller object itself. This probably means that the |  | ||||||
|      * execution is in the UI thread. Otherwise it returns false meaning that |  | ||||||
|      * some worker thread called this function. |  | ||||||
|      * @return Return true for the same caller thread that created this |  | ||||||
|      * object and false for every other. |  | ||||||
|      */ |  | ||||||
|     virtual bool is_main_thread() const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * @brief The frontend supports asynch execution. |  | ||||||
|      * |  | ||||||
|      * A Graphic UI will support this, a CLI may not. This can be used in |  | ||||||
|      * subclass methods to decide whether to start threads for block free UI. |  | ||||||
|      * |  | ||||||
|      * Note that even a progress indicator's update called regularly can solve |  | ||||||
|      * the blocking UI problem in some cases even when an event loop is present. |  | ||||||
|      * This is how wxWidgets gauge work but creating a separate thread will make |  | ||||||
|      * the UI even more fluent. |  | ||||||
|      * |  | ||||||
|      * @return true if a job or method can be executed asynchronously, false |  | ||||||
|      * otherwise. |  | ||||||
|      */ |  | ||||||
|     virtual bool supports_asynch() const = 0; |  | ||||||
| 
 |  | ||||||
|     virtual void process_events() = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * @brief Create a new progress indicator and return a smart pointer to it. |  | ||||||
|      * @param statenum The number of states for the given procedure. |  | ||||||
|      * @param title The title of the procedure. |  | ||||||
|      * @param firstmsg The message for the first subtask to be displayed. |  | ||||||
|      * @return Smart pointer to the created object. |  | ||||||
|      */ |  | ||||||
|     virtual ProgresIndicatorPtr create_progress_indicator( |  | ||||||
|             unsigned statenum, |  | ||||||
|             const std::string& title, |  | ||||||
|             const std::string& firstmsg = "") const = 0; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * @brief Implementation of AppControllerBase for the GUI app |  | ||||||
|  */ |  | ||||||
| class AppControllerGui: public AppControllerBase { |  | ||||||
| private: |  | ||||||
|     class PriData;   // Some structure to store progress indication data
 |  | ||||||
| 
 |  | ||||||
|     // Pimpl data for thread safe progress indication features
 |  | ||||||
|     std::unique_ptr<PriData> m_pri_data; |  | ||||||
| 
 |  | ||||||
| public: |  | ||||||
| 
 |  | ||||||
|     AppControllerGui(); |  | ||||||
| 
 |  | ||||||
|     virtual ~AppControllerGui(); |  | ||||||
| 
 |  | ||||||
|     virtual FilePathList query_destination_paths( |  | ||||||
|             const std::string& title, |  | ||||||
|             const std::string& extensions, |  | ||||||
|             const std::string& functionid, |  | ||||||
|             const std::string& hint) const override; |  | ||||||
| 
 |  | ||||||
|     virtual FilePathList query_destination_dirs( |  | ||||||
|             const std::string& /*title*/, |  | ||||||
|             const std::string& /*functionid*/, |  | ||||||
|             const std::string& /*hint*/) const override { return {}; } |  | ||||||
| 
 |  | ||||||
|     virtual FilePath query_destination_path( |  | ||||||
|             const std::string& title, |  | ||||||
|             const std::string& extensions, |  | ||||||
|             const std::string& functionid, |  | ||||||
|             const std::string& hint) const override; |  | ||||||
| 
 |  | ||||||
|     virtual bool report_issue(IssueType issuetype, |  | ||||||
|                               const std::string& description, |  | ||||||
|                               const std::string& brief = std::string()) override; |  | ||||||
| 
 |  | ||||||
|     virtual ProgresIndicatorPtr global_progress_indicator() override; |  | ||||||
| 
 |  | ||||||
|     virtual void global_progress_indicator(ProgresIndicatorPtr gpri) override; |  | ||||||
| 
 |  | ||||||
|     virtual bool is_main_thread() const override; |  | ||||||
| 
 |  | ||||||
|     virtual bool supports_asynch() const override; |  | ||||||
| 
 |  | ||||||
|     virtual void process_events() override; |  | ||||||
| 
 |  | ||||||
|     virtual ProgresIndicatorPtr create_progress_indicator( |  | ||||||
|             unsigned statenum, |  | ||||||
|             const std::string& title, |  | ||||||
|             const std::string& firstmsg) const override; |  | ||||||
| 
 |  | ||||||
| protected: |  | ||||||
| 
 |  | ||||||
|     // This is a global progress indicator placeholder. In the Slic3r UI it can
 |  | ||||||
|     // contain the progress indicator on the statusbar.
 |  | ||||||
|     ProgresIndicatorPtr m_global_progressind; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class AppControllerCli: public AppControllerBase { |  | ||||||
| 
 |  | ||||||
|     class CliProgress : public ProgressIndicator { |  | ||||||
|         std::string m_msg, m_title; |  | ||||||
|     public: |  | ||||||
|         virtual void message(const std::string& msg) override { |  | ||||||
|             m_msg = msg; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         virtual void title(const std::string& title) override { |  | ||||||
|             m_title = title; |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
| public: |  | ||||||
| 
 |  | ||||||
|     AppControllerCli() { |  | ||||||
|         std::cout << "Cli AppController ready..." << std::endl; |  | ||||||
|         m_global_progressind = std::make_shared<CliProgress>(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     virtual ~AppControllerCli() {} |  | ||||||
| 
 |  | ||||||
|     virtual FilePathList query_destination_paths( |  | ||||||
|             const std::string& /*title*/, |  | ||||||
|             const std::string& /*extensions*/, |  | ||||||
|             const std::string& /*functionid*/, |  | ||||||
|             const std::string& /*hint*/) const override { return {}; } |  | ||||||
| 
 |  | ||||||
|     virtual FilePathList query_destination_dirs( |  | ||||||
|             const std::string& /*title*/, |  | ||||||
|             const std::string& /*functionid*/, |  | ||||||
|             const std::string& /*hint*/) const override { return {}; } |  | ||||||
| 
 |  | ||||||
|     virtual FilePath query_destination_path( |  | ||||||
|             const std::string& /*title*/, |  | ||||||
|             const std::string& /*extensions*/, |  | ||||||
|             const std::string& /*functionid*/, |  | ||||||
|             const std::string& /*hint*/) const override { return "out.zip"; } |  | ||||||
| 
 |  | ||||||
|     virtual bool report_issue(IssueType /*issuetype*/, |  | ||||||
|                               const std::string& description, |  | ||||||
|                               const std::string& brief) override { |  | ||||||
|         std::cerr << brief << ": " << description << std::endl; |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     virtual ProgresIndicatorPtr global_progress_indicator() override { |  | ||||||
|         return m_global_progressind; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     virtual void global_progress_indicator(ProgresIndicatorPtr) override {} |  | ||||||
| 
 |  | ||||||
|     virtual bool is_main_thread() const override { return true; } |  | ||||||
| 
 |  | ||||||
|     virtual bool supports_asynch() const override { return false; } |  | ||||||
| 
 |  | ||||||
|     virtual void process_events() override {} |  | ||||||
| 
 |  | ||||||
|     virtual ProgresIndicatorPtr create_progress_indicator( |  | ||||||
|             unsigned /*statenum*/, |  | ||||||
|             const std::string& /*title*/, |  | ||||||
|             const std::string& /*firstmsg*/) const override { |  | ||||||
|         return std::make_shared<CliProgress>(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| protected: |  | ||||||
| 
 |  | ||||||
|     // This is a global progress indicator placeholder. In the Slic3r UI it can
 |  | ||||||
|     // contain the progress indicator on the statusbar.
 |  | ||||||
|     ProgresIndicatorPtr m_global_progressind; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class Zipper { |  | ||||||
|     struct Impl; |  | ||||||
|     std::unique_ptr<Impl> m_impl; |  | ||||||
| public: |  | ||||||
| 
 |  | ||||||
|     Zipper(const std::string& zipfilepath); |  | ||||||
|     ~Zipper(); |  | ||||||
| 
 |  | ||||||
|     void next_entry(const std::string& fname); |  | ||||||
| 
 |  | ||||||
|     std::string get_name() const; |  | ||||||
| 
 |  | ||||||
|     std::ostream& stream(); |  | ||||||
| 
 |  | ||||||
|     void close(); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * @brief Implementation of the printing logic. |  | ||||||
|  */ |  | ||||||
| class PrintController { |  | ||||||
|     Print *m_print = nullptr; |  | ||||||
|     std::function<void()> m_rempools; |  | ||||||
| protected: |  | ||||||
| 
 |  | ||||||
|     // Data structure with the png export input data
 |  | ||||||
|     struct PngExportData { |  | ||||||
|         std::string zippath;                        // output zip file
 |  | ||||||
|         unsigned long width_px = 1440;              // resolution - rows
 |  | ||||||
|         unsigned long height_px = 2560;             // resolution columns
 |  | ||||||
|         double width_mm = 68.0, height_mm = 120.0;  // dimensions in mm
 |  | ||||||
|         double exp_time_first_s = 35.0;             // first exposure time
 |  | ||||||
|         double exp_time_s = 8.0;                    // global exposure time
 |  | ||||||
|         double corr_x = 1.0;                        // offsetting in x
 |  | ||||||
|         double corr_y = 1.0;                        // offsetting in y
 |  | ||||||
|         double corr_z = 1.0;                        // offsetting in y
 |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     // Should display a dialog with the input fields for printing to png
 |  | ||||||
|     PngExportData query_png_export_data(const DynamicPrintConfig&); |  | ||||||
| 
 |  | ||||||
|     // The previous export data, to pre-populate the dialog
 |  | ||||||
|     PngExportData m_prev_expdata; |  | ||||||
| 
 |  | ||||||
|     void slice(ProgresIndicatorPtr pri); |  | ||||||
| 
 |  | ||||||
| public: |  | ||||||
| 
 |  | ||||||
|     // Must be public for perl to use it
 |  | ||||||
|     explicit inline PrintController(Print *print): m_print(print) {} |  | ||||||
| 
 |  | ||||||
|     PrintController(const PrintController&) = delete; |  | ||||||
|     PrintController(PrintController&&) = delete; |  | ||||||
| 
 |  | ||||||
|     using Ptr = std::unique_ptr<PrintController>; |  | ||||||
| 
 |  | ||||||
|     inline static Ptr create(Print *print) { |  | ||||||
|         return PrintController::Ptr( new PrintController(print) ); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * @brief Slice the loaded print scene. |  | ||||||
|      */ |  | ||||||
|     void slice(); |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * @brief Slice the print into zipped png files. |  | ||||||
|      */ |  | ||||||
|     void slice_to_png(); |  | ||||||
| 
 |  | ||||||
|     const PrintConfig& config() const; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * @brief Top level controller. |  | ||||||
|  */ |  | ||||||
| class AppController { |  | ||||||
|     Model *m_model = nullptr; |  | ||||||
|     PrintController::Ptr printctl; |  | ||||||
|     std::atomic<bool> m_arranging; |  | ||||||
| public: |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * @brief Get the print controller object. |  | ||||||
|      * |  | ||||||
|      * @return Return a raw pointer instead of a smart one for perl to be able |  | ||||||
|      * to use this function and access the print controller. |  | ||||||
|      */ |  | ||||||
|     PrintController * print_ctl() { return printctl.get(); } |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * @brief Set a model object. |  | ||||||
|      * |  | ||||||
|      * @param model A raw pointer to the model object. This can be used from |  | ||||||
|      * perl. |  | ||||||
|      */ |  | ||||||
|     void set_model(Model *model) { m_model = model; } |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * @brief Set the print object from perl. |  | ||||||
|      * |  | ||||||
|      * This will create a print controller that will then be accessible from |  | ||||||
|      * perl. |  | ||||||
|      * @param print A print object which can be a perl-ish extension as well. |  | ||||||
|      */ |  | ||||||
|     void set_print(Print *print) { |  | ||||||
|         printctl = PrintController::create(print); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * @brief Set up a global progress indicator. |  | ||||||
|      * |  | ||||||
|      * In perl we have a progress indicating status bar on the bottom of the |  | ||||||
|      * window which is defined and created in perl. We can pass the ID-s of the |  | ||||||
|      * gauge and the statusbar id and make a wrapper implementation of the |  | ||||||
|      * ProgressIndicator interface so we can use this GUI widget from C++. |  | ||||||
|      * |  | ||||||
|      * This function should be called from perl. |  | ||||||
|      * |  | ||||||
|      * @param gauge_id The ID of the gague widget of the status bar. |  | ||||||
|      * @param statusbar_id The ID of the status bar. |  | ||||||
|      */ |  | ||||||
|     void set_global_progress_indicator(ProgressStatusBar *prs); |  | ||||||
| 
 |  | ||||||
|     void arrange_model(); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #endif // APPCONTROLLER_HPP
 |  | ||||||
|  | @ -1,340 +0,0 @@ | ||||||
| #include "AppController.hpp" |  | ||||||
| 
 |  | ||||||
| #include <wx/stdstream.h> |  | ||||||
| #include <wx/wfstream.h> |  | ||||||
| #include <wx/zipstrm.h> |  | ||||||
| 
 |  | ||||||
| #include <thread> |  | ||||||
| #include <future> |  | ||||||
| 
 |  | ||||||
| #include <slic3r/GUI/GUI.hpp> |  | ||||||
| #include <slic3r/GUI/ProgressStatusBar.hpp> |  | ||||||
| 
 |  | ||||||
| #include <wx/app.h> |  | ||||||
| #include <wx/filedlg.h> |  | ||||||
| #include <wx/msgdlg.h> |  | ||||||
| #include <wx/progdlg.h> |  | ||||||
| #include <wx/gauge.h> |  | ||||||
| #include <wx/statusbr.h> |  | ||||||
| #include <wx/event.h> |  | ||||||
| 
 |  | ||||||
| // This source file implements the UI dependent methods of the AppControllers.
 |  | ||||||
| // It will be clear what is needed to be reimplemented in case of a UI framework
 |  | ||||||
| // change or a CLI client creation. In this particular case we use wxWidgets to
 |  | ||||||
| // implement everything.
 |  | ||||||
| 
 |  | ||||||
| namespace Slic3r { |  | ||||||
| 
 |  | ||||||
| bool AppControllerGui::supports_asynch() const |  | ||||||
| { |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void AppControllerGui::process_events() |  | ||||||
| { |  | ||||||
|     wxYieldIfNeeded(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| FilePathList AppControllerGui::query_destination_paths( |  | ||||||
|         const std::string &title, |  | ||||||
|         const std::string &extensions, |  | ||||||
|         const std::string &/*functionid*/, |  | ||||||
|         const std::string& hint) const |  | ||||||
| { |  | ||||||
| 
 |  | ||||||
|     wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) ); |  | ||||||
|     dlg.SetWildcard(extensions); |  | ||||||
| 
 |  | ||||||
|     dlg.SetFilename(hint); |  | ||||||
| 
 |  | ||||||
|     FilePathList ret; |  | ||||||
| 
 |  | ||||||
|     if(dlg.ShowModal() == wxID_OK) { |  | ||||||
|         wxArrayString paths; |  | ||||||
|         dlg.GetPaths(paths); |  | ||||||
|         for(auto& p : paths) ret.push_back(p.ToStdString()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return ret; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| FilePath AppControllerGui::query_destination_path( |  | ||||||
|         const std::string &title, |  | ||||||
|         const std::string &extensions, |  | ||||||
|         const std::string &/*functionid*/, |  | ||||||
|         const std::string& hint) const |  | ||||||
| { |  | ||||||
|     wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) ); |  | ||||||
|     dlg.SetWildcard(extensions); |  | ||||||
| 
 |  | ||||||
|     dlg.SetFilename(hint); |  | ||||||
| 
 |  | ||||||
|     FilePath ret; |  | ||||||
| 
 |  | ||||||
|     if(dlg.ShowModal() == wxID_OK) { |  | ||||||
|         ret = FilePath(dlg.GetPath()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return ret; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool AppControllerGui::report_issue(IssueType issuetype, |  | ||||||
|                                  const std::string &description, |  | ||||||
|                                  const std::string &brief) |  | ||||||
| { |  | ||||||
|     auto icon = wxICON_INFORMATION; |  | ||||||
|     auto style = wxOK|wxCENTRE; |  | ||||||
|     switch(issuetype) { |  | ||||||
|     case IssueType::INFO:   break; |  | ||||||
|     case IssueType::WARN:   icon = wxICON_WARNING; break; |  | ||||||
|     case IssueType::WARN_Q: icon = wxICON_WARNING; style |= wxCANCEL; break; |  | ||||||
|     case IssueType::ERR: |  | ||||||
|     case IssueType::FATAL:  icon = wxICON_ERROR; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     auto ret = wxMessageBox(_(description), _(brief), icon | style); |  | ||||||
|     return ret != wxCANCEL; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent); |  | ||||||
| 
 |  | ||||||
| struct Zipper::Impl { |  | ||||||
|     wxFileName fpath; |  | ||||||
|     wxFFileOutputStream zipfile; |  | ||||||
|     wxZipOutputStream zipstream; |  | ||||||
|     wxStdOutputStream pngstream; |  | ||||||
| 
 |  | ||||||
|     Impl(const std::string& zipfile_path): |  | ||||||
|         fpath(zipfile_path), |  | ||||||
|         zipfile(zipfile_path), |  | ||||||
|         zipstream(zipfile), |  | ||||||
|         pngstream(zipstream) |  | ||||||
|     { |  | ||||||
|         if(!zipfile.IsOk()) |  | ||||||
|             throw std::runtime_error(L("Cannot create zip file.")); |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| Zipper::Zipper(const std::string &zipfilepath) |  | ||||||
| { |  | ||||||
|     m_impl.reset(new Impl(zipfilepath)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| Zipper::~Zipper() {} |  | ||||||
| 
 |  | ||||||
| void Zipper::next_entry(const std::string &fname) |  | ||||||
| { |  | ||||||
|     m_impl->zipstream.PutNextEntry(fname); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::string Zipper::get_name() const |  | ||||||
| { |  | ||||||
|     return m_impl->fpath.GetName().ToStdString(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::ostream &Zipper::stream() |  | ||||||
| { |  | ||||||
|     return m_impl->pngstream; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void Zipper::close() |  | ||||||
| { |  | ||||||
|     m_impl->zipstream.Close(); |  | ||||||
|     m_impl->zipfile.Close(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| namespace  { |  | ||||||
| 
 |  | ||||||
| /*
 |  | ||||||
|  * A simple thread safe progress dialog implementation that can be used from |  | ||||||
|  * the main thread as well. |  | ||||||
|  */ |  | ||||||
| class GuiProgressIndicator: |  | ||||||
|         public ProgressIndicator, public wxEvtHandler { |  | ||||||
| 
 |  | ||||||
|     wxProgressDialog m_gauge; |  | ||||||
|     using Base = ProgressIndicator; |  | ||||||
|     wxString m_message; |  | ||||||
|     int m_range; wxString m_title; |  | ||||||
|     bool m_is_asynch = false; |  | ||||||
| 
 |  | ||||||
|     const int m_id = wxWindow::NewControlId(); |  | ||||||
| 
 |  | ||||||
|     // status update handler
 |  | ||||||
|     void _state( wxCommandEvent& evt) { |  | ||||||
|         unsigned st = evt.GetInt(); |  | ||||||
|         m_message = evt.GetString(); |  | ||||||
|         _state(st); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Status update implementation
 |  | ||||||
|     void _state( unsigned st) { |  | ||||||
|         if(!m_gauge.IsShown()) m_gauge.ShowModal(); |  | ||||||
|         Base::state(st); |  | ||||||
|         if(!m_gauge.Update(static_cast<int>(st), m_message)) { |  | ||||||
|             cancel(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| public: |  | ||||||
| 
 |  | ||||||
|     /// Setting whether it will be used from the UI thread or some worker thread
 |  | ||||||
|     inline void asynch(bool is) { m_is_asynch = is; } |  | ||||||
| 
 |  | ||||||
|     /// Get the mode of parallel operation.
 |  | ||||||
|     inline bool asynch() const { return m_is_asynch; } |  | ||||||
| 
 |  | ||||||
|     inline GuiProgressIndicator(int range, const wxString& title, |  | ||||||
|                                 const wxString& firstmsg) : |  | ||||||
|         m_gauge(title, firstmsg, range, wxTheApp->GetTopWindow(), |  | ||||||
|                wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT), |  | ||||||
| 
 |  | ||||||
|         m_message(firstmsg), |  | ||||||
|         m_range(range), m_title(title) |  | ||||||
|     { |  | ||||||
|         Base::max(static_cast<float>(range)); |  | ||||||
|         Base::states(static_cast<unsigned>(range)); |  | ||||||
| 
 |  | ||||||
|         Bind(PROGRESS_STATUS_UPDATE_EVENT, |  | ||||||
|              &GuiProgressIndicator::_state, |  | ||||||
|              this, m_id); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     virtual void state(float val) override { |  | ||||||
|         state(static_cast<unsigned>(val)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     void state(unsigned st) { |  | ||||||
|         // send status update event
 |  | ||||||
|         if(m_is_asynch) { |  | ||||||
|             auto evt = new wxCommandEvent(PROGRESS_STATUS_UPDATE_EVENT, m_id); |  | ||||||
|             evt->SetInt(st); |  | ||||||
|             evt->SetString(m_message); |  | ||||||
|             wxQueueEvent(this, evt); |  | ||||||
|         } else _state(st); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     virtual void message(const std::string & msg) override { |  | ||||||
|         m_message = _(msg); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     virtual void messageFmt(const std::string& fmt, ...) { |  | ||||||
|         va_list arglist; |  | ||||||
|         va_start(arglist, fmt); |  | ||||||
|         m_message = wxString::Format(_(fmt), arglist); |  | ||||||
|         va_end(arglist); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     virtual void title(const std::string & title) override { |  | ||||||
|         m_title = _(title); |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ProgresIndicatorPtr AppControllerGui::create_progress_indicator( |  | ||||||
|         unsigned statenum, |  | ||||||
|         const std::string& title, |  | ||||||
|         const std::string& firstmsg) const |  | ||||||
| { |  | ||||||
|     auto pri = |  | ||||||
|             std::make_shared<GuiProgressIndicator>(statenum, title, firstmsg); |  | ||||||
| 
 |  | ||||||
|     // We set up the mode of operation depending of the creator thread's
 |  | ||||||
|     // identity
 |  | ||||||
|     pri->asynch(!is_main_thread()); |  | ||||||
| 
 |  | ||||||
|     return pri; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| namespace { |  | ||||||
| 
 |  | ||||||
| class Wrapper: public ProgressIndicator, public wxEvtHandler { |  | ||||||
|     ProgressStatusBar *m_sbar; |  | ||||||
|     using Base = ProgressIndicator; |  | ||||||
|     wxString m_message; |  | ||||||
|     AppControllerBase& m_ctl; |  | ||||||
| 
 |  | ||||||
|     void showProgress(bool show = true) { |  | ||||||
|         m_sbar->show_progress(show); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     void _state(unsigned st) { |  | ||||||
|         if( st <= ProgressIndicator::max() ) { |  | ||||||
|             Base::state(st); |  | ||||||
|             m_sbar->set_status_text(m_message); |  | ||||||
|             m_sbar->set_progress(st); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // status update handler
 |  | ||||||
|     void _state( wxCommandEvent& evt) { |  | ||||||
|         unsigned st = evt.GetInt(); _state(st); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const int id_ = wxWindow::NewControlId(); |  | ||||||
| 
 |  | ||||||
| public: |  | ||||||
| 
 |  | ||||||
|     inline Wrapper(ProgressStatusBar *sbar, |  | ||||||
|                    AppControllerBase& ctl): |  | ||||||
|         m_sbar(sbar), m_ctl(ctl) |  | ||||||
|     { |  | ||||||
|         Base::max(static_cast<float>(m_sbar->get_range())); |  | ||||||
|         Base::states(static_cast<unsigned>(m_sbar->get_range())); |  | ||||||
| 
 |  | ||||||
|         Bind(PROGRESS_STATUS_UPDATE_EVENT, |  | ||||||
|              &Wrapper::_state, |  | ||||||
|              this, id_); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     virtual void state(float val) override { |  | ||||||
|         state(unsigned(val)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     virtual void max(float val) override { |  | ||||||
|         if(val > 1.0) { |  | ||||||
|             m_sbar->set_range(static_cast<int>(val)); |  | ||||||
|             ProgressIndicator::max(val); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     void state(unsigned st) { |  | ||||||
|         if(!m_ctl.is_main_thread()) { |  | ||||||
|             auto evt = new wxCommandEvent(PROGRESS_STATUS_UPDATE_EVENT, id_); |  | ||||||
|             evt->SetInt(st); |  | ||||||
|             wxQueueEvent(this, evt); |  | ||||||
|         } else { |  | ||||||
|             _state(st); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     virtual void message(const std::string & msg) override { |  | ||||||
|         m_message = _(msg); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     virtual void message_fmt(const std::string& fmt, ...) override { |  | ||||||
|         va_list arglist; |  | ||||||
|         va_start(arglist, fmt); |  | ||||||
|         m_message = wxString::Format(_(fmt), arglist); |  | ||||||
|         va_end(arglist); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     virtual void title(const std::string & /*title*/) override {} |  | ||||||
| 
 |  | ||||||
|     virtual void on_cancel(CancelFn fn) override { |  | ||||||
|         m_sbar->set_cancel_callback(fn); |  | ||||||
|         Base::on_cancel(fn); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void AppController::set_global_progress_indicator(ProgressStatusBar *prsb) |  | ||||||
| { |  | ||||||
|     if(prsb) { |  | ||||||
|         auto ctl = GUI::get_appctl(); |  | ||||||
|         ctl->global_progress_indicator(std::make_shared<Wrapper>(prsb, *ctl)); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -121,9 +121,6 @@ add_library(libslic3r_gui STATIC | ||||||
|     Utils/Time.hpp |     Utils/Time.hpp | ||||||
|     Utils/HexFile.cpp |     Utils/HexFile.cpp | ||||||
|     Utils/HexFile.hpp |     Utils/HexFile.hpp | ||||||
|     AppController.hpp |  | ||||||
|     AppController.cpp |  | ||||||
|     AppControllerWx.cpp |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| target_link_libraries(libslic3r_gui libslic3r avrdude) | target_link_libraries(libslic3r_gui libslic3r avrdude) | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| #include "GUI.hpp" | #include "GUI.hpp" | ||||||
| #include "GUI_App.hpp" | #include "GUI_App.hpp" | ||||||
| #include "../AppController.hpp" |  | ||||||
| #include "WipeTowerDialog.hpp" | #include "WipeTowerDialog.hpp" | ||||||
| 
 | 
 | ||||||
| #include <assert.h> | #include <assert.h> | ||||||
|  | @ -453,23 +452,4 @@ void desktop_open_datadir_folder() | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| namespace { |  | ||||||
| AppControllerPtr g_appctl; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| AppControllerPtr get_appctl() |  | ||||||
| { |  | ||||||
|     return g_appctl; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void set_cli_appctl() |  | ||||||
| { |  | ||||||
|     g_appctl = std::make_shared<AppControllerCli>(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void set_gui_appctl() |  | ||||||
| { |  | ||||||
|     g_appctl = std::make_shared<AppControllerGui>(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } } | } } | ||||||
|  |  | ||||||
|  | @ -11,7 +11,6 @@ | ||||||
| 
 | 
 | ||||||
| #include "Tab.hpp" | #include "Tab.hpp" | ||||||
| #include "PresetBundle.hpp" | #include "PresetBundle.hpp" | ||||||
| #include "../AppController.hpp" |  | ||||||
| #include "ProgressStatusBar.hpp" | #include "ProgressStatusBar.hpp" | ||||||
| #include "3DScene.hpp" | #include "3DScene.hpp" | ||||||
| #include "Print.hpp" | #include "Print.hpp" | ||||||
|  | @ -30,8 +29,6 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL | ||||||
|         m_no_plater(no_plater), |         m_no_plater(no_plater), | ||||||
|         m_loaded(loaded) |         m_loaded(loaded) | ||||||
| { | { | ||||||
|     m_appController = new Slic3r::AppController(); |  | ||||||
| 
 |  | ||||||
|     // Load the icon either from the exe, or from the ico file.
 |     // Load the icon either from the exe, or from the ico file.
 | ||||||
| #if _WIN32 | #if _WIN32 | ||||||
|     { |     { | ||||||
|  | @ -59,14 +56,6 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL | ||||||
|                                  SLIC3R_VERSION + |                                  SLIC3R_VERSION + | ||||||
|                                  _(L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases"))); |                                  _(L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases"))); | ||||||
| 
 | 
 | ||||||
|     m_appController->set_model(&m_plater->model()); |  | ||||||
|     m_appController->set_print(&m_plater->print()); |  | ||||||
| 
 |  | ||||||
| 	GUI::set_gui_appctl(); |  | ||||||
| 
 |  | ||||||
| 	// Make the global status bar and its progress indicator available in C++
 |  | ||||||
|     m_appController->set_global_progress_indicator(m_statusbar); |  | ||||||
| 
 |  | ||||||
|     m_loaded = true; |     m_loaded = true; | ||||||
| 
 | 
 | ||||||
|     // initialize layout
 |     // initialize layout
 | ||||||
|  | @ -373,7 +362,7 @@ void MainFrame::slice_to_png() | ||||||
| { | { | ||||||
| //     m_plater->stop_background_process();
 | //     m_plater->stop_background_process();
 | ||||||
| //     m_plater->async_apply_config();
 | //     m_plater->async_apply_config();
 | ||||||
|     m_appController->print_ctl()->slice_to_png(); | //    m_appController->print_ctl()->slice_to_png();
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG".
 | // To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG".
 | ||||||
|  |  | ||||||
|  | @ -20,7 +20,6 @@ class wxProgressDialog; | ||||||
| namespace Slic3r { | namespace Slic3r { | ||||||
| 
 | 
 | ||||||
| class ProgressStatusBar; | class ProgressStatusBar; | ||||||
| class AppController; |  | ||||||
| 
 | 
 | ||||||
| // #define _(s)    Slic3r::GUI::I18N::translate((s))
 | // #define _(s)    Slic3r::GUI::I18N::translate((s))
 | ||||||
| 
 | 
 | ||||||
|  | @ -54,7 +53,6 @@ class MainFrame : public wxFrame | ||||||
|     wxString    m_qs_last_output_file = wxEmptyString; |     wxString    m_qs_last_output_file = wxEmptyString; | ||||||
|     wxString    m_last_config = wxEmptyString; |     wxString    m_last_config = wxEmptyString; | ||||||
| 
 | 
 | ||||||
|     AppController*                  m_appController { nullptr }; |  | ||||||
|     std::map<std::string, Tab*>     m_options_tabs; |     std::map<std::string, Tab*>     m_options_tabs; | ||||||
| 
 | 
 | ||||||
|     wxMenuItem* m_menu_item_reslice_now { nullptr }; |     wxMenuItem* m_menu_item_reslice_now { nullptr }; | ||||||
|  | @ -97,8 +95,6 @@ public: | ||||||
|     void        select_tab(size_t tab) const; |     void        select_tab(size_t tab) const; | ||||||
|     void        select_view(const std::string& direction); |     void        select_view(const std::string& direction); | ||||||
| 
 | 
 | ||||||
|     AppController* app_controller() { return m_appController; } |  | ||||||
| 
 |  | ||||||
|     std::vector<PresetTab>& get_preset_tabs(); |     std::vector<PresetTab>& get_preset_tabs(); | ||||||
| 
 | 
 | ||||||
|     Plater*             m_plater { nullptr }; |     Plater*             m_plater { nullptr }; | ||||||
|  |  | ||||||
|  | @ -25,6 +25,7 @@ | ||||||
| #include "libslic3r/libslic3r.h" | #include "libslic3r/libslic3r.h" | ||||||
| #include "libslic3r/PrintConfig.hpp" | #include "libslic3r/PrintConfig.hpp" | ||||||
| #include "libslic3r/Model.hpp" | #include "libslic3r/Model.hpp" | ||||||
|  | #include "libslic3r/ModelArrange.hpp" | ||||||
| #include "libslic3r/Print.hpp" | #include "libslic3r/Print.hpp" | ||||||
| #include "libslic3r/SLAPrint.hpp" | #include "libslic3r/SLAPrint.hpp" | ||||||
| #include "libslic3r/GCode/PreviewData.hpp" | #include "libslic3r/GCode/PreviewData.hpp" | ||||||
|  | @ -33,7 +34,7 @@ | ||||||
| #include "libslic3r/Format/STL.hpp" | #include "libslic3r/Format/STL.hpp" | ||||||
| #include "libslic3r/Format/AMF.hpp" | #include "libslic3r/Format/AMF.hpp" | ||||||
| #include "libslic3r/Format/3mf.hpp" | #include "libslic3r/Format/3mf.hpp" | ||||||
| #include "slic3r/AppController.hpp" | //#include "slic3r/AppController.hpp"
 | ||||||
| #include "GUI.hpp" | #include "GUI.hpp" | ||||||
| #include "GUI_App.hpp" | #include "GUI_App.hpp" | ||||||
| #include "GUI_ObjectList.hpp" | #include "GUI_ObjectList.hpp" | ||||||
|  | @ -863,6 +864,7 @@ struct Plater::priv | ||||||
|     wxGLCanvas *canvas3D;    // TODO: Use GLCanvas3D when we can
 |     wxGLCanvas *canvas3D;    // TODO: Use GLCanvas3D when we can
 | ||||||
|     Preview *preview; |     Preview *preview; | ||||||
|     BackgroundSlicingProcess    background_process; |     BackgroundSlicingProcess    background_process; | ||||||
|  |     std::atomic<bool>           arranging; | ||||||
| 
 | 
 | ||||||
|     wxTimer                     background_process_timer; |     wxTimer                     background_process_timer; | ||||||
| 
 | 
 | ||||||
|  | @ -1446,13 +1448,86 @@ void Plater::priv::mirror(Axis axis) | ||||||
| 
 | 
 | ||||||
| void Plater::priv::arrange() | void Plater::priv::arrange() | ||||||
| { | { | ||||||
|  |     // don't do anything if currently arranging. Then this is a re-entrance
 | ||||||
|  |     if(arranging.load()) return; | ||||||
|  | 
 | ||||||
|  |     // Guard the arrange process
 | ||||||
|  |     arranging.store(true); | ||||||
|  | 
 | ||||||
|  |     _3DScene::enable_toolbar_item(canvas3D, "arrange", can_arrange()); | ||||||
|  | 
 | ||||||
|     this->background_process.stop(); |     this->background_process.stop(); | ||||||
|     main_frame->app_controller()->arrange_model(); |     unsigned count = 0; | ||||||
|  |     for(auto obj : model.objects) count += obj->instances.size(); | ||||||
|  | 
 | ||||||
|  |     auto prev_range = statusbar()->get_range(); | ||||||
|  |     statusbar()->set_range(count); | ||||||
|  | 
 | ||||||
|  |     auto statusfn = [this, count] (unsigned st, const std::string& msg) { | ||||||
|  |         /* // In case we would run the arrange asynchronously
 | ||||||
|  |         wxCommandEvent event(EVT_PROGRESS_BAR); | ||||||
|  |         event.SetInt(st); | ||||||
|  |         event.SetString(msg); | ||||||
|  |         wxQueueEvent(this->q, event.Clone()); */ | ||||||
|  |         statusbar()->set_progress(count - st); | ||||||
|  |         statusbar()->set_status_text(msg); | ||||||
|  | 
 | ||||||
|  |         // ok, this is dangerous, but we are protected by the atomic flag
 | ||||||
|  |         // 'arranging'. This call is needed for the cancel button to work.
 | ||||||
|  |         wxYieldIfNeeded(); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     statusbar()->set_cancel_callback([this, statusfn](){ | ||||||
|  |         arranging.store(false); | ||||||
|  |         statusfn(0, L("Arranging canceled")); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     static const std::string arrangestr = L("Arranging"); | ||||||
|  | 
 | ||||||
|  |     // 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);
 | ||||||
|  | 
 | ||||||
|  |     auto min_obj_distance = static_cast<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))); | ||||||
|  | 
 | ||||||
|  |     statusfn(0, arrangestr); | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |         arr::BedShapeHint hint; | ||||||
|  | 
 | ||||||
|  |         // TODO: from Sasha from GUI or
 | ||||||
|  |         hint.type = arr::BedShapeType::WHO_KNOWS; | ||||||
|  | 
 | ||||||
|  |         arr::arrange(model, | ||||||
|  |                      min_obj_distance, | ||||||
|  |                      bed, | ||||||
|  |                      hint, | ||||||
|  |                      false, // create many piles not just one pile
 | ||||||
|  |                      [statusfn](unsigned st) { statusfn(st, arrangestr); }, | ||||||
|  |                      [this] () { return !arranging.load(); }); | ||||||
|  |     } catch(std::exception& /*e*/) { | ||||||
|  |         GUI::show_error(this->q, L("Could not arrange model objects! " | ||||||
|  |                                    "Some geometries may be invalid.")); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     statusfn(0, L("Arranging done.")); | ||||||
|  |     statusbar()->set_range(prev_range); | ||||||
|  |     statusbar()->set_cancel_callback(); // remove cancel button
 | ||||||
|  |     arranging.store(false); | ||||||
|  | 
 | ||||||
|     this->schedule_background_process(); |     this->schedule_background_process(); | ||||||
| 
 | 
 | ||||||
|     // ignore arrange failures on purpose: user has visual feedback and we don't need to warn him
 |     // ignore arrange failures on purpose: user has visual feedback and we
 | ||||||
|     // when parts don't fit in print bed
 |     // don't need to warn him when parts don't fit in print bed
 | ||||||
| 
 | 
 | ||||||
|  |     _3DScene::enable_toolbar_item(canvas3D, "arrange", can_arrange()); | ||||||
|     update(); |     update(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -1908,7 +1983,7 @@ bool Plater::priv::can_delete_all() const | ||||||
| 
 | 
 | ||||||
| bool Plater::priv::can_arrange() const | bool Plater::priv::can_arrange() const | ||||||
| { | { | ||||||
|     return !model.objects.empty(); |     return !model.objects.empty() && !arranging.load(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool Plater::priv::can_mirror() const | bool Plater::priv::can_mirror() const | ||||||
|  |  | ||||||
|  | @ -1,29 +0,0 @@ | ||||||
| %module{Slic3r::XS}; |  | ||||||
| 
 |  | ||||||
| %{ |  | ||||||
| #include <xsinit.h> |  | ||||||
| #include "slic3r/AppController.hpp" |  | ||||||
| #include "libslic3r/Model.hpp" |  | ||||||
| #include "libslic3r/Print.hpp" |  | ||||||
| #include "slic3r/GUI/ProgressStatusBar.hpp" |  | ||||||
| %} |  | ||||||
| 
 |  | ||||||
| %name{Slic3r::PrintController} class PrintController { |  | ||||||
| 
 |  | ||||||
|     PrintController(Print *print); |  | ||||||
| 
 |  | ||||||
|     void slice_to_png();  |  | ||||||
|     void slice(); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| %name{Slic3r::AppController} class AppController { |  | ||||||
| 
 |  | ||||||
|     AppController(); |  | ||||||
| 
 |  | ||||||
|     PrintController *print_ctl(); |  | ||||||
|     void set_model(Model *model); |  | ||||||
|     void set_print(Print *print); |  | ||||||
|     void set_global_progress_indicator(ProgressStatusBar *prs); |  | ||||||
| 
 |  | ||||||
|     void arrange_model(); |  | ||||||
| }; |  | ||||||
|  | @ -216,8 +216,6 @@ Ref<PrintObjectSupportMaterial>   O_OBJECT_SLIC3R_T | ||||||
| Clone<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T | Clone<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T | ||||||
| 
 | 
 | ||||||
| AppConfig*	                O_OBJECT_SLIC3R | AppConfig*	                O_OBJECT_SLIC3R | ||||||
| AppController*              O_OBJECT_SLIC3R |  | ||||||
| PrintController*            O_OBJECT_SLIC3R |  | ||||||
| Ref<AppConfig>         		O_OBJECT_SLIC3R_T | Ref<AppConfig>         		O_OBJECT_SLIC3R_T | ||||||
| BackgroundSlicingProcess*   O_OBJECT_SLIC3R | BackgroundSlicingProcess*   O_OBJECT_SLIC3R | ||||||
| Ref<BackgroundSlicingProcess> O_OBJECT_SLIC3R_T | Ref<BackgroundSlicingProcess> O_OBJECT_SLIC3R_T | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 tamasmeszaros
						tamasmeszaros