mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Merge branch 'dev_native' of https://github.com/Prusa3d/Slic3r into dev_native
This commit is contained in:
		
						commit
						4f200c5bd5
					
				
					 22 changed files with 1037 additions and 1987 deletions
				
			
		|  | @ -108,6 +108,7 @@ add_library(libslic3r STATIC | |||
|     Model.cpp | ||||
|     Model.hpp | ||||
|     ModelArrange.hpp | ||||
|     ModelArrange.cpp | ||||
|     MotionPlanner.cpp | ||||
|     MotionPlanner.hpp | ||||
|     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 | ||||
| 
 | ||||
| #include "Model.hpp" | ||||
| #include "SVG.hpp" | ||||
| #include <libnest2d.h> | ||||
| 
 | ||||
| #include <numeric> | ||||
| #include <ClipperUtils.hpp> | ||||
| 
 | ||||
| #include <boost/geometry/index/rtree.hpp> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| class Model; | ||||
| 
 | ||||
| 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 { | ||||
|     Point center_; | ||||
|     double radius_; | ||||
|  | @ -556,9 +20,9 @@ public: | |||
|     inline double radius() const { return radius_; } | ||||
|     inline const Point& center() const { return center_; } | ||||
|     inline operator bool() { return !std::isnan(radius_); } | ||||
|     inline operator lnCircle() { | ||||
|         return lnCircle({center_(0), center_(1)}, radius_); | ||||
|     } | ||||
| //    inline operator lnCircle() {
 | ||||
| //        return lnCircle({center_(0), center_(1)}, radius_);
 | ||||
| //    }
 | ||||
| }; | ||||
| 
 | ||||
| enum class BedShapeType { | ||||
|  | @ -577,109 +41,7 @@ struct BedShapeHint { | |||
|     } shape; | ||||
| }; | ||||
| 
 | ||||
| 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); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| BedShapeHint bedShape(const Polyline& bed); | ||||
| 
 | ||||
