|
@@ -0,0 +1,485 @@
|
|
|
+#ifndef ARRANGEIMPL_HPP
|
|
|
+#define ARRANGEIMPL_HPP
|
|
|
+
|
|
|
+#include <random>
|
|
|
+
|
|
|
+#include "Arrange.hpp"
|
|
|
+
|
|
|
+#include "Core/ArrangeBase.hpp"
|
|
|
+#include "Core/ArrangeFirstFit.hpp"
|
|
|
+#include "Core/NFP/PackStrategyNFP.hpp"
|
|
|
+#include "Core/NFP/Kernels/TMArrangeKernel.hpp"
|
|
|
+#include "Core/NFP/Kernels/GravityKernel.hpp"
|
|
|
+#include "Core/NFP/RectangleOverfitPackingStrategy.hpp"
|
|
|
+#include "Core/Beds.hpp"
|
|
|
+
|
|
|
+#include "Items/MutableItemTraits.hpp"
|
|
|
+
|
|
|
+#include "SegmentedRectangleBed.hpp"
|
|
|
+
|
|
|
+#include "libslic3r/Execution/ExecutionTBB.hpp"
|
|
|
+#include "libslic3r/Geometry/ConvexHull.hpp"
|
|
|
+
|
|
|
+#ifndef NDEBUG
|
|
|
+#include "Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp"
|
|
|
+#endif
|
|
|
+
|
|
|
+namespace Slic3r { namespace arr2 {
|
|
|
+
|
|
|
+// arrange overload for SegmentedRectangleBed which is exactly what is used
|
|
|
+// by XL printers.
|
|
|
+template<class It,
|
|
|
+ class ConstIt,
|
|
|
+ class SelectionStrategy,
|
|
|
+ class PackStrategy, class...SBedArgs>
|
|
|
+void arrange(SelectionStrategy &&selstrategy,
|
|
|
+ PackStrategy &&packingstrategy,
|
|
|
+ const Range<It> &items,
|
|
|
+ const Range<ConstIt> &fixed,
|
|
|
+ const SegmentedRectangleBed<SBedArgs...> &bed)
|
|
|
+{
|
|
|
+ // Dispatch:
|
|
|
+ arrange(std::forward<SelectionStrategy>(selstrategy),
|
|
|
+ std::forward<PackStrategy>(packingstrategy), items, fixed,
|
|
|
+ RectangleBed{bed.bb}, SelStrategyTag<SelectionStrategy>{});
|
|
|
+
|
|
|
+ size_t beds = get_bed_count(crange(items));
|
|
|
+ size_t fixed_beds = std::max(beds, get_bed_count(fixed));
|
|
|
+ std::vector<bool> fixed_is_empty(fixed_beds, true);
|
|
|
+
|
|
|
+ std::vector<BoundingBox> pilebb(beds);
|
|
|
+
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (auto &fxitm : fixed) {
|
|
|
+ auto bedidx = get_bed_index(fxitm);
|
|
|
+ if (bedidx >= 0 || is_wipe_tower(fxitm))
|
|
|
+ fixed_is_empty[bedidx] = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ auto bedbb = bounding_box(bed);
|
|
|
+ auto piecesz = unscaled(bedbb).size();
|
|
|
+ piecesz.x() /= bed.segments_x();
|
|
|
+ piecesz.y() /= bed.segments_y();
|
|
|
+
|
|
|
+ using Pivots = RectPivots;
|
|
|
+
|
|
|
+ Pivots pivot = bed.alignment();
|
|
|
+
|
|
|
+ for (size_t bedidx = 0; bedidx < beds; ++bedidx) {
|
|
|
+ if (! fixed_is_empty[bedidx])
|
|
|
+ continue;
|
|
|
+
|
|
|
+ BoundingBox bb;
|
|
|
+ auto pilesz = unscaled(pilebb[bedidx]).size();
|
|
|
+ bb.max.x() = scaled(std::ceil(pilesz.x() / piecesz.x()) * piecesz.x());
|
|
|
+ bb.max.y() = scaled(std::ceil(pilesz.y() / piecesz.y()) * piecesz.y());
|
|
|
+
|
|
|
+ switch (pivot) {
|
|
|
+ case Pivots::BottomLeft:
|
|
|
+ bb.translate(bedbb.min - bb.min);
|
|
|
+ break;
|
|
|
+ case Pivots::TopRight:
|
|
|
+ bb.translate(bedbb.max - bb.max);
|
|
|
+ break;
|
|
|
+ case Pivots::BottomRight: {
|
|
|
+ Point bedref{bedbb.max.x(), bedbb.min.y()};
|
|
|
+ Point bbref {bb.max.x(), bb.min.y()};
|
|
|
+ bb.translate(bedref - bbref);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case Pivots::TopLeft: {
|
|
|
+ Point bedref{bedbb.min.x(), bedbb.max.y()};
|
|
|
+ Point bbref {bb.min.x(), bb.max.y()};
|
|
|
+ bb.translate(bedref - bbref);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case Pivots::Center: {
|
|
|
+ bb.translate(bedbb.center() - bb.center());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ ;
|
|
|
+ }
|
|
|
+
|
|
|
+ Vec2crd d = bb.center() - pilebb[bedidx].center();
|
|
|
+
|
|
|
+ auto pilebbx = pilebb[bedidx];
|
|
|
+ pilebbx.translate(d);
|
|
|
+
|
|
|
+ Point corr{0, 0};
|
|
|
+ corr.x() = -std::min(0, pilebbx.min.x() - bedbb.min.x())
|
|
|
+ -std::max(0, pilebbx.max.x() - bedbb.max.x());
|
|
|
+ corr.y() = -std::min(0, pilebbx.min.y() - bedbb.min.y())
|
|
|
+ -std::max(0, pilebbx.max.y() - bedbb.max.y());
|
|
|
+
|
|
|
+ d += corr;
|
|
|
+
|
|
|
+ for (auto &itm : items)
|
|
|
+ if (get_bed_index(itm) == static_cast<int>(bedidx) && !is_wipe_tower(itm))
|
|
|
+ translate(itm, d);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+using VariantKernel =
|
|
|
+ boost::variant<TMArrangeKernel, GravityKernel>;
|
|
|
+
|
|
|
+template<> struct KernelTraits_<VariantKernel> {
|
|
|
+ template<class ArrItem>
|
|
|
+ static double placement_fitness(const VariantKernel &kernel,
|
|
|
+ const ArrItem &itm,
|
|
|
+ const Vec2crd &transl)
|
|
|
+ {
|
|
|
+ double ret = NaNd;
|
|
|
+ boost::apply_visitor(
|
|
|
+ [&](auto &k) { ret = k.placement_fitness(itm, transl); }, kernel);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ template<class ArrItem, class Bed, class Ctx, class RemIt>
|
|
|
+ static bool on_start_packing(VariantKernel &kernel,
|
|
|
+ ArrItem &itm,
|
|
|
+ const Bed &bed,
|
|
|
+ const Ctx &packing_context,
|
|
|
+ const Range<RemIt> &remaining_items)
|
|
|
+ {
|
|
|
+ bool ret = false;
|
|
|
+
|
|
|
+ boost::apply_visitor([&](auto &k) {
|
|
|
+ ret = k.on_start_packing(itm, bed, packing_context, remaining_items);
|
|
|
+ }, kernel);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ template<class ArrItem>
|
|
|
+ static bool on_item_packed(VariantKernel &kernel, ArrItem &itm)
|
|
|
+ {
|
|
|
+ bool ret = false;
|
|
|
+ boost::apply_visitor([&](auto &k) { ret = k.on_item_packed(itm); },
|
|
|
+ kernel);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+template<class ArrItem>
|
|
|
+struct firstfit::ItemArrangedVisitor<ArrItem, DataStoreOnly<ArrItem>> {
|
|
|
+ template<class Bed, class PIt, class RIt>
|
|
|
+ static void on_arranged(ArrItem &itm,
|
|
|
+ const Bed &bed,
|
|
|
+ const Range<PIt> &packed,
|
|
|
+ const Range<RIt> &remaining)
|
|
|
+ {
|
|
|
+ using OnArrangeCb = std::function<void(StripCVRef<ArrItem> &)>;
|
|
|
+
|
|
|
+ auto cb = get_data<OnArrangeCb>(itm, "on_arranged");
|
|
|
+
|
|
|
+ if (cb) {
|
|
|
+ (*cb)(itm);
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+inline RectPivots xlpivots_to_rect_pivots(ArrangeSettingsView::XLPivots xlpivot)
|
|
|
+{
|
|
|
+ if (xlpivot == arr2::ArrangeSettingsView::xlpRandom) {
|
|
|
+ // means it should be random
|
|
|
+ std::random_device rd{};
|
|
|
+ std::mt19937 rng(rd());
|
|
|
+ std::uniform_int_distribution<std::mt19937::result_type>
|
|
|
+ dist(0, arr2::ArrangeSettingsView::xlpRandom - 1);
|
|
|
+ xlpivot = static_cast<ArrangeSettingsView::XLPivots>(dist(rng));
|
|
|
+ }
|
|
|
+
|
|
|
+ RectPivots rectpivot = RectPivots::Center;
|
|
|
+
|
|
|
+ switch(xlpivot) {
|
|
|
+ case arr2::ArrangeSettingsView::xlpCenter: rectpivot = RectPivots::Center; break;
|
|
|
+ case arr2::ArrangeSettingsView::xlpFrontLeft: rectpivot = RectPivots::BottomLeft; break;
|
|
|
+ case arr2::ArrangeSettingsView::xlpFrontRight: rectpivot = RectPivots::BottomRight; break;
|
|
|
+ case arr2::ArrangeSettingsView::xlpRearLeft: rectpivot = RectPivots::TopLeft; break;
|
|
|
+ case arr2::ArrangeSettingsView::xlpRearRight: rectpivot = RectPivots::TopRight; break;
|
|
|
+ default:
|
|
|
+ ;
|
|
|
+ }
|
|
|
+
|
|
|
+ return rectpivot;
|
|
|
+}
|
|
|
+
|
|
|
+template<class It, class Bed>
|
|
|
+void fill_rotations(const Range<It> &items,
|
|
|
+ const Bed &bed,
|
|
|
+ const ArrangeSettingsView &settings)
|
|
|
+{
|
|
|
+ if (!settings.is_rotation_enabled())
|
|
|
+ return;
|
|
|
+
|
|
|
+ for (auto &itm : items) {
|
|
|
+ if (is_wipe_tower(itm)) // Rotating the wipe tower is currently problematic
|
|
|
+ continue;
|
|
|
+
|
|
|
+ // Use the minimum bounding box rotation as a starting point.
|
|
|
+ auto minbbr = get_min_area_bounding_box_rotation(itm);
|
|
|
+ std::vector<double> rotations =
|
|
|
+ {minbbr,
|
|
|
+ minbbr + PI / 4., minbbr + PI / 2.,
|
|
|
+ minbbr + PI, minbbr + 3 * PI / 4.};
|
|
|
+
|
|
|
+ // Add the original rotation of the item if minbbr
|
|
|
+ // is not already the original rotation (zero)
|
|
|
+ if (std::abs(minbbr) > 0.)
|
|
|
+ rotations.emplace_back(0.);
|
|
|
+
|
|
|
+ // Also try to find the rotation that fits the item
|
|
|
+ // into a rectangular bed, given that it cannot fit,
|
|
|
+ // and there exists a rotation which can fit.
|
|
|
+ if constexpr (std::is_convertible_v<Bed, RectangleBed>) {
|
|
|
+ double fitbrot = get_fit_into_bed_rotation(itm, bed);
|
|
|
+ if (std::abs(fitbrot) > 0.)
|
|
|
+ rotations.emplace_back(fitbrot);
|
|
|
+ }
|
|
|
+
|
|
|
+ set_allowed_rotations(itm, rotations);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// An arranger put together to fulfill all the requirements of PrusaSlicer based
|
|
|
+// on the supplied ArrangeSettings
|
|
|
+template<class ArrItem>
|
|
|
+class DefaultArranger: public Arranger<ArrItem> {
|
|
|
+ ArrangeSettings m_settings;
|
|
|
+
|
|
|
+ static constexpr auto Accuracy = 1.;
|
|
|
+
|
|
|
+ template<class It, class FixIt, class Bed>
|
|
|
+ void arrange_(
|
|
|
+ const Range<It> &items,
|
|
|
+ const Range<FixIt> &fixed,
|
|
|
+ const Bed &bed,
|
|
|
+ ArrangerCtl<ArrItem> &ctl)
|
|
|
+ {
|
|
|
+ auto cmpfn = [](const auto &itm1, const auto &itm2) {
|
|
|
+ int pa = get_priority(itm1);
|
|
|
+ int pb = get_priority(itm2);
|
|
|
+
|
|
|
+ return pa == pb ? envelope_area(itm1) > envelope_area(itm2) :
|
|
|
+ pa > pb;
|
|
|
+ };
|
|
|
+
|
|
|
+ auto on_arranged = [&ctl](auto &itm, auto &bed, auto &ctx, auto &rem) {
|
|
|
+ ctl.update_status(rem.size());
|
|
|
+
|
|
|
+ ctl.on_packed(itm);
|
|
|
+
|
|
|
+ firstfit::DefaultOnArrangedFn{}(itm, bed, ctx, rem);
|
|
|
+ };
|
|
|
+
|
|
|
+ auto stop_cond = [&ctl] { return ctl.was_canceled(); };
|
|
|
+
|
|
|
+ firstfit::SelectionStrategy sel{cmpfn, on_arranged, stop_cond};
|
|
|
+
|
|
|
+ constexpr auto ep = ex_tbb;
|
|
|
+
|
|
|
+ VariantKernel basekernel;
|
|
|
+ switch (m_settings.get_arrange_strategy()) {
|
|
|
+ default:
|
|
|
+ [[fallthrough]];
|
|
|
+ case ArrangeSettingsView::asAuto:
|
|
|
+ basekernel = TMArrangeKernel{items.size(), area(bed)};
|
|
|
+ break;
|
|
|
+ case ArrangeSettingsView::asPullToCenter:
|
|
|
+ basekernel = GravityKernel{};
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+#ifndef NDEBUG
|
|
|
+ SVGDebugOutputKernelWrapper<VariantKernel> kernel{bounding_box(bed), basekernel};
|
|
|
+#else
|
|
|
+ auto & kernel = basekernel;
|
|
|
+#endif
|
|
|
+
|
|
|
+ fill_rotations(items, bed, m_settings);
|
|
|
+
|
|
|
+ bool with_wipe_tower = std::any_of(items.begin(), items.end(),
|
|
|
+ [](auto &itm) {
|
|
|
+ return is_wipe_tower(itm);
|
|
|
+ });
|
|
|
+
|
|
|
+ // With rectange bed, and no fixed items, let's use an infinite bed
|
|
|
+ // with RectangleOverfitKernelWrapper. It produces better results than
|
|
|
+ // a pure RectangleBed with inner-fit polygon calculation.
|
|
|
+ if (!with_wipe_tower &&
|
|
|
+ m_settings.get_arrange_strategy() == ArrangeSettingsView::asAuto &&
|
|
|
+ std::is_convertible_v<Bed, RectangleBed>) {
|
|
|
+ PackStrategyNFP base_strategy{std::move(kernel), ep, Accuracy, stop_cond};
|
|
|
+
|
|
|
+ RectangleOverfitPackingStrategy final_strategy{std::move(base_strategy)};
|
|
|
+
|
|
|
+ arr2::arrange(sel, final_strategy, items, fixed, bed);
|
|
|
+ } else {
|
|
|
+ PackStrategyNFP ps{std::move(kernel), ep, Accuracy, stop_cond};
|
|
|
+
|
|
|
+ arr2::arrange(sel, ps, items, fixed, bed);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+public:
|
|
|
+ explicit DefaultArranger(const ArrangeSettingsView &settings)
|
|
|
+ {
|
|
|
+ m_settings.set_from(settings);
|
|
|
+ }
|
|
|
+
|
|
|
+ void arrange(
|
|
|
+ std::vector<ArrItem> &items,
|
|
|
+ const std::vector<ArrItem> &fixed,
|
|
|
+ const ExtendedBed &bed,
|
|
|
+ ArrangerCtl<ArrItem> &ctl) override
|
|
|
+ {
|
|
|
+ visit_bed([this, &items, &fixed, &ctl](auto rawbed) {
|
|
|
+
|
|
|
+ if constexpr (IsSegmentedBed<decltype(rawbed)>)
|
|
|
+ rawbed.pivot = xlpivots_to_rect_pivots(
|
|
|
+ m_settings.get_xl_alignment());
|
|
|
+
|
|
|
+ arrange_(range(items), crange(fixed), rawbed, ctl);
|
|
|
+ }, bed);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+template<class ArrItem>
|
|
|
+std::unique_ptr<Arranger<ArrItem>> Arranger<ArrItem>::create(
|
|
|
+ const ArrangeSettingsView &settings)
|
|
|
+{
|
|
|
+ // Currently all that is needed is handled by DefaultArranger
|
|
|
+ return std::make_unique<DefaultArranger<ArrItem>>(settings);
|
|
|
+}
|
|
|
+
|
|
|
+template<class ArrItem>
|
|
|
+ArrItem ConvexItemConverter<ArrItem>::convert(const Arrangeable &arrbl,
|
|
|
+ coord_t offs) const
|
|
|
+{
|
|
|
+ auto bed_index = arrbl.get_bed_index();
|
|
|
+ Polygon outline = arrbl.convex_outline();
|
|
|
+ Polygon envelope = arrbl.convex_envelope();
|
|
|
+
|
|
|
+ coord_t infl = offs + coord_t(std::ceil(this->safety_dist() / 2.));
|
|
|
+
|
|
|
+ if (infl != 0) {
|
|
|
+ outline = Geometry::convex_hull(offset(outline, infl));
|
|
|
+ if (! envelope.empty())
|
|
|
+ envelope = Geometry::convex_hull(offset(envelope, infl));
|
|
|
+ }
|
|
|
+
|
|
|
+ ArrItem ret;
|
|
|
+ set_convex_shape(ret, outline);
|
|
|
+ if (! envelope.empty())
|
|
|
+ set_convex_envelope(ret, envelope);
|
|
|
+
|
|
|
+ set_bed_index(ret, bed_index);
|
|
|
+ set_priority(ret, arrbl.priority());
|
|
|
+
|
|
|
+ imbue_id(ret, arrbl.id());
|
|
|
+ if constexpr (IsWritableDataStore<ArrItem>)
|
|
|
+ arrbl.imbue_data(AnyWritableDataStore{ret});
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+template<class ArrItem>
|
|
|
+ArrItem AdvancedItemConverter<ArrItem>::convert(const Arrangeable &arrbl,
|
|
|
+ coord_t offs) const
|
|
|
+{
|
|
|
+ auto bed_index = arrbl.get_bed_index();
|
|
|
+ ArrItem ret = get_arritem(arrbl, offs);
|
|
|
+
|
|
|
+ set_bed_index(ret, bed_index);
|
|
|
+ set_priority(ret, arrbl.priority());
|
|
|
+ imbue_id(ret, arrbl.id());
|
|
|
+ if constexpr (IsWritableDataStore<ArrItem>)
|
|
|
+ arrbl.imbue_data(AnyWritableDataStore{ret});
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+template<class ArrItem>
|
|
|
+ArrItem AdvancedItemConverter<ArrItem>::get_arritem(const Arrangeable &arrbl,
|
|
|
+ coord_t offs) const
|
|
|
+{
|
|
|
+ coord_t infl = offs + coord_t(std::ceil(this->safety_dist() / 2.));
|
|
|
+
|
|
|
+ auto outline = arrbl.full_outline();
|
|
|
+ auto envelope = arrbl.full_envelope();
|
|
|
+
|
|
|
+ if (infl != 0) {
|
|
|
+ outline = offset_ex(outline, infl);
|
|
|
+ if (! envelope.empty())
|
|
|
+ envelope = offset_ex(envelope, infl);
|
|
|
+ }
|
|
|
+
|
|
|
+ auto simpl_tol = static_cast<double>(this->simplification_tolerance());
|
|
|
+
|
|
|
+ if (simpl_tol > 0)
|
|
|
+ {
|
|
|
+ outline = expolygons_simplify(outline, simpl_tol);
|
|
|
+ if (!envelope.empty())
|
|
|
+ envelope = expolygons_simplify(envelope, simpl_tol);
|
|
|
+ }
|
|
|
+
|
|
|
+ ArrItem ret;
|
|
|
+ set_shape(ret, outline);
|
|
|
+ if (! envelope.empty())
|
|
|
+ set_envelope(ret, envelope);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+template<class ArrItem>
|
|
|
+ArrItem BalancedItemConverter<ArrItem>::get_arritem(const Arrangeable &arrbl,
|
|
|
+ coord_t offs) const
|
|
|
+{
|
|
|
+ ArrItem ret = AdvancedItemConverter<ArrItem>::get_arritem(arrbl, offs);
|
|
|
+ set_convex_envelope(ret, envelope_convex_hull(ret));
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+template<class ArrItem>
|
|
|
+std::unique_ptr<ArrangeableToItemConverter<ArrItem>>
|
|
|
+ArrangeableToItemConverter<ArrItem>::create(
|
|
|
+ ArrangeSettingsView::GeometryHandling gh,
|
|
|
+ coord_t safety_d)
|
|
|
+{
|
|
|
+ std::unique_ptr<ArrangeableToItemConverter<ArrItem>> ret;
|
|
|
+
|
|
|
+ constexpr coord_t SimplifyTol = scaled(.2);
|
|
|
+
|
|
|
+ switch(gh) {
|
|
|
+ case arr2::ArrangeSettingsView::ghConvex:
|
|
|
+ ret = std::make_unique<ConvexItemConverter<ArrItem>>(safety_d);
|
|
|
+ break;
|
|
|
+ case arr2::ArrangeSettingsView::ghBalanced:
|
|
|
+ ret = std::make_unique<BalancedItemConverter<ArrItem>>(safety_d, SimplifyTol);
|
|
|
+ break;
|
|
|
+ case arr2::ArrangeSettingsView::ghAdvanced:
|
|
|
+ ret = std::make_unique<AdvancedItemConverter<ArrItem>>(safety_d, SimplifyTol);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ ;
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+}} // namespace Slic3r::arr2
|
|
|
+
|
|
|
+#endif // ARRANGEIMPL_HPP
|