mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-04 07:20:44 +08:00

Add functionality to libnest to support on_packed handler for all items. Use it to swap arrange polygon from envelope to core poly after packing was succesful. Solving brim issue by offset adjustments of arrange polygons Only in sla yet
760 lines
26 KiB
C++
760 lines
26 KiB
C++
#include "Arrange.hpp"
|
|
|
|
#include "BoundingBox.hpp"
|
|
|
|
#include <libnest2d/backends/libslic3r/geometries.hpp>
|
|
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
|
#include <libnest2d/placers/nfpplacer.hpp>
|
|
#include <libnest2d/selections/firstfit.hpp>
|
|
#include <libnest2d/utils/rotcalipers.hpp>
|
|
|
|
#include <numeric>
|
|
#include <ClipperUtils.hpp>
|
|
|
|
#include <boost/geometry/index/rtree.hpp>
|
|
|
|
#if defined(_MSC_VER) && defined(__clang__)
|
|
#define BOOST_NO_CXX17_HDR_STRING_VIEW
|
|
#endif
|
|
|
|
#include <boost/multiprecision/integer.hpp>
|
|
#include <boost/rational.hpp>
|
|
|
|
namespace libnest2d {
|
|
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
|
|
using LargeInt = __int128;
|
|
#else
|
|
using LargeInt = boost::multiprecision::int128_t;
|
|
template<> struct _NumTag<LargeInt>
|
|
{
|
|
using Type = ScalarTag;
|
|
};
|
|
#endif
|
|
|
|
template<class T> struct _NumTag<boost::rational<T>>
|
|
{
|
|
using Type = RationalTag;
|
|
};
|
|
|
|
namespace nfp {
|
|
|
|
template<class S> struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
|
|
{
|
|
NfpResult<S> operator()(const S &sh, const S &other)
|
|
{
|
|
return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other);
|
|
}
|
|
};
|
|
|
|
} // namespace nfp
|
|
} // namespace libnest2d
|
|
|
|
namespace Slic3r {
|
|
|
|
template<class Tout = double, class = FloatingOnly<Tout>, int...EigenArgs>
|
|
inline constexpr Eigen::Matrix<Tout, 2, EigenArgs...> unscaled(
|
|
const Slic3r::ClipperLib::IntPoint &v) noexcept
|
|
{
|
|
return Eigen::Matrix<Tout, 2, EigenArgs...>{unscaled<Tout>(v.x()),
|
|
unscaled<Tout>(v.y())};
|
|
}
|
|
|
|
namespace arrangement {
|
|
|
|
using namespace libnest2d;
|
|
|
|
// Get the libnest2d types for clipper backend
|
|
using Item = _Item<ExPolygon>;
|
|
using Box = _Box<Point>;
|
|
using Circle = _Circle<Point>;
|
|
using Segment = _Segment<Point>;
|
|
using MultiPolygon = ExPolygons;
|
|
|
|
// Summon the spatial indexing facilities from boost
|
|
namespace bgi = boost::geometry::index;
|
|
using SpatElement = std::pair<Box, unsigned>;
|
|
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
|
|
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
|
|
|
|
// A coefficient used in separating bigger items and smaller items.
|
|
const double BIG_ITEM_TRESHOLD = 0.02;
|
|
|
|
// Fill in the placer algorithm configuration with values carefully chosen for
|
|
// Slic3r.
|
|
template<class PConf>
|
|
void fill_config(PConf& pcfg, const ArrangeParams ¶ms) {
|
|
|
|
// Align the arranged pile into the center of the bin
|
|
switch (params.alignment) {
|
|
case Pivots::Center: pcfg.alignment = PConf::Alignment::CENTER; break;
|
|
case Pivots::BottomLeft: pcfg.alignment = PConf::Alignment::BOTTOM_LEFT; break;
|
|
case Pivots::BottomRight: pcfg.alignment = PConf::Alignment::BOTTOM_RIGHT; break;
|
|
case Pivots::TopLeft: pcfg.alignment = PConf::Alignment::TOP_LEFT; break;
|
|
case Pivots::TopRight: pcfg.alignment = PConf::Alignment::TOP_RIGHT; break;
|
|
}
|
|
|
|
// Start placing the items from the center of the print bed
|
|
pcfg.starting_point = PConf::Alignment::CENTER;
|
|
|
|
// TODO cannot use rotations until multiple objects of same geometry can
|
|
// handle different rotations.
|
|
if (params.allow_rotations)
|
|
pcfg.rotations = {0., PI / 2., PI, 3. * PI / 2. };
|
|
else
|
|
pcfg.rotations = {0.};
|
|
|
|
// The accuracy of optimization.
|
|
// Goes from 0.0 to 1.0 and scales performance as well
|
|
pcfg.accuracy = params.accuracy;
|
|
|
|
// Allow parallel execution.
|
|
pcfg.parallel = params.parallel;
|
|
}
|
|
|
|
// Apply penalty to object function result. This is used only when alignment
|
|
// after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN)
|
|
// Also, this will only work well for Box shaped beds.
|
|
static double fixed_overfit(const std::tuple<double, Box>& result, const Box &binbb)
|
|
{
|
|
double score = std::get<0>(result);
|
|
Box pilebb = std::get<1>(result);
|
|
Box fullbb = sl::boundingBox(pilebb, binbb);
|
|
auto diff = double(fullbb.area()) - binbb.area();
|
|
if(diff > 0) score += diff;
|
|
|
|
return score;
|
|
}
|
|
|
|
// A class encapsulating the libnest2d Nester class and extending it with other
|
|
// management and spatial index structures for acceleration.
|
|
template<class TBin>
|
|
class AutoArranger {
|
|
public:
|
|
// Useful type shortcuts...
|
|
using Placer = typename placers::_NofitPolyPlacer<ExPolygon, TBin>;
|
|
using Selector = selections::_FirstFitSelection<ExPolygon>;
|
|
using Packer = _Nester<Placer, Selector>;
|
|
using PConfig = typename Packer::PlacementConfig;
|
|
using Distance = TCoord<PointImpl>;
|
|
|
|
protected:
|
|
Packer m_pck;
|
|
PConfig m_pconf; // Placement configuration
|
|
TBin m_bin;
|
|
double m_bin_area;
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(push)
|
|
#pragma warning(disable: 4244)
|
|
#pragma warning(disable: 4267)
|
|
#endif
|
|
SpatIndex m_rtree; // spatial index for the normal (bigger) objects
|
|
SpatIndex m_smallsrtree; // spatial index for only the smaller items
|
|
#ifdef _MSC_VER
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
double m_norm; // A coefficient to scale distances
|
|
MultiPolygon m_merged_pile; // The already merged pile (vector of items)
|
|
Box m_pilebb; // The bounding box of the merged pile.
|
|
ItemGroup m_remaining; // Remaining items
|
|
ItemGroup m_items; // allready packed items
|
|
size_t m_item_count = 0; // Number of all items to be packed
|
|
|
|
template<class T> ArithmeticOnly<T, double> norm(T val)
|
|
{
|
|
return double(val) / m_norm;
|
|
}
|
|
|
|
// This is "the" object function which is evaluated many times for each
|
|
// vertex (decimated with the accuracy parameter) of each object.
|
|
// Therefore it is upmost crucial for this function to be as efficient
|
|
// as it possibly can be but at the same time, it has to provide
|
|
// reasonable results.
|
|
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
|
|
objfunc(const Item &item, const Point &bincenter)
|
|
{
|
|
const double bin_area = m_bin_area;
|
|
const SpatIndex& spatindex = m_rtree;
|
|
const SpatIndex& smalls_spatindex = m_smallsrtree;
|
|
|
|
// We will treat big items (compared to the print bed) differently
|
|
auto isBig = [bin_area](double a) {
|
|
return a/bin_area > BIG_ITEM_TRESHOLD ;
|
|
};
|
|
|
|
// Candidate item bounding box
|
|
auto ibb = item.boundingBox();
|
|
|
|
// Calculate the full bounding box of the pile with the candidate item
|
|
auto fullbb = sl::boundingBox(m_pilebb, ibb);
|
|
|
|
// The bounding box of the big items (they will accumulate in the center
|
|
// of the pile
|
|
Box bigbb;
|
|
if(spatindex.empty()) bigbb = fullbb;
|
|
else {
|
|
auto boostbb = spatindex.bounds();
|
|
boost::geometry::convert(boostbb, bigbb);
|
|
}
|
|
|
|
// Will hold the resulting score
|
|
double score = 0;
|
|
|
|
// Density is the pack density: how big is the arranged pile
|
|
double density = 0;
|
|
|
|
// Distinction of cases for the arrangement scene
|
|
enum e_cases {
|
|
// This branch is for big items in a mixed (big and small) scene
|
|
// OR for all items in a small-only scene.
|
|
BIG_ITEM,
|
|
|
|
// This branch is for the last big item in a mixed scene
|
|
LAST_BIG_ITEM,
|
|
|
|
// For small items in a mixed scene.
|
|
SMALL_ITEM
|
|
} compute_case;
|
|
|
|
bool bigitems = isBig(item.area()) || spatindex.empty();
|
|
if(bigitems && !m_remaining.empty()) compute_case = BIG_ITEM;
|
|
else if (bigitems && m_remaining.empty()) compute_case = LAST_BIG_ITEM;
|
|
else compute_case = SMALL_ITEM;
|
|
|
|
switch (compute_case) {
|
|
case BIG_ITEM: {
|
|
const Point& minc = ibb.minCorner(); // bottom left corner
|
|
const Point& maxc = ibb.maxCorner(); // top right corner
|
|
|
|
// top left and bottom right corners
|
|
Point top_left{getX(minc), getY(maxc)};
|
|
Point bottom_right{getX(maxc), getY(minc)};
|
|
|
|
// Now the distance of the gravity center will be calculated to the
|
|
// five anchor points and the smallest will be chosen.
|
|
std::array<double, 5> dists;
|
|
auto cc = fullbb.center(); // The gravity center
|
|
dists[0] = pl::distance(minc, cc);
|
|
dists[1] = pl::distance(maxc, cc);
|
|
dists[2] = pl::distance(ibb.center(), cc);
|
|
dists[3] = pl::distance(top_left, cc);
|
|
dists[4] = pl::distance(bottom_right, cc);
|
|
|
|
// The smalles distance from the arranged pile center:
|
|
double dist = norm(*(std::min_element(dists.begin(), dists.end())));
|
|
double bindist = norm(pl::distance(ibb.center(), bincenter));
|
|
dist = 0.8 * dist + 0.2 * bindist;
|
|
|
|
// Prepare a variable for the alignment score.
|
|
// This will indicate: how well is the candidate item
|
|
// aligned with its neighbors. We will check the alignment
|
|
// with all neighbors and return the score for the best
|
|
// alignment. So it is enough for the candidate to be
|
|
// aligned with only one item.
|
|
auto alignment_score = 1.0;
|
|
|
|
auto query = bgi::intersects(ibb);
|
|
auto& index = isBig(item.area()) ? spatindex : smalls_spatindex;
|
|
|
|
// Query the spatial index for the neighbors
|
|
std::vector<SpatElement> result;
|
|
result.reserve(index.size());
|
|
|
|
index.query(query, std::back_inserter(result));
|
|
|
|
// now get the score for the best alignment
|
|
for(auto& e : result) {
|
|
auto idx = e.second;
|
|
Item& p = m_items[idx];
|
|
auto parea = p.area();
|
|
if(std::abs(1.0 - parea/item.area()) < 1e-6) {
|
|
auto bb = sl::boundingBox(p.boundingBox(), ibb);
|
|
auto bbarea = bb.area();
|
|
auto ascore = 1.0 - (item.area() + parea)/bbarea;
|
|
|
|
if(ascore < alignment_score) alignment_score = ascore;
|
|
}
|
|
}
|
|
|
|
density = std::sqrt(norm(fullbb.width()) * norm(fullbb.height()));
|
|
double R = double(m_remaining.size()) / m_item_count;
|
|
|
|
// The final mix of the score is the balance between the
|
|
// distance from the full pile center, the pack density and
|
|
// the alignment with the neighbors
|
|
if (result.empty())
|
|
score = 0.50 * dist + 0.50 * density;
|
|
else
|
|
// Let the density matter more when fewer objects remain
|
|
score = 0.50 * dist + (1.0 - R) * 0.20 * density +
|
|
0.30 * alignment_score;
|
|
|
|
break;
|
|
}
|
|
case LAST_BIG_ITEM: {
|
|
score = norm(pl::distance(ibb.center(), m_pilebb.center()));
|
|
break;
|
|
}
|
|
case SMALL_ITEM: {
|
|
// Here there are the small items that should be placed around the
|
|
// already processed bigger items.
|
|
// No need to play around with the anchor points, the center will be
|
|
// just fine for small items
|
|
score = norm(pl::distance(ibb.center(), bigbb.center()));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return std::make_tuple(score, fullbb);
|
|
}
|
|
|
|
std::function<double(const Item&)> get_objfn();
|
|
|
|
public:
|
|
AutoArranger(const TBin & bin,
|
|
const ArrangeParams ¶ms,
|
|
std::function<void(unsigned)> progressind,
|
|
std::function<bool(void)> stopcond)
|
|
: m_pck(bin, params.min_obj_distance)
|
|
, m_bin(bin)
|
|
, m_bin_area(sl::area(bin))
|
|
, m_norm(std::sqrt(m_bin_area))
|
|
{
|
|
fill_config(m_pconf, params);
|
|
|
|
// Set up a callback that is called just before arranging starts
|
|
// This functionality is provided by the Nester class (m_pack).
|
|
m_pconf.before_packing =
|
|
[this](const MultiPolygon& merged_pile, // merged pile
|
|
const ItemGroup& items, // packed items
|
|
const ItemGroup& remaining) // future items to be packed
|
|
{
|
|
m_items = items;
|
|
m_merged_pile = merged_pile;
|
|
m_remaining = remaining;
|
|
|
|
m_pilebb = sl::boundingBox(merged_pile);
|
|
|
|
m_rtree.clear();
|
|
m_smallsrtree.clear();
|
|
|
|
// We will treat big items (compared to the print bed) differently
|
|
auto isBig = [this](double a) {
|
|
return a / m_bin_area > BIG_ITEM_TRESHOLD ;
|
|
};
|
|
|
|
for(unsigned idx = 0; idx < items.size(); ++idx) {
|
|
Item& itm = items[idx];
|
|
if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx});
|
|
m_smallsrtree.insert({itm.boundingBox(), idx});
|
|
}
|
|
};
|
|
|
|
m_pconf.object_function = get_objfn();
|
|
|
|
m_pconf.on_preload = [this](const ItemGroup &items, PConfig &cfg) {
|
|
if (items.empty()) return;
|
|
|
|
cfg.alignment = PConfig::Alignment::DONT_ALIGN;
|
|
auto bb = sl::boundingBox(m_bin);
|
|
auto bbcenter = bb.center();
|
|
cfg.object_function = [this, bb, bbcenter](const Item &item) {
|
|
return fixed_overfit(objfunc(item, bbcenter), bb);
|
|
};
|
|
};
|
|
|
|
auto on_packed = params.on_packed;
|
|
|
|
if (progressind || on_packed)
|
|
m_pck.progressIndicator([this, progressind, on_packed](unsigned rem) {
|
|
|
|
if (progressind)
|
|
progressind(rem);
|
|
|
|
if (on_packed) {
|
|
int last_bed = m_pck.lastPackedBinId();
|
|
if (last_bed >= 0) {
|
|
Item &last_packed = m_pck.lastResult()[last_bed].back();
|
|
ArrangePolygon ap;
|
|
ap.bed_idx = last_packed.binId();
|
|
ap.priority = last_packed.priority();
|
|
on_packed(ap);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (stopcond) m_pck.stopCondition(stopcond);
|
|
|
|
m_pck.configure(m_pconf);
|
|
}
|
|
|
|
template<class It> inline void operator()(It from, It to) {
|
|
m_rtree.clear();
|
|
m_item_count += size_t(to - from);
|
|
m_pck.execute(from, to);
|
|
m_item_count = 0;
|
|
}
|
|
|
|
PConfig& config() { return m_pconf; }
|
|
const PConfig& config() const { return m_pconf; }
|
|
|
|
inline void preload(std::vector<Item>& fixeditems) {
|
|
for(unsigned idx = 0; idx < fixeditems.size(); ++idx) {
|
|
Item& itm = fixeditems[idx];
|
|
itm.markAsFixedInBin(itm.binId());
|
|
}
|
|
|
|
m_item_count += fixeditems.size();
|
|
}
|
|
};
|
|
|
|
template<> std::function<double(const Item&)> AutoArranger<Box>::get_objfn()
|
|
{
|
|
auto bincenter = m_bin.center();
|
|
|
|
return [this, bincenter](const Item &itm) {
|
|
auto result = objfunc(itm, bincenter);
|
|
|
|
double score = std::get<0>(result);
|
|
auto& fullbb = std::get<1>(result);
|
|
|
|
double miss = Placer::overfit(fullbb, m_bin);
|
|
miss = miss > 0? miss : 0;
|
|
score += miss * miss;
|
|
|
|
return score;
|
|
};
|
|
}
|
|
|
|
template<> std::function<double(const Item&)> AutoArranger<Circle>::get_objfn()
|
|
{
|
|
auto bincenter = m_bin.center();
|
|
return [this, bincenter](const Item &item) {
|
|
|
|
auto result = objfunc(item, bincenter);
|
|
|
|
double score = std::get<0>(result);
|
|
|
|
return score;
|
|
};
|
|
}
|
|
|
|
// Specialization for a generalized polygon.
|
|
// Warning: this is unfinished business. It may or may not work.
|
|
template<>
|
|
std::function<double(const Item &)> AutoArranger<ExPolygon>::get_objfn()
|
|
{
|
|
auto bincenter = sl::boundingBox(m_bin).center();
|
|
return [this, bincenter](const Item &item) {
|
|
return std::get<0>(objfunc(item, bincenter));
|
|
};
|
|
}
|
|
|
|
template<class Bin> void remove_large_items(std::vector<Item> &items, Bin &&bin)
|
|
{
|
|
auto it = items.begin();
|
|
while (it != items.end())
|
|
sl::isInside(it->transformedShape(), bin) ?
|
|
++it : it = items.erase(it);
|
|
}
|
|
|
|
template<class S> Radians min_area_boundingbox_rotation(const S &sh)
|
|
{
|
|
return minAreaBoundingBox<S, TCompute<S>, boost::rational<LargeInt>>(sh)
|
|
.angleToX();
|
|
}
|
|
|
|
template<class S>
|
|
Radians fit_into_box_rotation(const S &sh, const _Box<TPoint<S>> &box)
|
|
{
|
|
return fitIntoBoxRotation<S, TCompute<S>, boost::rational<LargeInt>>(sh, box);
|
|
}
|
|
|
|
template<class BinT> // Arrange for arbitrary bin type
|
|
void _arrange(
|
|
std::vector<Item> & shapes,
|
|
std::vector<Item> & excludes,
|
|
const BinT & bin,
|
|
const ArrangeParams ¶ms,
|
|
std::function<void(unsigned)> progressfn,
|
|
std::function<bool()> stopfn)
|
|
{
|
|
// Integer ceiling the min distance from the bed perimeters
|
|
coord_t md = params.min_obj_distance;
|
|
md = md / 2 - params.min_bed_distance;
|
|
|
|
auto corrected_bin = bin;
|
|
sl::offset(corrected_bin, md);
|
|
ArrangeParams mod_params = params;
|
|
mod_params.min_obj_distance = 0;
|
|
|
|
AutoArranger<BinT> arranger{corrected_bin, mod_params, progressfn, stopfn};
|
|
|
|
auto infl = coord_t(std::ceil(params.min_obj_distance / 2.0));
|
|
for (Item& itm : shapes) itm.inflate(infl);
|
|
for (Item& itm : excludes) itm.inflate(infl);
|
|
|
|
remove_large_items(excludes, corrected_bin);
|
|
|
|
// If there is something on the plate
|
|
if (!excludes.empty()) arranger.preload(excludes);
|
|
|
|
std::vector<std::reference_wrapper<Item>> inp;
|
|
inp.reserve(shapes.size() + excludes.size());
|
|
for (auto &itm : shapes ) inp.emplace_back(itm);
|
|
for (auto &itm : excludes) inp.emplace_back(itm);
|
|
|
|
// Use the minimum bounding box rotation as a starting point.
|
|
// TODO: This only works for convex hull. If we ever switch to concave
|
|
// polygon nesting, a convex hull needs to be calculated.
|
|
if (params.allow_rotations) {
|
|
for (auto &itm : shapes) {
|
|
itm.rotation(min_area_boundingbox_rotation(itm.rawShape()));
|
|
|
|
// If the item is too big, try to find a rotation that makes it fit
|
|
if constexpr (std::is_same_v<BinT, Box>) {
|
|
auto bb = itm.boundingBox();
|
|
if (bb.width() >= bin.width() || bb.height() >= bin.height())
|
|
itm.rotate(fit_into_box_rotation(itm.transformedShape(), bin));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sl::area(corrected_bin) > 0)
|
|
arranger(inp.begin(), inp.end());
|
|
else {
|
|
for (Item &itm : inp)
|
|
itm.binId(BIN_ID_UNSET);
|
|
}
|
|
|
|
for (Item &itm : inp) itm.inflate(-infl);
|
|
}
|
|
|
|
inline Box to_nestbin(const BoundingBox &bb) { return Box{{bb.min(X), bb.min(Y)}, {bb.max(X), bb.max(Y)}};}
|
|
inline Circle to_nestbin(const CircleBed &c) { return Circle({c.center()(0), c.center()(1)}, c.radius()); }
|
|
inline ExPolygon to_nestbin(const Polygon &p) { return ExPolygon{p}; }
|
|
inline Box to_nestbin(const InfiniteBed &bed) { return Box::infinite({bed.center.x(), bed.center.y()}); }
|
|
|
|
inline coord_t width(const BoundingBox& box) { return box.max.x() - box.min.x(); }
|
|
inline coord_t height(const BoundingBox& box) { return box.max.y() - box.min.y(); }
|
|
inline double area(const BoundingBox& box) { return double(width(box)) * height(box); }
|
|
inline double poly_area(const Points &pts) { return std::abs(Polygon::area(pts)); }
|
|
inline double distance_to(const Point& p1, const Point& p2)
|
|
{
|
|
double dx = p2.x() - p1.x();
|
|
double dy = p2.y() - p1.y();
|
|
return std::sqrt(dx*dx + dy*dy);
|
|
}
|
|
|
|
static CircleBed to_circle(const Point ¢er, const Points& points) {
|
|
std::vector<double> vertex_distances;
|
|
double avg_dist = 0;
|
|
|
|
for (const Point& pt : points)
|
|
{
|
|
double distance = distance_to(center, pt);
|
|
vertex_distances.push_back(distance);
|
|
avg_dist += distance;
|
|
}
|
|
|
|
avg_dist /= vertex_distances.size();
|
|
|
|
CircleBed ret(center, avg_dist);
|
|
for(auto el : vertex_distances)
|
|
{
|
|
if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) {
|
|
ret = {};
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Create Item from Arrangeable
|
|
static void process_arrangeable(const ArrangePolygon &arrpoly,
|
|
std::vector<Item> & outp)
|
|
{
|
|
Polygon p = arrpoly.poly.contour;
|
|
const Vec2crd &offs = arrpoly.translation;
|
|
double rotation = arrpoly.rotation;
|
|
|
|
outp.emplace_back(std::move(p));
|
|
outp.back().rotation(rotation);
|
|
outp.back().translation({offs.x(), offs.y()});
|
|
outp.back().inflate(arrpoly.inflation);
|
|
outp.back().binId(arrpoly.bed_idx);
|
|
outp.back().priority(arrpoly.priority);
|
|
outp.back().setOnPackedFn([&arrpoly](Item &itm){
|
|
itm.inflate(-arrpoly.inflation);
|
|
});
|
|
}
|
|
|
|
template<class Fn> auto call_with_bed(const Points &bed, Fn &&fn)
|
|
{
|
|
if (bed.empty())
|
|
return fn(InfiniteBed{});
|
|
else if (bed.size() == 1)
|
|
return fn(InfiniteBed{bed.front()});
|
|
else {
|
|
auto bb = BoundingBox(bed);
|
|
CircleBed circ = to_circle(bb.center(), bed);
|
|
auto parea = poly_area(bed);
|
|
|
|
if ((1.0 - parea / area(bb)) < 1e-3)
|
|
return fn(RectangleBed{bb});
|
|
else if (!std::isnan(circ.radius()))
|
|
return fn(circ);
|
|
else
|
|
return fn(IrregularBed{ExPolygon(bed)});
|
|
}
|
|
}
|
|
|
|
bool is_box(const Points &bed)
|
|
{
|
|
return !bed.empty() &&
|
|
((1.0 - poly_area(bed) / area(BoundingBox(bed))) < 1e-3);
|
|
}
|
|
|
|
template<>
|
|
void arrange(ArrangePolygons & items,
|
|
const ArrangePolygons &excludes,
|
|
const Points & bed,
|
|
const ArrangeParams & params)
|
|
{
|
|
arrange(items, excludes, to_arrange_bed(bed), params);
|
|
}
|
|
|
|
template<class BedT>
|
|
void arrange(ArrangePolygons & arrangables,
|
|
const ArrangePolygons &excludes,
|
|
const BedT & bed,
|
|
const ArrangeParams & params)
|
|
{
|
|
namespace clppr = Slic3r::ClipperLib;
|
|
|
|
std::vector<Item> items, fixeditems;
|
|
items.reserve(arrangables.size());
|
|
|
|
for (ArrangePolygon &arrangeable : arrangables)
|
|
process_arrangeable(arrangeable, items);
|
|
|
|
for (const ArrangePolygon &fixed: excludes)
|
|
process_arrangeable(fixed, fixeditems);
|
|
|
|
for (Item &itm : fixeditems) itm.inflate(scaled(-2. * EPSILON));
|
|
|
|
auto &cfn = params.stopcondition;
|
|
auto &pri = params.progressind;
|
|
|
|
_arrange(items, fixeditems, to_nestbin(bed), params, pri, cfn);
|
|
|
|
for(size_t i = 0; i < items.size(); ++i) {
|
|
Point tr = items[i].translation();
|
|
arrangables[i].translation = {coord_t(tr.x()), coord_t(tr.y())};
|
|
arrangables[i].rotation = items[i].rotation();
|
|
arrangables[i].bed_idx = items[i].binId();
|
|
}
|
|
}
|
|
|
|
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms);
|
|
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms);
|
|
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms);
|
|
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms);
|
|
|
|
ArrangeBed to_arrange_bed(const Points &bedpts)
|
|
{
|
|
ArrangeBed ret;
|
|
|
|
call_with_bed(bedpts, [&](const auto &bed) {
|
|
ret = bed;
|
|
});
|
|
|
|
return ret;
|
|
}
|
|
|
|
void arrange(ArrangePolygons &items,
|
|
const ArrangePolygons &excludes,
|
|
const SegmentedRectangleBed &bed,
|
|
const ArrangeParams ¶ms)
|
|
{
|
|
arrange(items, excludes, bed.bb, params);
|
|
|
|
if (! excludes.empty())
|
|
return;
|
|
|
|
auto it = std::max_element(items.begin(), items.end(),
|
|
[](auto &i1, auto &i2) {
|
|
return i1.bed_idx < i2.bed_idx;
|
|
});
|
|
|
|
size_t beds = 0;
|
|
if (it != items.end())
|
|
beds = it->bed_idx + 1;
|
|
|
|
std::vector<BoundingBox> pilebb(beds);
|
|
|
|
for (auto &itm : items) {
|
|
if (itm.bed_idx >= 0)
|
|
pilebb[itm.bed_idx].merge(get_extents(itm.transformed_poly()));
|
|
}
|
|
|
|
auto piecesz = unscaled(bed.bb).size();
|
|
piecesz.x() /= bed.segments.x();
|
|
piecesz.y() /= bed.segments.y();
|
|
|
|
for (size_t bedidx = 0; bedidx < beds; ++bedidx) {
|
|
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 (params.alignment) {
|
|
case Pivots::BottomLeft:
|
|
bb.translate(bed.bb.min - bb.min);
|
|
break;
|
|
case Pivots::TopRight:
|
|
bb.translate(bed.bb.max - bb.max);
|
|
break;
|
|
case Pivots::BottomRight: {
|
|
Point bedref{bed.bb.max.x(), bed.bb.min.y()};
|
|
Point bbref {bb.max.x(), bb.min.y()};
|
|
bb.translate(bedref - bbref);
|
|
break;
|
|
}
|
|
case Pivots::TopLeft: {
|
|
Point bedref{bed.bb.min.x(), bed.bb.max.y()};
|
|
Point bbref {bb.min.x(), bb.max.y()};
|
|
bb.translate(bedref - bbref);
|
|
break;
|
|
}
|
|
case Pivots::Center: {
|
|
bb.translate(bed.bb.center() - bb.center());
|
|
break;
|
|
}
|
|
}
|
|
|
|
Vec2crd d = bb.center() - pilebb[bedidx].center();
|
|
|
|
auto bedbb = bed.bb;
|
|
bedbb.offset(-params.min_bed_distance);
|
|
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 (itm.bed_idx == bedidx)
|
|
itm.translation += d;
|
|
}
|
|
}
|
|
|
|
} // namespace arr
|
|
} // namespace Slic3r
|