| /**
 | ||||
|  * \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. | ||||
|  * \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, | ||||
|              BedShapeHint bedhint, | ||||
|              bool first_bin_only, | ||||
|              std::function<void(unsigned)> progressind, | ||||
|              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; | ||||
| } | ||||
|              std::function<bool(void)> stopcondition); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| } | ||||
| #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)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  | @ -123,9 +123,6 @@ add_library(libslic3r_gui STATIC | |||
|     Utils/Time.hpp | ||||
|     Utils/HexFile.cpp | ||||
|     Utils/HexFile.hpp | ||||
|     AppController.hpp | ||||
|     AppController.cpp | ||||
|     AppControllerWx.cpp | ||||
| ) | ||||
| 
 | ||||
| target_link_libraries(libslic3r_gui libslic3r avrdude) | ||||
|  |  | |||
|  | @ -3658,7 +3658,6 @@ void GLCanvas3D::reload_scene(bool force) | |||
| { | ||||
|     if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr)) | ||||
|         return; | ||||
| 
 | ||||
| #if !ENABLE_USE_UNIQUE_GLCONTEXT | ||||
|     // ensures this canvas is current
 | ||||
|     if (!set_current()) | ||||
|  | @ -3695,7 +3694,7 @@ void GLCanvas3D::reload_scene(bool force) | |||
| 
 | ||||
|     if (m_regenerate_volumes) | ||||
|     { | ||||
|         if (m_config->has("nozzle_diameter")) | ||||
|         if (m_config->has("nozzle_diameter") && wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) | ||||
|         { | ||||
|             // Should the wipe tower be visualized ?
 | ||||
|             unsigned int extruders_count = (unsigned int)dynamic_cast<const ConfigOptionFloats*>(m_config->option("nozzle_diameter"))->values.size(); | ||||
|  | @ -3717,7 +3716,6 @@ void GLCanvas3D::reload_scene(bool force) | |||
|                 float depth = m_print->get_wipe_tower_depth(); | ||||
|                 if (!m_print->is_step_done(psWipeTower)) | ||||
|                     depth = (900.f/w) * (float)(extruders_count - 1) ; | ||||
| 
 | ||||
|                 m_volumes.load_wipe_tower_preview(1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower), | ||||
|                                                   m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); | ||||
|             } | ||||
|  | @ -5165,7 +5163,7 @@ void GLCanvas3D::_update_gizmos_data() | |||
| 
 | ||||
|     bool enable_move_z = !m_selection.is_wipe_tower(); | ||||
|     m_gizmos.enable_grabber(Gizmos::Move, 2, enable_move_z); | ||||
|     bool enable_scale_xyz = m_selection.is_single_full_instance(); | ||||
|     bool enable_scale_xyz = m_selection.is_single_full_instance() || m_selection.is_single_volume() || m_selection.is_single_modifier(); | ||||
|     for (int i = 0; i < 6; ++i) | ||||
|     { | ||||
|         m_gizmos.enable_grabber(Gizmos::Scale, i, enable_scale_xyz); | ||||
|  | @ -6207,16 +6205,18 @@ void GLCanvas3D::_load_shells() | |||
|         ++object_id; | ||||
|     } | ||||
| 
 | ||||
|     // adds wipe tower's volume
 | ||||
|     double max_z = m_print->objects()[0]->model_object()->get_model()->bounding_box().max(2); | ||||
|     const PrintConfig& config = m_print->config(); | ||||
|     unsigned int extruders_count = config.nozzle_diameter.size(); | ||||
|     if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { | ||||
|         float depth = m_print->get_wipe_tower_depth(); | ||||
|         if (!m_print->is_step_done(psWipeTower)) | ||||
|             depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; | ||||
|         m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, | ||||
|                                           m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower), m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); | ||||
|     if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) { | ||||
|         // adds wipe tower's volume
 | ||||
|         double max_z = m_print->objects()[0]->model_object()->get_model()->bounding_box().max(2); | ||||
|         const PrintConfig& config = m_print->config(); | ||||
|         unsigned int extruders_count = config.nozzle_diameter.size(); | ||||
|         if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { | ||||
|             float depth = m_print->get_wipe_tower_depth(); | ||||
|             if (!m_print->is_step_done(psWipeTower)) | ||||
|                 depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; | ||||
|             m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, | ||||
|                                               m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower), m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -343,7 +343,7 @@ void GLGizmoRotate::on_render(const GLCanvas3D::Selection& selection) const | |||
|         return; | ||||
| 
 | ||||
|     const BoundingBoxf3& box = selection.get_bounding_box(); | ||||
|     bool single_instance = selection.is_single_full_instance(); | ||||
|     bool single_selection = selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume(); | ||||
| 
 | ||||
|     std::string axis; | ||||
|     switch (m_axis) | ||||
|  | @ -353,7 +353,7 @@ void GLGizmoRotate::on_render(const GLCanvas3D::Selection& selection) const | |||
|     case Z: { axis = "Z: "; break; } | ||||
|     } | ||||
| 
 | ||||
|     if ((single_instance && (m_hover_id == 0)) || m_dragging) | ||||
|     if ((single_selection && (m_hover_id == 0)) || m_dragging) | ||||
|         set_tooltip(axis + format((float)Geometry::rad2deg(m_angle), 4) + "\u00B0"); | ||||
|     else | ||||
|     { | ||||
|  | @ -720,19 +720,26 @@ void GLGizmoScale3D::on_process_double_click() | |||
| void GLGizmoScale3D::on_render(const GLCanvas3D::Selection& selection) const | ||||
| { | ||||
|     bool single_instance = selection.is_single_full_instance(); | ||||
|     bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); | ||||
|     bool single_selection = single_instance || single_volume; | ||||
| 
 | ||||
|     Vec3f scale = 100.0f * Vec3f::Ones(); | ||||
| #if ENABLE_MODELVOLUME_TRANSFORM | ||||
|     Vec3f scale = single_instance ? 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast<float>() : 100.0f * m_scale.cast<float>(); | ||||
|     if (single_instance) | ||||
|         scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast<float>(); | ||||
|     else if (single_volume) | ||||
|         scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast<float>(); | ||||
| #else | ||||
|     Vec3f scale = single_instance ? 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_scaling_factor().cast<float>() : 100.0f * m_scale.cast<float>(); | ||||
| #endif // ENABLE_MODELVOLUME_TRANSFORM
 | ||||
| 
 | ||||
|     if ((single_instance && ((m_hover_id == 0) || (m_hover_id == 1))) || m_grabbers[0].dragging || m_grabbers[1].dragging) | ||||
|     if ((single_selection && ((m_hover_id == 0) || (m_hover_id == 1))) || m_grabbers[0].dragging || m_grabbers[1].dragging) | ||||
|         set_tooltip("X: " + format(scale(0), 4) + "%"); | ||||
|     else if ((single_instance && ((m_hover_id == 2) || (m_hover_id == 3))) || m_grabbers[2].dragging || m_grabbers[3].dragging) | ||||
|     else if ((single_selection && ((m_hover_id == 2) || (m_hover_id == 3))) || m_grabbers[2].dragging || m_grabbers[3].dragging) | ||||
|         set_tooltip("Y: " + format(scale(1), 4) + "%"); | ||||
|     else if ((single_instance && ((m_hover_id == 4) || (m_hover_id == 5))) || m_grabbers[4].dragging || m_grabbers[5].dragging) | ||||
|     else if ((single_selection && ((m_hover_id == 4) || (m_hover_id == 5))) || m_grabbers[4].dragging || m_grabbers[5].dragging) | ||||
|         set_tooltip("Z: " + format(scale(2), 4) + "%"); | ||||
|     else if ((single_instance && ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9))) | ||||
|     else if ((single_selection && ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9))) | ||||
|         || m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging) | ||||
|     { | ||||
|         std::string tooltip = "X: " + format(scale(0), 4) + "%\n"; | ||||
|  | @ -761,21 +768,31 @@ void GLGizmoScale3D::on_render(const GLCanvas3D::Selection& selection) const | |||
|         const GLVolume* v = selection.get_volume(*idxs.begin()); | ||||
| #if ENABLE_MODELVOLUME_TRANSFORM | ||||
|         transform = v->world_matrix(); | ||||
| #else | ||||
|         transform = v->world_matrix().cast<double>(); | ||||
| #endif // ENABLE_MODELVOLUME_TRANSFORM
 | ||||
| 
 | ||||
|         // gets angles from first selected volume
 | ||||
| #if ENABLE_MODELVOLUME_TRANSFORM | ||||
|         angles = v->get_instance_rotation(); | ||||
| #else | ||||
|         angles = v->get_rotation(); | ||||
| #endif // ENABLE_MODELVOLUME_TRANSFORM
 | ||||
| 
 | ||||
|         // consider rotation+mirror only components of the transform for offsets
 | ||||
| #if ENABLE_MODELVOLUME_TRANSFORM | ||||
|         offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); | ||||
| #else | ||||
|         transform = v->world_matrix().cast<double>(); | ||||
|         // gets angles from first selected volume
 | ||||
|         angles = v->get_rotation(); | ||||
|         // consider rotation+mirror only components of the transform for offsets
 | ||||
|         offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_mirror()); | ||||
| #endif // ENABLE_MODELVOLUME_TRANSFORM
 | ||||
|     } | ||||
|     else if (single_volume) | ||||
|     { | ||||
|         const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
|         box = v->bounding_box; | ||||
| #if ENABLE_MODELVOLUME_TRANSFORM | ||||
|         transform = v->world_matrix(); | ||||
|         angles = Geometry::extract_euler_angles(transform); | ||||
|         // consider rotation+mirror only components of the transform for offsets
 | ||||
|         offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); | ||||
| #else | ||||
|         transform = v->world_matrix().cast<double>(); | ||||
|         angles = Geometry::extract_euler_angles(transform); | ||||
|         // consider rotation+mirror only components of the transform for offsets
 | ||||
|         offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_mirror()); | ||||
| #endif // ENABLE_MODELVOLUME_TRANSFORM
 | ||||
|     } | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| #include "GUI.hpp" | ||||
| #include "GUI_App.hpp" | ||||
| #include "../AppController.hpp" | ||||
| #include "WipeTowerDialog.hpp" | ||||
| 
 | ||||
| #include <assert.h> | ||||
|  | @ -453,23 +452,4 @@ void desktop_open_datadir_folder() | |||
| #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>(); | ||||
| } | ||||
| 
 | ||||
| } } | ||||
|  |  | |||
|  | @ -71,6 +71,8 @@ ObjectList::ObjectList(wxWindow* parent) : | |||
|     Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG,    [this](wxDataViewEvent& e) {on_begin_drag(e); }); | ||||
|     Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, [this](wxDataViewEvent& e) {on_drop_possible(e); }); | ||||
|     Bind(wxEVT_DATAVIEW_ITEM_DROP,          [this](wxDataViewEvent& e) {on_drop(e); }); | ||||
| 
 | ||||
|     Bind(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED,[this](wxCommandEvent& e)   {last_volume_is_deleted(e.GetInt()); }); | ||||
| } | ||||
| 
 | ||||
| ObjectList::~ObjectList() | ||||
|  | @ -88,6 +90,7 @@ void ObjectList::create_objects_ctrl() | |||
| 
 | ||||
|     m_objects_model = new PrusaObjectDataViewModel; | ||||
|     AssociateModel(m_objects_model); | ||||
|     m_objects_model->SetAssociatedControl(this); | ||||
| #if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE | ||||
|     EnableDragSource(wxDF_UNICODETEXT); | ||||
|     EnableDropTarget(wxDF_UNICODETEXT); | ||||
|  | @ -96,7 +99,7 @@ void ObjectList::create_objects_ctrl() | |||
|     // column 0(Icon+Text) of the view control: 
 | ||||
|     // And Icon can be consisting of several bitmaps
 | ||||
|     AppendColumn(new wxDataViewColumn(_(L("Name")), new PrusaBitmapTextRenderer(), | ||||
|         0, 250, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); | ||||
|         0, 200, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); | ||||
| 
 | ||||
|     // column 1 of the view control:
 | ||||
|     AppendColumn(create_objects_list_extruder_column(4)); | ||||
|  | @ -701,7 +704,7 @@ void ObjectList::load_subobject(int type) | |||
|     parts_changed(obj_idx); | ||||
| 
 | ||||
|     for (int i = 0; i < part_names.size(); ++i) { | ||||
|         const wxDataViewItem sel_item = m_objects_model->AddVolumeChild(item, part_names.Item(i), /**m_bmp_vector[*/type/*]*/); | ||||
|         const wxDataViewItem sel_item = m_objects_model->AddVolumeChild(item, part_names.Item(i), type); | ||||
| 
 | ||||
