mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	Prepare integration for arbitrary shaped print beds.
This commit is contained in:
		
						commit
						6cdec7ac9a
					
				
					 12 changed files with 555 additions and 549 deletions
				
			
		|  | @ -1,5 +1,5 @@ | |||
| min_slic3r_version = 1.41.0-alpha | ||||
| 0.2.0-alpha3 | ||||
| 0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost,  | ||||
| 0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material | ||||
| 0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0 | ||||
| 0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters | ||||
|  |  | |||
|  | @ -136,6 +136,7 @@ add_library(libslic3r STATIC | |||
|     ${LIBDIR}/libslic3r/Line.hpp | ||||
|     ${LIBDIR}/libslic3r/Model.cpp | ||||
|     ${LIBDIR}/libslic3r/Model.hpp | ||||
|     ${LIBDIR}/libslic3r/ModelArrange.hpp | ||||
|     ${LIBDIR}/libslic3r/MotionPlanner.cpp | ||||
|     ${LIBDIR}/libslic3r/MotionPlanner.hpp | ||||
|     ${LIBDIR}/libslic3r/MultiPoint.cpp | ||||
|  | @ -729,6 +730,7 @@ set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d" | |||
| 
 | ||||
| add_subdirectory(${LIBDIR}/libnest2d) | ||||
| target_include_directories(libslic3r PUBLIC BEFORE ${LIBNEST2D_INCLUDES}) | ||||
| target_include_directories(libslic3r_gui PUBLIC BEFORE ${LIBNEST2D_INCLUDES}) | ||||
| 
 | ||||
| message(STATUS "Libnest2D Libraries: ${LIBNEST2D_LIBRARIES}") | ||||
| target_link_libraries(libslic3r ${LIBNEST2D_LIBRARIES}) | ||||
|  |  | |||
|  | @ -544,25 +544,25 @@ void arrangeRectangles() { | |||
| //    input.insert(input.end(), proba.begin(), proba.end());
 | ||||
| //    input.insert(input.end(), crasher.begin(), crasher.end());
 | ||||
| 
 | ||||
| //    Box bin(250*SCALE, 210*SCALE);
 | ||||
|     PolygonImpl bin = { | ||||
|         { | ||||
|             {25*SCALE, 0}, | ||||
|             {0, 25*SCALE}, | ||||
|             {0, 225*SCALE}, | ||||
|             {25*SCALE, 250*SCALE}, | ||||
|             {225*SCALE, 250*SCALE}, | ||||
|             {250*SCALE, 225*SCALE}, | ||||
|             {250*SCALE, 25*SCALE}, | ||||
|             {225*SCALE, 0}, | ||||
|             {25*SCALE, 0} | ||||
|         }, | ||||
|         {} | ||||
|     }; | ||||
|     Box bin(250*SCALE, 210*SCALE); | ||||
| //    PolygonImpl bin = {
 | ||||
| //        {
 | ||||
| //            {25*SCALE, 0},
 | ||||
| //            {0, 25*SCALE},
 | ||||
| //            {0, 225*SCALE},
 | ||||
| //            {25*SCALE, 250*SCALE},
 | ||||
| //            {225*SCALE, 250*SCALE},
 | ||||
| //            {250*SCALE, 225*SCALE},
 | ||||
| //            {250*SCALE, 25*SCALE},
 | ||||
| //            {225*SCALE, 0},
 | ||||
| //            {25*SCALE, 0}
 | ||||
| //        },
 | ||||
| //        {}
 | ||||
| //    };
 | ||||
| 
 | ||||
|     auto min_obj_distance = static_cast<Coord>(0*SCALE); | ||||
| 
 | ||||
|     using Placer = strategies::_NofitPolyPlacer<PolygonImpl, PolygonImpl>; | ||||
|     using Placer = strategies::_NofitPolyPlacer<PolygonImpl, Box>; | ||||
|     using Packer = Arranger<Placer, FirstFitSelection>; | ||||
| 
 | ||||
|     Packer arrange(bin, min_obj_distance); | ||||
|  | @ -571,102 +571,102 @@ void arrangeRectangles() { | |||
|     pconf.alignment = Placer::Config::Alignment::CENTER; | ||||
|     pconf.starting_point = Placer::Config::Alignment::CENTER; | ||||
|     pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; | ||||
|     pconf.accuracy = 1.0; | ||||
|     pconf.accuracy = 0.5f; | ||||
| 
 | ||||
|     auto bincenter = ShapeLike::boundingBox(bin).center(); | ||||
|     pconf.object_function = [&bin, bincenter]( | ||||
|             Placer::Pile pile, const Item& item, | ||||
|             double /*area*/, double norm, double penality) { | ||||
| //    auto bincenter = ShapeLike::boundingBox(bin).center();
 | ||||
| //    pconf.object_function = [&bin, bincenter](
 | ||||
| //            Placer::Pile pile, const Item& item,
 | ||||
| //            double /*area*/, double norm, double penality) {
 | ||||
| 
 | ||||
|         using pl = PointLike; | ||||
| //        using pl = PointLike;
 | ||||
| 
 | ||||
|         static const double BIG_ITEM_TRESHOLD = 0.2; | ||||
|         static const double GRAVITY_RATIO = 0.5; | ||||
|         static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO; | ||||
| //        static const double BIG_ITEM_TRESHOLD = 0.2;
 | ||||
| //        static const double GRAVITY_RATIO = 0.5;
 | ||||
| //        static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO;
 | ||||
| 
 | ||||
|         // We will treat big items (compared to the print bed) differently
 | ||||
|         NfpPlacer::Pile bigs; | ||||
|         bigs.reserve(pile.size()); | ||||
|         for(auto& p : pile) { | ||||
|             auto pbb = ShapeLike::boundingBox(p); | ||||
|             auto na = std::sqrt(pbb.width()*pbb.height())/norm; | ||||
|             if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p); | ||||
|         } | ||||
| //        // We will treat big items (compared to the print bed) differently
 | ||||
| //        NfpPlacer::Pile bigs;
 | ||||
| //        bigs.reserve(pile.size());
 | ||||
| //        for(auto& p : pile) {
 | ||||
| //            auto pbb = ShapeLike::boundingBox(p);
 | ||||
| //            auto na = std::sqrt(pbb.width()*pbb.height())/norm;
 | ||||
| //            if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p);
 | ||||
| //        }
 | ||||
| 
 | ||||
|         // Candidate item bounding box
 | ||||
|         auto ibb = item.boundingBox(); | ||||
| //        // Candidate item bounding box
 | ||||
| //        auto ibb = item.boundingBox();
 | ||||
| 
 | ||||
|         // Calculate the full bounding box of the pile with the candidate item
 | ||||
|         pile.emplace_back(item.transformedShape()); | ||||
|         auto fullbb = ShapeLike::boundingBox(pile); | ||||
|         pile.pop_back(); | ||||
| //        // Calculate the full bounding box of the pile with the candidate item
 | ||||
| //        pile.emplace_back(item.transformedShape());
 | ||||
| //        auto fullbb = ShapeLike::boundingBox(pile);
 | ||||
| //        pile.pop_back();
 | ||||
| 
 | ||||
|         // The bounding box of the big items (they will accumulate in the center
 | ||||
|         // of the pile
 | ||||
|         auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs); | ||||
| //        // The bounding box of the big items (they will accumulate in the center
 | ||||
| //        // of the pile
 | ||||
| //        auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs);
 | ||||
| 
 | ||||
|         // The size indicator of the candidate item. This is not the area,
 | ||||
|         // but almost...
 | ||||
|         auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm; | ||||
| //        // The size indicator of the candidate item. This is not the area,
 | ||||
| //        // but almost...
 | ||||
| //        auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm;
 | ||||
| 
 | ||||
|         // Will hold the resulting score
 | ||||
|         double score = 0; | ||||
| //        // Will hold the resulting score
 | ||||
| //        double score = 0;
 | ||||
| 
 | ||||
|         if(itemnormarea > BIG_ITEM_TRESHOLD) { | ||||
|             // This branch is for the bigger items..
 | ||||
|             // Here we will use the closest point of the item bounding box to
 | ||||
|             // the already arranged pile. So not the bb center nor the a choosen
 | ||||
|             // corner but whichever is the closest to the center. This will
 | ||||
|             // prevent unwanted strange arrangements.
 | ||||
| //        if(itemnormarea > BIG_ITEM_TRESHOLD) {
 | ||||
| //            // This branch is for the bigger items..
 | ||||
| //            // Here we will use the closest point of the item bounding box to
 | ||||
| //            // the already arranged pile. So not the bb center nor the a choosen
 | ||||
| //            // corner but whichever is the closest to the center. This will
 | ||||
| //            // prevent unwanted strange arrangements.
 | ||||
| 
 | ||||
|             auto minc = ibb.minCorner(); // bottom left corner
 | ||||
|             auto maxc = ibb.maxCorner(); // top right corner
 | ||||
| //            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)}; | ||||
| //            // top left and bottom right corners
 | ||||
| //            auto top_left = PointImpl{getX(minc), getY(maxc)};
 | ||||
| //            auto bottom_right = PointImpl{getX(maxc), getY(minc)};
 | ||||
| 
 | ||||
|             auto cc = fullbb.center(); // The gravity center
 | ||||
| //            auto cc = fullbb.center(); // The gravity center
 | ||||
| 
 | ||||
|             // Now the distnce of the gravity center will be calculated to the
 | ||||
|             // five anchor points and the smallest will be chosen.
 | ||||
|             std::array<double, 5> dists; | ||||
|             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); | ||||
| //            // Now the distnce of the gravity center will be calculated to the
 | ||||
| //            // five anchor points and the smallest will be chosen.
 | ||||
| //            std::array<double, 5> dists;
 | ||||
| //            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);
 | ||||
| 
 | ||||
|             auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; | ||||
| //            auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
 | ||||
| 
 | ||||
|             // Density is the pack density: how big is the arranged pile
 | ||||
|             auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; | ||||
| //            // Density is the pack density: how big is the arranged pile
 | ||||
| //            auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
 | ||||
| 
 | ||||
|             // The score is a weighted sum of the distance from pile center
 | ||||
|             // and the pile size
 | ||||
|             score = GRAVITY_RATIO * dist + DENSITY_RATIO * density; | ||||
| //            // The score is a weighted sum of the distance from pile center
 | ||||
| //            // and the pile size
 | ||||
| //            score = GRAVITY_RATIO * dist + DENSITY_RATIO * density;
 | ||||
| 
 | ||||
|         } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { | ||||
|             // If there are no big items, only small, we should consider the
 | ||||
|             // density here as well to not get silly results
 | ||||
|             auto bindist = pl::distance(ibb.center(), bincenter) / norm; | ||||
|             auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; | ||||
|             score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density; | ||||
|         } 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; | ||||
|         } | ||||
| //        } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) {
 | ||||
