diff --git a/resources/icons/arrange.svg b/resources/icons/arrange.svg index 62cf939e9f..0ade9c6595 100644 --- a/resources/icons/arrange.svg +++ b/resources/icons/arrange.svg @@ -1,23 +1,87 @@ - + - - - - - - - - - - - - - - + + + + + + + + + diff --git a/resources/icons/arrange_current.svg b/resources/icons/arrange_current.svg new file mode 100644 index 0000000000..a516a1a3bb --- /dev/null +++ b/resources/icons/arrange_current.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 0d5c42eb57..3324b0edfb 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -388,7 +388,9 @@ int CLI::run(int argc, char **argv) // Loop through transform options. bool user_center_specified = false; - arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(m_print_config)); + + const BedsGrid::Gap gap{s_multiple_beds.get_bed_gap()}; + arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(m_print_config), gap); arr2::ArrangeSettings arrange_cfg; arrange_cfg.set_distance_from_objects(min_object_distance(m_print_config)); diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index e487d1a0d1..5b989ccabf 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -46,7 +46,7 @@ void arrange(SelectionStrategy &&selstrategy, // Dispatch: arrange(std::forward(selstrategy), std::forward(packingstrategy), items, fixed, - RectangleBed{bed.bb}, SelStrategyTag{}); + RectangleBed{bed.bb, bed.gap}, SelStrategyTag{}); std::vector bed_indices = get_bed_indices(items, fixed); std::map pilebb; @@ -399,6 +399,7 @@ ArrItem ConvexItemConverter::convert(const Arrangeable &arrbl, set_bed_index(ret, bed_index); set_priority(ret, arrbl.priority()); + set_bed_constraint(ret, arrbl.bed_constraint()); imbue_id(ret, arrbl.id()); if constexpr (IsWritableDataStore) @@ -416,6 +417,7 @@ ArrItem AdvancedItemConverter::convert(const Arrangeable &arrbl, set_bed_index(ret, bed_index); set_priority(ret, arrbl.priority()); + set_bed_constraint(ret, arrbl.bed_constraint()); imbue_id(ret, arrbl.id()); if constexpr (IsWritableDataStore) arrbl.imbue_data(AnyWritableDataStore{ret}); diff --git a/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp b/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp index 41514668ce..14cc3acbb5 100644 --- a/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp +++ b/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp @@ -139,6 +139,10 @@ void arrange( int bedidx = 0; while (!was_packed && !is_cancelled()) { for (; !was_packed && !is_cancelled(); bedidx++) { + const std::optional bed_constraint{get_bed_constraint(*it)}; + if (bed_constraint && bedidx != *bed_constraint) { + continue; + } set_bed_index(*it, bedidx); auto remaining = Range{std::next(static_cast(it)), @@ -157,6 +161,10 @@ void arrange( sel.on_arranged_fn(*it, bed, packed_range, remaining); } else { set_bed_index(*it, Unarranged); + if (bed_constraint && bedidx == *bed_constraint) { + // Leave the item as is as it does not fit on the enforced bed. + was_packed = true; + } } } } diff --git a/src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp b/src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp index 9c11dae4b0..d1f3695a6f 100644 --- a/src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp +++ b/src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp @@ -31,6 +31,11 @@ template struct ArrangeItemTraits_ { static int get_bed_index(const ArrItem &ap) { return ap.get_bed_index(); } + static std::optional get_bed_constraint(const ArrItem &ap) + { + return ap.get_bed_constraint(); + } + static int get_priority(const ArrItem &ap) { return ap.get_priority(); } // Setters: @@ -43,6 +48,11 @@ template struct ArrangeItemTraits_ { static void set_rotation(ArrItem &ap, double v) { ap.set_rotation(v); } static void set_bed_index(ArrItem &ap, int v) { ap.set_bed_index(v); } + + static void set_bed_constraint(ArrItem &ap, std::optional v) + { + ap.set_bed_constraint(v); + } }; template using ArrangeItemTraits = ArrangeItemTraits_>; @@ -69,6 +79,11 @@ template int get_priority(const T &itm) return ArrangeItemTraits::get_priority(itm); } +template std::optional get_bed_constraint(const T &itm) +{ + return ArrangeItemTraits::get_bed_constraint(itm); +} + // Setters: template void set_translation(T &itm, const Vec2crd &v) @@ -86,6 +101,11 @@ template void set_bed_index(T &itm, int v) ArrangeItemTraits::set_bed_index(itm, v); } +template void set_bed_constraint(T &itm, std::optional v) +{ + ArrangeItemTraits::set_bed_constraint(itm, v); +} + // Helper functions for arrange items template bool is_arranged(const ArrItem &ap) { diff --git a/src/libslic3r/Arrange/Core/Beds.cpp b/src/libslic3r/Arrange/Core/Beds.cpp index 657522a593..60c0b86692 100644 --- a/src/libslic3r/Arrange/Core/Beds.cpp +++ b/src/libslic3r/Arrange/Core/Beds.cpp @@ -83,7 +83,7 @@ inline double distance_to(const Point &p1, const Point &p2) return std::sqrt(dx * dx + dy * dy); } -static CircleBed to_circle(const Point ¢er, const Points &points) +static CircleBed to_circle(const Point ¢er, const Points &points, const BedsGrid::Gap &gap) { std::vector vertex_distances; double avg_dist = 0; @@ -96,7 +96,7 @@ static CircleBed to_circle(const Point ¢er, const Points &points) avg_dist /= vertex_distances.size(); - CircleBed ret(center, avg_dist); + CircleBed ret(center, avg_dist, gap); for (auto el : vertex_distances) { if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { ret = {}; @@ -107,7 +107,7 @@ static CircleBed to_circle(const Point ¢er, const Points &points) return ret; } -template auto call_with_bed(const Points &bed, Fn &&fn) +template auto call_with_bed(const Points &bed, const BedsGrid::Gap &gap, Fn &&fn) { if (bed.empty()) return fn(InfiniteBed{}); @@ -115,23 +115,23 @@ template auto call_with_bed(const Points &bed, Fn &&fn) return fn(InfiniteBed{bed.front()}); else { auto bb = BoundingBox(bed); - CircleBed circ = to_circle(bb.center(), bed); + CircleBed circ = to_circle(bb.center(), bed, gap); auto parea = poly_area(bed); if ((1.0 - parea / area(bb)) < 1e-3) { - return fn(RectangleBed{bb}); + return fn(RectangleBed{bb, gap}); } else if (!std::isnan(circ.radius()) && (1.0 - parea / area(circ)) < 1e-2) return fn(circ); else - return fn(IrregularBed{{ExPolygon(bed)}}); + return fn(IrregularBed{{ExPolygon(bed)}, gap}); } } -ArrangeBed to_arrange_bed(const Points &bedpts) +ArrangeBed to_arrange_bed(const Points &bedpts, const BedsGrid::Gap &gap) { ArrangeBed ret; - call_with_bed(bedpts, [&](const auto &bed) { ret = bed; }); + call_with_bed(bedpts, gap, [&](const auto &bed) { ret = bed; }); return ret; } diff --git a/src/libslic3r/Arrange/Core/Beds.hpp b/src/libslic3r/Arrange/Core/Beds.hpp index 5bb2ddfb31..d6e4ed9588 100644 --- a/src/libslic3r/Arrange/Core/Beds.hpp +++ b/src/libslic3r/Arrange/Core/Beds.hpp @@ -16,6 +16,7 @@ #include #include +#include "libslic3r/MultipleBeds.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/libslic3r.h" @@ -35,13 +36,18 @@ struct InfiniteBed { BoundingBox bounding_box(const InfiniteBed &bed); inline InfiniteBed offset(const InfiniteBed &bed, coord_t) { return bed; } +inline BedsGrid::Gap bed_gap(const InfiniteBed &) +{ + return BedsGrid::Gap::Zero(); +} struct RectangleBed { BoundingBox bb; + BedsGrid::Gap gap; - explicit RectangleBed(const BoundingBox &bedbb) : bb{bedbb} {} - explicit RectangleBed(coord_t w, coord_t h, Point c = {0, 0}): - bb{{c.x() - w / 2, c.y() - h / 2}, {c.x() + w / 2, c.y() + h / 2}} + explicit RectangleBed(const BoundingBox &bedbb, const BedsGrid::Gap &gap) : bb{bedbb}, gap{gap} {} + explicit RectangleBed(coord_t w, coord_t h, const BedsGrid::Gap &gap = BedsGrid::Gap::Zero(), Point c = {0, 0}): + bb{{c.x() - w / 2, c.y() - h / 2}, {c.x() + w / 2, c.y() + h / 2}}, gap{gap} {} coord_t width() const { return bb.size().x(); } @@ -54,6 +60,9 @@ inline RectangleBed offset(RectangleBed bed, coord_t v) bed.bb.offset(v); return bed; } +inline BedsGrid::Gap bed_gap(const RectangleBed &bed) { + return bed.gap; +} Polygon to_rectangle(const BoundingBox &bb); @@ -65,16 +74,19 @@ inline Polygon to_rectangle(const RectangleBed &bed) class CircleBed { Point m_center; double m_radius; + BedsGrid::Gap m_gap; public: - CircleBed(): m_center(0, 0), m_radius(NaNd) {} - explicit CircleBed(const Point& c, double r) + CircleBed(): m_center(0, 0), m_radius(NaNd), m_gap(BedsGrid::Gap::Zero()) {} + explicit CircleBed(const Point& c, double r, const BedsGrid::Gap &g) : m_center(c) , m_radius(r) + , m_gap(g) {} double radius() const { return m_radius; } const Point& center() const { return m_center; } + const BedsGrid::Gap &gap() const { return m_gap; } }; // Function to approximate a circle with a convex polygon @@ -89,10 +101,14 @@ inline BoundingBox bounding_box(const CircleBed &bed) } inline CircleBed offset(const CircleBed &bed, coord_t v) { - return CircleBed{bed.center(), bed.radius() + v}; + return CircleBed{bed.center(), bed.radius() + v, bed.gap()}; +} +inline BedsGrid::Gap bed_gap(const CircleBed &bed) +{ + return bed.gap(); } -struct IrregularBed { ExPolygons poly; }; +struct IrregularBed { ExPolygons poly; BedsGrid::Gap gap; }; inline BoundingBox bounding_box(const IrregularBed &bed) { return get_extents(bed.poly); @@ -103,6 +119,10 @@ inline IrregularBed offset(IrregularBed bed, coord_t v) bed.poly = offset_ex(bed.poly, v); return bed; } +inline BedsGrid::Gap bed_gap(const IrregularBed &bed) +{ + return bed.gap; +} using ArrangeBed = boost::variant; @@ -124,6 +144,15 @@ inline ArrangeBed offset(ArrangeBed bed, coord_t v) return bed; } +inline BedsGrid::Gap bed_gap(const ArrangeBed &bed) +{ + BedsGrid::Gap ret; + auto visitor = [&ret](const auto &b) { ret = bed_gap(b); }; + boost::apply_visitor(visitor, bed); + + return ret; +} + inline double area(const BoundingBox &bb) { auto bbsz = bb.size(); @@ -187,7 +216,7 @@ inline ExPolygons to_expolygons(const ArrangeBed &bed) return ret; } -ArrangeBed to_arrange_bed(const Points &bedpts); +ArrangeBed to_arrange_bed(const Points &bedpts, const BedsGrid::Gap &gap); template struct IsRectangular_ : public std::false_type {}; template<> struct IsRectangular_: public std::true_type {}; diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/RectangleOverfitKernelWrapper.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/RectangleOverfitKernelWrapper.hpp index d938d188b9..1a9fc8295b 100644 --- a/src/libslic3r/Arrange/Core/NFP/Kernels/RectangleOverfitKernelWrapper.hpp +++ b/src/libslic3r/Arrange/Core/NFP/Kernels/RectangleOverfitKernelWrapper.hpp @@ -74,7 +74,7 @@ struct RectangleOverfitKernelWrapper { for (auto &fitm : all_items_range(packing_context)) pilebb.merge(fixed_bounding_box(fitm)); - return KernelTraits::on_start_packing(k, itm, RectangleBed{binbb}, + return KernelTraits::on_start_packing(k, itm, RectangleBed{binbb, BedsGrid::Gap::Zero()}, packing_context, remaining_items); } diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp index f92b893a85..e05e7886aa 100644 --- a/src/libslic3r/Arrange/Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp +++ b/src/libslic3r/Arrange/Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp @@ -9,9 +9,9 @@ #include "KernelTraits.hpp" -#include "libslic3r/Arrange/Core/PackingContext.hpp" -#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" -#include "libslic3r/Arrange/Core/Beds.hpp" +#include "arrange/PackingContext.hpp" +#include "arrange/NFP/NFPArrangeItemTraits.hpp" +#include "arrange/Beds.hpp" #include diff --git a/src/libslic3r/Arrange/Items/ArrangeItem.cpp b/src/libslic3r/Arrange/Items/ArrangeItem.cpp index ee600a6860..9f57807b35 100644 --- a/src/libslic3r/Arrange/Items/ArrangeItem.cpp +++ b/src/libslic3r/Arrange/Items/ArrangeItem.cpp @@ -159,6 +159,7 @@ ArrangeItem &ArrangeItem::operator=(const ArrangeItem &other) m_datastore = other.m_datastore; m_bed_idx = other.m_bed_idx; m_priority = other.m_priority; + m_bed_constraint = other.m_bed_constraint; if (other.m_envelope.get() == &other.m_shape) m_envelope = &m_shape; @@ -190,6 +191,7 @@ ArrangeItem &ArrangeItem::operator=(ArrangeItem &&other) noexcept m_datastore = std::move(other.m_datastore); m_bed_idx = other.m_bed_idx; m_priority = other.m_priority; + m_bed_constraint = other.m_bed_constraint; if (other.m_envelope.get() == &other.m_shape) m_envelope = &m_shape; diff --git a/src/libslic3r/Arrange/Items/ArrangeItem.hpp b/src/libslic3r/Arrange/Items/ArrangeItem.hpp index 8fa3a12d49..bc30450057 100644 --- a/src/libslic3r/Arrange/Items/ArrangeItem.hpp +++ b/src/libslic3r/Arrange/Items/ArrangeItem.hpp @@ -154,6 +154,7 @@ private: int m_bed_idx{Unarranged}; // To which logical bed does this item belong int m_priority{0}; // For sorting + std::optional m_bed_constraint; public: ArrangeItem() = default; @@ -180,9 +181,11 @@ public: int bed_idx() const { return m_bed_idx; } int priority() const { return m_priority; } + std::optional bed_constraint() const { return m_bed_constraint; }; void bed_idx(int v) { m_bed_idx = v; } void priority(int v) { m_priority = v; } + void bed_constraint(std::optional v) { m_bed_constraint = v; } const ArbitraryDataStore &datastore() const { return m_datastore; } ArbitraryDataStore &datastore() { return m_datastore; } @@ -239,6 +242,11 @@ template<> struct ArrangeItemTraits_ return itm.priority(); } + static std::optional get_bed_constraint(const ArrangeItem &itm) + { + return itm.bed_constraint(); + } + // Setters: static void set_translation(ArrangeItem &itm, const Vec2crd &v) @@ -255,6 +263,11 @@ template<> struct ArrangeItemTraits_ { itm.bed_idx(v); } + + static void set_bed_constraint(ArrangeItem &itm, std::optional v) + { + itm.bed_constraint(v); + } }; // Some items can be containers of arbitrary data stored under string keys. diff --git a/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp b/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp index 6263ba5c7d..9964a72f78 100644 --- a/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp +++ b/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp @@ -37,6 +37,7 @@ class SimpleArrangeItem { double m_rotation = 0.; int m_priority = 0; int m_bed_idx = Unarranged; + std::optional m_bed_constraint; std::vector m_allowed_rotations = {0.}; ObjectID m_obj_id; @@ -50,11 +51,15 @@ public: double get_rotation() const noexcept { return m_rotation; } int get_priority() const noexcept { return m_priority; } int get_bed_index() const noexcept { return m_bed_idx; } + std::optional get_bed_constraint() const noexcept { + return m_bed_constraint; + } void set_translation(const Vec2crd &v) { m_translation = v; } void set_rotation(double v) noexcept { m_rotation = v; } void set_priority(int v) noexcept { m_priority = v; } void set_bed_index(int v) noexcept { m_bed_idx = v; } + void set_bed_constraint(std::optional v) noexcept { m_bed_constraint = v; } const Polygon &shape() const { return m_shape; } Polygon outline() const; diff --git a/src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp b/src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp index c5583276ff..5c647104eb 100644 --- a/src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp +++ b/src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp @@ -17,6 +17,7 @@ class TrafoOnlyArrangeItem { int m_priority = 0; Vec2crd m_translation = Vec2crd::Zero(); double m_rotation = 0.; + std::optional m_bed_constraint; ArbitraryDataStore m_datastore; @@ -28,13 +29,15 @@ public: : m_bed_idx{arr2::get_bed_index(other)}, m_priority{arr2::get_priority(other)}, m_translation(arr2::get_translation(other)), - m_rotation{arr2::get_rotation(other)} + m_rotation{arr2::get_rotation(other)}, + m_bed_constraint{arr2::get_bed_constraint(other)} {} const Vec2crd& get_translation() const noexcept { return m_translation; } double get_rotation() const noexcept { return m_rotation; } int get_bed_index() const noexcept { return m_bed_idx; } int get_priority() const noexcept { return m_priority; } + std::optional get_bed_constraint() const noexcept { return m_bed_constraint; } const ArbitraryDataStore &datastore() const noexcept { return m_datastore; } ArbitraryDataStore &datastore() { return m_datastore; } diff --git a/src/libslic3r/Arrange/Scene.hpp b/src/libslic3r/Arrange/Scene.hpp index ca3d2af617..571d3dc0b4 100644 --- a/src/libslic3r/Arrange/Scene.hpp +++ b/src/libslic3r/Arrange/Scene.hpp @@ -99,6 +99,8 @@ public: // objects are arranged first. virtual int priority() const { return 0; } + virtual std::optional bed_constraint() const { return std::nullopt; } + // Any implementation specific properties can be passed to the arrangement // core by overriding this method. This implies that the specific Arranger // will be able to interpret these properties. An example usage is to mark @@ -190,6 +192,14 @@ inline BoundingBox bounding_box(const ExtendedBed &bed) return bedbb; } +inline BedsGrid::Gap bed_gap(const ExtendedBed &bed) +{ + BedsGrid::Gap gap; + visit_bed([&gap](auto &rawbed) { gap = bed_gap(rawbed); }, bed); + + return gap; +} + class Scene; // SceneBuilderBase is intended for Scene construction. A simple constructor @@ -238,9 +248,9 @@ public: return std::move(static_cast(*this)); } - Subclass &&set_bed(const Points &pts) + Subclass &&set_bed(const Points &pts, const BedsGrid::Gap &gap) { - m_bed = arr2::to_arrange_bed(pts); + m_bed = arr2::to_arrange_bed(pts, gap); return std::move(static_cast(*this)); } diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index 9809ef59ab..c1c8456cff 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -14,6 +14,7 @@ #include #include "libslic3r/Model.hpp" +#include "libslic3r/MultipleBeds.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" @@ -189,7 +190,7 @@ void SceneBuilder::build_scene(Scene &sc) && if (m_fff_print && !m_sla_print) { if (is_infinite_bed(m_bed)) { - set_bed(*m_fff_print); + set_bed(*m_fff_print, BedsGrid::Gap::Zero()); } else { set_brim_and_skirt(); } @@ -211,28 +212,28 @@ void SceneBuilder::build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel m_vbed_handler = VirtualBedHandler::create(m_bed); } - if (!m_wipetower_handler) { - m_wipetower_handler = std::make_unique(); - } - if (m_fff_print && !m_xl_printer) m_xl_printer = is_XL_printer(m_fff_print->config()); - bool has_wipe_tower = false; - m_wipetower_handler->visit( - [&has_wipe_tower](const Arrangeable &arrbl) { has_wipe_tower = true; }); + const bool has_wipe_tower = !m_wipetower_handlers.empty(); if (m_xl_printer && !has_wipe_tower) { - m_bed = XLBed{bounding_box(m_bed)}; + m_bed = XLBed{bounding_box(m_bed), bed_gap(m_bed)}; } amodel.m_vbed_handler = std::move(m_vbed_handler); amodel.m_model = std::move(m_model); amodel.m_selmask = std::move(m_selection); - amodel.m_wth = std::move(m_wipetower_handler); + amodel.m_wths = std::move(m_wipetower_handlers); + amodel.m_bed_constraints = std::move(m_bed_constraints); - amodel.m_wth->set_selection_predicate( - [&amodel] { return amodel.m_selmask->is_wipe_tower(); }); + for (auto &wth : amodel.m_wths) { + wth->set_selection_predicate( + [&amodel](int wipe_tower_index){ + return amodel.m_selmask->is_wipe_tower_selected(wipe_tower_index); + } + ); + } } int XStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const @@ -331,40 +332,19 @@ Transform3d YStriderVBedHandler::get_physical_bed_trafo(int bed_index) const return tr; } -const int GridStriderVBedHandler::Cols = - 2 * static_cast(std::sqrt(std::numeric_limits::max()) / 2); - -const int GridStriderVBedHandler::HalfCols = Cols / 2; -const int GridStriderVBedHandler::Offset = HalfCols + Cols * HalfCols; - -Vec2i GridStriderVBedHandler::raw2grid(int bed_idx) const -{ - bed_idx += Offset; - - Vec2i ret{bed_idx % Cols - HalfCols, bed_idx / Cols - HalfCols}; - - return ret; -} - -int GridStriderVBedHandler::grid2raw(const Vec2i &crd) const -{ - // Overlapping virtual beds will happen if the crd values exceed limits - assert((crd.x() < HalfCols - 1 && crd.x() >= -HalfCols) && - (crd.y() < HalfCols - 1 && crd.y() >= -HalfCols)); - - return (crd.x() + HalfCols) + Cols * (crd.y() + HalfCols) - Offset; -} - int GridStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const { Vec2i crd = {m_xstrider.get_bed_index(obj), m_ystrider.get_bed_index(obj)}; - return grid2raw(crd); + return BedsGrid::grid_coords2index(crd); } bool GridStriderVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx) { - Vec2i crd = raw2grid(bed_idx); + if (bed_idx < 0) { + return false; + } + Vec2i crd = BedsGrid::index2grid_coords(bed_idx); bool retx = m_xstrider.assign_bed(inst, crd.x()); bool rety = m_ystrider.assign_bed(inst, crd.y()); @@ -374,7 +354,7 @@ bool GridStriderVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx) Transform3d GridStriderVBedHandler::get_physical_bed_trafo(int bed_idx) const { - Vec2i crd = raw2grid(bed_idx); + Vec2i crd = BedsGrid::index2grid_coords(bed_idx); Transform3d ret = m_xstrider.get_physical_bed_trafo(crd.x()) * m_ystrider.get_physical_bed_trafo(crd.y()); @@ -465,7 +445,7 @@ SceneBuilder &&SceneBuilder::set_sla_print(AnyPtr mdl_print) return std::move(*this); } -SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg) +SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg, const BedsGrid::Gap &gap) { Points bedpts = get_bed_shape(cfg); @@ -473,19 +453,19 @@ SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg) m_xl_printer = true; } - m_bed = arr2::to_arrange_bed(bedpts); + m_bed = arr2::to_arrange_bed(bedpts, gap); return std::move(*this); } -SceneBuilder &&SceneBuilder::set_bed(const Print &print) +SceneBuilder &&SceneBuilder::set_bed(const Print &print, const BedsGrid::Gap &gap) { Points bedpts = get_bed_shape(print.config()); if (is_XL_printer(print.config())) { - m_bed = XLBed{get_extents(bedpts)}; + m_bed = XLBed{get_extents(bedpts), gap}; } else { - m_bed = arr2::to_arrange_bed(bedpts); + m_bed = arr2::to_arrange_bed(bedpts, gap); } set_brim_and_skirt(); @@ -499,11 +479,13 @@ SceneBuilder &&SceneBuilder::set_sla_print(const SLAPrint *slaprint) return std::move(*this); } -int ArrangeableWipeTowerBase::get_bed_index() const { return PhysicalBedId; } +int ArrangeableWipeTowerBase::get_bed_index() const { + return this->bed_index; +} bool ArrangeableWipeTowerBase::assign_bed(int bed_idx) { - return bed_idx == PhysicalBedId; + return bed_idx == this->bed_index; } bool PhysicalOnlyVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx) @@ -523,7 +505,9 @@ void ArrangeableSlicerModel::for_each_arrangeable( { for_each_arrangeable_(*this, fn); - m_wth->visit(fn); + for (auto &wth : m_wths) { + wth->visit(fn); + } } void ArrangeableSlicerModel::for_each_arrangeable( @@ -531,7 +515,9 @@ void ArrangeableSlicerModel::for_each_arrangeable( { for_each_arrangeable_(*this, fn); - m_wth->visit(fn); + for (auto &wth : m_wths) { + wth->visit(fn); + } } ObjectID ArrangeableSlicerModel::add_arrangeable(const ObjectID &prototype_id) @@ -549,13 +535,30 @@ ObjectID ArrangeableSlicerModel::add_arrangeable(const ObjectID &prototype_id) return ret; } +std::optional get_bed_constraint( + const ObjectID &id, + const BedConstraints &bed_constraints +) { + const auto found_constraint{bed_constraints.find(id)}; + if (found_constraint == bed_constraints.end()) { + return std::nullopt; + } + return found_constraint->second; +} + template void ArrangeableSlicerModel::for_each_arrangeable_(Self &&self, Fn &&fn) { InstPos pos; for (auto *obj : self.m_model->objects) { for (auto *inst : obj->instances) { - ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), self.m_selmask.get(), pos}; + ArrangeableModelInstance ainst{ + inst, + self.m_vbed_handler.get(), + self.m_selmask.get(), + pos, + get_bed_constraint(inst->id(), self.m_bed_constraints) + }; fn(ainst); ++pos.inst_idx; } @@ -567,16 +570,23 @@ void ArrangeableSlicerModel::for_each_arrangeable_(Self &&self, Fn &&fn) template void ArrangeableSlicerModel::visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn) { - if (id == wipe_tower_instance_id(0)) { - self.m_wth->visit(fn); - - return; + for (auto &wth : self.m_wths) { + if (id == wth->get_id()) { + wth->visit(fn); + return; + } } auto [inst, pos] = find_instance_by_id(*self.m_model, id); if (inst) { - ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), self.m_selmask.get(), pos}; + ArrangeableModelInstance ainst{ + inst, + self.m_vbed_handler.get(), + self.m_selmask.get(), + pos, + get_bed_constraint(id, self.m_bed_constraints) + }; fn(ainst); } } @@ -600,7 +610,7 @@ void ArrangeableSLAPrint::for_each_arrangeable_(Self &&self, Fn &&fn) for (auto *obj : self.m_model->objects) { for (auto *inst : obj->instances) { ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), - self.m_selmask.get(), pos}; + self.m_selmask.get(), pos, std::nullopt}; auto obj_id = inst->get_object()->id(); const SLAPrintObject *po = @@ -627,7 +637,9 @@ void ArrangeableSLAPrint::for_each_arrangeable( { for_each_arrangeable_(*this, fn); - m_wth->visit(fn); + for (auto &wth : m_wths) { + wth->visit(fn); + } } void ArrangeableSLAPrint::for_each_arrangeable( @@ -635,7 +647,9 @@ void ArrangeableSLAPrint::for_each_arrangeable( { for_each_arrangeable_(*this, fn); - m_wth->visit(fn); + for (auto &wth : m_wths) { + wth->visit(fn); + } } template @@ -645,7 +659,7 @@ void ArrangeableSLAPrint::visit_arrangeable_(Self &&self, const ObjectID &id, Fn if (inst) { ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), - self.m_selmask.get(), pos}; + self.m_selmask.get(), pos, std::nullopt}; auto obj_id = inst->get_object()->id(); const SLAPrintObject *po = @@ -925,16 +939,12 @@ std::unique_ptr VirtualBedHandler::create(const ExtendedBed & if (is_infinite_bed(bed)) { ret = std::make_unique(); } else { - // The gap between logical beds expressed in ratio of - // the current bed width. - constexpr double LogicalBedGap = 1. / 10.; - + BedsGrid::Gap gap; + visit_bed([&gap](auto &rawbed) { gap = bed_gap(rawbed); }, bed); BoundingBox bedbb; visit_bed([&bedbb](auto &rawbed) { bedbb = bounding_box(rawbed); }, bed); - auto bedwidth = bedbb.size().x(); - coord_t xgap = LogicalBedGap * bedwidth; - ret = std::make_unique(bedbb, xgap); + ret = std::make_unique(bedbb, gap); } return ret; diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp index 2167718f7e..e98c64ca6c 100644 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -42,7 +42,7 @@ class DynamicPrintConfig; namespace arr2 { -using SelectionPredicate = std::function; +using SelectionPredicate = std::function; // Objects implementing this interface should know how to present the wipe tower // as an Arrangeable. If the wipe tower is not present, the overloads of visit() shouldn't do @@ -55,6 +55,7 @@ public: virtual void visit(std::function) = 0; virtual void visit(std::function) const = 0; virtual void set_selection_predicate(SelectionPredicate pred) = 0; + virtual ObjectID get_id() const = 0; }; // Something that has a bounding box and can be displaced by arbitrary 2D offset and rotated @@ -110,7 +111,7 @@ public: virtual std::vector selected_objects() const = 0; virtual std::vector selected_instances(int obj_id) const = 0; - virtual bool is_wipe_tower() const = 0; + virtual bool is_wipe_tower_selected(int wipe_tower_index) const = 0; }; class FixedSelection : public Slic3r::arr2::SelectionMask @@ -138,7 +139,7 @@ public: std::vector{}; } - bool is_wipe_tower() const override { return m_wp; } + bool is_wipe_tower_selected(int) const override { return m_wp; } }; // Common part of any Arrangeable which is a wipe tower @@ -148,13 +149,16 @@ struct ArrangeableWipeTowerBase: public Arrangeable Polygon poly; SelectionPredicate selection_pred; + int bed_index{0}; ArrangeableWipeTowerBase( const ObjectID &objid, Polygon shape, - SelectionPredicate selection_predicate = [] { return false; }) + int bed_index, + SelectionPredicate selection_predicate = [](int){ return false; }) : oid{objid}, poly{std::move(shape)}, + bed_index{bed_index}, selection_pred{std::move(selection_predicate)} {} @@ -174,7 +178,7 @@ struct ArrangeableWipeTowerBase: public Arrangeable bool is_selected() const override { - return selection_pred(); + return selection_pred(bed_index); } int get_bed_index() const override; @@ -182,6 +186,10 @@ struct ArrangeableWipeTowerBase: public Arrangeable int priority() const override { return 1; } + std::optional bed_constraint() const override { + return this->bed_index; + } + void transform(const Vec2d &transl, double rot) override {} void imbue_data(AnyWritable &datastore) const override @@ -194,15 +202,18 @@ class SceneBuilder; struct InstPos { size_t obj_idx = 0, inst_idx = 0; }; +using BedConstraints = std::map; + // Implementing ArrangeableModel interface for PrusaSlicer's Model, ModelObject, ModelInstance data // hierarchy class ArrangeableSlicerModel: public ArrangeableModel { protected: AnyPtr m_model; - AnyPtr m_wth; // Determines how wipe tower is handled + std::vector> m_wths; // Determines how wipe tower is handled AnyPtr m_vbed_handler; // Determines how virtual beds are handled AnyPtr m_selmask; // Determines which objects are selected/unselected + BedConstraints m_bed_constraints; private: friend class SceneBuilder; @@ -234,7 +245,8 @@ class SceneBuilder: public SceneBuilderBase { protected: AnyPtr m_model; - AnyPtr m_wipetower_handler; + std::vector> m_wipetower_handlers; + BedConstraints m_bed_constraints; AnyPtr m_vbed_handler; AnyPtr m_selection; @@ -259,18 +271,18 @@ public: using SceneBuilderBase::set_bed; - SceneBuilder &&set_bed(const DynamicPrintConfig &cfg); - SceneBuilder &&set_bed(const Print &print); + SceneBuilder &&set_bed(const DynamicPrintConfig &cfg, const BedsGrid::Gap &gap); + SceneBuilder &&set_bed(const Print &print, const BedsGrid::Gap &gap); - SceneBuilder && set_wipe_tower_handler(WipeTowerHandler &wth) + SceneBuilder && set_wipe_tower_handlers(std::vector> &&handlers) { - m_wipetower_handler = &wth; + m_wipetower_handlers = std::move(handlers); return std::move(*this); } - SceneBuilder && set_wipe_tower_handler(AnyPtr wth) + SceneBuilder && set_bed_constraints(BedConstraints &&bed_constraints) { - m_wipetower_handler = std::move(wth); + m_bed_constraints = std::move(bed_constraints); return std::move(*this); } @@ -295,13 +307,6 @@ public: void build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel); }; -struct MissingWipeTowerHandler : public WipeTowerHandler -{ - void visit(std::function) override {} - void visit(std::function) const override {} - void set_selection_predicate(std::function) override {} -}; - // Only a physical bed, non-zero bed index values are discarded. class PhysicalOnlyVBedHandler final : public VirtualBedHandler { @@ -368,29 +373,15 @@ public: class GridStriderVBedHandler: public VirtualBedHandler { - // This vbed handler defines a grid of virtual beds with a large number - // of columns so that it behaves as XStrider for regular cases. - // The goal is to handle objects residing at world coordinates - // not representable with scaled coordinates. Combining XStrider with - // YStrider takes care of the X and Y axis to be mapped into the physical - // bed's coordinate region (which is representable in scaled coords) - static const int Cols; - static const int HalfCols; - static const int Offset; - XStriderVBedHandler m_xstrider; YStriderVBedHandler m_ystrider; public: - GridStriderVBedHandler(const BoundingBox &bedbb, - coord_t gap) - : m_xstrider{bedbb, gap} - , m_ystrider{bedbb, gap} + GridStriderVBedHandler(const BoundingBox &bedbb, const BedsGrid::Gap &gap) + : m_xstrider{bedbb, gap.x()} + , m_ystrider{bedbb, gap.y()} {} - Vec2i raw2grid(int bedidx) const; - int grid2raw(const Vec2i &crd) const; - int get_bed_index(const VBedPlaceable &obj) const override; bool assign_bed(VBedPlaceable &inst, int bed_idx) override; @@ -451,13 +442,15 @@ class ArrangeableModelInstance : public Arrangeable, VBedPlaceable VBedHPtr *m_vbedh; const SelectionMask *m_selmask; InstPos m_pos_within_model; + std::optional m_bed_constraint; public: explicit ArrangeableModelInstance(InstPtr *mi, VBedHPtr *vbedh, const SelectionMask *selmask, - const InstPos &pos) - : m_mi{mi}, m_vbedh{vbedh}, m_selmask{selmask}, m_pos_within_model{pos} + const InstPos &pos, + const std::optional bed_constraint) + : m_mi{mi}, m_vbedh{vbedh}, m_selmask{selmask}, m_pos_within_model{pos}, m_bed_constraint(bed_constraint) { assert(m_mi != nullptr && m_vbedh != nullptr); } @@ -474,6 +467,8 @@ public: int get_bed_index() const override { return m_vbedh->get_bed_index(*this); } bool assign_bed(int bed_idx) override; + std::optional bed_constraint() const override { return m_bed_constraint; } + // VBedPlaceable: BoundingBoxf bounding_box() const override { return to_2d(instance_bounding_box(*m_mi)); } void displace(const Vec2d &transl, double rot) override diff --git a/src/libslic3r/Arrange/SegmentedRectangleBed.hpp b/src/libslic3r/Arrange/SegmentedRectangleBed.hpp index 08778911af..33a1ca60c9 100644 --- a/src/libslic3r/Arrange/SegmentedRectangleBed.hpp +++ b/src/libslic3r/Arrange/SegmentedRectangleBed.hpp @@ -20,14 +20,16 @@ template struct SegmentedRectangleBed { Vec<2, size_t> segments = Vec<2, size_t>::Ones(); BoundingBox bb; + BedsGrid::Gap gap; RectPivots pivot = RectPivots::Center; SegmentedRectangleBed() = default; SegmentedRectangleBed(const BoundingBox &bb, size_t segments_x, size_t segments_y, + const BedsGrid::Gap &gap, const RectPivots pivot = RectPivots::Center) - : segments{segments_x, segments_y}, bb{bb}, pivot{pivot} + : segments{segments_x, segments_y}, bb{bb}, gap{gap}, pivot{pivot} {} size_t segments_x() const noexcept { return segments.x(); } @@ -41,13 +43,16 @@ struct SegmentedRectangleBed, std::integral_constant> { BoundingBox bb; + BedsGrid::Gap gap; RectPivots pivot = RectPivots::Center; SegmentedRectangleBed() = default; explicit SegmentedRectangleBed(const BoundingBox &b, + const BedsGrid::Gap &gap, const RectPivots pivot = RectPivots::Center) - : bb{b} + : bb{b}, + gap{gap} {} size_t segments_x() const noexcept { return SegX; } @@ -62,10 +67,11 @@ struct SegmentedRectangleBed, std::integral_constant> { BoundingBox bb; + BedsGrid::Gap gap; SegmentedRectangleBed() = default; - explicit SegmentedRectangleBed(const BoundingBox &b) : bb{b} {} + explicit SegmentedRectangleBed(const BoundingBox &b, const BedsGrid::Gap &gap) : bb{b}, gap{gap} {} size_t segments_x() const noexcept { return SegX; } size_t segments_y() const noexcept { return SegY; } @@ -92,6 +98,12 @@ auto bounding_box(const SegmentedRectangleBed &bed) return bed.bb; } +template +auto bed_gap(const SegmentedRectangleBed &bed) +{ + return bed.gap; +} + template auto area(const SegmentedRectangleBed &bed) { diff --git a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp index aedc8fb71b..17a4da3125 100644 --- a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp @@ -10,6 +10,8 @@ #include #include "ArrangeTask.hpp" +#include "libslic3r/Arrange/Items/ArrangeItem.hpp" +#include "libslic3r/SVG.hpp" namespace Slic3r { namespace arr2 { @@ -42,12 +44,6 @@ void extract_selected(ArrangeTask &task, << "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what(); } }); - - // If the selection was empty arrange everything - if (task.printable.selected.empty() && task.unprintable.selected.empty()) { - task.printable.selected.swap(task.printable.unselected); - task.unprintable.selected.swap(task.unprintable.unselected); - } } template diff --git a/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp index a17a2a76e2..666210f5cf 100644 --- a/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp @@ -21,8 +21,8 @@ int calculate_items_needed_to_fill_bed(const ExtendedBed &bed, { double poly_area = fixed_area(prototype_item); - auto area_sum_fn = [](double s, const auto &itm) { - return s + (get_bed_index(itm) == 0) * fixed_area(itm); + auto area_sum_fn = [&](double s, const auto &itm) { + return s + (get_bed_index(itm) == get_bed_constraint(prototype_item)) * fixed_area(itm); }; double unsel_area = std::accumulate(fixed.begin(), @@ -82,20 +82,24 @@ void extract(FillBedTask &task, prototype_item_shrinked = itm_conv.convert(arrbl, -SCALED_EPSILON); }); + const int bed_constraint{get_bed_index(*task.prototype_item)}; + set_bed_constraint(*task.prototype_item, bed_constraint); set_bed_index(*task.prototype_item, Unarranged); auto collect_task_items = [&prototype_geometry_id, &task, - &itm_conv](const Arrangeable &arrbl) { + &itm_conv, &bed_constraint](const Arrangeable &arrbl) { try { - if (arrbl.geometry_id() == prototype_geometry_id) { - if (arrbl.is_printable()) { - auto itm = itm_conv.convert(arrbl); - raise_priority(itm); - task.selected.emplace_back(std::move(itm)); + if (arrbl.bed_constraint() == bed_constraint) { + if (arrbl.geometry_id() == prototype_geometry_id) { + if (arrbl.is_printable()) { + auto itm = itm_conv.convert(arrbl); + raise_priority(itm); + task.selected.emplace_back(std::move(itm)); + } + } else { + auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON); + task.unselected.emplace_back(std::move(itm)); } - } else { - auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON); - task.unselected.emplace_back(std::move(itm)); } } catch (const EmptyItemOutlineError &ex) { BOOST_LOG_TRIVIAL(error) @@ -170,7 +174,7 @@ std::unique_ptr FillBedTask::process_native( void on_packed(ArrItem &itm) override { // Stop at the first filler that is not on the physical bed - do_stop = get_bed_index(itm) > PhysicalBedId && get_priority(itm) == 0; + do_stop = get_bed_index(itm) > get_bed_constraint(itm) && get_priority(itm) == 0; } } subctl(ctl, *this); @@ -194,12 +198,13 @@ std::unique_ptr FillBedTask::process_native( auto to_add_range = Range{selected.begin() + selected_existing_count, selected.end()}; - for (auto &itm : to_add_range) - if (get_bed_index(itm) == PhysicalBedId) + for (auto &itm : to_add_range) { + if (get_bed_index(itm) == get_bed_constraint(itm)) result->add_new_item(itm); + } for (auto &itm : selected_fillers) - if (get_bed_index(itm) == PhysicalBedId) + if (get_bed_index(itm) == get_bed_constraint(itm)) result->add_new_item(itm); return result; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 67c261dcba..6719adb054 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -130,6 +130,16 @@ const ModelWipeTower& Model::wipe_tower() const return wipe_tower_vector[s_multiple_beds.get_active_bed()]; } +const ModelWipeTower& Model::wipe_tower(const int bed_index) const +{ + return wipe_tower_vector[bed_index]; +} + +ModelWipeTower& Model::wipe_tower(const int bed_index) +{ + return wipe_tower_vector[bed_index]; +} + CustomGCode::Info& Model::custom_gcode_per_print_z() { return const_cast(const_cast(this)->custom_gcode_per_print_z()); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 2fad659305..d110beece2 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1288,9 +1288,10 @@ public: ModelWipeTower& wipe_tower(); const ModelWipeTower& wipe_tower() const; + const ModelWipeTower& wipe_tower(const int bed_index) const; + ModelWipeTower& wipe_tower(const int bed_index); std::vector& get_wipe_tower_vector() { return wipe_tower_vector; } const std::vector& get_wipe_tower_vector() const { return wipe_tower_vector; } - CustomGCode::Info& custom_gcode_per_print_z(); const CustomGCode::Info& custom_gcode_per_print_z() const; diff --git a/src/libslic3r/MultipleBeds.cpp b/src/libslic3r/MultipleBeds.cpp index 2dd727e0aa..f617b7715a 100644 --- a/src/libslic3r/MultipleBeds.cpp +++ b/src/libslic3r/MultipleBeds.cpp @@ -12,17 +12,86 @@ MultipleBeds s_multiple_beds; bool s_reload_preview_after_switching_beds = false; bool s_beds_just_switched = false; +namespace BedsGrid { +Index grid_coords_abs2index(GridCoords coords) { + coords = {std::abs(coords.x()), std::abs(coords.y())}; + const int x{coords.x() + 1}; + const int y{coords.y() + 1}; + const int a{std::max(x, y)}; + + if (x == a && y == a) { + return a*a - 1; + } else if (x == a) { + return a*a - 2 * (a - 1) + coords.y() - 1; + } else { + assert(y == a); + return a*a - (a - 1) + coords.x() - 1; + } +} + +const int quadrant_offset{std::numeric_limits::max() / 4}; + +Index grid_coords2index(const GridCoords &coords) { + const int index{grid_coords_abs2index(coords)}; + + if (index >= quadrant_offset) { + throw std::runtime_error("Object is too far from center!"); + } + + if (coords.x() >= 0 && coords.y() >= 0) { + return index; + } else if (coords.x() >= 0 && coords.y() < 0) { + return quadrant_offset + index; + } else if (coords.x() < 0 && coords.y() >= 0) { + return 2*quadrant_offset + index; + } else { + return 3*quadrant_offset + index; + } +} + +GridCoords index2grid_coords(Index index) { + if (index < 0) { + throw std::runtime_error{"Negative bed index cannot be translated to coords!"}; + } + + const int quadrant{index / quadrant_offset}; + index = index % quadrant_offset; + + GridCoords result{GridCoords::Zero()}; + if (index == 0) { + return result; + } + + int id = index; + ++id; + int a = 1; + while ((a+1)*(a+1) < id) + ++a; + id = id - a*a; + result.x()=a; + result.y()=a; + if (id <= a) + result.y() = id-1; + else + result.x() = id-a-1; + + if (quadrant == 1) { + result.y() = -result.y(); + } else if (quadrant == 2) { + result.x() = -result.x(); + } else if (quadrant == 3) { + result.y() = -result.y(); + result.x() = -result.x(); + } else if (quadrant != 0){ + throw std::runtime_error{"Impossible bed index > max int!"}; + } + return result; +} +} Vec3d MultipleBeds::get_bed_translation(int id) const { - // The x value is bed gap as multiples of the actual printable area bounding box, - // so it can be matched to how the old slicer arranged things (in SceneBuilder.cpp). - // The y value is a multiple of the larger of printable area BB and bed model BB - - // this is to make sure that the bed models do not overlap. - const double bed_gap_x = 2./10; - const double bed_gap_y = 2./10; - if (id == 0) return Vec3d::Zero(); int x = 0; @@ -30,28 +99,13 @@ Vec3d MultipleBeds::get_bed_translation(int id) const if (m_layout_linear) x = id; else { - // Grid layout. - ++id; - int a = 1; - while ((a+1)*(a+1) < id) - ++a; - id = id - a*a; - x=a; - y=a; - if (id <= a) - y = id-1; - else - x=id-a-1; + BedsGrid::GridCoords coords{BedsGrid::index2grid_coords(id)}; + x = coords.x(); + y = coords.y(); } return Vec3d(x * m_build_volume_bb_incl_model.size().x() * (1. + bed_gap_x), y * m_build_volume_bb_incl_model.size().y() * (1. + bed_gap_y), 0.); } - - - - - - void MultipleBeds::clear_inst_map() { m_inst_to_bed.clear(); @@ -137,6 +191,21 @@ bool MultipleBeds::is_glvolume_on_thumbnail_bed(const Model& model, int obj_idx, return (m_bed_for_thumbnails_generation < 0 || it->second == m_bed_for_thumbnails_generation); } +void MultipleBeds::update_shown_beds(Model& model, const BuildVolume& build_volume) { + const int original_number_of_beds = m_number_of_beds; + const int stash_active = get_active_bed(); + m_number_of_beds = get_max_beds(); + model.update_print_volume_state(build_volume); + const int max_bed{std::accumulate( + this->m_inst_to_bed.begin(), this->m_inst_to_bed.end(), 0, + [](const int max_so_far, const std::pair &value){ + return std::max(max_so_far, value.second); + } + )}; + m_number_of_beds = std::min(this->get_max_beds(), max_bed + 1); + model.update_print_volume_state(build_volume); + set_active_bed(m_number_of_beds != original_number_of_beds ? 0 : stash_active); +} // Beware! This function is also needed for proper update of bed when normal grid project is loaded! bool MultipleBeds::update_after_load_or_arrange(Model& model, const BuildVolume& build_volume, std::function update_fn) @@ -214,6 +283,12 @@ bool MultipleBeds::update_after_load_or_arrange(Model& model, const BuildVolume& } +BedsGrid::Gap MultipleBeds::get_bed_gap() const { + const Vec2d size_with_gap{ + m_build_volume_bb_incl_model.size().cwiseProduct( + Vec2d::Ones() + Vec2d{bed_gap_x, bed_gap_y})}; + return scaled(Vec2d{(size_with_gap - m_build_volume_bb.size()) / 2.0}); +}; void MultipleBeds::ensure_wipe_towers_on_beds(Model& model, const std::vector>& prints) { diff --git a/src/libslic3r/MultipleBeds.hpp b/src/libslic3r/MultipleBeds.hpp index 1d341b21c5..3a6c32ba43 100644 --- a/src/libslic3r/MultipleBeds.hpp +++ b/src/libslic3r/MultipleBeds.hpp @@ -17,6 +17,14 @@ class Print; extern bool s_reload_preview_after_switching_beds; extern bool s_beds_just_switched; +namespace BedsGrid { +using GridCoords = Vec2crd; +using Index = int; +using Gap = Vec2crd; +Index grid_coords2index(const GridCoords &coords); +GridCoords index2grid_coords(Index index); +} + class MultipleBeds { public: MultipleBeds() = default; @@ -27,13 +35,14 @@ public: void clear_inst_map(); void set_instance_bed(ObjectID id, int bed_idx); void inst_map_updated(); + const std::map &get_inst_map() const { return m_inst_to_bed; } int get_number_of_beds() const { return m_number_of_beds; } bool should_show_next_bed() const { return m_show_next_bed; } void request_next_bed(bool show); int get_active_bed() const { return m_active_bed; } - + void set_active_bed(int i); void move_active_to_first_bed(Model& model, const BuildVolume& build_volume, bool to_or_from) const; @@ -44,21 +53,21 @@ public: void set_last_hovered_bed(int i) { m_last_hovered_bed = i; } int get_last_hovered_bed() const { return m_last_hovered_bed; } + void update_shown_beds(Model& model, const BuildVolume& build_volume); bool update_after_load_or_arrange(Model& model, const BuildVolume& build_volume, std::function update_fn); void set_loading_project_flag(bool project) { m_loading_project = project; } bool get_loading_project_flag() const { return m_loading_project; } - void update_build_volume(const BoundingBoxf& build_volume_bb, const BoundingBoxf& build_volume_bb_incl_model) { m_build_volume_bb = build_volume_bb; m_build_volume_bb_incl_model = build_volume_bb_incl_model; } + void update_build_volume(const BoundingBoxf& build_volume_bb, const BoundingBoxf& build_volume_bb_incl_model) { + m_build_volume_bb = build_volume_bb; + m_build_volume_bb_incl_model = build_volume_bb_incl_model; + } + BedsGrid::Gap get_bed_gap() const; void ensure_wipe_towers_on_beds(Model& model, const std::vector>& prints); - - - private: bool is_instance_on_active_bed(ObjectID id) const; - - int m_number_of_beds = 1; int m_active_bed = 0; int m_bed_for_thumbnails_generation = -1; @@ -70,13 +79,17 @@ private: BoundingBoxf m_build_volume_bb_incl_model; bool m_layout_linear = false; bool m_loading_project = false; + + // The x value is bed gap as multiples of the actual printable area bounding box, + // so it can be matched to how the old slicer arranged things (in SceneBuilder.cpp). + // The y value is a multiple of the larger of printable area BB and bed model BB - + // this is to make sure that the bed models do not overlap. + const double bed_gap_x = 2./10; + const double bed_gap_y = 2./10; + }; extern MultipleBeds s_multiple_beds; - - - - } // namespace Slic3r #endif // libslic3r_MultipleBeds_hpp_ diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 0da4491bcd..4ecf572ab5 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -248,7 +248,6 @@ GLVolume::GLVolume(float r, float g, float b, float a) , is_outside(false) , hover(HS_None) , is_modifier(false) - , is_wipe_tower(false) , is_extrusion_path(false) , force_native_color(false) , force_neutral_color(false) @@ -500,11 +499,11 @@ int GLVolumeCollection::load_object_volume( } #if SLIC3R_OPENGL_ES -int GLVolumeCollection::load_wipe_tower_preview( +GLVolume* GLVolumeCollection::load_wipe_tower_preview( float pos_x, float pos_y, float width, float depth, const std::vector>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, size_t idx, TriangleMesh* out_mesh) #else -int GLVolumeCollection::load_wipe_tower_preview( +GLVolume* GLVolumeCollection::load_wipe_tower_preview( float pos_x, float pos_y, float width, float depth, const std::vector>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, size_t idx) #endif // SLIC3R_OPENGL_ES @@ -591,9 +590,8 @@ int GLVolumeCollection::load_wipe_tower_preview( mesh.merge(cone_mesh); } - - volumes.emplace_back(new GLVolume(color)); - GLVolume& v = *volumes.back(); + GLVolume* result{new GLVolume(color)}; + GLVolume& v = *result; #if SLIC3R_OPENGL_ES if (out_mesh != nullptr) *out_mesh = mesh; @@ -607,9 +605,10 @@ int GLVolumeCollection::load_wipe_tower_preview( v.composite_id = GLVolume::CompositeID(INT_MAX - idx, 0, 0); v.geometry_id.first = 0; v.geometry_id.second = wipe_tower_instance_id(idx).id; - v.is_wipe_tower = true; + v.wipe_tower_bed_index = idx; v.shader_outside_printer_detection_enabled = !size_unknown; - return int(volumes.size() - 1); + + return result; } // Load SLA auxiliary GLVolumes (for support trees or pad). @@ -803,7 +802,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab const int obj_idx = volume.first->object_idx(); const int vol_idx = volume.first->volume_idx(); const bool render_as_mmu_painted = is_render_as_mmu_painted_enabled && !volume.first->selected && - !volume.first->is_outside && volume.first->hover == GLVolume::HS_None && !volume.first->is_wipe_tower && obj_idx >= 0 && vol_idx >= 0 && + !volume.first->is_outside && volume.first->hover == GLVolume::HS_None && !volume.first->is_wipe_tower() && obj_idx >= 0 && vol_idx >= 0 && !model_objects[obj_idx]->volumes[vol_idx]->mm_segmentation_facets.empty() && type != GLVolumeCollection::ERenderType::Transparent; // to filter out shells (not very nice) volume.first->set_render_color(true); @@ -882,7 +881,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab shader->set_uniform("print_volume.xy_data", m_print_volume.data); shader->set_uniform("print_volume.z_data", m_print_volume.zs); shader->set_uniform("volume_world_matrix", world_matrix); - shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower); + shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower()); shader->set_uniform("slope.volume_world_normal_matrix", static_cast(world_matrix_inv_transp.cast())); shader->set_uniform("slope.normal_z", m_slope.normal_z); @@ -1012,7 +1011,7 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con } for (GLVolume* volume : volumes) { - if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->is_sla_pad() || volume->is_sla_support()) + if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower() || volume->is_sla_pad() || volume->is_sla_support()) continue; int extruder_id = volume->extruder_id - 1; diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 05d3bb306d..8abed48747 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -189,8 +189,6 @@ public: bool is_outside : 1; // Wheter or not this volume has been generated from a modifier bool is_modifier : 1; - // Wheter or not this volume has been generated from the wipe tower - bool is_wipe_tower : 1; // Wheter or not this volume has been generated from an extrusion path bool is_extrusion_path : 1; // Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE) @@ -216,6 +214,13 @@ public: // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer. std::vector offsets; + std::optional wipe_tower_bed_index; + + // Wheter or not this volume has been generated from the wipe tower + bool is_wipe_tower() const { + return bool{wipe_tower_bed_index}; + } + // Bounding box of this volume, in unscaled coordinates. BoundingBoxf3 bounding_box() const { return this->model.get_bounding_box(); @@ -425,10 +430,10 @@ public: int instance_idx); #if SLIC3R_OPENGL_ES - int load_wipe_tower_preview( + GLVolume* load_wipe_tower_preview( float pos_x, float pos_y, float width, float depth, const std::vector>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, size_t idx, TriangleMesh* out_mesh = nullptr); #else - int load_wipe_tower_preview( + GLVolume* load_wipe_tower_preview( float pos_x, float pos_y, float width, float depth, const std::vector>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, size_t idx); #endif // SLIC3R_OPENGL_ES diff --git a/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp b/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp index 16b97575c5..c294fd0ecd 100644 --- a/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp +++ b/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp @@ -34,7 +34,7 @@ ArrangeSettingsDialogImgui::ArrangeSettingsDialogImgui( : m_imgui{imgui}, m_db{std::move(db)} {} -void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y) +void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y, bool current_bed) { assert(m_imgui && m_db); @@ -131,9 +131,12 @@ void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y) ImGui::SameLine(); - if (ImGuiPureWrap::button(_u8L("Arrange")) && m_on_arrange_btn) { + if (!current_bed && ImGuiPureWrap::button(_u8L("Arrange")) && m_on_arrange_btn) { m_on_arrange_btn(); } + if (current_bed && ImGuiPureWrap::button(_u8L("Arrange bed")) && m_on_arrange_bed_btn) { + m_on_arrange_bed_btn(); + } ImGuiPureWrap::end(); } diff --git a/src/slic3r/GUI/ArrangeSettingsDialogImgui.hpp b/src/slic3r/GUI/ArrangeSettingsDialogImgui.hpp index b7ac970089..107b700802 100644 --- a/src/slic3r/GUI/ArrangeSettingsDialogImgui.hpp +++ b/src/slic3r/GUI/ArrangeSettingsDialogImgui.hpp @@ -17,6 +17,7 @@ class ArrangeSettingsDialogImgui: public arr2::ArrangeSettingsView { AnyPtr m_db; std::function m_on_arrange_btn; + std::function m_on_arrange_bed_btn; std::function m_on_reset_btn; std::function m_show_xl_combo_predicate = [] { return true; }; @@ -24,7 +25,7 @@ class ArrangeSettingsDialogImgui: public arr2::ArrangeSettingsView { public: ArrangeSettingsDialogImgui(ImGuiWrapper *imgui, AnyPtr db); - void render(float pos_x, float pos_y); + void render(float pos_x, float pos_y, bool current_bed); void show_xl_align_combo(std::function pred) { @@ -36,6 +37,11 @@ public: m_on_arrange_btn = on_arrangefn; } + void on_arrange_bed_btn(std::function on_arrangefn) + { + m_on_arrange_bed_btn = on_arrangefn; + } + void on_reset_btn(std::function on_resetfn) { m_on_reset_btn = on_resetfn; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index e3191193a1..8db4c200b5 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1698,9 +1698,9 @@ void GCodeViewer::load_wipetower_shell(const Print& print) const std::vector> z_and_depth_pairs = print.wipe_tower_data(extruders_count).z_and_depth_pairs; const float brim_width = wipe_tower_data.brim_width; if (depth != 0.) { - m_shells.volumes.load_wipe_tower_preview(wxGetApp().plater()->model().wipe_tower().position.x(), wxGetApp().plater()->model().wipe_tower().position.y(), config.wipe_tower_width, depth, z_and_depth_pairs, - max_z, config.wipe_tower_cone_angle, wxGetApp().plater()->model().wipe_tower().rotation, false, brim_width, 0); - GLVolume* volume = m_shells.volumes.volumes.back(); + GLVolume* volume{m_shells.volumes.load_wipe_tower_preview(wxGetApp().plater()->model().wipe_tower().position.x(), wxGetApp().plater()->model().wipe_tower().position.y(), config.wipe_tower_width, depth, z_and_depth_pairs, + max_z, config.wipe_tower_cone_angle, wxGetApp().plater()->model().wipe_tower().rotation, false, brim_width, 0)}; + m_shells.volumes.volumes.emplace_back(volume); volume->color.a(0.25f); volume->force_native_color = true; volume->set_render_color(true); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 86252dcb41..1537c84dbe 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1032,6 +1032,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent); wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE_CURRENT_BED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event); @@ -1318,7 +1319,18 @@ PrinterTechnology GLCanvas3D::current_printer_technology() const bool GLCanvas3D::is_arrange_alignment_enabled() const { - return m_config ? is_XL_printer(*m_config) && !this->get_wipe_tower_info() : false; + if (m_config == nullptr) { + return false; + } + if (!is_XL_printer(*m_config)) { + return false; + } + for (const WipeTowerInfo &wti : this->get_wipe_tower_infos()) { + if (bool{wti}) { + return false; + } + } + return true; } GLCanvas3D::GLCanvas3D(wxGLCanvas *canvas, Bed3D &bed) @@ -1371,6 +1383,9 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas *canvas, Bed3D &bed) m_arrange_settings_dialog.on_arrange_btn([]{ wxGetApp().plater()->arrange(); }); + m_arrange_settings_dialog.on_arrange_bed_btn([]{ + wxGetApp().plater()->arrange_current_bed(); + }); } GLCanvas3D::~GLCanvas3D() @@ -1491,7 +1506,7 @@ bool GLCanvas3D::check_volumes_outside_state(GLVolumeCollection& volumes, ModelI const std::vector volumes_idxs = volumes_to_process_idxs(); for (unsigned int vol_idx : volumes_idxs) { GLVolume* volume = volumes.volumes[vol_idx]; - if (!volume->is_modifier && (volume->shader_outside_printer_detection_enabled || (!volume->is_wipe_tower && volume->composite_id.volume_id >= 0))) { + if (!volume->is_modifier && (volume->shader_outside_printer_detection_enabled || (!volume->is_wipe_tower() && volume->composite_id.volume_id >= 0))) { BuildVolume::ObjectState state; int bed_idx = -1; if (volume_below(*volume)) @@ -1571,7 +1586,7 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject { std::vector>* raycasters = get_raycasters_for_picking(SceneRaycaster::EType::Volume); for (GLVolume* vol : m_volumes.volumes) { - if (vol->is_wipe_tower) + if (vol->is_wipe_tower()) vol->is_active = (visible && mo == nullptr); else { if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) @@ -2283,7 +2298,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re instance_ids_selected.emplace_back(volume->geometry_id.second); if (mvs == nullptr || force_full_scene_refresh) { // This GLVolume will be released. - if (volume->is_wipe_tower) { + if (volume->is_wipe_tower()) { #if SLIC3R_OPENGL_ES m_wipe_tower_meshes.clear(); #endif // SLIC3R_OPENGL_ES @@ -2454,9 +2469,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re const float ca = dynamic_cast(m_config->option("wipe_tower_cone_angle"))->value; if (extruders_count > 1 && wt && !co) { - - - for (size_t bed_idx = 0; bed_idx < s_multiple_beds.get_number_of_beds(); ++bed_idx) { + for (size_t bed_idx = 0; bed_idx < s_multiple_beds.get_max_beds(); ++bed_idx) { const Print *print = wxGetApp().plater()->get_fff_prints()[bed_idx].get(); const float x = m_model->get_wipe_tower_vector()[bed_idx].position.x(); @@ -2471,24 +2484,36 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re const double height = height_real < 0.f ? std::max(m_model->max_z(), 10.0) : height_real; if (depth != 0.) { #if SLIC3R_OPENGL_ES - if (bed_idx >= m_wipe_tower_meshes.size()) - m_wipe_tower_meshes.resize(bed_idx + 1); - int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - x, y, w, depth, z_and_depth_pairs, (float)height, ca, a, !is_wipe_tower_step_done, - bw, bed_idx, &m_wipe_tower_meshes[bed_idx]); + if (bed_idx >= m_wipe_tower_meshes.size()) + m_wipe_tower_meshes.resize(bed_idx + 1); + GLVolume* volume = m_volumes.load_wipe_tower_preview( + x, y, w, depth, z_and_depth_pairs, (float)height, ca, a, !is_wipe_tower_step_done, + bw, bed_idx, &m_wipe_tower_meshes[bed_idx]); #else - int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - x, y, w, depth, z_and_depth_pairs, (float)height, ca, a, !is_wipe_tower_step_done, - bw, bed_idx); + GLVolume* volume = m_volumes.load_wipe_tower_preview( + x, y, w, depth, z_and_depth_pairs, (float)height, ca, a, !is_wipe_tower_step_done, + bw, bed_idx); #endif // SLIC3R_OPENGL_ES + const BoundingBoxf3& bb = volume->bounding_box(); + m_wipe_tower_bounding_boxes[bed_idx] = BoundingBoxf{to_2d(bb.min), to_2d(bb.max)}; + if(bed_idx < s_multiple_beds.get_number_of_beds()) { + m_volumes.volumes.emplace_back(volume); + const auto volume_idx_wipe_tower_new{static_cast(m_volumes.volumes.size() - 1)}; auto it = volume_idxs_wipe_towers_old.find(m_volumes.volumes.back()->geometry_id.second); if (it != volume_idxs_wipe_towers_old.end()) map_glvolume_old_to_new[it->second] = volume_idx_wipe_tower_new; m_volumes.volumes.back()->set_volume_offset(m_volumes.volumes.back()->get_volume_offset() + s_multiple_beds.get_bed_translation(bed_idx)); + } else { + delete volume; } + } } s_multiple_beds.ensure_wipe_towers_on_beds(wxGetApp().plater()->model(), wxGetApp().plater()->get_fff_prints()); + } else { + m_wipe_tower_bounding_boxes.fill(std::nullopt); } + } else { + m_wipe_tower_bounding_boxes.fill(std::nullopt); } update_volumes_colors_by_extruder(); @@ -2917,6 +2942,8 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'b': { zoom_to_bed(); break; } case 'C': case 'c': { m_gcode_viewer.toggle_gcode_window_visibility(); m_dirty = true; request_extra_frame(); break; } + case 'D': + case 'd': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE_CURRENT_BED)); break; } case 'E': case 'e': { m_labels.show(!m_labels.is_shown()); m_dirty = true; break; } case 'G': @@ -3834,7 +3861,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (!m_hover_volume_idxs.empty()) { // if right clicking on volume, propagate event through callback (shows context menu) int volume_idx = get_first_hover_volume_idx(); - if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower + if (!m_volumes.volumes[volume_idx]->is_wipe_tower() // no context menu for the wipe tower && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && m_gizmos.get_current_type() != GLGizmosManager::Measure)) // disable context menu when the gizmo is open { // forces the selection of the volume @@ -3859,7 +3886,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (!m_mouse.dragging) { // do not post the event if the user is panning the scene // or if right click was done over the wipe tower - const bool post_right_click_event = (m_hover_volume_idxs.empty() || !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower) && + const bool post_right_click_event = (m_hover_volume_idxs.empty() || !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower()) && m_gizmos.get_current_type() != GLGizmosManager::Measure; if (post_right_click_event) post_event(RBtnEvent(EVT_GLCANVAS_RIGHT_CLICK, { logical_pos, m_hover_volume_idxs.empty() })); @@ -4020,7 +4047,7 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) model_object->invalidate_bounding_box(); } } - else if (m_selection.is_wipe_tower() && v->is_wipe_tower && m_selection.contains_volume(vol_id)) { + else if (m_selection.is_wipe_tower() && v->is_wipe_tower() && m_selection.contains_volume(vol_id)) { // Move a wipe tower proxy. for (size_t bed_idx = 0; bed_idx < s_multiple_beds.get_max_beds(); ++bed_idx) { if (v->geometry_id.second == wipe_tower_instance_id(bed_idx).id) { @@ -4101,7 +4128,7 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) for (const GLVolume* v : m_volumes.volumes) { ++v_id; - if (v->is_wipe_tower) { + if (v->is_wipe_tower()) { if (m_selection.contains_volume(v_id)) { for (size_t bed_idx = 0; bed_idx < s_multiple_beds.get_max_beds(); ++bed_idx) { if (v->geometry_id.second == wipe_tower_instance_id(bed_idx).id) { @@ -4424,22 +4451,23 @@ void GLCanvas3D::update_ui_from_settings() wxGetApp().plater()->enable_collapse_toolbar(wxGetApp().app_config->get_bool("show_collapse_button") || !wxGetApp().sidebar().IsShown()); } -GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const +std::vector GLCanvas3D::get_wipe_tower_infos() const { - WipeTowerInfo wti; - - for (const GLVolume* vol : m_volumes.volumes) { - if (vol->is_wipe_tower) { - wti.m_pos = Vec2d(m_model->wipe_tower().position.x(), - m_model->wipe_tower().position.y()); - wti.m_rotation = (M_PI/180.) * m_model->wipe_tower().rotation; - const BoundingBoxf3& bb = vol->bounding_box(); - wti.m_bb = BoundingBoxf{to_2d(bb.min), to_2d(bb.max)}; - break; + std::vector result; + + for (size_t bed_idx = 0; bed_idx < s_multiple_beds.get_max_beds(); ++bed_idx) { + if (m_wipe_tower_bounding_boxes[bed_idx]) { + const ModelWipeTower &wipe_tower{m_model->wipe_tower(bed_idx)}; + WipeTowerInfo wti; + wti.m_pos = Vec2d(wipe_tower.position.x(), wipe_tower.position.y()); + wti.m_rotation = (M_PI/180.) * wipe_tower.rotation; + wti.m_bb = *m_wipe_tower_bounding_boxes[bed_idx]; + wti.m_bed_index = bed_idx; + result.push_back(std::move(wti)); } } - - return wti; + + return result; } Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) @@ -4536,7 +4564,7 @@ void GLCanvas3D::update_sequential_clearance(bool force_contours_generation) // second: fill temporary cache with data from volumes for (const GLVolume* v : m_volumes.volumes) { - if (v->is_wipe_tower) + if (v->is_wipe_tower()) continue; const int object_idx = v->object_idx(); @@ -4713,9 +4741,9 @@ bool GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) return action_taken; } -bool GLCanvas3D::_render_arrange_menu(float pos_x) +bool GLCanvas3D::_render_arrange_menu(float pos_x, bool current_bed) { - m_arrange_settings_dialog.render(pos_x, m_main_toolbar.get_height()); + m_arrange_settings_dialog.render(pos_x, m_main_toolbar.get_height(), current_bed); return true; } @@ -4730,7 +4758,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const GLVolumePtrs visible_volumes; for (GLVolume* vol : volumes.volumes) { - if (!vol->is_modifier && !vol->is_wipe_tower && (!thumbnail_params.parts_only || vol->composite_id.volume_id >= 0)) { + if (!vol->is_modifier && !vol->is_wipe_tower() && (!thumbnail_params.parts_only || vol->composite_id.volume_id >= 0)) { if (!thumbnail_params.printable_only || is_visible(*vol)) { if (s_multiple_beds.is_glvolume_on_thumbnail_bed(wxGetApp().model(), vol->composite_id.object_id, vol->composite_id.instance_id)) visible_volumes.emplace_back(vol); @@ -5161,7 +5189,23 @@ bool GLCanvas3D::_init_main_toolbar() item.right.toggable = true; item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) - _render_arrange_menu(0.5f * (left + right)); + _render_arrange_menu(0.5f * (left + right), false); + }; + if (!m_main_toolbar.add_item(item)) + return false; + + item.name = "arrangecurrent"; + item.icon_filename = "arrange_current.svg"; + item.tooltip = + _u8L("Arrange current bed") + " [D]\n" + + _u8L("Arrange selection on current bed") + " [Shift+D]\n"; + item.sprite_id = sprite_id++; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE_CURRENT_BED)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; + item.right.toggable = true; + item.right.render_callback = [this](float left, float right, float, float) { + if (m_canvas != nullptr) + _render_arrange_menu(0.5f * (left + right), true); }; if (!m_main_toolbar.add_item(item)) return false; @@ -6980,6 +7024,11 @@ bool GLCanvas3D::_deactivate_arrange_menu() return true; } + if (m_main_toolbar.is_item_pressed("arrangecurrent")) { + m_main_toolbar.force_right_action(m_main_toolbar.get_item_id("arrangecurrent"), *this); + return true; + } + return false; } @@ -7022,10 +7071,10 @@ const SLAPrint* GLCanvas3D::sla_print() const return (m_process == nullptr) ? nullptr : m_process->sla_print(); } -void GLCanvas3D::WipeTowerInfo::apply_wipe_tower(Vec2d pos, double rot) +void GLCanvas3D::WipeTowerInfo::apply_wipe_tower(Vec2d pos, double rot, int bed_index) { - wxGetApp().plater()->model().wipe_tower().position = pos; - wxGetApp().plater()->model().wipe_tower().rotation = (180. / M_PI) * rot; + wxGetApp().plater()->model().wipe_tower(bed_index).position = pos; + wxGetApp().plater()->model().wipe_tower(bed_index).rotation = (180. / M_PI) * rot; } void GLCanvas3D::RenderTimer::Notify() diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 61a42ae5fb..db8067ff93 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -158,6 +158,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent); wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE_CURRENT_BED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event); // data: +1 => increase, -1 => decrease @@ -512,6 +513,8 @@ private: #if SLIC3R_OPENGL_ES std::vector m_wipe_tower_meshes; #endif // SLIC3R_OPENGL_ES + std::array, MAX_NUMBER_OF_BEDS> m_wipe_tower_bounding_boxes; + GCodeViewer m_gcode_viewer; RenderTimer m_render_timer; @@ -904,28 +907,30 @@ public: int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; } int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } void set_selected_extruder(int extruder) { m_selected_extruder = extruder;} - + class WipeTowerInfo { protected: Vec2d m_pos = {NaNd, NaNd}; double m_rotation = 0.; BoundingBoxf m_bb; + int m_bed_index{0}; friend class GLCanvas3D; - public: + public: inline operator bool() const { return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y()); } - + 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(); } inline const BoundingBoxf& bounding_box() const { return m_bb; } + inline const int bed_index() const { return m_bed_index; } - static void apply_wipe_tower(Vec2d pos, double rot); + static void apply_wipe_tower(Vec2d pos, double rot, int bed_index); }; - - WipeTowerInfo get_wipe_tower_info() const; + + std::vector get_wipe_tower_infos() const; // Returns the view ray line, in world coordinate, at the given mouse position. Linef3 mouse_ray(const Point& mouse_pos); @@ -1072,7 +1077,7 @@ private: void _render_sla_slices(); void _render_selection_sidebar_hints() { m_selection.render_sidebar_hints(m_sidebar_field); } bool _render_undo_redo_stack(const bool is_undo, float pos_x); - bool _render_arrange_menu(float pos_x); + bool _render_arrange_menu(float pos_x, bool current_bed); void _render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type); // render thumbnail using an off-screen framebuffer void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type); diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index b0bec8edce..db15614e2c 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -25,6 +25,7 @@ wxDEFINE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_ARRANGE_CURRENT_BED, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent); diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 5f6ddf6c88..c79fbfbea5 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -24,6 +24,7 @@ wxDECLARE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_ARRANGE_CURRENT_BED, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent); diff --git a/src/slic3r/GUI/Jobs/ArrangeJob2.cpp b/src/slic3r/GUI/Jobs/ArrangeJob2.cpp index d913d93b0d..f3829491c0 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob2.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob2.cpp @@ -28,9 +28,13 @@ class GUISelectionMask: public arr2::SelectionMask { public: explicit GUISelectionMask(const Selection *sel) : m_sel{sel} {} - bool is_wipe_tower() const override + bool is_wipe_tower_selected(int wipe_tower_index) const override { - return m_sel->is_wipe_tower(); + const GLVolume *volume{GUI::get_selected_gl_volume(*m_sel)}; + if (volume != nullptr && volume->wipe_tower_bed_index == wipe_tower_index) { + return true; + } + return false; } std::vector selected_objects() const override @@ -69,6 +73,91 @@ public: } }; +class BedSelectionMask: public arr2::SelectionMask { + const int m_bed_index; + std::vector> m_selected_instances; + std::vector m_selected_objects; + +public: + explicit BedSelectionMask(const int bed_index, const ModelObjectPtrs &objects, const std::set &instances_on_bed): + m_bed_index{bed_index}, + m_selected_instances(get_selected_instances(objects, instances_on_bed)), + m_selected_objects(get_selected_objects(this->m_selected_instances)) + {} + + bool is_wipe_tower_selected(int wipe_tower_index) const override + { + return wipe_tower_index == m_bed_index; + } + + std::vector selected_objects() const override + { + return this->m_selected_objects; + } + + std::vector selected_instances(int obj_id) const override { + return this->m_selected_instances[obj_id]; + } + +private: + static std::vector get_selected_objects( + const std::vector> &selected_instances + ) { + std::vector result; + + std::transform( + selected_instances.begin(), + selected_instances.end(), + std::back_inserter(result), + [&](const std::vector &object_selected_instances) { + return std::any_of( + object_selected_instances.begin(), + object_selected_instances.end(), + [](const bool is_selected){ return is_selected; } + ); + } + ); + + return result; + } + + std::vector get_selected_instances( + const ModelObject &object, + const std::set &instances_on_bed + ) { + std::vector result; + std::transform( + object.instances.begin(), + object.instances.end(), + std::back_inserter(result), + [&](const ModelInstance *instance) { + const auto instance_bed_index{instances_on_bed.find(instance->id())}; + if(instance_bed_index != instances_on_bed.end()) { + return true; + } + return false; + } + ); + return result; + } + + std::vector> get_selected_instances( + const ModelObjectPtrs &objects, + const std::set &instances_on_bed + ) { + std::vector> result; + std::transform( + objects.begin(), + objects.end(), + std::back_inserter(result), + [&](const ModelObject *object){ + return get_selected_instances(*object, instances_on_bed); + } + ); + return result; + } +}; + static Polygon get_wtpoly(const GLCanvas3D::WipeTowerInfo &wti) { @@ -97,9 +186,9 @@ class ArrangeableWT: public arr2::ArrangeableWipeTowerBase public: explicit ArrangeableWT(const ObjectID &oid, const GLCanvas3D::WipeTowerInfo &wti, - std::function sel_pred, + std::function sel_pred, const BoundingBox xl_bb = {}) - : arr2::ArrangeableWipeTowerBase{oid, get_wtpoly(wti), std::move(sel_pred)} + : arr2::ArrangeableWipeTowerBase{oid, get_wtpoly(wti), wti.bed_index(), std::move(sel_pred)} , m_orig_tr{wti.pos()} , m_orig_rot{wti.rotation()} , m_xl_bb{xl_bb} @@ -108,7 +197,7 @@ public: // Rotation is disabled for wipe tower in arrangement void transform(const Vec2d &transl, double /*rot*/) override { - GLCanvas3D::WipeTowerInfo::apply_wipe_tower(m_orig_tr + transl, m_orig_rot); + GLCanvas3D::WipeTowerInfo::apply_wipe_tower(m_orig_tr + transl, m_orig_rot, this->bed_index); } void imbue_data(arr2::AnyWritable &datastore) const override @@ -132,12 +221,12 @@ struct WTH : public arr2::WipeTowerHandler { GLCanvas3D::WipeTowerInfo wti; ObjectID oid; - std::function sel_pred; + std::function sel_pred; BoundingBox xl_bb; WTH(const ObjectID &objid, const GLCanvas3D::WipeTowerInfo &w, - std::function sel_predicate = [] { return false; }) + std::function sel_predicate = [](int){ return false; }) : wti(w), oid{objid}, sel_pred{std::move(sel_predicate)} {} @@ -158,39 +247,77 @@ struct WTH : public arr2::WipeTowerHandler visit_(*this, fn); } - void set_selection_predicate(std::function pred) override + void set_selection_predicate(std::function pred) override { sel_pred = std::move(pred); } + + ObjectID get_id() const override { + return this->oid; + } }; arr2::SceneBuilder build_scene(Plater &plater, ArrangeSelectionMode mode) { arr2::SceneBuilder builder; + const int current_bed{s_multiple_beds.get_active_bed()}; if (mode == ArrangeSelectionMode::SelectionOnly) { auto sel = std::make_unique(&plater.get_selection()); builder.set_selection(std::move(sel)); + } else if (mode == ArrangeSelectionMode::CurrentBedSelectionOnly) { + arr2::BedConstraints constraints; + for (const ModelObject *object : plater.model().objects) { + for (const ModelInstance *instance : object->instances) { + constraints.insert({instance->id(), current_bed}); + } + } + builder.set_bed_constraints(std::move(constraints)); + + auto gui_selection{std::make_unique(&plater.get_selection())}; + builder.set_selection(std::move(gui_selection)); + } else if (mode == ArrangeSelectionMode::CurrentBedFull) { + std::set instances_on_bed; + arr2::BedConstraints constraints; + for (const auto &instance_bed : s_multiple_beds.get_inst_map()) { + if (instance_bed.second == current_bed) { + instances_on_bed.emplace(instance_bed.first); + constraints.emplace(instance_bed); + } + } + builder.set_bed_constraints(std::move(constraints)); + + auto bed_selection{std::make_unique( + current_bed, + plater.model().objects, + instances_on_bed + )}; + builder.set_selection(std::move(bed_selection)); } builder.set_arrange_settings(plater.canvas3D()->get_arrange_settings_view()); - auto wti = plater.canvas3D()->get_wipe_tower_info(); + const auto wipe_tower_infos = plater.canvas3D()->get_wipe_tower_infos(); - AnyPtr wth; + std::vector> handlers; - if (wti) { - wth = std::make_unique(wipe_tower_instance_id(0), wti); - } - - if (plater.config()) { - builder.set_bed(*plater.config()); - if (wth && is_XL_printer(*plater.config())) { - wth->xl_bb = bounding_box(get_bed_shape(*plater.config())); + for (const auto &info : wipe_tower_infos) { + if (info) { + auto handler{std::make_unique(wipe_tower_instance_id(info.bed_index()), info)}; + if (plater.config() && is_XL_printer(*plater.config())) { + handler->xl_bb = bounding_box(get_bed_shape(*plater.config())); + } + handlers.push_back(std::move(handler)); } } - builder.set_wipe_tower_handler(std::move(wth)); + if (plater.config()) { + const BedsGrid::Gap gap{s_multiple_beds.get_bed_gap()}; + builder.set_bed(*plater.config(), gap); + } + + builder.set_wipe_tower_handlers(std::move(handlers)); + builder.set_model(plater.model()); if (plater.printer_technology() == ptSLA) diff --git a/src/slic3r/GUI/Jobs/ArrangeJob2.hpp b/src/slic3r/GUI/Jobs/ArrangeJob2.hpp index 7e4aba887c..0634382768 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob2.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob2.hpp @@ -27,7 +27,7 @@ namespace GUI { class Plater; -enum class ArrangeSelectionMode { SelectionOnly, Full }; +enum class ArrangeSelectionMode { SelectionOnly, Full, CurrentBedFull, CurrentBedSelectionOnly }; arr2::SceneBuilder build_scene( Plater &plater, ArrangeSelectionMode mode = ArrangeSelectionMode::Full); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e0cc2acbe9..7c2c9e2cab 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -705,6 +705,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this); view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); }); view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); }); + view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE_CURRENT_BED, [this](SimpleEvent&) { this->q->arrange_current_bed(); }); view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); }); view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event& evt) @@ -736,6 +737,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [this](SimpleEvent&) { delete_all_objects_from_model(); }); // view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE_CURRENT_BED, [this](SimpleEvent&) { this->q->arrange_current_bed(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); }); @@ -1091,6 +1093,7 @@ void Plater::priv::update(unsigned int flags) // Update the SLAPrint from the current Model, so that the reload_scene() // pulls the correct data. update_status = this->update_background_process(false, flags & (unsigned int)UpdateParams::POSTPONE_VALIDATION_ERROR_MESSAGE); + s_multiple_beds.update_shown_beds(model, q->build_volume()); this->view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH); this->preview->reload_print(); if (force_background_processing_restart) @@ -5410,7 +5413,7 @@ void Plater::fill_bed_with_instances() }; auto scene = arr2::Scene{ - build_scene(*this, ArrangeSelectionMode::SelectionOnly)}; + build_scene(*this, ArrangeSelectionMode::CurrentBedSelectionOnly)}; cbs.on_finished = [this](arr2::FillBedTaskResult &result) { auto [prototype_mi, pos] = arr2::find_instance_by_id(model(), result.prototype_id); @@ -5520,11 +5523,14 @@ void Plater::apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs& ne Selection& selection = p->get_selection(); size_t last_id = p->model.objects.size() - 1; - for (size_t i = 0; i < new_objects.size(); ++i) + for (size_t i = 0; i < new_objects.size(); ++i) { selection.add_object((unsigned int)(last_id - i), i == 0); + const ObjectID instance_id{p->model.objects[last_id - i]->instances.front()->id().id}; + s_multiple_beds.set_instance_bed(instance_id, s_multiple_beds.get_active_bed()); + } UIThreadWorker w; - arrange(w, true); + arrange(w, ArrangeSelectionMode::CurrentBedSelectionOnly); w.wait_for_idle(); } @@ -6737,18 +6743,33 @@ static std::string concat_strings(const std::set &strings, void Plater::arrange() { + const auto mode{ + wxGetKeyState(WXK_SHIFT) ? + ArrangeSelectionMode::SelectionOnly : + ArrangeSelectionMode::Full + }; + if (p->can_arrange()) { auto &w = get_ui_job_worker(); - arrange(w, wxGetKeyState(WXK_SHIFT)); + arrange(w, mode); } } -void Plater::arrange(Worker &w, bool selected) +void Plater::arrange_current_bed() { - ArrangeSelectionMode mode = selected ? - ArrangeSelectionMode::SelectionOnly : - ArrangeSelectionMode::Full; + const auto mode{ + wxGetKeyState(WXK_SHIFT) ? + ArrangeSelectionMode::CurrentBedSelectionOnly : + ArrangeSelectionMode::CurrentBedFull + }; + if (p->can_arrange()) { + auto &w = get_ui_job_worker(); + arrange(w, mode); + } +} +void Plater::arrange(Worker &w, const ArrangeSelectionMode &mode) +{ arr2::Scene arrscene{build_scene(*this, mode)}; ArrangeJob2::Callbacks cbs; @@ -6782,10 +6803,7 @@ void Plater::arrange(Worker &w, bool selected) concat_strings(names, "\n"))); } - s_multiple_beds.update_after_load_or_arrange(model(), build_volume(), [this]() { - canvas3D()->check_volumes_outside_state(); - s_multiple_beds.ensure_wipe_towers_on_beds(model(), get_fff_prints()); - }); + canvas3D()->check_volumes_outside_state(); update(static_cast(UpdateParams::FORCE_FULL_SCREEN_REFRESH)); wxGetApp().obj_manipul()->set_dirty(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index e489bb0bc0..cdd5a6bc78 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -64,6 +64,7 @@ struct Camera; class GLToolbar; class UserAccount; class PresetArchiveDatabase; +enum class ArrangeSelectionMode; class Plater: public wxPanel { @@ -285,9 +286,10 @@ public: GLCanvas3D* get_current_canvas3D(); void render_sliders(GLCanvas3D& canvas); - + void arrange(); - void arrange(Worker &w, bool selected); + void arrange_current_bed(); + void arrange(Worker &w, const ArrangeSelectionMode &selected); void set_current_canvas_as_dirty(); void unbind_canvas_event_handlers(); diff --git a/src/slic3r/GUI/SceneRaycaster.cpp b/src/slic3r/GUI/SceneRaycaster.cpp index 3a98c008a5..b3dac5ce46 100644 --- a/src/slic3r/GUI/SceneRaycaster.cpp +++ b/src/slic3r/GUI/SceneRaycaster.cpp @@ -117,7 +117,7 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came const Selection& selection = wxGetApp().plater()->get_selection(); if (selection.is_single_volume() || selection.is_single_modifier()) { const GLVolume* volume = selection.get_first_volume(); - if (!volume->is_wipe_tower && !volume->is_sla_pad() && !volume->is_sla_support()) + if (!volume->is_wipe_tower() && !volume->is_sla_pad() && !volume->is_sla_support()) m_selected_volume_id = *selection.get_volume_idxs().begin(); } } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 4cf5bdb566..361969aa39 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -148,7 +148,7 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec return; // wipe tower is already selected - if (is_wipe_tower() && volume->is_wipe_tower && contains_volume(volume_idx)) + if (is_wipe_tower() && volume->is_wipe_tower() && contains_volume(volume_idx)) return; bool keep_instance_mode = (m_mode == Instance) && !as_single_selection; @@ -156,8 +156,8 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec // resets the current list if needed bool needs_reset = as_single_selection && !already_contained; - needs_reset |= volume->is_wipe_tower; - needs_reset |= is_wipe_tower() && !volume->is_wipe_tower; + needs_reset |= volume->is_wipe_tower(); + needs_reset |= is_wipe_tower() && !volume->is_wipe_tower(); needs_reset |= as_single_selection && !is_any_modifier() && volume->is_modifier; needs_reset |= is_any_modifier() && !volume->is_modifier; @@ -381,7 +381,7 @@ void Selection::add_all() unsigned int count = 0; for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { - if (!(*m_volumes)[i]->is_wipe_tower) + if (!(*m_volumes)[i]->is_wipe_tower()) ++count; } @@ -394,7 +394,7 @@ void Selection::add_all() clear(); for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { - if (!(*m_volumes)[i]->is_wipe_tower) + if (!(*m_volumes)[i]->is_wipe_tower()) do_add_volume(i); } @@ -1445,7 +1445,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co if (done.size() == m_volumes->size()) break; - if ((*m_volumes)[i]->is_wipe_tower) + if ((*m_volumes)[i]->is_wipe_tower()) continue; const int object_idx = (*m_volumes)[i]->object_idx(); @@ -1919,7 +1919,7 @@ void Selection::update_type() m_type = Empty; else if (m_list.size() == 1) { const GLVolume* first = (*m_volumes)[*m_list.begin()]; - if (first->is_wipe_tower) + if (first->is_wipe_tower()) m_type = WipeTower; else if (first->is_modifier) { m_type = SingleModifier; @@ -2740,7 +2740,7 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ if (done.size() == m_volumes->size()) break; const GLVolume* volume_i = (*m_volumes)[i]; - if (volume_i->is_wipe_tower) + if (volume_i->is_wipe_tower()) continue; const int object_idx = volume_i->object_idx(); @@ -2785,7 +2785,7 @@ void Selection::synchronize_unselected_volumes() { for (unsigned int i : m_list) { const GLVolume* volume = (*m_volumes)[i]; - if (volume->is_wipe_tower) + if (volume->is_wipe_tower()) continue; const int object_idx = volume->object_idx(); @@ -2813,7 +2813,7 @@ void Selection::ensure_on_bed() for (size_t i = 0; i < m_volumes->size(); ++i) { GLVolume* volume = (*m_volumes)[i]; - if (!volume->is_wipe_tower && !volume->is_modifier && + if (!volume->is_wipe_tower() && !volume->is_modifier && std::find(m_cache.sinking_volumes.begin(), m_cache.sinking_volumes.end(), i) == m_cache.sinking_volumes.end()) { const double min_z = volume->transformed_convex_hull_bounding_box().min.z(); std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); @@ -2840,7 +2840,7 @@ void Selection::ensure_not_below_bed() for (size_t i = 0; i < m_volumes->size(); ++i) { GLVolume* volume = (*m_volumes)[i]; - if (!volume->is_wipe_tower && !volume->is_modifier) { + if (!volume->is_wipe_tower() && !volume->is_modifier) { const double max_z = volume->transformed_convex_hull_bounding_box().max.z(); const std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); InstancesToZMap::iterator it = instances_max_z.find(instance); diff --git a/tests/arrange/test_arrange.cpp b/tests/arrange/test_arrange.cpp index e1d478fb1b..86d1131a69 100644 --- a/tests/arrange/test_arrange.cpp +++ b/tests/arrange/test_arrange.cpp @@ -386,7 +386,7 @@ template<> inline Slic3r::arr2::RectangleBed init_bed inline Slic3r::arr2::CircleBed init_bed() { - return Slic3r::arr2::CircleBed{Slic3r::Point::Zero(), scaled(300.)}; + return Slic3r::arr2::CircleBed{Slic3r::Point::Zero(), scaled(300.), Slic3r::BedsGrid::Gap{0, 0}}; } template<> inline Slic3r::arr2::IrregularBed init_bed() @@ -640,6 +640,7 @@ struct RectangleItem { void set_bed_index(int idx) { bed_index = idx; } int get_bed_index() const noexcept { return bed_index; } + std::optional get_bed_constraint() const { return std::nullopt; } void set_translation(const Vec2crd &tr) { translation = tr; } const Vec2crd & get_translation() const noexcept { return translation; } diff --git a/tests/arrange/test_arrange_integration.cpp b/tests/arrange/test_arrange_integration.cpp index 9a5f8dc6d3..129176d602 100644 --- a/tests/arrange/test_arrange_integration.cpp +++ b/tests/arrange/test_arrange_integration.cpp @@ -135,7 +135,7 @@ TEST_CASE("ModelInstance should be retrievable when imbued into ArrangeItem", arr2::ArrangeItem itm; arr2::PhysicalOnlyVBedHandler vbedh; auto vbedh_ptr = static_cast(&vbedh); - auto arrbl = arr2::ArrangeableModelInstance{mi, vbedh_ptr, nullptr, {0, 0}}; + auto arrbl = arr2::ArrangeableModelInstance{mi, vbedh_ptr, nullptr, {0, 0}, std::nullopt}; arr2::imbue_id(itm, arrbl.id()); std::optional id_returned = arr2::retrieve_id(itm); @@ -330,7 +330,7 @@ auto create_vbed_handler(const Slic3r::Boundi template<> auto create_vbed_handler(const Slic3r::BoundingBox &bedbb, coord_t gap) { - return Slic3r::arr2::GridStriderVBedHandler{bedbb, gap}; + return Slic3r::arr2::GridStriderVBedHandler{bedbb, {gap, gap}}; } TEMPLATE_TEST_CASE("Common virtual bed handlers", @@ -628,7 +628,7 @@ TEMPLATE_TEST_CASE("Bed needs to be completely filled with 1cm cubes", ModelObject* new_object = m.add_object(); new_object->name = "10mm_box"; - new_object->add_instance(); + ModelInstance *instance = new_object->add_instance(); TriangleMesh mesh = make_cube(10., 10., 10.); ModelVolume* new_volume = new_object->add_volume(mesh); new_volume->name = new_object->name; @@ -641,11 +641,15 @@ TEMPLATE_TEST_CASE("Bed needs to be completely filled with 1cm cubes", arr2::FixedSelection sel({{true}}); + arr2::BedConstraints constraints; + constraints.insert({instance->id(), 0}); + arr2::Scene scene{arr2::SceneBuilder{} .set_model(m) .set_arrange_settings(settings) .set_selection(&sel) - .set_bed(cfg)}; + .set_bed_constraints(std::move(constraints)) + .set_bed(cfg, Point::new_scale(10, 10))}; auto task = arr2::FillBedTask::create(scene); auto result = task->process_native(arr2::DummyCtl{}); @@ -654,7 +658,7 @@ TEMPLATE_TEST_CASE("Bed needs to be completely filled with 1cm cubes", store_3mf("fillbed_10mm_result.3mf", &m, &cfg, false); Points bedpts = get_bed_shape(cfg); - arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts); + arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts, Point::new_scale(10, 10)); REQUIRE(bed.which() == 1); // Rectangle bed @@ -799,7 +803,7 @@ TEST_CASE("Testing arrangement involving virtual beds", "[arrange2][integration] DynamicPrintConfig cfg; cfg.load_from_ini(std::string(TEST_DATA_DIR PATH_SEPARATOR) + "default_fff.ini", ForwardCompatibilitySubstitutionRule::Enable); - auto bed = arr2::to_arrange_bed(get_bed_shape(cfg)); + auto bed = arr2::to_arrange_bed(get_bed_shape(cfg), Point::new_scale(10, 10)); auto bedbb = bounding_box(bed); auto bedsz = unscaled(bedbb.size()); @@ -815,7 +819,7 @@ TEST_CASE("Testing arrangement involving virtual beds", "[arrange2][integration] arr2::Scene scene{arr2::SceneBuilder{} .set_model(model) .set_arrange_settings(settings) - .set_bed(cfg)}; + .set_bed(cfg, Point::new_scale(10, 10))}; auto itm_conv = arr2::ArrangeableToItemConverter::create(scene); @@ -883,7 +887,7 @@ public: }; class MocWTH : public WipeTowerHandler { - std::function m_sel_pred; + std::function m_sel_pred; ObjectID m_id; public: @@ -891,18 +895,22 @@ public: void visit(std::function fn) override { - MocWT wt{m_id, Polygon{}, m_sel_pred}; + MocWT wt{m_id, Polygon{}, 0, m_sel_pred}; fn(wt); } void visit(std::function fn) const override { - MocWT wt{m_id, Polygon{}, m_sel_pred}; + MocWT wt{m_id, Polygon{}, 0, m_sel_pred}; fn(wt); } - void set_selection_predicate(std::function pred) override + void set_selection_predicate(std::function pred) override { m_sel_pred = std::move(pred); } + + ObjectID get_id() const override { + return m_id; + } }; }} // namespace Slic3r::arr2 @@ -974,7 +982,7 @@ TEST_CASE("Test SceneBuilder", "[arrange2][integration]") WHEN("a scene is built with a bed initialized from this DynamicPrintConfig") { - arr2::Scene scene(arr2::SceneBuilder{}.set_bed(cfg)); + arr2::Scene scene(arr2::SceneBuilder{}.set_bed(cfg, Point::new_scale(10, 10))); auto bedbb = bounding_box(get_bed_shape(cfg)); @@ -1001,7 +1009,10 @@ TEST_CASE("Test SceneBuilder", "[arrange2][integration]") arr2::SceneBuilder bld; Model mdl; bld.set_model(mdl); - bld.set_wipe_tower_handler(std::make_unique(wipe_tower_instance_id(0))); + + std::vector> handlers; + handlers.push_back(std::make_unique(wipe_tower_instance_id(0))); + bld.set_wipe_tower_handlers(std::move(handlers)); WHEN("the selection mask is initialized as a fallback default in the created scene") { diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index b28196b2bd..b6ecaa4281 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -254,7 +254,7 @@ void init_print(std::vector &&meshes, Slic3r::Print &print, Slic3r double distance = min_object_distance(config); arr2::ArrangeSettings arrange_settings{}; arrange_settings.set_distance_from_objects(distance); - arr2::ArrangeBed bed{arr2::to_arrange_bed(get_bed_shape(config))}; + arr2::ArrangeBed bed{arr2::to_arrange_bed(get_bed_shape(config), BedsGrid::Gap{0, 0})}; if (duplicate_count > 1) { duplicate(model, duplicate_count, bed, arrange_settings); } diff --git a/tests/fff_print/test_model.cpp b/tests/fff_print/test_model.cpp index 243cb10d1d..29e3cd9d98 100644 --- a/tests/fff_print/test_model.cpp +++ b/tests/fff_print/test_model.cpp @@ -43,7 +43,7 @@ SCENARIO("Model construction", "[Model]") { } model_object->add_instance(); arrange_objects(model, - arr2::to_arrange_bed(get_bed_shape(config)), + arr2::to_arrange_bed(get_bed_shape(config), Point::new_scale(10, 10)), arr2::ArrangeSettings{}.set_distance_from_objects( min_object_distance(config))); diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 60a6cd789e..8f11e14f87 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -26,6 +26,7 @@ add_executable(${_TEST_NAME}_tests test_stl.cpp test_meshboolean.cpp test_marchingsquares.cpp + test_multiple_beds.cpp test_region_expansion.cpp test_timeutils.cpp test_utils.cpp diff --git a/tests/libslic3r/test_multiple_beds.cpp b/tests/libslic3r/test_multiple_beds.cpp new file mode 100644 index 0000000000..10640a4c3d --- /dev/null +++ b/tests/libslic3r/test_multiple_beds.cpp @@ -0,0 +1,35 @@ +#include + +#include +#include + +using namespace Slic3r; +TEST_CASE("Conversion between grid coords and index", "[MultipleBeds]") +{ + std::vector original_indices(10); + std::iota(original_indices.begin(), original_indices.end(), 0); + + // Add indexes covering the whole int positive range. + const int n{100}; + std::generate_n(std::back_inserter(original_indices), n, [i = 1]() mutable { + return std::numeric_limits::max() / n * i++; + }); + + std::vector coords; + std::transform( + original_indices.begin(), + original_indices.end(), + std::back_inserter(coords), + BedsGrid::index2grid_coords + ); + + std::vector indices; + std::transform( + coords.begin(), + coords.end(), + std::back_inserter(indices), + BedsGrid::grid_coords2index + ); + + CHECK(original_indices == indices); +} diff --git a/tests/slic3rutils/slic3r_arrangejob_tests.cpp b/tests/slic3rutils/slic3r_arrangejob_tests.cpp index b76861af16..4aac99b963 100644 --- a/tests/slic3rutils/slic3r_arrangejob_tests.cpp +++ b/tests/slic3rutils/slic3r_arrangejob_tests.cpp @@ -90,14 +90,14 @@ TEST_CASE("Basic arrange with cube", "[arrangejob]") { arr2::ArrangeSettings settings; Points bedpts = get_bed_shape(cfg); - arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts); + arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts, BedsGrid::Gap{0, 0}); SECTION("Single cube needs to be centered") { w.push(std::make_unique(arr2::Scene{ arr2::SceneBuilder{} .set_model(m) .set_arrange_settings(&settings) - .set_bed(cfg)})); + .set_bed(cfg, BedsGrid::Gap{0, 0})})); w.process_events(); @@ -126,7 +126,7 @@ TEST_CASE("Basic arrange with cube", "[arrangejob]") { arr2::Scene scene{arr2::SceneBuilder{} .set_model(m) .set_arrange_settings(&settings) - .set_bed(cfg) + .set_bed(cfg, BedsGrid::Gap{0, 0}) .set_selection(&sel)}; w.push(std::make_unique(std::move(scene))); @@ -160,7 +160,7 @@ TEST_CASE("Basic arrange with cube", "[arrangejob]") { arr2::Scene scene{arr2::SceneBuilder{} .set_model(m) .set_arrange_settings(&settings) - .set_bed(cfg) + .set_bed(cfg, BedsGrid::Gap{0, 0}) .set_selection(&sel)}; w.push(std::make_unique(std::move(scene))); @@ -217,7 +217,7 @@ TEST_CASE("Basic arrange with cube", "[arrangejob]") { arr2::Scene scene{arr2::SceneBuilder{} .set_model(m) .set_arrange_settings(&settings) - .set_bed(cfg)}; + .set_bed(cfg, Point::new_scale(10, 10))}; w.push(std::make_unique(std::move(scene))); w.process_events(); @@ -266,7 +266,7 @@ TEST_CASE("Test for modifying model during arrangement", "[arrangejob][fillbedjo new_volume->name = new_object->name; Points bedpts = get_bed_shape(cfg); - arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts); + arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts, BedsGrid::Gap{0, 0}); BoostThreadWorker w(std::make_unique()); RandomArrangeSettings settings; @@ -278,7 +278,7 @@ TEST_CASE("Test for modifying model during arrangement", "[arrangejob][fillbedjo arr2::Scene scene{arr2::SceneBuilder{} .set_model(m) .set_arrange_settings(&settings) - .set_bed(cfg)}; + .set_bed(cfg, BedsGrid::Gap{0, 0})}; ArrangeJob2::Callbacks cbs; cbs.on_prepared = [&m] (auto &) {