mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-01 05:12:00 +08:00
502 lines
15 KiB
C++
502 lines
15 KiB
C++
///|/ Copyright (c) Prusa Research 2023 Tomáš Mészáros @tamasmeszaros
|
|
///|/
|
|
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
|
///|/
|
|
#ifndef ARRANGEIMPL_HPP
|
|
#define ARRANGEIMPL_HPP
|
|
|
|
#include <random>
|
|
#include <map>
|
|
|
|
#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>{});
|
|
|
|
std::vector<int> bed_indices = get_bed_indices(items, fixed);
|
|
std::map<int, BoundingBox> pilebb;
|
|
std::map<int, bool> 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))
|
|
bed_occupied[bedidx] = true;
|
|
}
|
|
}
|
|
|
|
for (auto &fxitm : fixed) {
|
|
auto bedidx = get_bed_index(fxitm);
|
|
if (bedidx >= 0)
|
|
bed_occupied[bedidx] = true;
|
|
}
|
|
|
|
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 (int bedidx : bed_indices) {
|
|
if (auto occup_it = bed_occupied.find(bedidx);
|
|
occup_it != bed_occupied.end() && occup_it->second)
|
|
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 ? area(envelope_convex_hull(itm1)) > area(envelope_convex_hull(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:
|
|
if constexpr (std::is_convertible_v<Bed, CircleBed>){
|
|
basekernel = GravityKernel{};
|
|
} else {
|
|
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();
|
|
|
|
if (outline.empty())
|
|
throw EmptyItemOutlineError{};
|
|
|
|
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();
|
|
|
|
if (outline.empty())
|
|
throw EmptyItemOutlineError{};
|
|
|
|
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
|