| //            // If there are no big items, only small, we should consider the
 | ||||
| //            // density here as well to not get silly results
 | ||||
| //            auto bindist = pl::distance(ibb.center(), bincenter) / norm;
 | ||||
| //            auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
 | ||||
| //            score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density;
 | ||||
| //        } 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;
 | ||||
| //        }
 | ||||
| 
 | ||||
|         // If it does not fit into the print bed we will beat it
 | ||||
|         // with a large penality. If we would not do this, there would be only
 | ||||
|         // one big pile that doesn't care whether it fits onto the print bed.
 | ||||
|         if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score; | ||||
| //        // If it does not fit into the print bed we will beat it
 | ||||
| //        // with a large penality. If we would not do this, there would be only
 | ||||
| //        // one big pile that doesn't care whether it fits onto the print bed.
 | ||||
| //        if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score;
 | ||||
| 
 | ||||
|         return score; | ||||
|     }; | ||||
| //        return score;
 | ||||
| //    };
 | ||||
| 
 | ||||
|     Packer::SelectionConfig sconf; | ||||
| //    sconf.allow_parallel = false;
 | ||||
|  | @ -707,7 +707,7 @@ void arrangeRectangles() { | |||
|     std::vector<double> eff; | ||||
|     eff.reserve(result.size()); | ||||
| 
 | ||||
|     auto bin_area = ShapeLike::area(bin); | ||||
|     auto bin_area = ShapeLike::area<PolygonImpl>(bin); | ||||
|     for(auto& r : result) { | ||||
|         double a = 0; | ||||
|         std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); }); | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ | |||
| #include <libnest2d/clipper_backend/clipper_backend.hpp> | ||||
| 
 | ||||
