diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index 11c032fae..d91b3c8f9 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -68,7 +68,7 @@ class _Item { BBCache(): valid(false) {} } bb_cache_; - int binid_{BIN_ID_UNSET}; + int binid_{BIN_ID_UNSET}, priority_{0}; bool fixed_{false}; public: @@ -130,8 +130,12 @@ public: inline bool isFixed() const noexcept { return fixed_; } inline void markAsFixed(bool fixed = true) { fixed_ = fixed; } + inline void binId(int idx) { binid_ = idx; } inline int binId() const noexcept { return binid_; } + + inline void priority(int p) { priority_ = p; } + inline int priority() const noexcept { return priority_; } /** * @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 74207f8cf..5585e565d 100644 --- a/src/libnest2d/include/libnest2d/selections/firstfit.hpp +++ b/src/libnest2d/include/libnest2d/selections/firstfit.hpp @@ -62,9 +62,10 @@ public: placers.back().configure(pconfig); placers.back().preload(ig); } - + auto sortfunc = [](Item& i1, Item& i2) { - return i1.area() > i2.area(); + int p1 = i1.priority(), p2 = i2.priority(); + return p1 == p2 ? i1.area() > i2.area() : p1 > p2; }; std::sort(store_.begin(), store_.end(), sortfunc); diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index fd3573699..8086dc3fd 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -584,6 +584,7 @@ void arrange(ArrangePolygons & arrangables, outp.back().rotation(rotation); outp.back().translation({offs.x(), offs.y()}); outp.back().binId(arrpoly.bed_idx); + outp.back().priority(arrpoly.priority); }; for (ArrangePolygon &arrangeable : arrangables) @@ -595,7 +596,7 @@ void arrange(ArrangePolygons & arrangables, for (Item &itm : fixeditems) itm.inflate(-2 * SCALED_EPSILON); // Integer ceiling the min distance from the bed perimeters - coord_t md = min_obj_dist - SCALED_EPSILON; + coord_t md = min_obj_dist - 2 * scaled(0.1 + EPSILON); md = (md % 2) ? md / 2 + 1 : md / 2; auto &cfn = stopcondition; diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index c705b612b..3391eb0d7 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -140,15 +140,18 @@ static const constexpr int UNARRANGED = -1; /// polygon belongs: UNARRANGED means no place for the polygon /// (also the initial state before arrange), 0..N means the index of the bed. /// Zero is the physical bed, larger than zero means a virtual bed. -struct ArrangePolygon { - const ExPolygon poly; /// The 2D silhouette to be arranged +struct ArrangePolygon { + ExPolygon poly; /// The 2D silhouette to be arranged Vec2crd translation{0, 0}; /// The translation of the poly double rotation{0.0}; /// The rotation of the poly in radians int bed_idx{UNARRANGED}; /// To which logical bed does poly belong... + int priority{0}; - ArrangePolygon(ExPolygon p, const Vec2crd &tr = {}, double rot = 0.0) - : poly{std::move(p)}, translation{tr}, rotation{rot} - {} + /// Optional setter function which can store arbitrary data in its closure + std::function setter = nullptr; + + /// Helper function to call the setter with the arrange data arguments + void apply() const { if (setter) setter(*this); } }; using ArrangePolygons = std::vector; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 5f296cc5f..cf0f7c855 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -387,20 +387,24 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) } arrangement::BedShapeHint bedhint; - - if (bb) + coord_t bedwidth = 0; + + if (bb) { + bedwidth = scaled(bb->size().x()); bedhint = arrangement::BedShapeHint( BoundingBox(scaled(bb->min), scaled(bb->max))); + } arrangement::arrange(input, scaled(dist), bedhint); bool ret = true; + coord_t stride = bedwidth + bedwidth / 5; for(size_t i = 0; i < input.size(); ++i) { - if (input[i].bed_idx == 0) { // no logical beds are allowed - instances[i]->apply_arrange_result(input[i].translation, - input[i].rotation); - } else ret = false; + if (input[i].bed_idx != 0) ret = false; + if (input[i].bed_idx >= 0) + instances[i]->apply_arrange_result(input[i], + {input[i].bed_idx * stride, 0}); } return ret; @@ -1822,22 +1826,24 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const // this may happen for malformed models, see: // https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (p.points.empty()) return {{}}; + if (p.points.empty()) return {}; Polygons pp{p}; pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); if (!pp.empty()) p = pp.front(); m_arrange_cache.poly.contour = std::move(p); + m_arrange_cache.bed_origin = {0, 0}; + m_arrange_cache.bed_idx = arrangement::UNARRANGED; m_arrange_cache.valid = true; } - arrangement::ArrangePolygon ret{m_arrange_cache.poly, - Vec2crd{scaled(get_offset(X)), - scaled(get_offset(Y))}, - get_rotation(Z)}; - - ret.bed_idx = m_arrange_cache.bed_idx; - + arrangement::ArrangePolygon ret; + ret.poly = m_arrange_cache.poly; + ret.translation = Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))} - + m_arrange_cache.bed_origin; + ret.rotation = get_rotation(Z); + ret.bed_idx = m_arrange_cache.bed_idx; + return ret; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 7ce790c7c..548e0f015 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -558,13 +558,15 @@ public: arrangement::ArrangePolygon get_arrange_polygon() const; // Apply the arrange result on the ModelInstance - void apply_arrange_result(Vec2crd offs, double rot_rads, int bed_idx = 0) + void apply_arrange_result(const arrangement::ArrangePolygon& ap, + const Vec2crd& bed_origin = {0, 0}) { // write the transformation data into the model instance - set_rotation(Z, rot_rads); - set_offset(X, unscale(offs(X))); - set_offset(Y, unscale(offs(Y))); - m_arrange_cache.bed_idx = bed_idx; + set_rotation(Z, ap.rotation); + set_offset(X, unscale(ap.translation(X) + bed_origin.x())); + set_offset(Y, unscale(ap.translation(Y) + bed_origin.y())); + m_arrange_cache.bed_origin = bed_origin; + m_arrange_cache.bed_idx = ap.bed_idx; } protected: @@ -599,8 +601,9 @@ private: // Warning! This object is not guarded against concurrency. mutable struct ArrangeCache { bool valid = false; - int bed_idx { arrangement::UNARRANGED }; + Vec2crd bed_origin {0, 0}; ExPolygon poly; + int bed_idx = arrangement::UNARRANGED; } m_arrange_cache; }; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 771d092a7..c674172e3 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5739,11 +5739,8 @@ const SLAPrint* GLCanvas3D::sla_print() const return (m_process == nullptr) ? nullptr : m_process->sla_print(); } -void GLCanvas3D::WipeTowerInfo::apply_arrange_result(Vec2crd off, double rotation_rads) +void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const { - Vec2d offset = unscaled(off); - 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 a28791ed0..6c5e6475c 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -613,9 +613,10 @@ public: int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } class WipeTowerInfo { + protected: Vec2d m_pos = {std::nan(""), std::nan("")}; - Vec2d m_bb_size; - double m_rotation; + Vec2d m_bb_size = {0., 0.}; + double m_rotation = 0.; friend class GLCanvas3D; public: @@ -623,22 +624,12 @@ public: { return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y()); } - - void apply_arrange_result(Vec2crd offset, double rotation_rads); - arrangement::ArrangePolygon get_arrange_polygon() const - { - Polygon p({ - {coord_t(0), coord_t(0)}, - {scaled(m_bb_size(X)), coord_t(0)}, - {scaled(m_bb_size)}, - {coord_t(0), scaled(m_bb_size(Y))}, - {coord_t(0), coord_t(0)}, - }); - - ExPolygon ep; ep.contour = std::move(p); - return {ep, scaled(m_pos), m_rotation}; - } + inline const Vec2d& pos() const { return m_pos; } + inline double rotation() const { return m_rotation; } + inline const Vec2d bb_size() const { return m_bb_size; } + + void apply_wipe_tower() const; }; WipeTowerInfo get_wipe_tower_info() const; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3ae5c3005..a6360a020 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1262,6 +1262,56 @@ struct Plater::priv BackgroundSlicingProcess background_process; bool suppressed_backround_processing_update { false }; + // Cache the wti info + class WipeTower: public GLCanvas3D::WipeTowerInfo { + Vec2d m_bed_origin = {0., 0.}; + int m_bed_idx = arrangement::UNARRANGED; + friend priv; + public: + + void apply_arrange_result(const arrangement::ArrangePolygon& ap, + const Vec2crd& bedc) { + m_bed_origin = unscaled(bedc); + m_pos = unscaled(ap.translation) + m_bed_origin; + m_rotation = ap.rotation; + m_bed_idx = ap.bed_idx; + apply_wipe_tower(); + } + + arrangement::ArrangePolygon get_arrange_polygon() const + { + Polygon p({ + {coord_t(0), coord_t(0)}, + {scaled(m_bb_size(X)), coord_t(0)}, + {scaled(m_bb_size)}, + {coord_t(0), scaled(m_bb_size(Y))}, + {coord_t(0), coord_t(0)}, + }); + + arrangement::ArrangePolygon ret; + ret.poly.contour = std::move(p); + ret.translation = scaled(m_pos) - scaled(m_bed_origin); + ret.rotation = m_rotation; + ret.bed_idx = m_bed_idx; + return ret; + } + + // For future use + int bed_index() const { return m_bed_idx; } + }; + +private: + WipeTower m_wipetower; + +public: + WipeTower& wipe_tower() { + auto wti = view3D->get_canvas3d()->get_wipe_tower_info(); + m_wipetower.m_pos = wti.pos(); + m_wipetower.m_rotation = wti.rotation(); + m_wipetower.m_bb_size = wti.bb_size(); + return m_wipetower; + } + // A class to handle UI jobs like arranging and optimizing rotation. // These are not instant jobs, the user has to be informed about their // state in the status progress indicator. On the other hand they are @@ -1410,40 +1460,20 @@ struct Plater::priv class ArrangeJob : public Job { + using ArrangePolygon = arrangement::ArrangePolygon; + using ArrangePolygons = arrangement::ArrangePolygons; + // The gap between logical beds in the x axis expressed in ratio of // the current bed width. static const constexpr double LOGICAL_BED_GAP = 1. / 5.; + static const constexpr int UNARRANGED = arrangement::UNARRANGED; - // Cache the wti info - GLCanvas3D::WipeTowerInfo m_wti; - - // Cache the selected instances needed to write back the arrange - // result. The order of instances is the same as the arrange polys - struct IndexedArrangePolys { - ModelInstancePtrs insts; - arrangement::ArrangePolygons polys; - - void reserve(size_t cap) { insts.reserve(cap); polys.reserve(cap); } - void clear() { insts.clear(); polys.clear(); } - - void emplace_back(ModelInstance *inst) { - insts.emplace_back(inst); - polys.emplace_back(inst->get_arrange_polygon()); - } - - void swap(IndexedArrangePolys &pp) { - insts.swap(pp.insts); polys.swap(pp.polys); - } - }; - - IndexedArrangePolys m_selected, m_unselected; + ArrangePolygons m_selected, m_unselected; protected: void prepare() override { - m_wti = plater().view3D->get_canvas3d()->get_wipe_tower_info(); - // Get the selection map Selection& sel = plater().get_selection(); const Selection::ObjectIdxsToInstanceIdxsMap &selmap = @@ -1458,59 +1488,57 @@ struct Plater::priv m_selected.reserve(count + 1 /* for optional wti */); m_unselected.reserve(count + 1 /* for optional wti */); - // Go through the objects and check if inside the selection - for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { - auto oit = selmap.find(int(oidx)); - - if (oit != selmap.end()) { // Object is selected - auto &iids = oit->second; - - // Go through instances and check if inside selection - size_t instcnt = model.objects[oidx]->instances.size(); - for (size_t iidx = 0; iidx < instcnt; ++iidx) { - auto instit = iids.find(iidx); - ModelInstance *oi = model.objects[oidx] - ->instances[iidx]; - - // Instance is selected - instit != iids.end() ? - m_selected.emplace_back(oi) : - m_unselected.emplace_back(oi); - } - } else // object not selected, all instances are unselected - for (ModelInstance *oi : model.objects[oidx]->instances) - m_unselected.emplace_back(oi); - } - - if (m_wti) - sel.is_wipe_tower() ? - m_selected.polys.emplace_back(m_wti.get_arrange_polygon()) : - m_unselected.polys.emplace_back(m_wti.get_arrange_polygon()); - - // If the selection is completely empty, consider all items as the - // selection - if (m_selected.insts.empty() && m_selected.polys.empty()) - m_selected.swap(m_unselected); - // Stride between logical beds double bedwidth = plater().bed_shape_bb().size().x(); coord_t stride = scaled((1. + LOGICAL_BED_GAP) * bedwidth); - for (arrangement::ArrangePolygon &ap : m_selected.polys) - if (ap.bed_idx > 0) ap.translation.x() -= ap.bed_idx * stride; + // Go through the objects and check if inside the selection + for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { + auto oit = selmap.find(int(oidx)); + ModelObject *mo = model.objects[oidx]; + + std::vector inst_sel(mo->instances.size(), false); + + if (oit != selmap.end()) + for (auto inst_id : oit->second) inst_sel[inst_id] = true; + + for (size_t i = 0; i < inst_sel.size(); ++i) { + ModelInstance *mi = mo->instances[i]; + ArrangePolygon ap = mi->get_arrange_polygon(); + ap.priority = 0; + ap.setter = [mi, stride](const ArrangePolygon &p) { + if (p.bed_idx != UNARRANGED) + mi->apply_arrange_result(p, {p.bed_idx * stride, 0}); + }; + + inst_sel[i] ? + m_selected.emplace_back(std::move(ap)) : + m_unselected.emplace_back(std::move(ap)); + } + } - for (arrangement::ArrangePolygon &ap : m_unselected.polys) - if (ap.bed_idx > 0) ap.translation.x() -= ap.bed_idx * stride; + auto& wti = plater().wipe_tower(); + if (wti) { + ArrangePolygon ap = wti.get_arrange_polygon(); + ap.setter = [&wti, stride](const ArrangePolygon &p) { + if (p.bed_idx != UNARRANGED) + wti.apply_arrange_result(p, {p.bed_idx * stride, 0}); + }; + + ap.priority = 1; + sel.is_wipe_tower() ? + m_selected.emplace_back(std::move(ap)) : + m_unselected.emplace_back(std::move(ap)); + } + + // If the selection was empty arrange everything + if (m_selected.empty()) m_selected.swap(m_unselected); } public: using Job::Job; - - int status_range() const override - { - return int(m_selected.polys.size()); - } + int status_range() const override { return int(m_selected.size()); } void process() override; @@ -1521,30 +1549,8 @@ struct Plater::priv return; } - // Stride between logical beds - double bedwidth = plater().bed_shape_bb().size().x(); - coord_t stride = scaled((1. + LOGICAL_BED_GAP) * bedwidth); - - for(size_t i = 0; i < m_selected.insts.size(); ++i) { - if (m_selected.polys[i].bed_idx != arrangement::UNARRANGED) { - Vec2crd offs = m_selected.polys[i].translation; - double rot = m_selected.polys[i].rotation; - int bdidx = m_selected.polys[i].bed_idx; - offs.x() += bdidx * stride; - m_selected.insts[i]->apply_arrange_result(offs, rot, bdidx); - } - } - - // Handle the wipe tower - if (m_wti && m_selected.polys.size() > m_selected.insts.size()) { - auto &wtipoly = m_selected.polys.back(); - if (wtipoly.bed_idx != arrangement::UNARRANGED) { - Vec2crd o = wtipoly.translation; - double r = wtipoly.rotation; - o.x() += wtipoly.bed_idx * stride; - m_wti.apply_arrange_result(o, r); - } - } + // Apply the arrange result to all selected objects + for (ArrangePolygon &ap : m_selected) ap.apply(); // Call original finalize (will update the scene) Job::finalize(); @@ -2531,7 +2537,8 @@ arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const { return arrangement::BedShapeHint(bedpoly); } -void Plater::priv::find_new_position(const ModelInstancePtrs &instances, coord_t min_d) +void Plater::priv::find_new_position(const ModelInstancePtrs &instances, + coord_t min_d) { arrangement::ArrangePolygons movable, fixed; @@ -2546,15 +2553,14 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances, coord_t movable.emplace_back(std::move(arrpoly)); } - auto wti = view3D->get_canvas3d()->get_wipe_tower_info(); - if (wti) fixed.emplace_back(wti.get_arrange_polygon()); + if (wipe_tower()) + fixed.emplace_back(m_wipetower.get_arrange_polygon()); arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint()); for (size_t i = 0; i < instances.size(); ++i) if (movable[i].bed_idx == 0) - instances[i]->apply_arrange_result(movable[i].translation, - movable[i].rotation); + instances[i]->apply_arrange_result(movable[i]); } void Plater::priv::ArrangeJob::process() { @@ -2567,16 +2573,15 @@ void Plater::priv::ArrangeJob::process() { dist = PrintConfig::min_object_distance(plater().config); } - coord_t min_obj_distance = scaled(dist); - auto count = unsigned(m_selected.polys.size()); + coord_t min_d = scaled(dist); + auto count = unsigned(m_selected.size()); arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); try { - arrangement::arrange(m_selected.polys, m_unselected.polys, - min_obj_distance, - bedshape, + arrangement::arrange(m_selected, m_unselected, min_d, bedshape, [this, count](unsigned st) { - if (st > 0) // will not finalize after last one + if (st > + 0) // will not finalize after last one update_status(count - st, arrangestr); }, [this]() { return was_canceled(); });