|         if (i == part_names.size() - 1) | ||||
|             select_item(sel_item); | ||||
|  | @ -783,8 +786,11 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const int | |||
|     const auto& sz = BoundingBoxf(bed_shape).size(); | ||||
|     const auto side = 0.1 * std::max(sz(0), sz(1)); | ||||
| 
 | ||||
|     if (type_name == _("Box")) | ||||
|     if (type_name == _("Box")) { | ||||
|         mesh = make_cube(side, side, side); | ||||
|         // box sets the base coordinate at 0, 0, move to center of plate
 | ||||
|         mesh.translate(-side * 0.5, -side * 0.5, 0); | ||||
|     } | ||||
|     else if (type_name == _("Cylinder")) | ||||
|         mesh = make_cylinder(0.5*side, side); | ||||
|     else if (type_name == _("Sphere")) | ||||
|  | @ -1248,7 +1254,8 @@ void ObjectList::update_selections() | |||
|     { | ||||
|         sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); | ||||
|     } | ||||
|     else if (selection.is_single_volume() || selection.is_multiple_volume() || selection.is_multiple_full_object()) { | ||||
|     else if (selection.is_single_volume() || selection.is_modifier() ||  | ||||
|              selection.is_multiple_volume() || selection.is_multiple_full_object()) { | ||||
|         for (auto idx : selection.get_volume_idxs()) { | ||||
|             const auto gl_vol = selection.get_volume(idx); | ||||
|             if (selection.is_multiple_full_object()) | ||||
|  | @ -1434,5 +1441,19 @@ void ObjectList::change_part_type() | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void ObjectList::last_volume_is_deleted(const int obj_idx) | ||||
| { | ||||
| 
 | ||||
|     if (obj_idx < 0 || (*m_objects).empty() || (*m_objects)[obj_idx]->volumes.empty()) | ||||
|         return; | ||||
|     auto volume = (*m_objects)[obj_idx]->volumes[0]; | ||||
| 
 | ||||
|     // clear volume's config values
 | ||||
|     volume->config.clear(); | ||||
| 
 | ||||
|     // set a default extruder value, since user can't add it manually
 | ||||
|     volume->config.set_key_value("extruder", new ConfigOptionInt(0)); | ||||
| } | ||||
| 
 | ||||
| } //namespace GUI
 | ||||
| } //namespace Slic3r 
 | ||||
|  | @ -149,6 +149,8 @@ public: | |||
| 
 | ||||
|     ModelVolume* get_selected_model_volume(); | ||||
|     void change_part_type(); | ||||
| 
 | ||||
|     void last_volume_is_deleted(const int obj_idx); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -86,6 +86,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent): | |||
| 
 | ||||