| // We include the stock optimizers for local and global optimization
 | ||||
| #include <libnest2d/optimizers/simplex.hpp>     // Local simplex for NfpPlacer | ||||
| #include <libnest2d/optimizers/subplex.hpp>     // Local subplex for NfpPlacer | ||||
| #include <libnest2d/optimizers/genetic.hpp>     // Genetic for min. bounding box | ||||
| 
 | ||||
| #include <libnest2d/libnest2d.hpp> | ||||
|  |  | |||
|  | @ -53,8 +53,8 @@ class _Item { | |||
| 
 | ||||
|     enum class Convexity: char { | ||||
|         UNCHECKED, | ||||
|         TRUE, | ||||
|         FALSE | ||||
|         C_TRUE, | ||||
|         C_FALSE | ||||
|     }; | ||||
| 
 | ||||
|     mutable Convexity convexity_ = Convexity::UNCHECKED; | ||||
|  | @ -213,10 +213,10 @@ public: | |||
|         switch(convexity_) { | ||||
|         case Convexity::UNCHECKED: | ||||
|             ret = sl::isConvex<RawShape>(sl::getContour(transformedShape())); | ||||
|             convexity_ = ret? Convexity::TRUE : Convexity::FALSE; | ||||
|             convexity_ = ret? Convexity::C_TRUE : Convexity::C_FALSE; | ||||
|             break; | ||||
|         case Convexity::TRUE: ret = true; break; | ||||
|         case Convexity::FALSE:; | ||||
|         case Convexity::C_TRUE: ret = true; break; | ||||
|         case Convexity::C_FALSE:; | ||||
|         } | ||||
| 
 | ||||
|         return ret; | ||||
|  |  | |||
|  | @ -625,7 +625,7 @@ public: | |||
|                 opt::StopCriteria stopcr; | ||||
|                 stopcr.max_iterations = 1000; | ||||
|                 stopcr.absolute_score_difference = 1e-20*norm_; | ||||
|                 opt::TOptimizer<opt::Method::L_SIMPLEX> solver(stopcr); | ||||
|                 opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr); | ||||
| 
 | ||||
|                 Optimum optimum(0, 0); | ||||
|                 double best_score = penality_; | ||||
|  |  | |||
|  | @ -7,11 +7,6 @@ | |||
| #include "Format/STL.hpp" | ||||
| #include "Format/3mf.hpp" | ||||
| 
 | ||||
| #include <numeric> | ||||
| #include <libnest2d.h> | ||||
| #include <ClipperUtils.hpp> | ||||
| #include "slic3r/GUI/GUI.hpp" | ||||
| 
 | ||||
| #include <float.h> | ||||
| 
 | ||||
