diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 3652c0174..9d214ce40 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -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 diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 95ff990aa..d41b4c13a 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -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}) diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index 883d12610..d6b2ccc34 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -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(0*SCALE); - using Placer = strategies::_NofitPolyPlacer; + using Placer = strategies::_NofitPolyPlacer; using Packer = Arranger; 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 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 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 eff; eff.reserve(result.size()); - auto bin_area = ShapeLike::area(bin); + auto bin_area = ShapeLike::area(bin); for(auto& r : result) { double a = 0; std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); }); diff --git a/xs/src/libnest2d/libnest2d.h b/xs/src/libnest2d/libnest2d.h index e0ad05c41..c9e21ecfb 100644 --- a/xs/src/libnest2d/libnest2d.h +++ b/xs/src/libnest2d/libnest2d.h @@ -6,7 +6,7 @@ #include // We include the stock optimizers for local and global optimization -#include // Local simplex for NfpPlacer +#include // Local subplex for NfpPlacer #include // Genetic for min. bounding box #include diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index 1aa672447..fad38b9a3 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/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(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; diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 6ae71bb48..06163b00a 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -625,7 +625,7 @@ public: opt::StopCriteria stopcr; stopcr.max_iterations = 1000; stopcr.absolute_score_difference = 1e-20*norm_; - opt::TOptimizer solver(stopcr); + opt::TOptimizer solver(stopcr); Optimum optimum(0, 0); double best_score = penality_; diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 5f9c7a7d4..24e34d75b 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -7,11 +7,6 @@ #include "Format/STL.hpp" #include "Format/3mf.hpp" -#include -#include -#include -#include "slic3r/GUI/GUI.hpp" - #include #include @@ -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>; - -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 progressind) -{ - using ArrangeResult = _IndexedPackGroup; - - bool ret = true; - - // Create the arranger config - auto min_obj_distance = static_cast(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> 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(bbb.min.x), - static_cast(bbb.min.y) - }, - { - static_cast(bbb.max.x), - static_cast(bbb.max.y) - }); - } - - // Will use the DJD selection heuristic with the BottomLeft placement - // strategy - using Arranger = Arranger; - 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 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(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 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. diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index f5e97fb6a..4c650f0de 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -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 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); diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp new file mode 100644 index 000000000..af4bfcf70 --- /dev/null +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -0,0 +1,405 @@ +#ifndef MODELARRANGE_HPP +#define MODELARRANGE_HPP + +#include "Model.hpp" +#include "SVG.hpp" +#include + +#include +#include + +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>; + +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 progressind) +{ + using ArrangeResult = _IndexedPackGroup; + + bool ret = true; + + // Create the arranger config + auto min_obj_distance = static_cast(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> 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(bbb.min.x), + static_cast(bbb.min.y) + }, + { + static_cast(bbb.max.x), + static_cast(bbb.max.y) + }); + } + + // Will use the DJD selection heuristic with the BottomLeft placement + // strategy + using Arranger = Arranger; + 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 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(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 diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 77006cebe..2f07ca51a 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -14,7 +14,7 @@ #include #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; diff --git a/xs/src/slic3r/AppController.cpp b/xs/src/slic3r/AppController.cpp index 151b7f880..1d4b7d545 100644 --- a/xs/src/slic3r/AppController.cpp +++ b/xs/src/slic3r/AppController.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -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) { diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 6396233e8..46bcf4f44 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -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)