|         if (option_name == "Rotation") | ||||
|             def.min = -360; | ||||
|         else | ||||
|             def.min == -1000; | ||||
| 
 | ||||
|         const std::string lower_name = boost::algorithm::to_lower_copy(option_name); | ||||
| 
 | ||||
|  | @ -164,7 +166,7 @@ int ObjectManipulation::ol_selection() | |||
| void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& selection) | ||||
| { | ||||
| #if ENABLE_MODELVOLUME_TRANSFORM | ||||
|     if (selection.is_single_full_instance()) | ||||
|     if (selection.is_single_full_instance() || selection.is_single_full_object()) | ||||
| #else | ||||
|     if (selection.is_single_full_object()) | ||||
|     { | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ namespace Slic3r { | |||
| namespace GUI { | ||||
| 
 | ||||
| 
 | ||||
| Preview::Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data) | ||||
| Preview::Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process_func) | ||||
|     : m_canvas(nullptr) | ||||
|     , m_double_slider_sizer(nullptr) | ||||
|     , m_label_view_type(nullptr) | ||||
|  | @ -43,6 +43,7 @@ Preview::Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, | |||
|     , m_loaded(false) | ||||
|     , m_enabled(false) | ||||
|     , m_force_sliders_full_range(false) | ||||
|     , m_schedule_background_process(schedule_background_process_func) | ||||
| { | ||||
|     if (init(notebook, config, print, gcode_preview_data)) | ||||
|     { | ||||
|  | @ -488,6 +489,7 @@ void Preview::create_double_slider() | |||
|     Bind(wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) { | ||||
|             auto& config = wxGetApp().preset_bundle->project_config; | ||||
|             ((config.option<ConfigOptionFloats>("colorprint_heights"))->values) = (m_slider->GetTicksValues()); | ||||
|             m_schedule_background_process(); | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
|  | @ -529,13 +531,16 @@ void Preview::fill_slider_values(std::vector<std::pair<int, double>> &values, | |||
|     } | ||||
| 
 | ||||
|     // All ticks that would end up outside the slider range should be erased.
 | ||||
|     // TODO: this should probably be placed into more appropriate part of code,
 | ||||
|     //  this way it relies on the Preview tab being active.
 | ||||
|     // TODO: this should be placed into more appropriate part of code,
 | ||||
|     // this function is e.g. not called when the last object is deleted
 | ||||
|     auto& config = wxGetApp().preset_bundle->project_config; | ||||
|     std::vector<double> &ticks_from_config = (config.option<ConfigOptionFloats>("colorprint_heights"))->values; | ||||
|     unsigned int old_size = ticks_from_config.size(); | ||||
|     ticks_from_config.erase(std::remove_if(ticks_from_config.begin(), ticks_from_config.end(), | ||||
|                                            [values](double val) { return values.back().second < val; }), | ||||
|                             ticks_from_config.end()); | ||||
|     if (ticks_from_config.size() != old_size) | ||||
|         m_schedule_background_process(); | ||||
| } | ||||
| 
 | ||||
| void Preview::set_double_slider_thumbs(const bool force_sliders_full_range, | ||||
|  |  | |||
|  | @ -40,6 +40,9 @@ class Preview : public wxPanel | |||
|     Print* m_print; | ||||
|     GCodePreviewData* m_gcode_preview_data; | ||||
| 
 | ||||
|     // Calling this function object forces Plater::schedule_background_process.
 | ||||
|     std::function<void()> m_schedule_background_process; | ||||
| 
 | ||||
|     unsigned int m_number_extruders; | ||||
|     std::string m_preferred_color_mode; | ||||
| 
 | ||||
|  | @ -50,7 +53,7 @@ class Preview : public wxPanel | |||
|     PrusaDoubleSlider* m_slider {nullptr}; | ||||
| 
 | ||||
| public: | ||||
|     Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data); | ||||
|     Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process = [](){}); | ||||
|     virtual ~Preview(); | ||||
| 
 | ||||
|     wxGLCanvas* get_wxglcanvas() { return m_canvas; } | ||||
|  |  | |||
|  | @ -11,7 +11,6 @@ | |||
| 
 | ||||
| #include "Tab.hpp" | ||||
| #include "PresetBundle.hpp" | ||||
| #include "../AppController.hpp" | ||||
| #include "ProgressStatusBar.hpp" | ||||
| #include "3DScene.hpp" | ||||
| #include "Print.hpp" | ||||
|  | @ -30,8 +29,6 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL | |||
|         m_no_plater(no_plater), | ||||
|         m_loaded(loaded) | ||||
| { | ||||
|     m_appController = new Slic3r::AppController(); | ||||
| 
 | ||||
|     // Load the icon either from the exe, or from the ico file.
 | ||||
| #if _WIN32 | ||||
|     { | ||||
|  | @ -59,14 +56,6 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL | |||
|                                  SLIC3R_VERSION + | ||||
|                                  _(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; | ||||
| 
 | ||||
|     // initialize layout
 | ||||
|  | @ -373,7 +362,7 @@ void MainFrame::slice_to_png() | |||
| { | ||||
| //     m_plater->stop_background_process();
 | ||||
| //     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".
 | ||||
|  |  | |||
|  | @ -20,7 +20,6 @@ class wxProgressDialog; | |||
| namespace Slic3r { | ||||
| 
 | ||||
| class ProgressStatusBar; | ||||
| class AppController; | ||||
| 
 | ||||
| // #define _(s)    Slic3r::GUI::I18N::translate((s))
 | ||||
| 
 | ||||
|  | @ -54,7 +53,6 @@ class MainFrame : public wxFrame | |||
|     wxString    m_qs_last_output_file = wxEmptyString; | ||||
|     wxString    m_last_config = wxEmptyString; | ||||
| 
 | ||||
|     AppController*                  m_appController { nullptr }; | ||||
|     std::map<std::string, Tab*>     m_options_tabs; | ||||
| 
 | ||||
|     wxMenuItem* m_menu_item_reslice_now { nullptr }; | ||||
|  | @ -97,8 +95,6 @@ public: | |||
|     void        select_tab(size_t tab) const; | ||||
|     void        select_view(const std::string& direction); | ||||
| 
 | ||||
|     AppController* app_controller() { return m_appController; } | ||||
| 
 | ||||
|     std::vector<PresetTab>& get_preset_tabs(); | ||||
| 
 | ||||
|     Plater*             m_plater { nullptr }; | ||||
|  | @ -110,4 +106,4 @@ public: | |||
| } // GUI
 | ||||
| } //Slic3r
 | ||||
| 
 | ||||
| #endif // slic3r_MainFrame_hpp_
 | ||||
| #endif // slic3r_MainFrame_hpp_
 | ||||
|  |  | |||
|  | @ -25,6 +25,7 @@ | |||
| #include "libslic3r/libslic3r.h" | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
| #include "libslic3r/Model.hpp" | ||||
| #include "libslic3r/ModelArrange.hpp" | ||||
| #include "libslic3r/Print.hpp" | ||||
| #include "libslic3r/SLAPrint.hpp" | ||||
| #include "libslic3r/GCode/PreviewData.hpp" | ||||
|  | @ -33,7 +34,7 @@ | |||
| #include "libslic3r/Format/STL.hpp" | ||||
| #include "libslic3r/Format/AMF.hpp" | ||||
| #include "libslic3r/Format/3mf.hpp" | ||||
| #include "slic3r/AppController.hpp" | ||||
| //#include "slic3r/AppController.hpp"
 | ||||
| #include "GUI.hpp" | ||||
| #include "GUI_App.hpp" | ||||
| #include "GUI_ObjectList.hpp" | ||||
|  | @ -887,6 +888,7 @@ struct Plater::priv | |||
|     wxGLCanvas *canvas3D;    // TODO: Use GLCanvas3D when we can
 | ||||
|     Preview *preview; | ||||
|     BackgroundSlicingProcess    background_process; | ||||
|     std::atomic<bool>           arranging; | ||||
| 
 | ||||
|     wxTimer                     background_process_timer; | ||||
| 
 | ||||
|  | @ -1000,7 +1002,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) : | |||
|     _3DScene::add_canvas(canvas3D); | ||||
|     _3DScene::allow_multisample(canvas3D, GLCanvas3DManager::can_multisample()); | ||||
|     notebook->AddPage(canvas3D, _(L("3D"))); | ||||
|     preview = new GUI::Preview(notebook, config, &print, &gcode_preview_data); | ||||
|     preview = new GUI::Preview(notebook, config, &print, &gcode_preview_data, [this](){ schedule_background_process(); }); | ||||
| 
 | ||||
|     // XXX: If have OpenGL
 | ||||
|     _3DScene::enable_picking(canvas3D, true); | ||||
|  | @ -1470,13 +1472,86 @@ void Plater::priv::mirror(Axis axis) | |||
| 
 | ||||
| void Plater::priv::arrange() | ||||
| { | ||||
| 	this->background_process.stop(); | ||||
|     main_frame->app_controller()->arrange_model(); | ||||
|     // 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(); | ||||
|     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(); | ||||
| 
 | ||||
|     // ignore arrange failures on purpose: user has visual feedback and we don't need to warn him
 | ||||
|     // when parts don't fit in print bed
 | ||||
|     // ignore arrange failures on purpose: user has visual feedback and we
 | ||||
|     // don't need to warn him when parts don't fit in print bed
 | ||||
| 
 | ||||
|     _3DScene::enable_toolbar_item(canvas3D, "arrange", can_arrange()); | ||||
|     update(); | ||||
| } | ||||
| 
 | ||||
|  | @ -1932,7 +2007,7 @@ bool Plater::priv::can_delete_all() const | |||
| 
 | ||||
| bool Plater::priv::can_arrange() const | ||||
| { | ||||
|     return !model.objects.empty(); | ||||
|     return !model.objects.empty() && !arranging.load(); | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::can_mirror() const | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
| #include "Model.hpp" | ||||
| 
 | ||||
| wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent); | ||||
| wxDEFINE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); | ||||
| 
 | ||||
| wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, | ||||
|     std::function<void(wxCommandEvent& event)> cb, const std::string& icon, wxEvtHandler* event_handler) | ||||
|  | @ -564,12 +565,22 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) | |||
| 	// NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_
 | ||||
| 	//       thus removing the node from it doesn't result in freeing it
 | ||||
| 	if (node_parent) { | ||||
|         if (node->m_type == itInstanceRoot) | ||||
|         { | ||||
|             for (int i = node->GetChildCount() - 1; i > 0; i--) | ||||
|                 Delete(wxDataViewItem(node->GetNthChild(i))); | ||||
|             return parent; | ||||
|         } | ||||
| 
 | ||||
| 		auto id = node_parent->GetChildren().Index(node); | ||||
|         auto idx = node->GetIdx(); | ||||
| 		node_parent->GetChildren().Remove(node); | ||||
| 
 | ||||
|         if (node->m_type == itVolume) | ||||
| 
 | ||||
|         if (node->m_type == itVolume) { | ||||
|             node_parent->m_volumes_cnt--; | ||||
|             DeleteSettings(item); | ||||
|         } | ||||
| 		node_parent->GetChildren().Remove(node); | ||||
| 
 | ||||
| 		if (id > 0) {  | ||||
| 			if(id == node_parent->GetChildCount()) id--; | ||||
|  | @ -600,21 +611,70 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) | |||
|             obj_node->GetChildren().Remove(node_parent); | ||||
|             delete node_parent; | ||||
|             ret_item = wxDataViewItem(obj_node); | ||||
|             ItemDeleted(ret_item, wxDataViewItem(node_parent)); | ||||
| 
 | ||||
| #ifndef __WXGTK__ | ||||
|             if (obj_node->GetChildCount() == 0) | ||||
|                 obj_node->m_container = false; | ||||
| #endif //__WXGTK__
 | ||||
|             ItemDeleted(ret_item, wxDataViewItem(node_parent)); | ||||
|             return ret_item; | ||||
|         } | ||||
| 
 | ||||
|         // if there is last volume item after deleting, delete this last volume too
 | ||||
|         if (node_parent->GetChildCount() <= 3) | ||||
|         { | ||||
|             int vol_cnt = 0; | ||||
|             int vol_idx = 0; | ||||
|             for (int i = 0; i < node_parent->GetChildCount(); ++i) { | ||||
|                 if (node_parent->GetNthChild(i)->GetType() == itVolume) { | ||||
|                     vol_idx = i; | ||||
|                     vol_cnt++; | ||||
|                 } | ||||
|                 if (vol_cnt > 1) | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
|             if (vol_cnt == 1) { | ||||
|                 delete node; | ||||
|                 ItemDeleted(parent, item); | ||||
| 
 | ||||
|                 PrusaObjectDataViewModelNode *last_child_node = node_parent->GetNthChild(vol_idx); | ||||
|                 DeleteSettings(wxDataViewItem(last_child_node)); | ||||
|                 node_parent->GetChildren().Remove(last_child_node); | ||||
|                 node_parent->m_volumes_cnt = 0; | ||||
|                 delete last_child_node; | ||||
| 
 | ||||
| #ifndef __WXGTK__ | ||||
|                 if (node_parent->GetChildCount() == 0) | ||||
|                     node_parent->m_container = false; | ||||
| #endif //__WXGTK__
 | ||||
|                 ItemDeleted(parent, wxDataViewItem(last_child_node)); | ||||
| 
 | ||||
|                 wxCommandEvent event(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED); | ||||
|                 auto it = find(m_objects.begin(), m_objects.end(), node_parent); | ||||
|                 event.SetInt(it == m_objects.end() ? -1 : it - m_objects.begin()); | ||||
|                 wxPostEvent(m_ctrl, event); | ||||
| 
 | ||||
|                 ret_item = parent; | ||||
| 
 | ||||
|                 return ret_item; | ||||
|             } | ||||
|         } | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		auto it = find(m_objects.begin(), m_objects.end(), node); | ||||
| 		auto id = it - m_objects.begin(); | ||||
| 		if (it != m_objects.end()) | ||||
| 		{ | ||||
|             // Delete all sub-items
 | ||||
|             int i = m_objects[id]->GetChildCount() - 1; | ||||
|             while (i >= 0) { | ||||
|                 Delete(wxDataViewItem(m_objects[id]->GetNthChild(i))); | ||||
|                 i = m_objects[id]->GetChildCount() - 1; | ||||
|             } | ||||
| 			m_objects.erase(it); | ||||
|         } | ||||
| 		if (id > 0) {  | ||||
| 			if(id == m_objects.size()) id--; | ||||
| 			ret_item = wxDataViewItem(m_objects[id]); | ||||
|  | @ -733,8 +793,8 @@ void PrusaObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent) | |||
|             continue; | ||||
| 
 | ||||
|         auto item = wxDataViewItem(node); | ||||
|         DeleteSettings(item); | ||||
|         children.RemoveAt(id); | ||||
|         root->m_volumes_cnt--; | ||||
| 
 | ||||
|         // free the node
 | ||||
|         delete node; | ||||
|  | @ -742,6 +802,7 @@ void PrusaObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent) | |||
|         // notify control
 | ||||
|         ItemDeleted(parent, item); | ||||
|     } | ||||
|     root->m_volumes_cnt = 0; | ||||
| 
 | ||||
|     // set m_containet to FALSE if parent has no child
 | ||||
| #ifndef __WXGTK__ | ||||
|  | @ -749,6 +810,21 @@ void PrusaObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent) | |||
| #endif //__WXGTK__
 | ||||