| #include <boost/algorithm/string/predicate.hpp> | ||||
|  | @ -304,438 +299,36 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb | |||
|     return result; | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
|             tmpmesh.scale(objinst->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.x << ", " | ||||
|                                                     << v.y << "},\n"; | ||||
|                 { | ||||
|                     auto v = expoly.contour.points.front(); | ||||
|                     ss << "\t\t\t{" << v.x << ", " << v.y << "},\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.x << ", " | ||||
|                                            << v.y << "},\n"; | ||||
|                     { | ||||
|                         auto v = h.points.front(); | ||||
|                         ss << "\t\t\t\t{" << v.x << ", " << v.y << "},\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->scaling_factor); | ||||
|             objinst->transform_mesh(&tmpmesh); | ||||
|             ExPolygons expolys = tmpmesh.horizontal_projection(); | ||||
|             svg.draw(expolys); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // 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(), 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; | ||||
| 
 | ||||
|                     tmpmesh.scale(objinst->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) { | ||||
|                         item.rotation(objinst->rotation); | ||||
|                         item.translation( { | ||||
|                             ClipperLib::cInt(objinst->offset.x/SCALING_FACTOR), | ||||
|                             ClipperLib::cInt(objinst->offset.y/SCALING_FACTOR) | ||||
|                         }); | ||||
|                         ret.emplace_back(objinst, item); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * \brief Arranges the model objects on the screen. | ||||
|  * | ||||
|  * The arrangement considers multiple bins (aka. print beds) for placing all | ||||
|  * the items provided in the model argument. If the items don't fit on one | ||||
|  * print bed, the remaining will be placed onto newly created print beds. | ||||
|  * The first_bin_only parameter, if set to true, disables this behaviour and | ||||
|  * makes sure that only one print bed is filled and the remaining items will be | ||||
|  * untouched. When set to false, the items which could not fit onto the | ||||
|  * print bed will be placed next to the print bed so the user should see a | ||||
|  * pile of items on the print bed and some other piles outside the print | ||||
|  * area that can be dragged later onto the print bed as a group. | ||||
|  * | ||||
|  * \param model The model object with the 3D content. | ||||
|  * \param dist The minimum distance which is allowed for any pair of items | ||||
|  * on the print bed  in any direction. | ||||
|  * \param bb The bounding box of the print bed. It corresponds to the 'bin' | ||||
|  * for bin packing. | ||||
|  * \param first_bin_only This parameter controls whether to place the | ||||
|  * remaining items which do not fit onto the print area next to the print | ||||
|  * bed or leave them untouched (let the user arrange them by hand or remove | ||||
|  * them). | ||||
|  */ | ||||
| bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, | ||||
|              bool first_bin_only, | ||||
|              std::function<void(unsigned)> progressind) | ||||
| { | ||||
|     using ArrangeResult = _IndexedPackGroup<PolygonImpl>; | ||||
| 
 | ||||
|     bool ret = true; | ||||
| 
 | ||||
|     // Create the arranger config
 | ||||
|     auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR); | ||||
| 
 | ||||
|     // Get the 2D projected shapes with their 3D model instance pointers
 | ||||
|     auto shapemap = arr::projectModelFromTop(model); | ||||
| 
 | ||||
|     bool hasbin = bb != nullptr && bb->defined; | ||||
|     double area_max = 0; | ||||
| 
 | ||||
|     // 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, min_obj_distance, &area_max, hasbin] | ||||
|                   (ShapeData2D::value_type& it) | ||||
|     { | ||||
|         shapes.push_back(std::ref(it.second)); | ||||
|     }); | ||||
| 
 | ||||
|     Box bin; | ||||
| 
 | ||||
|     if(hasbin) { | ||||
|         // Scale up the bounding box to clipper scale.
 | ||||
|         BoundingBoxf bbb = *bb; | ||||
|         bbb.scale(1.0/SCALING_FACTOR); | ||||
| 
 | ||||
|         bin = Box({ | ||||
|                     static_cast<libnest2d::Coord>(bbb.min.x), | ||||
|                     static_cast<libnest2d::Coord>(bbb.min.y) | ||||
|                 }, | ||||
|                 { | ||||
|                     static_cast<libnest2d::Coord>(bbb.max.x), | ||||
|                     static_cast<libnest2d::Coord>(bbb.max.y) | ||||
|                 }); | ||||
|     } | ||||
| 
 | ||||
|     // Will use the DJD selection heuristic with the BottomLeft placement
 | ||||
|     // strategy
 | ||||
|     using Arranger = Arranger<NfpPlacer, FirstFitSelection>; | ||||
|     using PConf = Arranger::PlacementConfig; | ||||
|     using SConf = Arranger::SelectionConfig; | ||||
| 
 | ||||
|     PConf pcfg;     // Placement configuration
 | ||||
|     SConf scfg;     // Selection configuration
 | ||||
| 
 | ||||
|     // 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
 | ||||
|     pcfg.accuracy = 0.8; | ||||
| 
 | ||||
|     // Magic: we will specify what is the goal of arrangement... In this case
 | ||||
|     // we override the default object function to make the larger items go into
 | ||||
|     // the center of the pile and smaller items orbit it so the resulting pile
 | ||||
|     // has a circle-like shape. This is good for the print bed's heat profile.
 | ||||
|     // We alse sacrafice a bit of pack efficiency for this to work. As a side
 | ||||
|     // effect, the arrange procedure is a lot faster (we do not need to
 | ||||
|     // calculate the convex hulls)
 | ||||
|     pcfg.object_function = [bin, hasbin]( | ||||
|             NfpPlacer::Pile& pile,   // The currently arranged pile
 | ||||
|             const Item &item, | ||||
|             double /*area*/,        // Sum area of items (not needed)
 | ||||
|             double norm,            // A norming factor for physical dimensions
 | ||||
|             double penality)        // Min penality in case of bad arrangement
 | ||||
|     { | ||||
|         using pl = PointLike; | ||||
| 
 | ||||
|         static const double BIG_ITEM_TRESHOLD = 0.2; | ||||
|         static const double GRAVITY_RATIO = 0.5; | ||||
|         static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO; | ||||
| 
 | ||||
|         // We will treat big items (compared to the print bed) differently
 | ||||
|         NfpPlacer::Pile bigs; | ||||
|         bigs.reserve(pile.size()); | ||||
|         for(auto& p : pile) { | ||||
|             auto pbb = ShapeLike::boundingBox(p); | ||||
|             auto na = std::sqrt(pbb.width()*pbb.height())/norm; | ||||
|             if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p); | ||||
|         } | ||||
| 
 | ||||
|         // Candidate item bounding box
 | ||||
|         auto ibb = item.boundingBox(); | ||||
| 
 | ||||
|         // Calculate the full bounding box of the pile with the candidate item
 | ||||
|         pile.emplace_back(item.transformedShape()); | ||||
|         auto fullbb = ShapeLike::boundingBox(pile); | ||||
|         pile.pop_back(); | ||||
| 
 | ||||
|         // The bounding box of the big items (they will accumulate in the center
 | ||||
|         // of the pile
 | ||||
|         auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs); | ||||
| 
 | ||||
|         // The size indicator of the candidate item. This is not the area,
 | ||||
|         // but almost...
 | ||||
|         auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm; | ||||
| 
 | ||||
|         // Will hold the resulting score
 | ||||
|         double score = 0; | ||||
| 
 | ||||
|         if(itemnormarea > BIG_ITEM_TRESHOLD) { | ||||
|             // This branch is for the bigger items..
 | ||||
|             // Here we will use the closest point of the item bounding box to
 | ||||
|             // the already arranged pile. So not the bb center nor the a choosen
 | ||||
|             // corner but whichever is the closest to the center. This will
 | ||||
|             // prevent unwanted strange arrangements.
 | ||||
| 
 | ||||
|             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)}; | ||||
| 
 | ||||
|             auto cc = fullbb.center(); // The gravity center
 | ||||
