From cb3596e90b20798c3a5488bb3250bc4adf4998e0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 16 Aug 2023 17:34:42 +0200 Subject: [PATCH 01/10] Complete arrangement prevention of integer coordinate overflows --- src/libslic3r/Arrange/Arrange.hpp | 8 +++ src/libslic3r/Arrange/ArrangeImpl.hpp | 8 +++ src/libslic3r/Arrange/SceneBuilder.cpp | 59 ++++++++++++++++--- src/libslic3r/Arrange/SceneBuilder.hpp | 3 + .../Arrange/Tasks/ArrangeTaskImpl.hpp | 15 +++-- .../Arrange/Tasks/FillBedTaskImpl.hpp | 23 +++++--- .../Tasks/MultiplySelectionTaskImpl.hpp | 23 +++++--- 7 files changed, 112 insertions(+), 27 deletions(-) diff --git a/src/libslic3r/Arrange/Arrange.hpp b/src/libslic3r/Arrange/Arrange.hpp index 36ab838739..75e632ad95 100644 --- a/src/libslic3r/Arrange/Arrange.hpp +++ b/src/libslic3r/Arrange/Arrange.hpp @@ -83,11 +83,19 @@ void Arranger::arrange(std::vector &items, arrange(items, fixed, bed, DefaultArrangerCtl{ctl}); } +class EmptyItemOutlineError: public std::exception { + static constexpr const char *Msg = "No outline can be derived for object"; + +public: + const char* what() const noexcept override { return Msg; } +}; + template class ArrangeableToItemConverter { public: virtual ~ArrangeableToItemConverter() = default; + // May throw EmptyItemOutlineError virtual ArrItem convert(const Arrangeable &arrbl, coord_t offs = 0) const = 0; // Returns the extent of simplification that the converter utilizes when diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index 2b7f62e87f..d827be3df6 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -370,6 +370,10 @@ ArrItem ConvexItemConverter::convert(const Arrangeable &arrbl, { auto bed_index = arrbl.get_bed_index(); Polygon outline = arrbl.convex_outline(); + + if (outline.empty()) + throw EmptyItemOutlineError{}; + Polygon envelope = arrbl.convex_envelope(); coord_t infl = offs + coord_t(std::ceil(this->safety_dist() / 2.)); @@ -418,6 +422,10 @@ ArrItem AdvancedItemConverter::get_arritem(const Arrangeable &arrbl, coord_t infl = offs + coord_t(std::ceil(this->safety_dist() / 2.)); auto outline = arrbl.full_outline(); + + if (outline.empty()) + throw EmptyItemOutlineError{}; + auto envelope = arrbl.full_envelope(); if (infl != 0) { diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index a6e37c0b74..bf77dcd948 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -64,16 +64,18 @@ void transform_instance(ModelInstance &mi, mi.invalidate_object_bounding_box(); } -BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, bool dont_translate) +BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, + const Transform3d &tr, + bool dont_translate) { BoundingBoxf3 bb; const Transform3d inst_matrix = dont_translate ? mi.get_transformation().get_matrix_no_offset() - : mi.get_transformation().get_matrix(); + : mi.get_transformation().get_matrix(); for (ModelVolume *v : mi.get_object()->volumes) { if (v->is_model_part()) { - bb.merge(v->mesh().transformed_bounding_box(inst_matrix + bb.merge(v->mesh().transformed_bounding_box(tr * inst_matrix * v->get_matrix())); } } @@ -81,11 +83,32 @@ BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, bool dont_translate return bb; } +BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, bool dont_translate) +{ + return instance_bounding_box(mi, Transform3d::Identity(), dont_translate); +} + +bool check_coord_bounds(const BoundingBoxf &bb) +{ + constexpr double hi = 1000.; + + return std::abs(bb.min.x()) < hi && + std::abs(bb.min.y()) < hi && + std::abs(bb.max.x()) < hi && + std::abs(bb.max.y()) < hi; +} + ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr) { ExPolygons outline; for (const ModelVolume *v : inst.get_object()->volumes) { Polygons vol_outline; + + if (!check_coord_bounds(to_2d(v->mesh().transformed_bounding_box(tr)))) { + outline.clear(); + break; + } + vol_outline = project_mesh(v->mesh().its, tr * inst.get_matrix() * v->get_matrix(), [] {}); @@ -105,7 +128,14 @@ ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr Polygon extract_convex_outline(const ModelInstance &inst, const Transform3d &tr) { - return inst.get_object()->convex_hull_2d(tr * inst.get_matrix()); + auto bb = to_2d(instance_bounding_box(inst, tr)); + Polygon ret; + + if (check_coord_bounds(bb)) { + ret = inst.get_object()->convex_hull_2d(tr * inst.get_matrix()); + } + + return ret; } inline static bool is_infinite_bed(const ExtendedBed &ebed) noexcept @@ -190,7 +220,14 @@ int XStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const auto reference_pos_x = (instance_bb.min.x() - bedx); auto stride = unscaled(stride_s); - bedidx = static_cast(std::floor(reference_pos_x / stride)); + auto bedidx_d = std::floor(reference_pos_x / stride); + + if (bedidx_d < std::numeric_limits::min()) + bedidx = std::numeric_limits::min(); + else if (bedidx_d > std::numeric_limits::max()) + bedidx = std::numeric_limits::max(); + else + bedidx = static_cast(bedidx_d); } return bedidx; @@ -231,7 +268,14 @@ int YStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const auto reference_pos_y = (instance_bb.min.y() - ystart); auto stride = unscaled(stride_s); - bedidx = static_cast(std::floor(reference_pos_y / stride)); + auto bedidx_d = std::floor(reference_pos_y / stride); + + if (bedidx_d < std::numeric_limits::min()) + bedidx = std::numeric_limits::min(); + else if (bedidx_d > std::numeric_limits::max()) + bedidx = std::numeric_limits::max(); + else + bedidx = static_cast(bedidx_d); } return bedidx; @@ -274,7 +318,8 @@ Vec2i GridStriderVBedHandler::raw2grid(int bed_idx) const int GridStriderVBedHandler::grid2raw(const Vec2i &crd) const { - assert(crd.x() < ColsOutside - 1 && crd.y() < ColsOutside - 1); + assert(std::abs(crd.x()) < ColsOutside - 1 && + std::abs(crd.y()) < ColsOutside - 1); return crd.y() * ColsOutside + crd.x(); } diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp index c19bd6ed69..f056ed3ff2 100644 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -377,6 +377,9 @@ void transform_instance(ModelInstance &mi, BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, bool dont_translate = false); +BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, + const Transform3d &tr, + bool dont_translate = false); ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr = Transform3d::Identity()); diff --git a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp index c9f48f93e4..8925ea4b95 100644 --- a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp @@ -3,6 +3,8 @@ #include +#include + #include "ArrangeTask.hpp" namespace Slic3r { namespace arr2 { @@ -20,16 +22,21 @@ void extract_selected(ArrangeTask &task, bool selected = arrbl.is_selected(); bool printable = arrbl.is_printable(); - auto itm = itm_conv.convert(arrbl, selected ? 0 : -SCALED_EPSILON); + try { + auto itm = itm_conv.convert(arrbl, selected ? 0 : -SCALED_EPSILON); - auto &container_parent = printable ? task.printable : + auto &container_parent = printable ? task.printable : task.unprintable; - auto &container = selected ? + auto &container = selected ? container_parent.selected : container_parent.unselected; - container.emplace_back(std::move(itm)); + container.emplace_back(std::move(itm)); + } catch (const EmptyItemOutlineError &ex) { + BOOST_LOG_TRIVIAL(error) + << "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what(); + } }); // If the selection was empty arrange everything diff --git a/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp index a99102418f..e874be4575 100644 --- a/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp @@ -5,6 +5,8 @@ #include "Arrange/Core/NFP/NFPArrangeItemTraits.hpp" +#include + namespace Slic3r { namespace arr2 { template @@ -81,15 +83,20 @@ void extract(FillBedTask &task, auto collect_task_items = [&prototype_geometry_id, &task, &itm_conv](const Arrangeable &arrbl) { - 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)); + 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)); + } + } 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) + << "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what(); } }; diff --git a/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp index d4c925c3bf..422c3da243 100644 --- a/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp @@ -3,6 +3,8 @@ #include "MultiplySelectionTask.hpp" +#include + namespace Slic3r { namespace arr2 { template @@ -45,15 +47,20 @@ std::unique_ptr> MultiplySelectionTask:: auto collect_task_items = [&prototype_geometry_id, &task, &itm_conv](const Arrangeable &arrbl) { - 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)); + 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)); + } + } 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) + << "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what(); } }; From aea278ab5587fdda72e20d6408a2756af567d7c6 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 16 Aug 2023 18:52:05 +0200 Subject: [PATCH 02/10] Fix failing tests still the arrange is broken --- src/libslic3r/Arrange/SceneBuilder.cpp | 10 ++++------ src/libslic3r/Arrange/SceneBuilder.hpp | 2 ++ tests/arrange/test_arrange_integration.cpp | 7 ++++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index bf77dcd948..75990e2f84 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -90,12 +90,10 @@ BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, bool dont_translate bool check_coord_bounds(const BoundingBoxf &bb) { - constexpr double hi = 1000.; - - return std::abs(bb.min.x()) < hi && - std::abs(bb.min.y()) < hi && - std::abs(bb.max.x()) < hi && - std::abs(bb.max.y()) < hi; + return std::abs(bb.min.x()) < UnscaledCoordLimit && + std::abs(bb.min.y()) < UnscaledCoordLimit && + std::abs(bb.max.x()) < UnscaledCoordLimit && + std::abs(bb.max.y()) < UnscaledCoordLimit; } ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr) diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp index f056ed3ff2..d159669f51 100644 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -381,6 +381,8 @@ BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, const Transform3d &tr, bool dont_translate = false); +constexpr double UnscaledCoordLimit = 1000.; + ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr = Transform3d::Identity()); diff --git a/tests/arrange/test_arrange_integration.cpp b/tests/arrange/test_arrange_integration.cpp index b60b87fce9..6d8fda6bff 100644 --- a/tests/arrange/test_arrange_integration.cpp +++ b/tests/arrange/test_arrange_integration.cpp @@ -49,8 +49,8 @@ static Slic3r::Model get_example_model_with_random_cube_objects(size_t N = 0) for (size_t i = 0; i < cube_count; ++i) { ModelInstance *inst = new_object->add_instance(); arr2::transform_instance(*inst, - Vec2d{random_value(-200., 200.), - random_value(-200., 200.)}, + Vec2d{random_value(-arr2::UnscaledCoordLimit / 10., arr2::UnscaledCoordLimit / 10.), + random_value(-arr2::UnscaledCoordLimit / 10., arr2::UnscaledCoordLimit / 10.)}, random_value(0., 2 * PI)); } @@ -168,7 +168,8 @@ TEMPLATE_TEST_CASE("Writing arrange transformations into ModelInstance should be { auto [tx, ty, rot] = GENERATE(map( [](int i) { - return std::make_tuple(-500. + i * 20., -500. + i * 20., + return std::make_tuple(-Slic3r::arr2::UnscaledCoordLimit / 2. + i * Slic3r::arr2::UnscaledCoordLimit / 100., + -Slic3r::arr2::UnscaledCoordLimit / 2. + i * Slic3r::arr2::UnscaledCoordLimit / 100., -PI + i * (2 * PI / 100.)); }, range(0, 100))); From 1d4594ad6645b5051c34a0dc949a18cd187b25b5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 17 Aug 2023 19:27:13 +0200 Subject: [PATCH 03/10] Fix faulty virtual bed handling Changes to firstfit selection to use a map of bed contexts to avoid memory overuse when a fixed item has a large bed index number. fixes SPE-1844 --- src/libslic3r/Arrange/ArrangeImpl.hpp | 26 +++++++--- src/libslic3r/Arrange/Core/ArrangeBase.hpp | 42 ++++++++++++++-- .../Arrange/Core/ArrangeFirstFit.hpp | 48 ++++++++++--------- src/libslic3r/Arrange/SceneBuilder.cpp | 18 ++++--- src/libslic3r/Arrange/SceneBuilder.hpp | 4 +- .../Arrange/Tasks/ArrangeTaskImpl.hpp | 31 +++++++++--- 6 files changed, 123 insertions(+), 46 deletions(-) diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index d827be3df6..2bbe9ab29c 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -2,6 +2,7 @@ #define ARRANGEIMPL_HPP #include +#include #include "Arrange.hpp" @@ -43,25 +44,36 @@ void arrange(SelectionStrategy &&selstrategy, std::forward(packingstrategy), items, fixed, RectangleBed{bed.bb}, SelStrategyTag{}); - size_t beds = get_bed_count(crange(items)); - size_t fixed_beds = std::max(beds, get_bed_count(fixed)); - std::vector fixed_is_empty(fixed_beds, true); + std::vector bed_indices = get_bed_indices(items, fixed); - std::vector pilebb(beds); + size_t beds = bed_indices.size(); + + auto fixed_is_empty = [&bed_indices](int bidx) { + auto it = std::lower_bound(bed_indices.begin(), bed_indices.end(), bidx); + return it == bed_indices.end() || *it != bidx; + }; + + auto set_bed_as_empty = [&bed_indices](int bidx) { + auto it = std::lower_bound(bed_indices.begin(), bed_indices.end(), bidx); + if (it != bed_indices.end()) + bed_indices.erase(it); + }; + + std::vector pilebb(bed_indices.size()); for (auto &itm : items) { auto bedidx = get_bed_index(itm); if (bedidx >= 0) { pilebb[bedidx].merge(fixed_bounding_box(itm)); if (is_wipe_tower(itm)) - fixed_is_empty[bedidx] = false; + set_bed_as_empty(bedidx); } } for (auto &fxitm : fixed) { auto bedidx = get_bed_index(fxitm); if (bedidx >= 0 || is_wipe_tower(fxitm)) - fixed_is_empty[bedidx] = false; + set_bed_as_empty(bedidx); } auto bedbb = bounding_box(bed); @@ -74,7 +86,7 @@ void arrange(SelectionStrategy &&selstrategy, Pivots pivot = bed.alignment(); for (size_t bedidx = 0; bedidx < beds; ++bedidx) { - if (! fixed_is_empty[bedidx]) + if (! fixed_is_empty(bedidx)) continue; BoundingBox bb; diff --git a/src/libslic3r/Arrange/Core/ArrangeBase.hpp b/src/libslic3r/Arrange/Core/ArrangeBase.hpp index 97177d4b3c..d341903d72 100644 --- a/src/libslic3r/Arrange/Core/ArrangeBase.hpp +++ b/src/libslic3r/Arrange/Core/ArrangeBase.hpp @@ -233,8 +233,44 @@ void arrange(SelectionStrategy &&selstrategy, "Arrange unimplemented for this selection strategy"); } +template +std::vector get_bed_indices(const Range &items) +{ + auto bed_indices = reserve_vector(items.size()); + + for (auto &itm : items) + bed_indices.emplace_back(get_bed_index(itm)); + + std::sort(bed_indices.begin(), bed_indices.end()); + auto endit = std::unique(bed_indices.begin(), bed_indices.end()); + + bed_indices.erase(endit, bed_indices.end()); + + return bed_indices; +} + +template +std::vector get_bed_indices(const Range &items, const Range &fixed) +{ + std::vector ret; + + auto iitems = get_bed_indices(items); + auto ifixed = get_bed_indices(fixed); + ret.reserve(std::max(iitems.size(), ifixed.size())); + std::set_union(iitems.begin(), iitems.end(), + ifixed.begin(), ifixed.end(), + std::back_inserter(ret)); + + return ret; +} + template size_t get_bed_count(const Range &items) +{ + return get_bed_indices(items).size(); +} + +template int get_max_bed_index(const Range &items) { auto it = std::max_element(items.begin(), items.end(), @@ -242,11 +278,11 @@ size_t get_bed_count(const Range &items) return get_bed_index(i1) < get_bed_index(i2); }); - size_t beds = 0; + int ret = Unarranged; if (it != items.end()) - beds = get_bed_index(*it) + 1; + ret = get_bed_index(*it); - return beds; + return ret; } struct DefaultStopCondition { diff --git a/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp b/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp index ee6940b770..cf8d3e457a 100644 --- a/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp +++ b/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp @@ -2,6 +2,7 @@ #define ARRANGEFIRSTFIT_HPP #include +#include #include @@ -88,21 +89,28 @@ void arrange( sorted_items.emplace_back(itm); } - int max_bed_idx = get_bed_count(fixed); - using Context = PackStrategyContext; - auto bed_contexts = reserve_vector(max_bed_idx + 1); + std::map bed_contexts; + auto get_or_init_context = [&ps, &bed, &bed_contexts](int bedidx) -> Context& { + auto ctx_it = bed_contexts.find(bedidx); + if (ctx_it == bed_contexts.end()) { + auto res = bed_contexts.emplace( + bedidx, create_context(ps, bed, bedidx)); + + assert(res.second); + + ctx_it = res.first; + } + + return ctx_it->second; + }; for (auto &itm : fixed) { - if (get_bed_index(itm) >= 0) { - auto bedidx = static_cast(get_bed_index(itm)); - - while (bed_contexts.size() <= bedidx) - bed_contexts.emplace_back( - create_context(ps, bed, bedidx)); - - add_fixed_item(bed_contexts[bedidx], itm); + auto bedidx = get_bed_index(itm); + if (bedidx >= 0) { + Context &ctx = get_or_init_context(bedidx); + add_fixed_item(ctx, itm); } } @@ -124,18 +132,20 @@ void arrange( while (it != sorted_items.end() && !is_cancelled()) { bool was_packed = false; - size_t j = 0; + int bedidx = 0; while (!was_packed && !is_cancelled()) { - for (; j < bed_contexts.size() && !was_packed && !is_cancelled(); j++) { - set_bed_index(*it, int(j)); + for (; !was_packed && !is_cancelled(); bedidx++) { + set_bed_index(*it, bedidx); auto remaining = Range{std::next(static_cast(it)), sorted_items.cend()}; - was_packed = pack(ps, bed, *it, bed_contexts[j], remaining); + Context &ctx = get_or_init_context(bedidx); + + was_packed = pack(ps, bed, *it, ctx, remaining); if(was_packed) { - add_packed_item(bed_contexts[j], *it); + add_packed_item(ctx, *it); auto packed_range = Range{sorted_items.cbegin(), static_cast(it)}; @@ -145,12 +155,6 @@ void arrange( set_bed_index(*it, Unarranged); } } - - if (!was_packed) { - bed_contexts.emplace_back( - create_context(ps, bed, bed_contexts.size())); - j = bed_contexts.size() - 1; - } } ++it; } diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index 75990e2f84..12c4556670 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -304,22 +304,28 @@ Transform3d YStriderVBedHandler::get_physical_bed_trafo(int bed_index) const return tr; } -const int GridStriderVBedHandler::ColsOutside = - static_cast(std::sqrt(std::numeric_limits::max())); +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 { - Vec2i ret{bed_idx % ColsOutside, bed_idx / ColsOutside}; + bed_idx += Offset; + + Vec2i ret{bed_idx % Cols - HalfCols, bed_idx / Cols - HalfCols}; return ret; } int GridStriderVBedHandler::grid2raw(const Vec2i &crd) const { - assert(std::abs(crd.x()) < ColsOutside - 1 && - std::abs(crd.y()) < ColsOutside - 1); + // 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.y() * ColsOutside + crd.x(); + return (crd.x() + HalfCols) + Cols * (crd.y() + HalfCols) - Offset; } int GridStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp index d159669f51..54146cd3cc 100644 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -338,7 +338,9 @@ class GridStriderVBedHandler: public VirtualBedHandler // 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 ColsOutside; + static const int Cols; + static const int HalfCols; + static const int Offset; XStriderVBedHandler m_xstrider; YStriderVBedHandler m_ystrider; diff --git a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp index 8925ea4b95..a46152ed98 100644 --- a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp @@ -75,6 +75,21 @@ void prepare_fixed_unselected(ItemCont &items, int shift) items.end()); } +inline int find_first_empty_bed(const std::vector& bed_indices, + int starting_from = 0) { + int ret = starting_from; + + for (int idx : bed_indices) { + if (idx == ret) { + ret++; + } else if (idx > ret) { + break; + } + } + + return ret; +} + template std::unique_ptr ArrangeTask::process_native(Ctl &ctl) @@ -109,15 +124,17 @@ ArrangeTask::process_native(Ctl &ctl) arranger->arrange(printable.selected, fixed_items, bed, subctl); - // Unprintable items should go to the first bed not containing any printable - // items - auto beds = std::max(get_bed_count(crange(printable.selected)), - get_bed_count(crange(printable.unselected))); + std::vector printable_bed_indices = + get_bed_indices(crange(printable.selected), crange(printable.unselected)); // If there are no printables, leave the physical bed empty - beds = std::max(beds, size_t{1}); + constexpr int SearchFrom = 1; - prepare_fixed_unselected(unprintable.unselected, beds); + // Unprintable items should go to the first logical (!) bed not containing + // any printable items + int first_empty_bed = find_first_empty_bed(printable_bed_indices, SearchFrom); + + prepare_fixed_unselected(unprintable.unselected, first_empty_bed); arranger->arrange(unprintable.selected, unprintable.unselected, bed, ctl); @@ -125,7 +142,7 @@ ArrangeTask::process_native(Ctl &ctl) for (auto &itm : unprintable.selected) { if (is_arranged(itm)) { - int bedidx = get_bed_index(itm) + beds; + int bedidx = get_bed_index(itm) + first_empty_bed; arr2::set_bed_index(itm, bedidx); } From 3277b5ee061ffa2a0117d18da446ab43d0722179 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 17 Aug 2023 19:29:16 +0200 Subject: [PATCH 04/10] Fix compilation on in VS 2019 in certain environments MSVC crashes when compiling Thanks to @enricoturri1966 for reporting and fixing --- tests/arrange/test_arrange.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/arrange/test_arrange.cpp b/tests/arrange/test_arrange.cpp index 0304a29db9..56bba72a53 100644 --- a/tests/arrange/test_arrange.cpp +++ b/tests/arrange/test_arrange.cpp @@ -760,7 +760,7 @@ TEST_CASE("First fit selection strategy", "[arrange2]") THEN("The item should be left unpacked") { - REQUIRE(std::all_of(items.begin(), items.end(), [](auto &itm) { + REQUIRE(std::all_of(items.begin(), items.end(), [](const ArrItem &itm) { return !Slic3r::arr2::is_arranged(itm); })); } @@ -878,7 +878,7 @@ TEST_CASE("First fit selection strategy", "[arrange2]") THEN("all items should fit onto the beds from index 0 to 4") { - REQUIRE(std::all_of(items.begin(), items.end(), [](auto &itm) { + REQUIRE(std::all_of(items.begin(), items.end(), [](const ArrItem &itm) { auto bed_idx = Slic3r::arr2::get_bed_index(itm); return bed_idx >= 0 && bed_idx < Count / Capacity; })); From d69d477480b4ae51661a5084c100d80e1ecb6010 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 18 Aug 2023 13:07:44 +0200 Subject: [PATCH 05/10] Fix wipe tower arrangement --- src/libslic3r/Arrange/ArrangeImpl.hpp | 28 ++++++++------------------ src/libslic3r/Arrange/Scene.hpp | 8 ++++++++ src/libslic3r/Arrange/SceneBuilder.cpp | 17 +++++++++++++--- src/libslic3r/Arrange/SceneBuilder.hpp | 7 ++++--- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index 2bbe9ab29c..afe919ccff 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -45,35 +45,22 @@ void arrange(SelectionStrategy &&selstrategy, RectangleBed{bed.bb}, SelStrategyTag{}); std::vector bed_indices = get_bed_indices(items, fixed); - - size_t beds = bed_indices.size(); - - auto fixed_is_empty = [&bed_indices](int bidx) { - auto it = std::lower_bound(bed_indices.begin(), bed_indices.end(), bidx); - return it == bed_indices.end() || *it != bidx; - }; - - auto set_bed_as_empty = [&bed_indices](int bidx) { - auto it = std::lower_bound(bed_indices.begin(), bed_indices.end(), bidx); - if (it != bed_indices.end()) - bed_indices.erase(it); - }; - - std::vector pilebb(bed_indices.size()); + std::map pilebb; + std::map bed_occupied; for (auto &itm : items) { auto bedidx = get_bed_index(itm); if (bedidx >= 0) { pilebb[bedidx].merge(fixed_bounding_box(itm)); if (is_wipe_tower(itm)) - set_bed_as_empty(bedidx); + bed_occupied[bedidx] = true; } } for (auto &fxitm : fixed) { auto bedidx = get_bed_index(fxitm); - if (bedidx >= 0 || is_wipe_tower(fxitm)) - set_bed_as_empty(bedidx); + if (bedidx >= 0) + bed_occupied[bedidx] = true; } auto bedbb = bounding_box(bed); @@ -85,8 +72,9 @@ void arrange(SelectionStrategy &&selstrategy, Pivots pivot = bed.alignment(); - for (size_t bedidx = 0; bedidx < beds; ++bedidx) { - if (! fixed_is_empty(bedidx)) + for (int bedidx : bed_indices) { + if (auto occup_it = bed_occupied.find(bedidx); + occup_it != bed_occupied.end() && occup_it->second) continue; BoundingBox bb; diff --git a/src/libslic3r/Arrange/Scene.hpp b/src/libslic3r/Arrange/Scene.hpp index f917ed3c2f..e7f6c2a7c0 100644 --- a/src/libslic3r/Arrange/Scene.hpp +++ b/src/libslic3r/Arrange/Scene.hpp @@ -161,6 +161,14 @@ template void visit_bed(BedFn &&fn, ExtendedBed &bed) boost::apply_visitor(fn, bed); } +inline BoundingBox bounding_box(const ExtendedBed &bed) +{ + BoundingBox bedbb; + visit_bed([&bedbb](auto &rawbed) { bedbb = bounding_box(rawbed); }, bed); + + return bedbb; +} + class Scene; // SceneBuilderBase is intended for Scene construction. A simple constructor diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index 12c4556670..f9453c9721 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -199,6 +199,17 @@ void SceneBuilder::build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel 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; }); + + if (m_xl_printer && !has_wipe_tower) { + m_bed = XLBed{bounding_box(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); @@ -443,11 +454,11 @@ SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg) Points bedpts = get_bed_shape(cfg); if (is_XL_printer(cfg)) { - m_bed = XLBed{get_extents(bedpts)}; - } else { - m_bed = arr2::to_arrange_bed(bedpts); + m_xl_printer = true; } + m_bed = arr2::to_arrange_bed(bedpts); + return std::move(*this); } diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp index 54146cd3cc..8f4f8a7797 100644 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -205,6 +205,7 @@ protected: AnyPtr m_sla_print; AnyPtr m_fff_print; + bool m_xl_printer = false; void set_brim_and_skirt(); @@ -498,9 +499,9 @@ class ArrangeableSLAPrint : public ArrangeableSlicerModel { static void visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn); public: - explicit ArrangeableSLAPrint(const SLAPrint *slaprint, - SceneBuilder &builder) - : m_slaprint{slaprint}, ArrangeableSlicerModel{builder} + explicit ArrangeableSLAPrint(const SLAPrint *slaprint, SceneBuilder &builder) + : m_slaprint{slaprint} + , ArrangeableSlicerModel{builder} { assert(slaprint != nullptr); } From 44d91ec3538fd8dbbc16fee485cfd9f5d6729866 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 18 Aug 2023 16:49:01 +0200 Subject: [PATCH 06/10] Use gravity kernel for non rectangular beds Also change sorting of outlines based on convex hull area and --- src/libslic3r/Arrange/ArrangeImpl.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index afe919ccff..5c567d646f 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -272,7 +272,7 @@ class DefaultArranger: public Arranger { int pa = get_priority(itm1); int pb = get_priority(itm2); - return pa == pb ? envelope_area(itm1) > envelope_area(itm2) : + return pa == pb ? area(envelope_convex_hull(itm1)) > area(envelope_convex_hull(itm2)) : pa > pb; }; @@ -295,7 +295,11 @@ class DefaultArranger: public Arranger { default: [[fallthrough]]; case ArrangeSettingsView::asAuto: - basekernel = TMArrangeKernel{items.size(), area(bed)}; + if constexpr (std::is_convertible_v){ + basekernel = TMArrangeKernel{items.size(), area(bed)}; + } else { + basekernel = GravityKernel{}; + } break; case ArrangeSettingsView::asPullToCenter: basekernel = GravityKernel{}; From 01d5f04295fa553121dd0839b0f2448cc52203d2 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 18 Aug 2023 16:49:50 +0200 Subject: [PATCH 07/10] Fix outline bound check for full outlines --- src/libslic3r/Arrange/SceneBuilder.cpp | 32 ++++++++++++-------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index f9453c9721..53a785ea8d 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -99,25 +99,23 @@ bool check_coord_bounds(const BoundingBoxf &bb) ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr) { ExPolygons outline; - for (const ModelVolume *v : inst.get_object()->volumes) { - Polygons vol_outline; - if (!check_coord_bounds(to_2d(v->mesh().transformed_bounding_box(tr)))) { - outline.clear(); - break; - } + if (check_coord_bounds(to_2d(instance_bounding_box(inst, tr)))) { + for (const ModelVolume *v : inst.get_object()->volumes) { + Polygons vol_outline; - vol_outline = project_mesh(v->mesh().its, - tr * inst.get_matrix() * v->get_matrix(), - [] {}); - switch (v->type()) { - case ModelVolumeType::MODEL_PART: - outline = union_ex(outline, vol_outline); - break; - case ModelVolumeType::NEGATIVE_VOLUME: - outline = diff_ex(outline, vol_outline); - break; - default:; + vol_outline = project_mesh(v->mesh().its, + tr * inst.get_matrix() * v->get_matrix(), + [] {}); + switch (v->type()) { + case ModelVolumeType::MODEL_PART: + outline = union_ex(outline, vol_outline); + break; + case ModelVolumeType::NEGATIVE_VOLUME: + outline = diff_ex(outline, vol_outline); + break; + default:; + } } } From b2d43c8719c5d141e20f11dba21736e25179bca9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 18 Aug 2023 16:50:35 +0200 Subject: [PATCH 08/10] Fix arrange in fff tests --- src/libslic3r/Arrange/Scene.cpp | 4 ++-- src/libslic3r/Arrange/Scene.hpp | 22 +++++++++++++++++++--- src/libslic3r/ModelArrange.cpp | 19 ++++++------------- tests/fff_print/test_data.cpp | 2 +- tests/fff_print/test_model.cpp | 8 ++++++-- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/Arrange/Scene.cpp b/src/libslic3r/Arrange/Scene.cpp index a59b3f9aec..ddcc5ead67 100644 --- a/src/libslic3r/Arrange/Scene.cpp +++ b/src/libslic3r/Arrange/Scene.cpp @@ -54,11 +54,11 @@ std::set selected_geometry_ids(const Scene &sc) return result; } -void arrange(Scene &scene, ArrangeTaskCtl &ctl) +bool arrange(Scene &scene, ArrangeTaskCtl &ctl) { auto task = ArrangeTaskBase::create(Tasks::Arrange, scene); auto result = task->process(ctl); - result->apply_on(scene.model()); + return result->apply_on(scene.model()); } }} // namespace Slic3r::arr2 diff --git a/src/libslic3r/Arrange/Scene.hpp b/src/libslic3r/Arrange/Scene.hpp index e7f6c2a7c0..16622e464a 100644 --- a/src/libslic3r/Arrange/Scene.hpp +++ b/src/libslic3r/Arrange/Scene.hpp @@ -373,10 +373,26 @@ public: } }; -void arrange(Scene &scene, ArrangeTaskCtl &ctl); -inline void arrange(Scene &scene, ArrangeTaskCtl &&ctl = DummyCtl{}) +bool arrange(Scene &scene, ArrangeTaskCtl &ctl); +inline bool arrange(Scene &scene, ArrangeTaskCtl &&ctl = DummyCtl{}) { - arrange(scene, ctl); + return arrange(scene, ctl); +} + +inline bool arrange(Scene &&scene, ArrangeTaskCtl &ctl) +{ + return arrange(scene, ctl); +} + +inline bool arrange(Scene &&scene, ArrangeTaskCtl &&ctl = DummyCtl{}) +{ + return arrange(scene, ctl); +} + +template +bool arrange(SceneBuilderBase &&builder, Ctl &&ctl = {}) +{ + return arrange(Scene{std::move(builder)}, ctl); } } // namespace arr2 diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index f0294e8a05..b5f53dfed5 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -1,14 +1,11 @@ #include "ModelArrange.hpp" - #include +#include +#include #include #include -#include "Arrange/Core/ArrangeItemTraits.hpp" -#include "Arrange/Items/ArrangeItem.hpp" - -#include "MTUtils.hpp" namespace Slic3r { @@ -27,14 +24,10 @@ bool arrange_objects(Model &model, const arr2::ArrangeBed &bed, const arr2::ArrangeSettingsView &settings) { - arr2::Scene scene{arr2::SceneBuilder{} - .set_bed(bed) - .set_arrange_settings(settings) - .set_model(model)}; - - auto task = arr2::ArrangeTaskBase::create(arr2::Tasks::Arrange, scene); - auto result = task->process(); - return result->apply_on(scene.model()); + return arrange(arr2::SceneBuilder{} + .set_bed(bed) + .set_arrange_settings(settings) + .set_model(model)); } void duplicate_objects(Model &model, diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index 8f9367ce51..fa41f88e2f 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -228,7 +228,7 @@ void init_print(std::vector &&meshes, Slic3r::Print &print, Slic3r object->add_volume(std::move(t)); object->add_instance(); } - arrange_objects(model, arr2::InfiniteBed{}, arr2::ArrangeSettings{}.set_distance_from_objects(min_object_distance(config))); + arrange_objects(model, arr2::to_arrange_bed(get_bed_shape(config)), arr2::ArrangeSettings{}.set_distance_from_objects(min_object_distance(config))); model.center_instances_around_point({100, 100}); for (ModelObject *mo : model.objects) { mo->ensure_on_bed(); diff --git a/tests/fff_print/test_model.cpp b/tests/fff_print/test_model.cpp index d59a655663..243cb10d1d 100644 --- a/tests/fff_print/test_model.cpp +++ b/tests/fff_print/test_model.cpp @@ -42,8 +42,12 @@ SCENARIO("Model construction", "[Model]") { } } model_object->add_instance(); - arrange_objects(model, arr2::InfiniteBed{scaled(Vec2d(100, 100))}, arr2::ArrangeSettings{}.set_distance_from_objects(min_object_distance(config))); - model_object->ensure_on_bed(); + arrange_objects(model, + arr2::to_arrange_bed(get_bed_shape(config)), + arr2::ArrangeSettings{}.set_distance_from_objects( + min_object_distance(config))); + + model_object->ensure_on_bed(); print.auto_assign_extruders(model_object); THEN("Print works?") { print.set_status_silent(); From e6a722ab28ce5cda05694cf8f24489f8bbf7e225 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 18 Aug 2023 17:42:47 +0200 Subject: [PATCH 09/10] Use gravity kernel only for circular beds --- src/libslic3r/Arrange/ArrangeImpl.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index 5c567d646f..79f474bc2d 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -295,10 +295,10 @@ class DefaultArranger: public Arranger { default: [[fallthrough]]; case ArrangeSettingsView::asAuto: - if constexpr (std::is_convertible_v){ - basekernel = TMArrangeKernel{items.size(), area(bed)}; - } else { + if constexpr (std::is_convertible_v){ basekernel = GravityKernel{}; + } else { + basekernel = TMArrangeKernel{items.size(), area(bed)}; } break; case ArrangeSettingsView::asPullToCenter: From 08b0684bac2772d7b43d7c995a16586fa4333f2f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 18 Aug 2023 17:43:06 +0200 Subject: [PATCH 10/10] Fix circular bed shape recognition --- src/libslic3r/Arrange/Core/Beds.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Arrange/Core/Beds.cpp b/src/libslic3r/Arrange/Core/Beds.cpp index 90d5f4f77d..1c96723767 100644 --- a/src/libslic3r/Arrange/Core/Beds.cpp +++ b/src/libslic3r/Arrange/Core/Beds.cpp @@ -110,7 +110,7 @@ template auto call_with_bed(const Points &bed, Fn &&fn) if ((1.0 - parea / area(bb)) < 1e-3) { return fn(RectangleBed{bb}); - } else if (!std::isnan(circ.radius())) + } else if (!std::isnan(circ.radius()) && (1.0 - parea / area(circ)) < 1e-2) return fn(circ); else return fn(IrregularBed{{ExPolygon(bed)}});