diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index b8a542752..4831c1fb6 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -67,6 +67,7 @@ class _Item { } bb_cache_; std::function applyfn_; + bool fixed_{false}; public: @@ -143,6 +144,9 @@ public: { if (applyfn_) applyfn_(*this, binidx); } + + inline bool isFixed() const noexcept { return fixed_; } + inline void markAsFixed(bool fixed = true) { fixed_ = fixed; } /** * @brief Convert the polygon to string representation. The format depends diff --git a/src/libnest2d/include/libnest2d/selections/firstfit.hpp b/src/libnest2d/include/libnest2d/selections/firstfit.hpp index d521673b4..287204c08 100644 --- a/src/libnest2d/include/libnest2d/selections/firstfit.hpp +++ b/src/libnest2d/include/libnest2d/selections/firstfit.hpp @@ -39,6 +39,15 @@ public: std::vector placers; placers.reserve(last-first); + + std::for_each(first, last, [this](Item& itm) { + if(itm.isFixed()) { + if(packed_bins_.empty()) packed_bins_.emplace_back(); + packed_bins_.front().emplace_back(itm); + } else { + store_.emplace_back(itm); + } + }); // If the packed_items array is not empty we have to create as many // placers as there are elements in packed bins and preload each item @@ -49,8 +58,6 @@ public: placers.back().preload(ig); } - std::copy(first, last, std::back_inserter(store_)); - auto sortfunc = [](Item& i1, Item& i2) { return i1.area() > i2.area(); }; @@ -76,7 +83,6 @@ public: } } - auto it = store_.begin(); while(it != store_.end() && !cancelled()) { diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 885979648..6d9c6007f 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -1,5 +1,4 @@ #include "ModelArrange.hpp" -//#include "Model.hpp" #include "Geometry.hpp" #include "SVG.hpp" #include "MTUtils.hpp" @@ -656,34 +655,35 @@ BedShapeHint bedShape(const Polyline &bed) { //static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; +//template +//PackGroup _arrange(std::vector & items, +// const BinT & bin, +// coord_t minobjd, +// std::function prind, +// std::function stopfn) +//{ +// AutoArranger arranger{bin, minobjd, prind, stopfn}; +// return arranger(items.begin(), items.end()); +//} + template -PackGroup _arrange(std::vector & items, +PackGroup _arrange(std::vector & shapes, + const PackGroup & preshapes, const BinT & bin, coord_t minobjd, std::function prind, std::function stopfn) { - AutoArranger arranger{bin, minobjd, prind, stopfn}; - return arranger(items.begin(), items.end()); -} - -//template -//IndexedPackGroup _arrange(std::vector> &shapes, -// const PackGroup & preshapes, -// std::vector &minstances, -// const BinT & bin, -// coord_t minobjd) -//{ // auto binbb = sl::boundingBox(bin); -// AutoArranger arranger{bin, minobjd}; + AutoArranger arranger{bin, minobjd, prind, stopfn}; -// if(!preshapes.front().empty()) { // If there is something on the plate -// arranger.preload(preshapes); + if(!preshapes.front().empty()) { // If there is something on the plate + arranger.preload(preshapes); -// // Try to put the first item to the center, as the arranger will not -// // do this for us. + // Try to put the first item to the center, as the arranger will not + // do this for us. // auto shptrit = minstances.begin(); // for(auto shit = shapes.begin(); shit != shapes.end(); ++shit, ++shptrit) // { @@ -712,10 +712,10 @@ PackGroup _arrange(std::vector & items, // break; // } // } -// } + } -// return arranger(shapes.begin(), shapes.end()); -//} + return arranger(shapes.begin(), shapes.end()); +} inline SLIC3R_CONSTEXPR coord_t stride_padding(coord_t w) { @@ -725,53 +725,73 @@ inline SLIC3R_CONSTEXPR coord_t stride_padding(coord_t w) //// The final client function to arrange the Model. A progress indicator and //// a stop predicate can be also be passed to control the process. bool arrange(Arrangeables & arrangables, + const Arrangeables & excludes, coord_t min_obj_distance, - BedShapeHint bedhint, + const BedShapeHint & bedhint, std::function progressind, std::function stopcondition) { bool ret = true; namespace clppr = ClipperLib; - std::vector items; + std::vector items, excluded_items; items.reserve(arrangables.size()); coord_t binwidth = 0; - for (Arrangeable *arrangeable : arrangables) { - assert(arrangeable); - - auto arrangeitem = arrangeable->get_arrange_polygon(); - - Polygon& p = std::get<0>(arrangeitem); - const Vec2crd& offs = std::get<1>(arrangeitem); - double rotation = std::get<2>(arrangeitem); - - if (p.is_counter_clockwise()) p.reverse(); - - clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); - - auto firstp = clpath.Contour.front(); - clpath.Contour.emplace_back(firstp); + PackGroup preshapes{ {} }; // pack group with one initial bin for preloading - items.emplace_back( + auto process_arrangeable = + [](const Arrangeable * arrangeable, + std::vector & outp, + std::function applyfn) + { + assert(arrangeable); + + auto arrangeitem = arrangeable->get_arrange_polygon(); + + Polygon & p = std::get<0>(arrangeitem); + const Vec2crd &offs = std::get<1>(arrangeitem); + double rotation = std::get<2>(arrangeitem); + + if (p.is_counter_clockwise()) p.reverse(); + + clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); + + auto firstp = clpath.Contour.front(); + clpath.Contour.emplace_back(firstp); + + outp.emplace_back(applyfn, std::move(clpath)); + outp.front().rotation(rotation); + outp.front().translation({offs.x(), offs.y()}); + }; + + for (Arrangeable *arrangeable : arrangables) { + process_arrangeable( + arrangeable, + items, // callback called by arrange to apply the result on the arrangeable [arrangeable, &binwidth](const Item &itm, unsigned binidx) { clppr::cInt stride = binidx * stride_padding(binwidth); clppr::IntPoint offs = itm.translation(); - arrangeable->apply_arrange_result({unscaled(offs.X + stride), + arrangeable->apply_arrange_result({unscaled(offs.X + + stride), unscaled(offs.Y)}, itm.rotation()); - }, - std::move(clpath)); - items.front().rotation(rotation); - items.front().translation({offs.x(), offs.y()}); + }); } + for (const Arrangeable * fixed: excludes) + process_arrangeable(fixed, excluded_items, nullptr); + + for(Item& excl : excluded_items) preshapes.front().emplace_back(excl); + // Integer ceiling the min distance from the bed perimeters coord_t md = min_obj_distance - SCALED_EPSILON; md = (md % 2) ? md / 2 + 1 : md / 2; - + + auto& cfn = stopcondition; + switch (bedhint.type) { case BedShapeType::BOX: { // Create the arranger for the box shaped bed @@ -781,14 +801,14 @@ bool arrange(Arrangeables & arrangables, Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; binwidth = coord_t(binbb.width()); - _arrange(items, binbb, min_obj_distance, progressind, stopcondition); + _arrange(items, preshapes, binbb, min_obj_distance, progressind, cfn); break; } case BedShapeType::CIRCLE: { auto c = bedhint.shape.circ; auto cc = to_lnCircle(c); binwidth = scaled(c.radius()); - _arrange(items, cc, min_obj_distance, progressind, stopcondition); + _arrange(items, preshapes, cc, min_obj_distance, progressind, cfn); break; } case BedShapeType::IRREGULAR: { @@ -796,11 +816,11 @@ bool arrange(Arrangeables & arrangables, auto irrbed = sl::create(std::move(ctour)); BoundingBox polybb(bedhint.shape.polygon); binwidth = (polybb.max(X) - polybb.min(X)); - _arrange(items, irrbed, min_obj_distance, progressind, stopcondition); + _arrange(items, preshapes, irrbed, min_obj_distance, progressind, cfn); break; } case BedShapeType::WHO_KNOWS: { - _arrange(items, false, min_obj_distance, progressind, stopcondition); + _arrange(items, preshapes, false, min_obj_distance, progressind, cfn); break; } }; @@ -810,99 +830,15 @@ bool arrange(Arrangeables & arrangables, return ret; } -//void find_new_position(const Model &model, -// ModelInstancePtrs toadd, -// coord_t min_obj_distance, -// const Polyline &bed, -// WipeTowerInfo& wti) -//{ -// // Get the 2D projected shapes with their 3D model instance pointers -// auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); - -// // Copy the references for the shapes only, as the arranger expects a -// // sequence of objects convertible to Item or ClipperPolygon -// PackGroup preshapes; preshapes.emplace_back(); -// ItemGroup shapes; -// preshapes.front().reserve(shapemap.size()); - -// std::vector shapes_ptr; shapes_ptr.reserve(toadd.size()); -// IndexedPackGroup result; - -// // If there is no hint about the shape, we will try to guess -// BedShapeHint bedhint = bedShape(bed); - -// BoundingBox bbb(bed); - -// // Integer ceiling the min distance from the bed perimeters -// coord_t md = min_obj_distance - SCALED_EPSILON; -// md = (md % 2) ? md / 2 + 1 : md / 2; - -// auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md, -// ClipperLib::cInt{bbb.min(1)} - md}, -// {ClipperLib::cInt{bbb.max(0)} + md, -// ClipperLib::cInt{bbb.max(1)} + md}); - -// for(auto it = shapemap.begin(); it != shapemap.end(); ++it) { -// // `toadd` vector contains the instance pointers which have to be -// // considered by arrange. If `it` points to an ModelInstance, which -// // is NOT in `toadd`, add it to preshapes. -// if(std::find(toadd.begin(), toadd.end(), it->first) == toadd.end()) { -// if(it->second.isInside(binbb)) // just ignore items which are outside -// preshapes.front().emplace_back(std::ref(it->second)); -// } -// else { -// shapes_ptr.emplace_back(it->first); -// shapes.emplace_back(std::ref(it->second)); -// } -// } - -// switch(bedhint.type) { -// case BedShapeType::BOX: { -// // Create the arranger for the box shaped bed -// result = _arrange(shapes, preshapes, shapes_ptr, binbb, min_obj_distance); -// break; -// } -// case BedShapeType::CIRCLE: { -// auto c = bedhint.shape.circ; -// auto cc = to_lnCircle(c); -// result = _arrange(shapes, preshapes, shapes_ptr, cc, min_obj_distance); -// break; -// } -// case BedShapeType::IRREGULAR: -// case BedShapeType::WHO_KNOWS: { -// auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); -// ClipperLib::Polygon irrbed = sl::create(std::move(ctour)); -// result = _arrange(shapes, preshapes, shapes_ptr, irrbed, min_obj_distance); -// break; -// } -// }; - -// // Now we go through the result which will contain the fixed and the moving -// // polygons as well. We will have to search for our item. - -// ClipperLib::cInt stride = stride_padding(binbb.width()); -// ClipperLib::cInt batch_offset = 0; - -// for(auto& group : result) { -// for(auto& r : group) if(r.first < shapes.size()) { -// Item& resultitem = r.second; -// unsigned idx = r.first; -// auto offset = resultitem.translation(); -// Radians rot = resultitem.rotation(); -// ModelInstance *minst = shapes_ptr[idx]; -// Vec3d foffset(unscaled(offset.X + batch_offset), -// unscaled(offset.Y), -// minst->get_offset()(Z)); - -// // write the transformation data into the model instance -// minst->set_rotation(Z, rot); -// minst->set_offset(foffset); -// } -// batch_offset += stride; -// } -//} - +/// Arrange, without the fixed items (excludes) +bool arrange(Arrangeables & inp, + coord_t min_d, + const BedShapeHint & bedhint, + std::function prfn, + std::function stopfn) +{ + return arrange(inp, {}, min_d, bedhint, prfn, stopfn); } - -} +} // namespace arr +} // namespace Slic3r diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index 45dde13d6..306081eb8 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -1,16 +1,14 @@ #ifndef MODELARRANGE_HPP #define MODELARRANGE_HPP -//#include "Model.hpp" #include "Polygon.hpp" #include "BoundingBox.hpp" namespace Slic3r { -class Model; - namespace arr { +/// A geometry abstraction for a circular print bed. Similarly to BoundingBox. class CircleBed { Point center_; double radius_; @@ -24,6 +22,7 @@ public: inline operator bool() { return !std::isnan(radius_); } }; +/// Types of print bed shapes. enum class BedShapeType { BOX, CIRCLE, @@ -31,6 +30,7 @@ enum class BedShapeType { WHO_KNOWS }; +/// Info about the print bed for the arrange() function. struct BedShapeHint { BedShapeType type = BedShapeType::WHO_KNOWS; /*union*/ struct { // I know but who cares... TODO: use variant from cpp17? @@ -40,13 +40,19 @@ struct BedShapeHint { } shape; }; +/// Get a bed shape hint for arrange() from a naked Polyline. BedShapeHint bedShape(const Polyline& bed); +/** + * @brief Classes implementing the Arrangeable interface can be used as input + * to the arrange function. + */ class Arrangeable { public: virtual ~Arrangeable() = default; + /// Apply the result transformation calculated by the arrangement. virtual void apply_arrange_result(Vec2d offset, double rotation_rads) = 0; /// Get the 2D silhouette to arrange and an initial offset and rotation @@ -58,56 +64,48 @@ using Arrangeables = std::vector; /** * \brief Arranges the model objects on the screen. * - * The arrangement considers multiple bins (aka. print beds) for placing all - * the items provided in the model argument. If the items don't fit on one - * print bed, the remaining will be placed onto newly created print beds. - * The first_bin_only parameter, if set to true, disables this behavior and - * makes sure that only one print bed is filled and the remaining items will be - * untouched. When set to false, the items which could not fit onto the - * print bed will be placed next to the print bed so the user should see a - * pile of items on the print bed and some other piles outside the print - * area that can be dragged later onto the print bed as a group. + * The arrangement considers multiple bins (aka. print beds) for placing + * all the items provided in the model argument. If the items don't fit on + * one print bed, the remaining will be placed onto newly created print + * beds. The first_bin_only parameter, if set to true, disables this + * behavior and makes sure that only one print bed is filled and the + * remaining items will be untouched. When set to false, the items which + * could not fit onto the print bed will be placed next to the print bed so + * the user should see a pile of items on the print bed and some other + * piles outside the print area that can be dragged later onto the print + * bed as a group. + * + * \param items Input which are object pointers implementing the + * Arrangeable interface. + * + * \param min_obj_distance The minimum distance which is allowed for any + * pair of items on the print bed in any direction. + * + * \param bedhint Info about the shape and type of the + * bed. remaining items which do not fit onto the print area next to the + * print bed or leave them untouched (let the user arrange them by hand or + * remove them). + * + * \param progressind Progress indicator callback called when + * an object gets packed. The unsigned argument is the number of items + * remaining to pack. * - * \param model The model object with the 3D content. - * \param dist The minimum distance which is allowed for any pair of items - * on the print bed in any direction. - * \param bb The bounding box of the print bed. It corresponds to the 'bin' - * for bin packing. - * \param first_bin_only This parameter controls whether to place the - * remaining items which do not fit onto the print area next to the print - * bed or leave them untouched (let the user arrange them by hand or remove - * them). - * \param progressind Progress indicator callback called when an object gets - * packed. The unsigned argument is the number of items remaining to pack. * \param stopcondition A predicate returning true if abort is needed. */ -//bool arrange(Model &model, -// WipeTowerInfo& wipe_tower_info, -// coord_t min_obj_distance, -// const Slic3r::Polyline& bed, -// BedShapeHint bedhint, -// bool first_bin_only, -// std::function progressind, -// std::function stopcondition); - bool arrange(Arrangeables &items, coord_t min_obj_distance, - BedShapeHint bedhint, + const BedShapeHint& bedhint, std::function progressind, std::function stopcondition); -/// This will find a suitable position for a new object instance and leave the -/// old items untouched. -//void find_new_position(const Model& model, -// ModelInstancePtrs instances_to_add, -// coord_t min_obj_distance, -// const Slic3r::Polyline& bed, -// WipeTowerInfo& wti); -void find_new_position(Arrangeables &items, - const Arrangeables &instances_to_add, - coord_t min_obj_distance, - BedShapeHint bedhint); - +/// Same as the previous, only that it takes unmovable items as an +/// additional argument. +bool arrange(Arrangeables &items, + const Arrangeables &excludes, + coord_t min_obj_distance, + const BedShapeHint& bedhint, + std::function progressind, + std::function stopcondition); } // arr } // Slic3r diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 193390f85..aba8ae71d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2461,132 +2461,68 @@ void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { update_status(m_count, was_canceled() ? _(L("Arranging canceled.")) : _(L("Arranging done."))); +} - // TODO: we should decide whether to allow arrange when the search is - // running we should probably disable explicit slicing and background - // processing - -// static const auto arrangestr = _(L("Arranging")); - -// auto &config = plater().config; -// auto &view3D = plater().view3D; -// auto &model = plater().model; - -// // FIXME: I don't know how to obtain the minimum distance, it depends -// // on printer technology. I guess the following should work but it crashes. -// double dist = 6; // PrintConfig::min_object_distance(config); -// if (plater().printer_technology == ptFFF) { -// dist = PrintConfig::min_object_distance(config); -// } - -// auto min_obj_distance = coord_t(dist / SCALING_FACTOR); - -// const auto *bed_shape_opt = config->opt( -// "bed_shape"); - -// assert(bed_shape_opt); -// auto & bedpoints = bed_shape_opt->values; -// Polyline bed; -// bed.points.reserve(bedpoints.size()); -// for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - -// update_status(0, arrangestr); - -// arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); - -// try { -// arr::BedShapeHint hint; - -// // TODO: from Sasha from GUI or -// hint.type = arr::BedShapeType::WHO_KNOWS; - -// arr::arrange(model, -// wti, -// min_obj_distance, -// bed, -// hint, -// false, // create many piles not just one pile -// [this](unsigned st) { -// if (st > 0) -// update_status(count - int(st), arrangestr); -// }, -// [this]() { return was_canceled(); }); -// } catch (std::exception & /*e*/) { -// GUI::show_error(plater().q, -// L("Could not arrange model objects! " -// "Some geometries may be invalid.")); -// } - -// update_status(count, -// was_canceled() ? _(L("Arranging canceled.")) -// : _(L("Arranging done."))); - -// // it remains to move the wipe tower: -// view3D->get_canvas3d()->arrange_wipe_tower(wti); +void find_new_position(const Model & model, + ModelInstancePtrs instances, + coord_t min_d, + const arr::BedShapeHint &bedhint) +{ + + // TODO } void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() { -// int obj_idx = plater().get_selected_object_idx(); -// if (obj_idx < 0) { return; } + int obj_idx = plater().get_selected_object_idx(); + if (obj_idx < 0) { return; } -// ModelObject *o = plater().model.objects[size_t(obj_idx)]; + ModelObject *o = plater().model.objects[size_t(obj_idx)]; -// auto r = sla::find_best_rotation( -// *o, -// .005f, -// [this](unsigned s) { -// if (s < 100) -// update_status(int(s), -// _(L("Searching for optimal orientation"))); -// }, -// [this]() { return was_canceled(); }); + auto r = sla::find_best_rotation( + *o, + .005f, + [this](unsigned s) { + if (s < 100) + update_status(int(s), + _(L("Searching for optimal orientation"))); + }, + [this]() { return was_canceled(); }); -// const auto *bed_shape_opt = -// plater().config->opt("bed_shape"); + + double mindist = 6.0; // FIXME -// assert(bed_shape_opt); - -// auto & bedpoints = bed_shape_opt->values; -// Polyline bed; -// bed.points.reserve(bedpoints.size()); -// for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - -// double mindist = 6.0; // FIXME + if (!was_canceled()) { + for(ModelInstance * oi : o->instances) { + oi->set_rotation({r[X], r[Y], r[Z]}); -// if (!was_canceled()) { -// for(ModelInstance * oi : o->instances) { -// oi->set_rotation({r[X], r[Y], r[Z]}); - -// auto trmatrix = oi->get_transformation().get_matrix(); -// Polygon trchull = o->convex_hull_2d(trmatrix); + auto trmatrix = oi->get_transformation().get_matrix(); + Polygon trchull = o->convex_hull_2d(trmatrix); -// MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); -// double r = rotbb.angle_to_X(); + MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); + double r = rotbb.angle_to_X(); -// // The box should be landscape -// if(rotbb.width() < rotbb.height()) r += PI / 2; + // The box should be landscape + if(rotbb.width() < rotbb.height()) r += PI / 2; -// Vec3d rt = oi->get_rotation(); rt(Z) += r; + Vec3d rt = oi->get_rotation(); rt(Z) += r; -// oi->set_rotation(rt); -// } + oi->set_rotation(rt); + } -// arr::WipeTowerInfo wti; // useless in SLA context -// arr::find_new_position(plater().model, -// o->instances, -// coord_t(mindist / SCALING_FACTOR), -// bed, -// wti); - -// // Correct the z offset of the object which was corrupted be -// // the rotation -// o->ensure_on_bed(); -// } + find_new_position(plater().model, + o->instances, + scaled(mindist), + plater().get_bed_shape_hint()); -// update_status(100, -// was_canceled() ? _(L("Orientation search canceled.")) -// : _(L("Orientation found."))); + // Correct the z offset of the object which was corrupted be + // the rotation + o->ensure_on_bed(); + } + + update_status(100, + was_canceled() ? _(L("Orientation search canceled.")) + : _(L("Orientation found."))); } void Plater::priv::split_object()