| 
 | ||||
|             // Now the distnce of the gravity center will be calculated to the
 | ||||
|             // five anchor points and the smallest will be chosen.
 | ||||
|             std::array<double, 5> dists; | ||||
|             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); | ||||
| 
 | ||||
|             auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; | ||||
| 
 | ||||
|             // Density is the pack density: how big is the arranged pile
 | ||||
|             auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; | ||||
| 
 | ||||
|             // The score is a weighted sum of the distance from pile center
 | ||||
|             // and the pile size
 | ||||
|             score = GRAVITY_RATIO * dist + DENSITY_RATIO * density; | ||||
| 
 | ||||
|         } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { | ||||
|             // If there are no big items, only small, we should consider the
 | ||||
|             // density here as well to not get silly results
 | ||||
|             auto bindist = pl::distance(ibb.center(), bin.center()) / norm; | ||||
|             auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; | ||||
|             score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density; | ||||
|         } 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; | ||||
|         } | ||||
| 
 | ||||
|         // If it does not fit into the print bed we will beat it
 | ||||
|         // with a large penality. If we would not do this, there would be only
 | ||||
|         // one big pile that doesn't care whether it fits onto the print bed.
 | ||||
|         if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score; | ||||
| 
 | ||||
|         return score; | ||||
|     }; | ||||
| 
 | ||||
|     // Create the arranger object
 | ||||
|     Arranger arranger(bin, min_obj_distance, pcfg, scfg); | ||||
| 
 | ||||
|     // Set the progress indicator for the arranger.
 | ||||
|     arranger.progressIndicator(progressind); | ||||
| 
 | ||||
|     // Arrange and return the items with their respective indices within the
 | ||||
|     // input sequence.
 | ||||
|     auto result = arranger.arrangeIndexed(shapes.begin(), shapes.end()); | ||||
| 
 | ||||
|     auto applyResult = [&shapemap](ArrangeResult::value_type& group, | ||||
|             Coord batch_offset) | ||||
|     { | ||||
|         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 tranformation data from the item object and scale it
 | ||||
|             // appropriately
 | ||||
|             auto off = item.translation(); | ||||
|             Radians rot = item.rotation(); | ||||
|             Pointf foff(off.X*SCALING_FACTOR + batch_offset, | ||||
|                         off.Y*SCALING_FACTOR); | ||||
| 
 | ||||
|             // write the tranformation data into the model instance
 | ||||
|             inst_ptr->rotation = rot; | ||||
|             inst_ptr->offset = foff; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     if(first_bin_only) { | ||||
|         applyResult(result.front(), 0); | ||||
|     } else { | ||||
| 
 | ||||
|         const auto STRIDE_PADDING = 1.2; | ||||
| 
 | ||||
|         Coord stride = static_cast<Coord>(STRIDE_PADDING* | ||||
|                                           bin.width()*SCALING_FACTOR); | ||||
|         Coord batch_offset = 0; | ||||
| 
 | ||||
|         for(auto& group : result) { | ||||
|             applyResult(group, batch_offset); | ||||
| 
 | ||||
|             // 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; | ||||
| } | ||||
| } | ||||
| 
 | ||||
| /*  arrange objects preserving their instance count
 | ||||
|     but altering their instance positions */ | ||||
| bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb, | ||||
|                             std::function<void(unsigned)> progressind) | ||||
| bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) | ||||
| { | ||||
|     bool ret = false; | ||||
|     if(bb != nullptr && bb->defined) { | ||||
|         // Despite the new arrange is able to run without a specified bin,
 | ||||
|         // the perl testsuit still fails for this case. For now the safest
 | ||||
|         // thing to do is to use the new arrange only when a proper bin is
 | ||||
|         // specified.
 | ||||
|         ret = arr::arrange(*this, dist, bb, false, progressind); | ||||
|     } else { | ||||
|         // get the (transformed) size of each instance so that we take
 | ||||
|         // into account their different transformations when packing
 | ||||
|         Pointfs instance_sizes; | ||||
|         Pointfs instance_centers; | ||||
|         for (const ModelObject *o : this->objects) | ||||
|             for (size_t i = 0; i < o->instances.size(); ++ i) { | ||||
|                 // an accurate snug bounding box around the transformed mesh.
 | ||||
|                 BoundingBoxf3 bbox(o->instance_bounding_box(i, true)); | ||||
|                 instance_sizes.push_back(bbox.size()); | ||||
|                 instance_centers.push_back(bbox.center()); | ||||
|             } | ||||
| 
 | ||||
|         Pointfs positions; | ||||
|         if (! _arrange(instance_sizes, dist, bb, positions)) | ||||
|             return false; | ||||
| 
 | ||||
|         size_t idx = 0; | ||||
|         for (ModelObject *o : this->objects) { | ||||
|             for (ModelInstance *i : o->instances) { | ||||
|                 i->offset = positions[idx] - instance_centers[idx]; | ||||
|                 ++ idx; | ||||
|             } | ||||
|             o->invalidate_bounding_box(); | ||||
|     // get the (transformed) size of each instance so that we take
 | ||||
|     // into account their different transformations when packing
 | ||||
|     Pointfs instance_sizes; | ||||
|     Pointfs instance_centers; | ||||
|     for (const ModelObject *o : this->objects) | ||||
|         for (size_t i = 0; i < o->instances.size(); ++ i) { | ||||
|             // an accurate snug bounding box around the transformed mesh.
 | ||||
|             BoundingBoxf3 bbox(o->instance_bounding_box(i, true)); | ||||
|             instance_sizes.push_back(bbox.size()); | ||||
|             instance_centers.push_back(bbox.center()); | ||||
|         } | ||||
| 
 | ||||
|     Pointfs positions; | ||||
|     if (! _arrange(instance_sizes, dist, bb, positions)) | ||||
|         return false; | ||||
| 
 | ||||
|     size_t idx = 0; | ||||
|     for (ModelObject *o : this->objects) { | ||||
|         for (ModelInstance *i : o->instances) { | ||||
|             i->offset = positions[idx] - instance_centers[idx]; | ||||
|             ++ idx; | ||||
|         } | ||||
|         o->invalidate_bounding_box(); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| // Duplicate the entire model preserving instance relative positions.
 | ||||
|  |  | |||
|  | @ -290,8 +290,7 @@ public: | |||
|     void center_instances_around_point(const Pointf &point); | ||||
|     void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } | ||||
|     TriangleMesh mesh() const; | ||||
|     bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL, | ||||
|                          std::function<void(unsigned)> progressind = [](unsigned){}); | ||||
|     bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); | ||||
|     // Croaks if the duplicated objects do not fit the print bed.
 | ||||
|     void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); | ||||
|     void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); | ||||
|  |  | |||
							
								
								
									
										405
									
								
								xs/src/libslic3r/ModelArrange.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										405
									
								
								xs/src/libslic3r/ModelArrange.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,405 @@ | |||
