diff --git a/src/libnest2d/include/libnest2d.h b/src/libnest2d/include/libnest2d.h index a6eb36a4b..f1d2506f4 100644 --- a/src/libnest2d/include/libnest2d.h +++ b/src/libnest2d/include/libnest2d.h @@ -30,9 +30,7 @@ using Circle = _Circle; using Item = _Item; using Rectangle = _Rectangle; - using PackGroup = _PackGroup; -using IndexedPackGroup = _IndexedPackGroup; using FillerSelection = selections::_FillerSelection; using FirstFitSelection = selections::_FirstFitSelection; diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index 5d74aa3d9..b8a542752 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -65,6 +65,8 @@ class _Item { Box bb; bool valid; BBCache(): valid(false) {} } bb_cache_; + + std::function applyfn_; public: @@ -121,8 +123,26 @@ public: inline _Item(TContour&& contour, THolesContainer&& holes): - sh_(sl::create(std::move(contour), - std::move(holes))) {} + sh_(sl::create(std::move(contour), std::move(holes))) {} + + template + _Item(std::function applyfn, Args &&... args): + _Item(std::forward(args)...) + { + applyfn_ = std::move(applyfn); + } + + // Call the apply callback set in constructor. Within the callback, the + // original caller can apply the stored transformation to the original + // objects inteded for nesting. It might not be the shape handed over + // to _Item (e.g. arranging 3D shapes based on 2D silhouette or the + // client uses a simplified or processed polygon for nesting) + // This callback, if present, will be called for each item after the nesting + // is finished. + inline void callApplyFunction(unsigned binidx) const + { + if (applyfn_) applyfn_(*this, binidx); + } /** * @brief Convert the polygon to string representation. The format depends @@ -492,24 +512,6 @@ template using _ItemGroup = std::vector<_ItemRef>; template using _PackGroup = std::vector>>; -/** - * \brief A list of packed (index, item) pair vectors. Each vector represents a - * bin. - * - * The index is points to the position of the item in the original input - * sequence. This way the caller can use the items as a transformation data - * carrier and transform the original objects manually. - */ -template -using _IndexedPackGroup = std::vector< - std::vector< - std::pair< - unsigned, - _ItemRef - > - > - >; - template struct ConstItemRange { Iterator from; @@ -768,13 +770,9 @@ public: using BinType = typename TPlacer::BinType; using PlacementConfig = typename TPlacer::Config; using SelectionConfig = typename TSel::Config; - using Unit = TCoord>; - - using IndexedPackGroup = _IndexedPackGroup; using PackGroup = _PackGroup; using ResultType = PackGroup; - using ResultTypeIndexed = IndexedPackGroup; private: BinType bin_; @@ -786,6 +784,7 @@ private: using TSItem = remove_cvref_t; std::vector item_cache_; + StopCondition stopfn_; public: @@ -814,11 +813,13 @@ public: void configure(const PlacementConfig& pconf) { pconfig_ = pconf; } void configure(const SelectionConfig& sconf) { selector_.configure(sconf); } - void configure(const PlacementConfig& pconf, const SelectionConfig& sconf) { + void configure(const PlacementConfig& pconf, const SelectionConfig& sconf) + { pconfig_ = pconf; selector_.configure(sconf); } - void configure(const SelectionConfig& sconf, const PlacementConfig& pconf) { + void configure(const SelectionConfig& sconf, const PlacementConfig& pconf) + { pconfig_ = pconf; selector_.configure(sconf); } @@ -836,26 +837,6 @@ public: return _execute(from, to); } - /** - * A version of the arrange method returning an IndexedPackGroup with - * the item indexes into the original input sequence. - * - * Takes a little longer to collect the indices. Scales linearly with the - * input sequence size. - */ - template - inline IndexedPackGroup executeIndexed(TIterator from, TIterator to) - { - return _executeIndexed(from, to); - } - - /// Shorthand to normal arrange method. - template - inline PackGroup operator() (TIterator from, TIterator to) - { - return _execute(from, to); - } - /// Set a progress indicator function object for the selector. inline Nester& progressIndicator(ProgressFunction func) { @@ -865,7 +846,7 @@ public: /// Set a predicate to tell when to abort nesting. inline Nester& stopCondition(StopCondition fn) { - selector_.stopCondition(fn); return *this; + stopfn_ = fn; selector_.stopCondition(fn); return *this; } inline const PackGroup& lastResult() const @@ -878,16 +859,6 @@ public: selector_.preload(pgrp); } - inline void preload(const IndexedPackGroup& ipgrp) - { - PackGroup pgrp; pgrp.reserve(ipgrp.size()); - for(auto& ig : ipgrp) { - pgrp.emplace_back(); pgrp.back().reserve(ig.size()); - for(auto& r : ig) pgrp.back().emplace_back(r.second); - } - preload(pgrp); - } - private: template, - - // This function will be used only if the iterators are pointing to - // a type compatible with the libnest2d::_Item template. - // This way we can use references to input elements as they will - // have to exist for the lifetime of this call. - class T = enable_if_t< std::is_convertible::value, IT> - > - inline IndexedPackGroup _executeIndexed(TIterator from, - TIterator to, - bool = false) - { - __execute(from, to); - return createIndexedPackGroup(from, to, selector_); - } - - template, - class T = enable_if_t::value, IT> - > - inline IndexedPackGroup _executeIndexed(TIterator from, - TIterator to, - int = false) - { - item_cache_ = {from, to}; - __execute(item_cache_.begin(), item_cache_.end()); - return createIndexedPackGroup(from, to, selector_); - } - - template - static IndexedPackGroup createIndexedPackGroup(TIterator from, - TIterator to, - TSel& selector) - { - IndexedPackGroup pg; - pg.reserve(selector.getResult().size()); - - const PackGroup& pckgrp = selector.getResult(); - - for(size_t i = 0; i < pckgrp.size(); i++) { - auto items = pckgrp[i]; - pg.emplace_back(); - pg[i].reserve(items.size()); - - for(Item& itemA : items) { - auto it = from; - unsigned idx = 0; - while(it != to) { - Item& itemB = *it; - if(&itemB == &itemA) break; - it++; idx++; - } - pg[i].emplace_back(idx, itemA); - } - } - - return pg; - } - template inline void __execute(TIter from, TIter to) { if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { @@ -985,10 +896,19 @@ private: selector_.template packItems( from, to, bin_, pconfig_); - - if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) { + + if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { item.removeOffset(); }); + + if(stopfn_ && !stopfn_()) { // Ignore results if nesting was stopped. + const PackGroup& bins = lastResult(); + unsigned binidx = 0; + for(auto& bin : bins) { + for(const Item& itm : bin) itm.callApplyFunction(binidx); + ++binidx; + } + } } }; diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index 2f2b9beb5..72a600dbb 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -366,7 +366,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) Nester arrange(Box(210, 250)); - auto groups = arrange(rects.begin(), rects.end()); + auto groups = arrange.execute(rects.begin(), rects.end()); ASSERT_EQ(groups.size(), 1u); ASSERT_EQ(groups[0].size(), rects.size()); @@ -420,7 +420,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) Nester arrange(Box(210, 250), min_obj_distance); - auto groups = arrange(rects.begin(), rects.end()); + auto groups = arrange.execute(rects.begin(), rects.end()); ASSERT_EQ(groups.size(), 1u); ASSERT_EQ(groups[0].size(), rects.size()); diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index e7f7ed3e6..01f0095bf 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -303,7 +303,7 @@ inline SLIC3R_CONSTEXPR ScaledCoordOnly scaled(const Tin &v) SLIC3R_NOEXCE template> inline EigenVec, N> scaled(const EigenVec &v) { - return v.template cast() /*/ SCALING_FACTOR*/; + return (v / SCALING_FACTOR).template cast(); } // Conversion from arithmetic scaled type to floating point unscaled diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 6b88987df..18f3f2f5e 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1801,7 +1801,7 @@ void ModelInstance::transform_polygon(Polygon* polygon) const polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin } -Polygon ModelInstance::get_arrange_polygon() const +std::tuple ModelInstance::get_arrange_polygon() const { static const double SIMPLIFY_TOLERANCE_MM = 0.1; @@ -1827,7 +1827,9 @@ Polygon ModelInstance::get_arrange_polygon() const pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); if (!pp.empty()) p = pp.front(); - return p; + return std::make_tuple(p, Vec2crd{scaled(get_offset(X)), + scaled(get_offset(Y))}, + get_rotation(Z)); } // Test whether the two models contain the same number of ModelObjects with the same set of IDs diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 85a94b0fd..c02280827 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -554,15 +554,21 @@ public: bool is_printable() const { return print_volume_state == PVS_Inside; } - virtual void set_arrange_result(Vec2d offs, double rot_rads) final + // ///////////////////////////////////////////////////////////////////////// + // Implement arr::Arrangeable interface + // ///////////////////////////////////////////////////////////////////////// + + // Getting the input polygon for arrange + virtual std::tuple get_arrange_polygon() const final; + + // Apply the arrange result on the ModelInstance + virtual void apply_arrange_result(Vec2d offs, double rot_rads) final { // write the transformation data into the model instance - set_rotation(Z, get_rotation(Z) + rot_rads); - set_offset(X, get_offset(X) + offs(X)); - set_offset(Y, get_offset(Y) + offs(Y)); + set_rotation(Z, rot_rads); + set_offset(X, offs(X)); + set_offset(Y, offs(Y)); } - - virtual Polygon get_arrange_polygon() const final; protected: friend class Print; diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 1fe7552b0..885979648 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -4,7 +4,10 @@ #include "SVG.hpp" #include "MTUtils.hpp" -#include +#include +#include +#include +#include #include #include @@ -18,14 +21,20 @@ namespace libnest2d { using LargeInt = __int128; #else using LargeInt = boost::multiprecision::int128_t; -template<> struct _NumTag { using Type = ScalarTag; }; +template<> struct _NumTag +{ + using Type = ScalarTag; +}; #endif -template struct _NumTag> { using Type = RationalTag; }; + +template struct _NumTag> +{ + using Type = RationalTag; +}; namespace nfp { -template -struct NfpImpl +template struct NfpImpl { NfpResult operator()(const S &sh, const S &other) { @@ -33,16 +42,22 @@ struct NfpImpl } }; -} -} +} // namespace nfp +} // namespace libnest2d namespace Slic3r { namespace arr { using namespace libnest2d; +namespace clppr = ClipperLib; -using Shape = ClipperLib::Polygon; +using Item = _Item; +using Box = _Box; +using Circle = _Circle; +using Segment = _Segment; +using MultiPolygon = TMultiShape; +using PackGroup = _PackGroup; // Only for debugging. Prints the model object vertices on stdout. //std::string toString(const Model& model, bool holes = true) { @@ -131,7 +146,7 @@ namespace bgi = boost::geometry::index; using SpatElement = std::pair; using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; -using ItemGroup = std::vector>>; +using ItemGroup = std::vector>; const double BIG_ITEM_TRESHOLD = 0.02; @@ -156,10 +171,10 @@ Box boundingBox(const Box& pilebb, const Box& ibb ) { // at the same time, it has to provide reasonable results. std::tuple objfunc(const PointImpl& bincenter, - const TMultiShape& merged_pile, + const MultiPolygon& merged_pile, const Box& pilebb, const ItemGroup& items, - const _Item &item, + const Item &item, double bin_area, double norm, // A norming factor for physical dimensions // a spatial index to quickly get neighbors of the candidate item @@ -224,8 +239,8 @@ objfunc(const PointImpl& bincenter, auto mp = merged_pile; mp.emplace_back(item.transformedShape()); auto chull = sl::convexHull(mp); - - placers::EdgeCache ec(chull); + + placers::EdgeCache ec(chull); double circ = ec.circumference() / norm; double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; @@ -256,7 +271,7 @@ objfunc(const PointImpl& bincenter, for(auto& e : result) { // now get the score for the best alignment auto idx = e.second; - _Item& p = items[idx]; + Item& p = items[idx]; auto parea = p.area(); if(std::abs(1.0 - parea/item.area()) < 1e-6) { auto bb = boundingBox(p.boundingBox(), ibb); @@ -322,12 +337,11 @@ class _ArrBase { public: // Useful type shortcuts... - using Placer = typename placers::_NofitPolyPlacer; - using Selector = selections::_FirstFitSelection; + using Placer = typename placers::_NofitPolyPlacer; + using Selector = selections::_FirstFitSelection; using Packer = Nester; using PConfig = typename Packer::PlacementConfig; using Distance = TCoord; - using Pile = TMultiShape; protected: @@ -337,7 +351,7 @@ protected: SpatIndex m_rtree; // spatial index for the normal (bigger) objects SpatIndex m_smallsrtree; // spatial index for only the smaller items double m_norm; // A coefficient to scale distances - Pile m_merged_pile; // The already merged pile (vector of items) + MultiPolygon m_merged_pile; // The already merged pile (vector of items) Box m_pilebb; // The bounding box of the merged pile. ItemGroup m_remaining; // Remaining items (m_items at the beginning) ItemGroup m_items; // The items to be packed @@ -354,7 +368,7 @@ public: // Set up a callback that is called just before arranging starts // This functionality is provided by the Nester class (m_pack). m_pconf.before_packing = - [this](const Pile& merged_pile, // merged pile + [this](const MultiPolygon& merged_pile, // merged pile const ItemGroup& items, // packed items const ItemGroup& remaining) // future items to be packed { @@ -373,7 +387,7 @@ public: }; for(unsigned idx = 0; idx < items.size(); ++idx) { - _Item& itm = items[idx]; + Item& itm = items[idx]; if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx}); m_smallsrtree.insert({itm.boundingBox(), idx}); } @@ -383,12 +397,12 @@ public: m_pck.stopCondition(stopcond); } - template inline _PackGroup operator()(Args&&...args) { + template inline PackGroup operator()(Args&&...args) { m_rtree.clear(); return m_pck.execute(std::forward(args)...); } - inline void preload(const _PackGroup& pg) { + inline void preload(const PackGroup& pg) { m_pconf.alignment = PConfig::Alignment::DONT_ALIGN; m_pconf.object_function = nullptr; // drop the special objectfunction m_pck.preload(pg); @@ -396,14 +410,14 @@ public: // Build the rtree for queries to work for(const ItemGroup& grp : pg) for(unsigned idx = 0; idx < grp.size(); ++idx) { - _Item& itm = grp[idx]; + Item& itm = grp[idx]; m_rtree.insert({itm.boundingBox(), idx}); } m_pck.configure(m_pconf); } - bool is_colliding(const _Item& item) { + bool is_colliding(const Item& item) { if(m_rtree.empty()) return false; std::vector result; m_rtree.query(bgi::intersects(item.boundingBox()), @@ -425,7 +439,7 @@ public: // Here we set up the actual object function that calls the common // object function for all bin shapes than does an additional inside // check for the arranged pile. - m_pconf.object_function = [this, bin] (const _Item &item) { + m_pconf.object_function = [this, bin] (const Item &item) { auto result = objfunc(bin.center(), m_merged_pile, @@ -452,23 +466,21 @@ public: } }; -using lnCircle = libnest2d::_Circle; - -inline lnCircle to_lnCircle(const Circle& circ) { - return lnCircle({circ.center()(0), circ.center()(1)}, circ.radius()); +inline Circle to_lnCircle(const CircleBed& circ) { + return Circle({circ.center()(0), circ.center()(1)}, circ.radius()); } // Arranger specialization for circle shaped bin. -template<> class AutoArranger: public _ArrBase { +template<> class AutoArranger: public _ArrBase { public: - AutoArranger(const lnCircle& bin, Distance dist, + AutoArranger(const Circle& bin, Distance dist, std::function progressind = [](unsigned){}, std::function stopcond = [](){return false;}): - _ArrBase(bin, dist, progressind, stopcond) { + _ArrBase(bin, dist, progressind, stopcond) { // As with the box, only the inside check is different. - m_pconf.object_function = [this, &bin] (const _Item &item) { + m_pconf.object_function = [this, &bin] (const Item &item) { auto result = objfunc(bin.center(), m_merged_pile, @@ -483,7 +495,7 @@ public: double score = std::get<0>(result); - auto isBig = [this](const _Item& itm) { + auto isBig = [this](const Item& itm) { return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ; }; @@ -512,7 +524,7 @@ public: std::function stopcond = [](){return false;}): _ArrBase(bin, dist, progressind, stopcond) { - m_pconf.object_function = [this, &bin] (const _Item &item) { + m_pconf.object_function = [this, &bin] (const Item &item) { auto binbb = sl::boundingBox(bin); auto result = objfunc(binbb.center(), @@ -544,7 +556,7 @@ public: std::function stopcond): _ArrBase(Box(0, 0), dist, progressind, stopcond) { - this->m_pconf.object_function = [this] (const _Item &item) { + this->m_pconf.object_function = [this] (const Item &item) { auto result = objfunc({0, 0}, m_merged_pile, @@ -563,152 +575,12 @@ public: } }; -// 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, -// const WipeTowerInfo &wti, -// double tolerance) -//{ -// ShapeData2D ret; - -// // Count all the items on the bin (all the object's instances) -// auto s = std::accumulate(model.objects.begin(), model.objects.end(), -// size_t(0), [](size_t s, ModelObject* o) -// { -// return s + o->instances.size(); -// }); - -// ret.reserve(s); - -// for(ModelObject* objptr : model.objects) { -// if (! objptr->instances.empty()) { - -// // TODO export the exact 2D projection. Cannot do it as libnest2d -// // does not support concave shapes (yet). -// ClipperLib::Path clpath; - -// // Object instances should carry the same scaling and -// // x, y rotation that is why we use the first instance. -// { -// ModelInstance *finst = objptr->instances.front(); -// Vec3d rotation = finst->get_rotation(); -// rotation.z() = 0.; -// Transform3d trafo_instance = Geometry::assemble_transform( -// Vec3d::Zero(), -// rotation, -// finst->get_scaling_factor(), -// finst->get_mirror()); -// Polygon p = objptr->convex_hull_2d(trafo_instance); - -// assert(!p.points.empty()); - -// // this may happen for malformed models, see: -// // https://github.com/prusa3d/PrusaSlicer/issues/2209 -// if (p.points.empty()) continue; - -// if(tolerance > EPSILON) { -// Polygons pp { p }; -// pp = p.simplify(scaled(tolerance)); -// if (!pp.empty()) p = pp.front(); -// } - -// p.reverse(); -// assert(!p.is_counter_clockwise()); -// clpath = Slic3rMultiPoint_to_ClipperPath(p); -// auto firstp = clpath.front(); clpath.emplace_back(firstp); -// } - -// Vec3d rotation0 = objptr->instances.front()->get_rotation(); -// rotation0(2) = 0.; -// for(ModelInstance* objinst : objptr->instances) { -// ClipperLib::Polygon pn; -// pn.Contour = clpath; - -// // Efficient conversion to item. -// Item item(std::move(pn)); - -// // Invalid geometries would throw exceptions when arranging -// if(item.vertexCount() > 3) { -// item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation())); -// item.translation({ -// scaled(objinst->get_offset(X)), -// scaled(objinst->get_offset(Y)) -// }); -// ret.emplace_back(objinst, item); -// } -// } -// } -// } - -// // The wipe tower is a separate case (in case there is one), let's duplicate the code -// if (wti.is_wipe_tower) { -// Points pts; -// pts.emplace_back(coord_t(scale_(0.)), coord_t(scale_(0.))); -// pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(0.))); -// pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(wti.bb_size(1)))); -// pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(wti.bb_size(1)))); -// pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(0.))); -// Polygon p(std::move(pts)); -// ClipperLib::Path clpath = Slic3rMultiPoint_to_ClipperPath(p); -// ClipperLib::Polygon pn; -// pn.Contour = clpath; -// // Efficient conversion to item. -// Item item(std::move(pn)); -// item.rotation(wti.rotation), -// item.translation({ -// scaled(wti.pos(0)), -// scaled(wti.pos(1)) -// }); -// ret.emplace_back(nullptr, item); -// } - -// return ret; -//} - -// Apply the calculated translations and rotations (currently disabled) to -// the Model object instances. -//void applyResult(IndexedPackGroup::value_type &group, -// ClipperLib::cInt batch_offset, -// ShapeData2D & shapemap, -// WipeTowerInfo & wti) -//{ -// for(auto& r : group) { -// auto idx = r.first; // get the original item index -// Item& item = r.second; // get the item itself - -// // Get the model instance from the shapemap using the index -// ModelInstance *inst_ptr = shapemap[idx].first; - -// // Get the transformation data from the item object and scale it -// // appropriately -// auto off = item.translation(); -// Radians rot = item.rotation(); - -// Vec3d foff(unscaled(off.X + batch_offset), -// unscaled(off.Y), -// inst_ptr ? inst_ptr->get_offset()(Z) : 0.); - -// if (inst_ptr) { -// // write the transformation data into the model instance -// inst_ptr->set_rotation(Z, rot); -// inst_ptr->set_offset(foff); -// } -// else { // this is the wipe tower - we will modify the struct with the info -// // and leave it up to the called to actually move the wipe tower -// wti.pos = Vec2d(foff(0), foff(1)); -// wti.rotation = rot; -// } -// } -//} - // Get the type of bed geometry from a simple vector of points. BedShapeHint bedShape(const Polyline &bed) { BedShapeHint ret; - auto x = [](const Point& p) { return p(0); }; - auto y = [](const Point& p) { return p(1); }; + auto x = [](const Point& p) { return p(X); }; + auto y = [](const Point& p) { return p(Y); }; auto width = [x](const BoundingBox& box) { return x(box.max) - x(box.min); @@ -721,7 +593,7 @@ BedShapeHint bedShape(const Polyline &bed) { auto area = [&width, &height](const BoundingBox& box) { double w = width(box); double h = height(box); - return w*h; + return w * h; }; auto poly_area = [](Polyline p) { @@ -752,11 +624,11 @@ BedShapeHint bedShape(const Polyline &bed) { avg_dist /= vertex_distances.size(); - Circle ret(center, avg_dist); + CircleBed ret(center, avg_dist); for(auto el : vertex_distances) { if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { - ret = Circle(); + ret = CircleBed(); break; } } @@ -785,14 +657,14 @@ BedShapeHint bedShape(const Polyline &bed) { //static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; template -_PackGroup _arrange(std::vector &shapes, - const BinT & bin, - coord_t minobjd, - std::function prind, - std::function stopfn) +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(shapes.begin(), shapes.end()); + return arranger(items.begin(), items.end()); } //template @@ -850,185 +722,94 @@ inline SLIC3R_CONSTEXPR coord_t stride_padding(coord_t w) return w + w / 5; } -bool arrange(ArrangeableRefs & arrangables, +//// 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, coord_t min_obj_distance, BedShapeHint bedhint, std::function progressind, std::function stopcondition) { bool ret = true; + namespace clppr = ClipperLib; - std::vector shapes; - shapes.reserve(arrangables.size()); - size_t id = 0; - for (Arrangeable &iref : arrangables) { - Polygon p = iref.get_arrange_polygon(); + std::vector items; + items.reserve(arrangables.size()); + coord_t binwidth = 0; + + for (Arrangeable *arrangeable : arrangables) { + assert(arrangeable); - p.reverse(); - assert(!p.is_counter_clockwise()); + auto arrangeitem = arrangeable->get_arrange_polygon(); - Shape clpath(/*id++,*/ Slic3rMultiPoint_to_ClipperPath(p)); + Polygon& p = std::get<0>(arrangeitem); + const Vec2crd& offs = std::get<1>(arrangeitem); + double rotation = std::get<2>(arrangeitem); - auto firstp = clpath.Contour.front(); clpath.Contour.emplace_back(firstp); - shapes.emplace_back(std::move(clpath)); + if (p.is_counter_clockwise()) p.reverse(); + + clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); + + auto firstp = clpath.Contour.front(); + clpath.Contour.emplace_back(firstp); + + items.emplace_back( + // 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), + unscaled(offs.Y)}, + itm.rotation()); + }, + std::move(clpath)); + items.front().rotation(rotation); + items.front().translation({offs.x(), offs.y()}); } - _PackGroup result; - - auto& cfn = stopcondition; - // 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; - coord_t binwidth = 0; switch (bedhint.type) { case BedShapeType::BOX: { // Create the arranger for the box shaped bed BoundingBox bbb = bedhint.shape.box; - - 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}); - - result = _arrange(shapes, binbb, min_obj_distance, progressind, cfn); + bbb.min -= Point{md, md}, bbb.max += Point{md, md}; + + 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); break; } case BedShapeType::CIRCLE: { auto c = bedhint.shape.circ; auto cc = to_lnCircle(c); - result = _arrange(shapes, cc, min_obj_distance, progressind, cfn); binwidth = scaled(c.radius()); + _arrange(items, cc, min_obj_distance, progressind, stopcondition); break; } case BedShapeType::IRREGULAR: { auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.shape.polygon); - ClipperLib::Polygon irrbed = sl::create(std::move(ctour)); - result = _arrange(shapes, irrbed, min_obj_distance, progressind, cfn); + 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); break; } case BedShapeType::WHO_KNOWS: { - result = _arrange(shapes, false, min_obj_distance, progressind, cfn); + _arrange(items, false, min_obj_distance, progressind, stopcondition); break; } }; - if(result.empty() || stopcondition()) return false; - - ClipperLib::cInt stride = stride_padding(binwidth); - ClipperLib::cInt batch_offset = 0; - - for (const auto &group : result) { - for (_Item &itm : group) { - ClipperLib::IntPoint offs = itm.translation(); -// arrangables[itm.id()].get().set_arrange_result({offs.X, offs.Y}, -// itm.rotation()); - } - - // 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; - } + if(stopcondition()) return false; return ret; } -//// 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(Model &model, // The model with the geometries -// WipeTowerInfo& wti, // Wipe tower info -// coord_t min_obj_distance, // Has to be in scaled (clipper) measure -// const Polyline &bed, // The bed geometry. -// BedShapeHint bedhint, // Hint about the bed geometry type. -// bool first_bin_only, // What to do is not all items fit. - -// // Controlling callbacks. -// std::function progressind, -// std::function stopcondition) -//{ -// bool ret = true; - -// // 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 -// std::vector> shapes; -// shapes.reserve(shapemap.size()); -// std::for_each(shapemap.begin(), shapemap.end(), -// [&shapes] (ShapeData2D::value_type& it) -// { -// shapes.push_back(std::ref(it.second)); -// }); - -// IndexedPackGroup result; - -// // If there is no hint about the shape, we will try to guess -// if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); - -// BoundingBox bbb(bed); - -// auto& cfn = stopcondition; - -// // 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}); - -// switch(bedhint.type) { -// case BedShapeType::BOX: { -// // Create the arranger for the box shaped bed -// result = _arrange(shapes, binbb, min_obj_distance, progressind, cfn); -// break; -// } -// case BedShapeType::CIRCLE: { -// auto c = bedhint.shape.circ; -// auto cc = to_lnCircle(c); -// result = _arrange(shapes, cc, min_obj_distance, progressind, cfn); -// 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, irrbed, min_obj_distance, progressind, cfn); -// break; -// } -// }; - -// if(result.empty() || stopcondition()) return false; - -// if(first_bin_only) { -// applyResult(result.front(), 0, shapemap, wti); -// } else { - -// ClipperLib::cInt stride = stride_padding(binbb.width()); -// ClipperLib::cInt batch_offset = 0; - -// for(auto& group : result) { -// applyResult(group, batch_offset, shapemap, wti); - -// // 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; -//} - //void find_new_position(const Model &model, // ModelInstancePtrs toadd, // coord_t min_obj_distance, diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index 77b357f4e..45dde13d6 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -11,13 +11,13 @@ class Model; namespace arr { -class Circle { +class CircleBed { Point center_; double radius_; public: - inline Circle(): center_(0, 0), radius_(std::nan("")) {} - inline Circle(const Point& c, double r): center_(c), radius_(r) {} + inline CircleBed(): center_(0, 0), radius_(std::nan("")) {} + inline CircleBed(const Point& c, double r): center_(c), radius_(r) {} inline double radius() const { return radius_; } inline const Point& center() const { return center_; } @@ -34,7 +34,7 @@ enum class BedShapeType { struct BedShapeHint { BedShapeType type = BedShapeType::WHO_KNOWS; /*union*/ struct { // I know but who cares... TODO: use variant from cpp17? - Circle circ; + CircleBed circ; BoundingBox box; Polyline polygon; } shape; @@ -47,12 +47,13 @@ public: virtual ~Arrangeable() = default; - virtual void set_arrange_result(Vec2d offset, double rotation_rads) = 0; + virtual void apply_arrange_result(Vec2d offset, double rotation_rads) = 0; - virtual Polygon get_arrange_polygon() const = 0; + /// Get the 2D silhouette to arrange and an initial offset and rotation + virtual std::tuple get_arrange_polygon() const = 0; }; -using ArrangeableRefs = std::vector>; +using Arrangeables = std::vector; /** * \brief Arranges the model objects on the screen. @@ -89,7 +90,7 @@ using ArrangeableRefs = std::vector>; // std::function progressind, // std::function stopcondition); -bool arrange(ArrangeableRefs &items, +bool arrange(Arrangeables &items, coord_t min_obj_distance, BedShapeHint bedhint, std::function progressind, @@ -102,8 +103,8 @@ bool arrange(ArrangeableRefs &items, // coord_t min_obj_distance, // const Slic3r::Polyline& bed, // WipeTowerInfo& wti); -void find_new_position(ArrangeableRefs &items, - const ArrangeableRefs &instances_to_add, +void find_new_position(Arrangeables &items, + const Arrangeables &instances_to_add, coord_t min_obj_distance, BedShapeHint bedhint); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index bf892fe68..4e3093489 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5739,10 +5739,10 @@ const SLAPrint* GLCanvas3D::sla_print() const return (m_process == nullptr) ? nullptr : m_process->sla_print(); } -void GLCanvas3D::WipeTowerInfo::set_arrange_result(Vec2d offset, double rotation_rads) +void GLCanvas3D::WipeTowerInfo::apply_arrange_result(Vec2d offset, double rotation_rads) { - m_pos += offset; - m_rotation += rotation_rads; + m_pos = offset; + m_rotation = rotation_rads; DynamicPrintConfig cfg; cfg.opt("wipe_tower_x", true)->value = m_pos(X); cfg.opt("wipe_tower_y", true)->value = m_pos(Y); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 1872d2f37..2d3c3b27f 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -613,7 +613,7 @@ public: int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } class WipeTowerInfo: public arr::Arrangeable { - Vec2d m_pos = {std::nan(""), std::nan("")}; + Vec2d m_pos = {std::nan(""), std::nan("")}; Vec2d m_bb_size; double m_rotation; friend class GLCanvas3D; @@ -621,12 +621,12 @@ public: inline operator bool() const { - return std::isnan(m_pos.x()) || std::isnan(m_pos.y()); + return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y()); } - virtual void set_arrange_result(Vec2d offset, double rotation_rads) final; + virtual void apply_arrange_result(Vec2d offset, double rotation_rads) final; - virtual Polygon get_arrange_polygon() const final + virtual std::tuple get_arrange_polygon() const final { Polygon p({ {coord_t(0), coord_t(0)}, @@ -636,9 +636,7 @@ public: {coord_t(0), coord_t(0)}, }); - p.rotate(m_rotation); - p.translate(scaled(m_pos)); - return p; + return std::make_tuple(p, scaled(m_pos), m_rotation); } }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b4afdfe67..193390f85 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2357,7 +2357,7 @@ void Plater::priv::remove(size_t obj_idx) void Plater::priv::delete_object_from_model(size_t obj_idx) -{ +{ model.delete_object(obj_idx); update(); object_list_changed(); @@ -2422,11 +2422,15 @@ arr::BedShapeHint Plater::priv::get_bed_shape_hint() const { void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { static const auto arrangestr = _(L("Arranging")); - - arr::ArrangeableRefs arrangeinput; arrangeinput.reserve(m_count); + + // Collect the model instances and place them into the input vector + arr::Arrangeables arrangeinput; arrangeinput.reserve(m_count); for(ModelObject *mo : plater().model.objects) for(ModelInstance *minst : mo->instances) - arrangeinput.emplace_back(std::ref(*minst)); + arrangeinput.emplace_back(minst); + + // Place back the wipe tower if that's available. + if (m_wti) arrangeinput.emplace_back(&m_wti); // 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. @@ -3456,7 +3460,7 @@ void Plater::priv::set_bed_shape(const Pointfs& shape) bool Plater::priv::can_delete() const { - return !get_selection().is_empty() && !get_selection().is_wipe_tower(); + return !get_selection().is_empty() && !get_selection().is_wipe_tower() && !m_ui_jobs.is_any_running(); } bool Plater::priv::can_delete_all() const