| } | ||||
| 
 | ||||
| void PrusaObjectDataViewModel::DeleteSettings(const wxDataViewItem& parent) | ||||
| { | ||||
|     PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)parent.GetID(); | ||||
|     if (!node) return; | ||||
| 
 | ||||
|     // if volume has a "settings"item, than delete it before volume deleting
 | ||||
|     if (node->GetChildCount() > 0 && node->GetNthChild(0)->GetType() == itSettings) { | ||||
|         auto settings_node = node->GetNthChild(0); | ||||
|         auto settings_item = wxDataViewItem(settings_node); | ||||
|         node->GetChildren().RemoveAt(0); | ||||
|         delete settings_node; | ||||
|         ItemDeleted(parent, settings_item); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| wxDataViewItem PrusaObjectDataViewModel::GetItemById(int obj_idx) | ||||
| { | ||||
| 	if (obj_idx >= m_objects.size()) | ||||
|  | @ -841,7 +917,7 @@ void PrusaObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& | |||
|     type = itUndef; | ||||
| 
 | ||||
|     PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); | ||||
|     if (!node || node->GetIdx() < 0 && !(node->GetType() & (itObject|itSettings|itInstanceRoot)))  | ||||
|     if (!node || node->GetIdx() <-1 || node->GetIdx() ==-1 && !(node->GetType() & (itObject | itSettings | itInstanceRoot))) | ||||
|         return; | ||||
| 
 | ||||
|     idx = node->GetIdx(); | ||||
|  |  | |||
|  | @ -422,11 +422,16 @@ private: | |||
| // PrusaObjectDataViewModel
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| // custom message the model sends to associated control to notify a last volume deleted from the object:
 | ||||
| wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); | ||||
| 
 | ||||