| #ifndef MODELARRANGE_HPP | ||||
| #define MODELARRANGE_HPP | ||||
| 
 | ||||
| #include "Model.hpp" | ||||
| #include "SVG.hpp" | ||||
| #include <libnest2d.h> | ||||
| 
 | ||||
| #include <numeric> | ||||
| #include <ClipperUtils.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; | ||||
|             tmpmesh.scale(objinst->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.x << ", " | ||||
|                                                     << v.y << "},\n"; | ||||
|                 { | ||||
|                     auto v = expoly.contour.points.front(); | ||||
|                     ss << "\t\t\t{" << v.x << ", " << v.y << "},\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.x << ", " | ||||
|                                            << v.y << "},\n"; | ||||
|                     { | ||||
|                         auto v = h.points.front(); | ||||
|                         ss << "\t\t\t\t{" << v.x << ", " << v.y << "},\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->scaling_factor); | ||||
|             objinst->transform_mesh(&tmpmesh); | ||||
|             ExPolygons expolys = tmpmesh.horizontal_projection(); | ||||
|             svg.draw(expolys); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // 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(), 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; | ||||
| 
 | ||||
|                     tmpmesh.scale(objinst->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) { | ||||
|                         item.rotation(objinst->rotation); | ||||
|                         item.translation( { | ||||
|                             ClipperLib::cInt(objinst->offset.x/SCALING_FACTOR), | ||||
|                             ClipperLib::cInt(objinst->offset.y/SCALING_FACTOR) | ||||
|                         }); | ||||
|                         ret.emplace_back(objinst, item); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * \brief Arranges the model objects on the screen. | ||||
|  * | ||||
|  * The arrangement considers multiple bins (aka. print beds) for placing all | ||||
|  * the items provided in the model argument. If the items don't fit on one | ||||
|  * print bed, the remaining will be placed onto newly created print beds. | ||||
|  * The first_bin_only parameter, if set to true, disables this behaviour and | ||||
|  * makes sure that only one print bed is filled and the remaining items will be | ||||
|  * untouched. When set to false, the items which could not fit onto the | ||||
|  * print bed will be placed next to the print bed so the user should see a | ||||
|  * pile of items on the print bed and some other piles outside the print | ||||
|  * area that can be dragged later onto the print bed as a group. | ||||
|  * | ||||
|  * \param model The model object with the 3D content. | ||||
|  * \param dist The minimum distance which is allowed for any pair of items | ||||
|  * on the print bed  in any direction. | ||||
|  * \param bb The bounding box of the print bed. It corresponds to the 'bin' | ||||
|  * for bin packing. | ||||
|  * \param first_bin_only This parameter controls whether to place the | ||||
|  * remaining items which do not fit onto the print area next to the print | ||||
|  * bed or leave them untouched (let the user arrange them by hand or remove | ||||
|  * them). | ||||
|  */ | ||||
| bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, | ||||
|              bool first_bin_only, | ||||
|              std::function<void(unsigned)> progressind) | ||||
| { | ||||
|     using ArrangeResult = _IndexedPackGroup<PolygonImpl>; | ||||
| 
 | ||||
|     bool ret = true; | ||||
| 
 | ||||
|     // Create the arranger config
 | ||||
|     auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR); | ||||
| 
 | ||||
|     // Get the 2D projected shapes with their 3D model instance pointers
 | ||||
|     auto shapemap = arr::projectModelFromTop(model); | ||||
| 
 | ||||
|     bool hasbin = bb != nullptr && bb->defined; | ||||
|     double area_max = 0; | ||||
| 
 | ||||
|     // 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, min_obj_distance, &area_max, hasbin] | ||||
|                   (ShapeData2D::value_type& it) | ||||
|     { | ||||
|         shapes.push_back(std::ref(it.second)); | ||||
|     }); | ||||
| 
 | ||||
|     Box bin; | ||||
| 
 | ||||
|     if(hasbin) { | ||||
|         // Scale up the bounding box to clipper scale.
 | ||||
|         BoundingBoxf bbb = *bb; | ||||
|         bbb.scale(1.0/SCALING_FACTOR); | ||||
| 
 | ||||
|         bin = Box({ | ||||
|                     static_cast<libnest2d::Coord>(bbb.min.x), | ||||
|                     static_cast<libnest2d::Coord>(bbb.min.y) | ||||
|                 }, | ||||
|                 { | ||||
|                     static_cast<libnest2d::Coord>(bbb.max.x), | ||||
|                     static_cast<libnest2d::Coord>(bbb.max.y) | ||||
|                 }); | ||||
|     } | ||||
| 
 | ||||
|     // Will use the DJD selection heuristic with the BottomLeft placement
 | ||||
|     // strategy
 | ||||
|     using Arranger = Arranger<NfpPlacer, FirstFitSelection>; | ||||
|     using PConf = Arranger::PlacementConfig; | ||||
|     using SConf = Arranger::SelectionConfig; | ||||
| 
 | ||||
|     PConf pcfg;     // Placement configuration
 | ||||
|     SConf scfg;     // Selection configuration
 | ||||
| 
 | ||||
|     // 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
 | ||||
|     pcfg.accuracy = 0.4f; | ||||
| 
 | ||||
|     // Magic: we will specify what is the goal of arrangement... In this case
 | ||||
|     // we override the default object function to make the larger items go into
 | ||||
|     // the center of the pile and smaller items orbit it so the resulting pile
 | ||||
|     // has a circle-like shape. This is good for the print bed's heat profile.
 | ||||
|     // We alse sacrafice a bit of pack efficiency for this to work. As a side
 | ||||
|     // effect, the arrange procedure is a lot faster (we do not need to
 | ||||
|     // calculate the convex hulls)
 | ||||
|     pcfg.object_function = [bin, hasbin]( | ||||
|             NfpPlacer::Pile& pile,   // The currently arranged pile
 | ||||
|             const Item &item, | ||||
|             double /*area*/,        // Sum area of items (not needed)
 | ||||
|             double norm,            // A norming factor for physical dimensions
 | ||||
|             double penality)        // Min penality in case of bad arrangement
 | ||||
|     { | ||||
|         using pl = PointLike; | ||||
| 
 | ||||
|         static const double BIG_ITEM_TRESHOLD = 0.2; | ||||
|         static const double GRAVITY_RATIO = 0.5; | ||||
|         static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO; | ||||
| 
 | ||||
|         // We will treat big items (compared to the print bed) differently
 | ||||
|         NfpPlacer::Pile bigs; | ||||
|         bigs.reserve(pile.size()); | ||||
|         for(auto& p : pile) { | ||||
|             auto pbb = ShapeLike::boundingBox(p); | ||||
|             auto na = std::sqrt(pbb.width()*pbb.height())/norm; | ||||
|             if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p); | ||||
|         } | ||||
| 
 | ||||
|         // Candidate item bounding box
 | ||||
|         auto ibb = item.boundingBox(); | ||||
| 
 | ||||
|         // Calculate the full bounding box of the pile with the candidate item
 | ||||
|         pile.emplace_back(item.transformedShape()); | ||||
|         auto fullbb = ShapeLike::boundingBox(pile); | ||||
|         pile.pop_back(); | ||||
| 
 | ||||
|         // The bounding box of the big items (they will accumulate in the center
 | ||||
|         // of the pile
 | ||||
|         auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs); | ||||
| 
 | ||||
|         // The size indicator of the candidate item. This is not the area,
 | ||||
|         // but almost...
 | ||||
|         auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm; | ||||
| 
 | ||||
|         // Will hold the resulting score
 | ||||
|         double score = 0; | ||||
| 
 | ||||
|         if(itemnormarea > BIG_ITEM_TRESHOLD) { | ||||
|             // This branch is for the bigger items..
 | ||||
|             // Here we will use the closest point of the item bounding box to
 | ||||
|             // the already arranged pile. So not the bb center nor the a choosen
 | ||||
|             // corner but whichever is the closest to the center. This will
 | ||||
|             // prevent unwanted strange arrangements.
 | ||||
| 
 | ||||
|             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)}; | ||||
| 
 | ||||
|             auto cc = fullbb.center(); // The gravity center
 | ||||
| 
 | ||||
|             // Now the distnce of the gravity center will be calculated to the
 | ||||
|             // five anchor points and the smallest will be chosen.
 | ||||
|             std::array<double, 5> dists; | ||||
|             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); | ||||
| 
 | ||||
|             auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; | ||||
| 
 | ||||
|             // Density is the pack density: how big is the arranged pile
 | ||||
|             auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; | ||||
| 
 | ||||
|             // The score is a weighted sum of the distance from pile center
 | ||||
|             // and the pile size
 | ||||
|             score = GRAVITY_RATIO * dist + DENSITY_RATIO * density; | ||||
| 
 | ||||
|         } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { | ||||
|             // If there are no big items, only small, we should consider the
 | ||||
|             // density here as well to not get silly results
 | ||||
|             auto bindist = pl::distance(ibb.center(), bin.center()) / norm; | ||||
|             auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; | ||||
|             score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density; | ||||
|         } 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; | ||||
|         } | ||||
| 
 | ||||
|         // If it does not fit into the print bed we will beat it
 | ||||
|         // with a large penality. If we would not do this, there would be only
 | ||||
|         // one big pile that doesn't care whether it fits onto the print bed.
 | ||||
|         if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score; | ||||
| 
 | ||||
|         return score; | ||||
|     }; | ||||
| 
 | ||||
|     // Create the arranger object
 | ||||
|     Arranger arranger(bin, min_obj_distance, pcfg, scfg); | ||||
| 
 | ||||
|     // Set the progress indicator for the arranger.
 | ||||
|     arranger.progressIndicator(progressind); | ||||
| 
 | ||||
|     // Arrange and return the items with their respective indices within the
 | ||||
|     // input sequence.
 | ||||
|     auto result = arranger.arrangeIndexed(shapes.begin(), shapes.end()); | ||||
| 
 | ||||