| class PrusaObjectDataViewModel :public wxDataViewModel | ||||
| { | ||||
| 	std::vector<PrusaObjectDataViewModelNode*>  m_objects; | ||||
|     std::vector<wxBitmap*>                      m_volume_bmps; | ||||
| 
 | ||||
|     wxDataViewCtrl*                             m_ctrl{ nullptr }; | ||||
| 
 | ||||
| public: | ||||
|     PrusaObjectDataViewModel(); | ||||
|     ~PrusaObjectDataViewModel(); | ||||
|  | @ -444,6 +449,7 @@ public: | |||
| 	void DeleteAll(); | ||||
|     void DeleteChildren(wxDataViewItem& parent); | ||||
|     void DeleteVolumeChildren(wxDataViewItem& parent); | ||||
|     void DeleteSettings(const wxDataViewItem& parent); | ||||
| 	wxDataViewItem GetItemById(int obj_idx); | ||||
| 	wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); | ||||
| 	wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); | ||||
|  | @ -500,6 +506,8 @@ public: | |||
| 
 | ||||
|     void    SetVolumeBitmaps(const std::vector<wxBitmap*>& volume_bmps) { m_volume_bmps = volume_bmps; } | ||||
|     void    SetVolumeType(const wxDataViewItem &item, const int type); | ||||
| 
 | ||||
|     void    SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } | ||||
| }; | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
| AppConfig*	                O_OBJECT_SLIC3R | ||||
| AppController*              O_OBJECT_SLIC3R | ||||
| PrintController*            O_OBJECT_SLIC3R | ||||
| Ref<AppConfig>         		O_OBJECT_SLIC3R_T | ||||
| BackgroundSlicingProcess*   O_OBJECT_SLIC3R | ||||
| Ref<BackgroundSlicingProcess> O_OBJECT_SLIC3R_T | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 bubnikv
						bubnikv