|     auto applyResult = [&shapemap](ArrangeResult::value_type& group, | ||||
|             Coord batch_offset) | ||||
|     { | ||||
|         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 tranformation data from the item object and scale it
 | ||||
|             // appropriately
 | ||||
|             auto off = item.translation(); | ||||
|             Radians rot = item.rotation(); | ||||
|             Pointf foff(off.X*SCALING_FACTOR + batch_offset, | ||||
|                         off.Y*SCALING_FACTOR); | ||||
| 
 | ||||
|             // write the tranformation data into the model instance
 | ||||
|             inst_ptr->rotation = rot; | ||||
|             inst_ptr->offset = foff; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     if(first_bin_only) { | ||||
|         applyResult(result.front(), 0); | ||||
|     } else { | ||||
| 
 | ||||
|         const auto STRIDE_PADDING = 1.2; | ||||
| 
 | ||||
|         Coord stride = static_cast<Coord>(STRIDE_PADDING* | ||||
|                                           bin.width()*SCALING_FACTOR); | ||||
|         Coord batch_offset = 0; | ||||
| 
 | ||||
|         for(auto& group : result) { | ||||
|             applyResult(group, batch_offset); | ||||
| 
 | ||||
|             // Only the first pack group can be placed onto the print bed. The
 | ||||
|             // other objects which could not fit will be placed next to the
 | ||||
|             // print bed
 | ||||
|             batch_offset += stride; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     for(auto objptr : model.objects) objptr->invalidate_bounding_box(); | ||||
| 
 | ||||
|     return ret && result.size() == 1; | ||||
| } | ||||
| 
 | ||||
| } | ||||
| } | ||||
| #endif // MODELARRANGE_HPP
 | ||||
|  | @ -14,7 +14,7 @@ | |||
| #include <boost/thread.hpp> | ||||
| 
 | ||||
| #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" | ||||
| #define SLIC3R_VERSION "1.41.0-alpha2" | ||||
| #define SLIC3R_VERSION "1.41.0-alpha3" | ||||
| #define SLIC3R_BUILD "UNKNOWN" | ||||
| 
 | ||||
| typedef int32_t coord_t; | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ | |||
| #include <unordered_map> | ||||
| 
 | ||||
| #include <slic3r/GUI/GUI.hpp> | ||||
| #include <ModelArrange.hpp> | ||||
| #include <slic3r/GUI/PresetBundle.hpp> | ||||
| 
 | ||||
| #include <Geometry.hpp> | ||||
|  | @ -310,12 +311,13 @@ void AppController::arrange_model() | |||
| 
 | ||||
|         auto dist = print_ctl()->config().min_object_distance(); | ||||
| 
 | ||||
| 
 | ||||
|         BoundingBoxf bb(print_ctl()->config().bed_shape.values); | ||||
| 
 | ||||
|         if(pind) pind->update(0, _(L("Arranging objects..."))); | ||||
| 
 | ||||
|         try { | ||||
|             model_->arrange_objects(dist, &bb, [pind, count](unsigned rem){ | ||||
|             arr::arrange(*model_, dist, &bb, false, [pind, count](unsigned rem){ | ||||
|                 if(pind) pind->update(count - rem, _(L("Arranging objects..."))); | ||||
|             }); | ||||
|         } catch(std::exception& e) { | ||||
|  |  | |||
|  | @ -2697,9 +2697,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|     }  | ||||
|     else if (evt.Leaving()) | ||||
|     { | ||||
|         // to remove hover when mouse goes out of this canvas
 | ||||
|         m_mouse.position = Pointf((coordf_t)pos.x, (coordf_t)pos.y); | ||||
|         render(); | ||||
|         // to remove hover on objects when the mouse goes out of this canvas
 | ||||
|         m_mouse.position = Pointf(-1.0, -1.0); | ||||
|         m_dirty = true; | ||||
|     } | ||||
|     else if (evt.LeftDClick() && (m_hover_volume_id != -1)) | ||||
|         m_on_double_click_callback.call(); | ||||
|  | @ -3403,20 +3403,22 @@ void GLCanvas3D::_picking_pass() const | |||
|         if (m_multisample_allowed) | ||||
|             ::glEnable(GL_MULTISAMPLE); | ||||
| 
 | ||||
|         const Size& cnv_size = get_canvas_size(); | ||||
| 
 | ||||
|         GLubyte color[4]; | ||||
|         ::glReadPixels(pos.x, cnv_size.get_height() - pos.y - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color); | ||||
|         int volume_id = color[0] + color[1] * 256 + color[2] * 256 * 256; | ||||
| 
 | ||||
|         m_hover_volume_id = -1; | ||||
| 
 | ||||
|         int volume_id = -1; | ||||
|         for (GLVolume* vol : m_volumes.volumes) | ||||
|         { | ||||
|             vol->hover = false; | ||||
|         } | ||||
| 
 | ||||
|         if (volume_id < (int)m_volumes.volumes.size()) | ||||
|         GLubyte color[4] = { 0, 0, 0, 0 }; | ||||
|         const Size& cnv_size = get_canvas_size(); | ||||
|         bool inside = (0 <= pos.x) && (pos.x < cnv_size.get_width()) && (0 <= pos.y) && (pos.y < cnv_size.get_height()); | ||||
|         if (inside) | ||||
|         { | ||||
|             ::glReadPixels(pos.x, cnv_size.get_height() - pos.y - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color); | ||||
|             volume_id = color[0] + color[1] * 256 + color[2] * 256 * 256; | ||||
|         } | ||||
| 
 | ||||
|         if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) | ||||
|         { | ||||
|             m_hover_volume_id = volume_id; | ||||
|             m_volumes.volumes[volume_id]->hover = true; | ||||
|  | @ -3432,7 +3434,10 @@ void GLCanvas3D::_picking_pass() const | |||
|             m_gizmos.set_hover_id(-1); | ||||
|         } | ||||
|         else | ||||
|             m_gizmos.set_hover_id(254 - (int)color[2]); | ||||
|         { | ||||
|             m_hover_volume_id = -1; | ||||
|             m_gizmos.set_hover_id(inside ? (254 - (int)color[2]) : -1); | ||||
|         } | ||||
| 
 | ||||
|         // updates gizmos overlay
 | ||||
|         if (_get_first_selected_object_id() != -1) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 tamasmeszaros
						tamasmeszaros