mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-07-31 13:31:58 +08:00
Arrange: Remove old arrange
This commit is contained in:
parent
3e33631abf
commit
ffc369e187
@ -1,780 +0,0 @@
|
||||
///|/ Copyright (c) Prusa Research 2018 - 2023 Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#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>
|
||||
#include <boost/container/small_vector.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
|
||||
boost::container::small_vector<SpatElement, 100> 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 == int(bedidx))
|
||||
itm.translation += d;
|
||||
}
|
||||
}
|
||||
|
||||
BoundingBox bounding_box(const InfiniteBed &bed)
|
||||
{
|
||||
BoundingBox ret;
|
||||
using C = coord_t;
|
||||
|
||||
// It is important for Mx and My to be strictly less than half of the
|
||||
// range of type C. width(), height() and area() will not overflow this way.
|
||||
C Mx = C((std::numeric_limits<C>::lowest() + 2 * bed.center.x()) / 4.01);
|
||||
C My = C((std::numeric_limits<C>::lowest() + 2 * bed.center.y()) / 4.01);
|
||||
|
||||
ret.max = bed.center - Point{Mx, My};
|
||||
ret.min = bed.center + Point{Mx, My};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace arr
|
||||
} // namespace Slic3r
|
@ -1,220 +0,0 @@
|
||||
///|/ Copyright (c) Prusa Research 2018 - 2023 Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef ARRANGE_HPP
|
||||
#define ARRANGE_HPP
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
#include <libslic3r/BoundingBox.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class BoundingBox;
|
||||
|
||||
namespace arrangement {
|
||||
|
||||
/// Representing an unbounded bed.
|
||||
struct InfiniteBed {
|
||||
Point center;
|
||||
explicit InfiniteBed(const Point &p = {0, 0}): center{p} {}
|
||||
};
|
||||
|
||||
struct RectangleBed {
|
||||
BoundingBox bb;
|
||||
};
|
||||
|
||||
/// A geometry abstraction for a circular print bed. Similarly to BoundingBox.
|
||||
class CircleBed {
|
||||
Point center_;
|
||||
double radius_;
|
||||
public:
|
||||
|
||||
inline CircleBed(): center_(0, 0), radius_(NaNd) {}
|
||||
explicit inline CircleBed(const Point& c, double r): center_(c), radius_(r) {}
|
||||
|
||||
inline double radius() const { return radius_; }
|
||||
inline const Point& center() const { return center_; }
|
||||
};
|
||||
|
||||
struct SegmentedRectangleBed {
|
||||
Vec<2, size_t> segments;
|
||||
BoundingBox bb;
|
||||
|
||||
SegmentedRectangleBed (const BoundingBox &bb,
|
||||
size_t segments_x,
|
||||
size_t segments_y)
|
||||
: segments{segments_x, segments_y}
|
||||
, bb{bb}
|
||||
{}
|
||||
};
|
||||
|
||||
struct IrregularBed {
|
||||
ExPolygon poly;
|
||||
};
|
||||
|
||||
//enum BedType { Infinite, Rectangle, Circle, SegmentedRectangle, Irregular };
|
||||
|
||||
using ArrangeBed = boost::variant<InfiniteBed, RectangleBed, CircleBed, SegmentedRectangleBed, IrregularBed>;
|
||||
|
||||
BoundingBox bounding_box(const InfiniteBed &bed);
|
||||
inline BoundingBox bounding_box(const RectangleBed &b) { return b.bb; }
|
||||
inline BoundingBox bounding_box(const SegmentedRectangleBed &b) { return b.bb; }
|
||||
inline BoundingBox bounding_box(const CircleBed &b)
|
||||
{
|
||||
auto r = static_cast<coord_t>(std::round(b.radius()));
|
||||
Point R{r, r};
|
||||
|
||||
return {b.center() - R, b.center() + R};
|
||||
}
|
||||
inline BoundingBox bounding_box(const ArrangeBed &b)
|
||||
{
|
||||
BoundingBox ret;
|
||||
auto visitor = [&ret](const auto &b) { ret = bounding_box(b); };
|
||||
boost::apply_visitor(visitor, b);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ArrangeBed to_arrange_bed(const Points &bedpts);
|
||||
|
||||
/// A logical bed representing an object not being arranged. Either the arrange
|
||||
/// has not yet successfully run on this ArrangePolygon or it could not fit the
|
||||
/// object due to overly large size or invalid geometry.
|
||||
static const constexpr int UNARRANGED = -1;
|
||||
|
||||
/// Input/Output structure for the arrange() function. The poly field will not
|
||||
/// be modified during arrangement. Instead, the translation and rotation fields
|
||||
/// will mark the needed transformation for the polygon to be in the arranged
|
||||
/// position. These can also be set to an initial offset and rotation.
|
||||
///
|
||||
/// The bed_idx field will indicate the logical bed into which the
|
||||
/// polygon belongs: UNARRANGED means no place for the polygon
|
||||
/// (also the initial state before arrange), 0..N means the index of the bed.
|
||||
/// Zero is the physical bed, larger than zero means a virtual bed.
|
||||
struct ArrangePolygon {
|
||||
ExPolygon poly; /// The 2D silhouette to be arranged
|
||||
Vec2crd translation{0, 0}; /// The translation of the poly
|
||||
double rotation{0.0}; /// The rotation of the poly in radians
|
||||
coord_t inflation = 0; /// Arrange with inflated polygon
|
||||
int bed_idx{UNARRANGED}; /// To which logical bed does poly belong...
|
||||
int priority{0};
|
||||
|
||||
// If empty, any rotation is allowed (currently unsupported)
|
||||
// If only a zero is there, no rotation is allowed
|
||||
std::vector<double> allowed_rotations = {0.};
|
||||
|
||||
/// Optional setter function which can store arbitrary data in its closure
|
||||
std::function<void(const ArrangePolygon&)> setter = nullptr;
|
||||
|
||||
/// Helper function to call the setter with the arrange data arguments
|
||||
void apply() const { if (setter) setter(*this); }
|
||||
|
||||
/// Test if arrange() was called previously and gave a successful result.
|
||||
bool is_arranged() const { return bed_idx != UNARRANGED; }
|
||||
|
||||
inline ExPolygon transformed_poly() const
|
||||
{
|
||||
ExPolygon ret = poly;
|
||||
ret.rotate(rotation);
|
||||
ret.translate(translation.x(), translation.y());
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
using ArrangePolygons = std::vector<ArrangePolygon>;
|
||||
|
||||
enum class Pivots {
|
||||
Center, TopLeft, BottomLeft, BottomRight, TopRight
|
||||
};
|
||||
|
||||
struct ArrangeParams {
|
||||
|
||||
/// The minimum distance which is allowed for any
|
||||
/// pair of items on the print bed in any direction.
|
||||
coord_t min_obj_distance = 0;
|
||||
|
||||
/// The minimum distance of any object from bed edges
|
||||
coord_t min_bed_distance = 0;
|
||||
|
||||
/// The accuracy of optimization.
|
||||
/// Goes from 0.0 to 1.0 and scales performance as well
|
||||
float accuracy = 1.f;
|
||||
|
||||
/// Allow parallel execution.
|
||||
bool parallel = true;
|
||||
|
||||
bool allow_rotations = false;
|
||||
|
||||
/// Final alignment of the merged pile after arrangement
|
||||
Pivots alignment = Pivots::Center;
|
||||
|
||||
/// Starting position hint for the arrangement
|
||||
Pivots starting_point = Pivots::Center;
|
||||
|
||||
/// Progress indicator callback called when an object gets packed.
|
||||
/// The unsigned argument is the number of items remaining to pack.
|
||||
std::function<void(unsigned)> progressind;
|
||||
|
||||
std::function<void(const ArrangePolygon &)> on_packed;
|
||||
|
||||
/// A predicate returning true if abort is needed.
|
||||
std::function<bool(void)> stopcondition;
|
||||
|
||||
ArrangeParams() = default;
|
||||
explicit ArrangeParams(coord_t md) : min_obj_distance(md) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Arranges the input polygons.
|
||||
*
|
||||
* WARNING: Currently, only convex polygons are supported by the libnest2d
|
||||
* library which is used to do the arrangement. This might change in the future
|
||||
* this is why the interface contains a general polygon capable to have holes.
|
||||
*
|
||||
* \param items Input vector of ArrangePolygons. The transformation, rotation
|
||||
* and bin_idx fields will be changed after the call finished and can be used
|
||||
* to apply the result on the input polygon.
|
||||
*/
|
||||
template<class TBed> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const TBed &bed, const ArrangeParams ¶ms = {});
|
||||
|
||||
// A dispatch function that determines the bed shape from a set of points.
|
||||
template<> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Points &bed, const ArrangeParams ¶ms);
|
||||
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms);
|
||||
|
||||
inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const RectangleBed &bed, const ArrangeParams ¶ms)
|
||||
{
|
||||
arrange(items, excludes, bed.bb, params);
|
||||
}
|
||||
|
||||
inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const IrregularBed &bed, const ArrangeParams ¶ms)
|
||||
{
|
||||
arrange(items, excludes, bed.poly.contour, params);
|
||||
}
|
||||
|
||||
void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const SegmentedRectangleBed &bed, const ArrangeParams ¶ms);
|
||||
|
||||
inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const ArrangeBed &bed, const ArrangeParams ¶ms)
|
||||
{
|
||||
auto call_arrange = [&](const auto &realbed) { arrange(items, excludes, realbed, params); };
|
||||
boost::apply_visitor(call_arrange, bed);
|
||||
}
|
||||
|
||||
inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
|
||||
bool is_box(const Points &bed);
|
||||
|
||||
}} // namespace Slic3r::arrangement
|
||||
|
||||
#endif // MODELARRANGE_HPP
|
@ -273,7 +273,6 @@ set(SLIC3R_SOURCES
|
||||
MeasureUtils.hpp
|
||||
CustomGCode.cpp
|
||||
CustomGCode.hpp
|
||||
Arrange/Arrange.hpp
|
||||
Arrange/ArrangeImpl.hpp
|
||||
Arrange/Items/ArrangeItem.hpp
|
||||
Arrange/Items/ArrangeItem.cpp
|
||||
|
@ -1,459 +0,0 @@
|
||||
///|/ Copyright (c) Prusa Research 2020 - 2023 Tomáš Mészáros @tamasmeszaros, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, David Kocík @kocikdav, Filip Sykala @Jony01, Lukáš Matěna @lukasmatena
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include "ArrangeJob.hpp"
|
||||
|
||||
#include "libslic3r/BuildVolume.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
#include "libslic3r/Geometry/ConvexHull.hpp"
|
||||
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||
#include "slic3r/GUI/NotificationManager.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
|
||||
|
||||
#include "libnest2d/common.hpp"
|
||||
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// Cache the wti info
|
||||
class WipeTower: public GLCanvas3D::WipeTowerInfo {
|
||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||
public:
|
||||
explicit WipeTower(const GLCanvas3D::WipeTowerInfo &wti)
|
||||
: GLCanvas3D::WipeTowerInfo(wti)
|
||||
{}
|
||||
|
||||
explicit WipeTower(GLCanvas3D::WipeTowerInfo &&wti)
|
||||
: GLCanvas3D::WipeTowerInfo(std::move(wti))
|
||||
{}
|
||||
|
||||
void apply_arrange_result(const Vec2d& tr, double rotation)
|
||||
{
|
||||
m_pos = unscaled(tr); m_rotation = rotation;
|
||||
apply_wipe_tower();
|
||||
}
|
||||
|
||||
ArrangePolygon get_arrange_polygon() const
|
||||
{
|
||||
Polygon ap({
|
||||
{scaled(m_bb.min)},
|
||||
{scaled(m_bb.max.x()), scaled(m_bb.min.y())},
|
||||
{scaled(m_bb.max)},
|
||||
{scaled(m_bb.min.x()), scaled(m_bb.max.y())}
|
||||
});
|
||||
|
||||
ArrangePolygon ret;
|
||||
ret.poly.contour = std::move(ap);
|
||||
ret.translation = scaled(m_pos);
|
||||
ret.rotation = m_rotation;
|
||||
++ret.priority;
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
static WipeTower get_wipe_tower(const Plater &plater)
|
||||
{
|
||||
return WipeTower{plater.canvas3D()->get_wipe_tower_info()};
|
||||
}
|
||||
|
||||
void ArrangeJob::clear_input()
|
||||
{
|
||||
const Model &model = m_plater->model();
|
||||
|
||||
size_t count = 0, cunprint = 0; // To know how much space to reserve
|
||||
for (auto obj : model.objects)
|
||||
for (auto mi : obj->instances)
|
||||
mi->printable ? count++ : cunprint++;
|
||||
|
||||
m_selected.clear();
|
||||
m_unselected.clear();
|
||||
m_unprintable.clear();
|
||||
m_unarranged.clear();
|
||||
m_selected.reserve(count + 1 /* for optional wti */);
|
||||
m_unselected.reserve(count + 1 /* for optional wti */);
|
||||
m_unprintable.reserve(cunprint /* for optional wti */);
|
||||
}
|
||||
|
||||
void ArrangeJob::prepare_all() {
|
||||
clear_input();
|
||||
|
||||
for (ModelObject *obj: m_plater->model().objects)
|
||||
for (ModelInstance *mi : obj->instances) {
|
||||
ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
|
||||
cont.emplace_back(get_arrange_poly_(mi));
|
||||
}
|
||||
|
||||
if (auto wti = get_wipe_tower_arrangepoly(*m_plater))
|
||||
m_selected.emplace_back(std::move(*wti));
|
||||
}
|
||||
|
||||
void ArrangeJob::prepare_selected() {
|
||||
clear_input();
|
||||
|
||||
Model &model = m_plater->model();
|
||||
|
||||
std::vector<const Selection::InstanceIdxsList *>
|
||||
obj_sel(model.objects.size(), nullptr);
|
||||
|
||||
for (auto &s : m_plater->get_selection().get_content())
|
||||
if (s.first < int(obj_sel.size()))
|
||||
obj_sel[size_t(s.first)] = &s.second;
|
||||
|
||||
// Go through the objects and check if inside the selection
|
||||
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
|
||||
const Selection::InstanceIdxsList * instlist = obj_sel[oidx];
|
||||
ModelObject *mo = model.objects[oidx];
|
||||
|
||||
std::vector<bool> inst_sel(mo->instances.size(), false);
|
||||
|
||||
if (instlist)
|
||||
for (auto inst_id : *instlist)
|
||||
inst_sel[size_t(inst_id)] = true;
|
||||
|
||||
for (size_t i = 0; i < inst_sel.size(); ++i) {
|
||||
ModelInstance * mi = mo->instances[i];
|
||||
ArrangePolygon &&ap = get_arrange_poly_(mi);
|
||||
|
||||
ArrangePolygons &cont = mo->instances[i]->printable ?
|
||||
(inst_sel[i] ? m_selected :
|
||||
m_unselected) :
|
||||
m_unprintable;
|
||||
|
||||
cont.emplace_back(std::move(ap));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto wti = get_wipe_tower(*m_plater)) {
|
||||
ArrangePolygon &&ap = get_arrange_poly(wti, m_plater);
|
||||
|
||||
auto &cont = m_plater->get_selection().is_wipe_tower() ? m_selected :
|
||||
m_unselected;
|
||||
cont.emplace_back(std::move(ap));
|
||||
}
|
||||
|
||||
// If the selection was empty arrange everything
|
||||
if (m_selected.empty())
|
||||
m_selected.swap(m_unselected);
|
||||
}
|
||||
|
||||
static void update_arrangepoly_slaprint(arrangement::ArrangePolygon &ret,
|
||||
const SLAPrintObject &po,
|
||||
const ModelInstance &inst)
|
||||
{
|
||||
// The 1.1 multiplier is a safety gap, as the offset might be bigger
|
||||
// in sharp edges of a polygon, depending on clipper's offset algorithm
|
||||
coord_t pad_infl = 0;
|
||||
{
|
||||
double infl = po.config().pad_enable.getBool() * (
|
||||
po.config().pad_brim_size.getFloat() +
|
||||
po.config().pad_around_object.getBool() *
|
||||
po.config().pad_object_gap.getFloat() );
|
||||
|
||||
pad_infl = scaled(1.1 * infl);
|
||||
}
|
||||
|
||||
auto laststep = po.last_completed_step();
|
||||
|
||||
if (laststep < slaposCount && laststep > slaposSupportTree) {
|
||||
auto omesh = po.get_mesh_to_print();
|
||||
auto &smesh = po.support_mesh();
|
||||
|
||||
Transform3f trafo_instance = inst.get_matrix().cast<float>();
|
||||
trafo_instance = trafo_instance * po.trafo().cast<float>().inverse();
|
||||
|
||||
Polygons polys;
|
||||
polys.reserve(3);
|
||||
auto zlvl = -po.get_elevation();
|
||||
|
||||
if (omesh) {
|
||||
polys.emplace_back(its_convex_hull_2d_above(*omesh, trafo_instance, zlvl));
|
||||
}
|
||||
|
||||
polys.emplace_back(its_convex_hull_2d_above(smesh.its, trafo_instance, zlvl));
|
||||
ret.poly.contour = Geometry::convex_hull(polys);
|
||||
ret.poly.holes = {};
|
||||
}
|
||||
|
||||
ret.inflation = pad_infl;
|
||||
}
|
||||
|
||||
static coord_t brim_offset(const PrintObject &po, const ModelInstance &inst)
|
||||
{
|
||||
const BrimType brim_type = po.config().brim_type.value;
|
||||
const float brim_separation = po.config().brim_separation.getFloat();
|
||||
const float brim_width = po.config().brim_width.getFloat();
|
||||
const bool has_outer_brim = brim_type == BrimType::btOuterOnly ||
|
||||
brim_type == BrimType::btOuterAndInner;
|
||||
|
||||
// How wide is the brim? (in scaled units)
|
||||
return has_outer_brim ? scaled(brim_width + brim_separation) : 0;
|
||||
}
|
||||
|
||||
arrangement::ArrangePolygon ArrangeJob::get_arrange_poly_(ModelInstance *mi)
|
||||
{
|
||||
arrangement::ArrangePolygon ap = get_arrange_poly(mi, m_plater);
|
||||
|
||||
auto setter = ap.setter;
|
||||
ap.setter = [this, setter, mi](const arrangement::ArrangePolygon &set_ap) {
|
||||
setter(set_ap);
|
||||
if (!set_ap.is_arranged())
|
||||
m_unarranged.emplace_back(mi);
|
||||
};
|
||||
|
||||
return ap;
|
||||
}
|
||||
|
||||
coord_t get_skirt_offset(const Plater* plater) {
|
||||
float skirt_inset = 0.f;
|
||||
// Try to subtract the skirt from the bed shape so we don't arrange outside of it.
|
||||
if (plater->printer_technology() == ptFFF && plater->fff_print().has_skirt()) {
|
||||
const auto& print = plater->fff_print();
|
||||
if (!print.objects().empty()) {
|
||||
skirt_inset = print.config().skirts.value * print.skirt_flow().width() +
|
||||
print.config().skirt_distance.value;
|
||||
}
|
||||
}
|
||||
|
||||
return scaled(skirt_inset);
|
||||
}
|
||||
|
||||
void ArrangeJob::prepare()
|
||||
{
|
||||
m_selection_only ? prepare_selected() :
|
||||
prepare_all();
|
||||
|
||||
coord_t min_offset = 0;
|
||||
for (auto &ap : m_selected) {
|
||||
min_offset = std::max(ap.inflation, min_offset);
|
||||
}
|
||||
|
||||
if (m_plater->printer_technology() == ptSLA) {
|
||||
// Apply the max offset for all the objects
|
||||
for (auto &ap : m_selected) {
|
||||
ap.inflation = min_offset;
|
||||
}
|
||||
} else { // it's fff, brims only need to be minded from bed edges
|
||||
for (auto &ap : m_selected) {
|
||||
ap.inflation = 0;
|
||||
}
|
||||
m_min_bed_inset = min_offset;
|
||||
}
|
||||
|
||||
double stride = bed_stride(m_plater);
|
||||
get_bed_shape(*m_plater->config(), m_bed);
|
||||
assign_logical_beds(m_unselected, m_bed, stride);
|
||||
}
|
||||
|
||||
void ArrangeJob::process(Ctl &ctl)
|
||||
{
|
||||
static const auto arrangestr = _u8L("Arranging");
|
||||
|
||||
arrangement::ArrangeParams params;
|
||||
ctl.call_on_main_thread([this, ¶ms]{
|
||||
prepare();
|
||||
params = get_arrange_params(m_plater);
|
||||
coord_t min_inset = get_skirt_offset(m_plater) + m_min_bed_inset;
|
||||
params.min_bed_distance = std::max(params.min_bed_distance, min_inset);
|
||||
}).wait();
|
||||
|
||||
auto count = unsigned(m_selected.size() + m_unprintable.size());
|
||||
|
||||
if (count == 0) // Should be taken care of by plater, but doesn't hurt
|
||||
return;
|
||||
|
||||
ctl.update_status(0, arrangestr);
|
||||
|
||||
params.stopcondition = [&ctl]() { return ctl.was_canceled(); };
|
||||
|
||||
params.progressind = [this, count, &ctl](unsigned st) {
|
||||
st += m_unprintable.size();
|
||||
if (st > 0) ctl.update_status(int(count - st) * 100 / status_range(), arrangestr);
|
||||
};
|
||||
|
||||
ctl.update_status(0, arrangestr);
|
||||
|
||||
arrangement::arrange(m_selected, m_unselected, m_bed, params);
|
||||
|
||||
params.progressind = [this, count, &ctl](unsigned st) {
|
||||
if (st > 0) ctl.update_status(int(count - st) * 100 / status_range(), arrangestr);
|
||||
};
|
||||
|
||||
arrangement::arrange(m_unprintable, {}, m_bed, params);
|
||||
|
||||
// finalize just here.
|
||||
ctl.update_status(int(count) * 100 / status_range(), ctl.was_canceled() ?
|
||||
_u8L("Arranging canceled.") :
|
||||
_u8L("Arranging done."));
|
||||
}
|
||||
|
||||
ArrangeJob::ArrangeJob(Mode mode)
|
||||
: m_plater{wxGetApp().plater()},
|
||||
m_selection_only{mode == Mode::SelectionOnly}
|
||||
{}
|
||||
|
||||
static std::string concat_strings(const std::set<std::string> &strings,
|
||||
const std::string &delim = "\n")
|
||||
{
|
||||
return std::accumulate(
|
||||
strings.begin(), strings.end(), std::string(""),
|
||||
[delim](const std::string &s, const std::string &name) {
|
||||
return s + name + delim;
|
||||
});
|
||||
}
|
||||
|
||||
void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) {
|
||||
try {
|
||||
if (eptr)
|
||||
std::rethrow_exception(eptr);
|
||||
} catch (libnest2d::GeometryException &) {
|
||||
show_error(m_plater, _(L("Could not arrange model objects! "
|
||||
"Some geometries may be invalid.")));
|
||||
eptr = nullptr;
|
||||
} catch(...) {
|
||||
eptr = std::current_exception();
|
||||
}
|
||||
|
||||
if (canceled || eptr)
|
||||
return;
|
||||
|
||||
// Unprintable items go to the last virtual bed
|
||||
int beds = 0;
|
||||
|
||||
// Apply the arrange result to all selected objects
|
||||
for (ArrangePolygon &ap : m_selected) {
|
||||
beds = std::max(ap.bed_idx, beds);
|
||||
ap.apply();
|
||||
}
|
||||
|
||||
// Get the virtual beds from the unselected items
|
||||
for (ArrangePolygon &ap : m_unselected)
|
||||
beds = std::max(ap.bed_idx, beds);
|
||||
|
||||
// Move the unprintable items to the last virtual bed.
|
||||
for (ArrangePolygon &ap : m_unprintable) {
|
||||
if (ap.bed_idx >= 0)
|
||||
ap.bed_idx += beds + 1;
|
||||
|
||||
ap.apply();
|
||||
}
|
||||
|
||||
m_plater->update(static_cast<unsigned int>(
|
||||
Plater::UpdateParams::FORCE_FULL_SCREEN_REFRESH));
|
||||
|
||||
wxGetApp().obj_manipul()->set_dirty();
|
||||
|
||||
if (!m_unarranged.empty()) {
|
||||
std::set<std::string> names;
|
||||
for (ModelInstance *mi : m_unarranged)
|
||||
names.insert(mi->get_object()->name);
|
||||
|
||||
m_plater->get_notification_manager()->push_notification(GUI::format(
|
||||
_L("Arrangement ignored the following objects which can't fit into a single bed:\n%s"),
|
||||
concat_strings(names, "\n")));
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<arrangement::ArrangePolygon>
|
||||
get_wipe_tower_arrangepoly(const Plater &plater)
|
||||
{
|
||||
if (auto wti = get_wipe_tower(plater))
|
||||
return get_arrange_poly(wti, &plater);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
double bed_stride(const Plater *plater) {
|
||||
double bedwidth = plater->build_volume().bounding_volume().size().x();
|
||||
return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
|
||||
}
|
||||
|
||||
template<>
|
||||
arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst,
|
||||
const Plater * plater)
|
||||
{
|
||||
auto ap = get_arrange_poly(PtrWrapper{inst}, plater);
|
||||
|
||||
auto obj_id = inst->get_object()->id();
|
||||
if (plater->printer_technology() == ptSLA) {
|
||||
const SLAPrintObject *po =
|
||||
plater->sla_print().get_print_object_by_model_object_id(obj_id);
|
||||
|
||||
if (po) {
|
||||
update_arrangepoly_slaprint(ap, *po, *inst);
|
||||
}
|
||||
} else {
|
||||
const PrintObject *po =
|
||||
plater->fff_print().get_print_object_by_model_object_id(obj_id);
|
||||
|
||||
if (po) {
|
||||
ap.inflation = brim_offset(*po, *inst);
|
||||
}
|
||||
}
|
||||
|
||||
return ap;
|
||||
}
|
||||
|
||||
arrangement::ArrangeParams get_arrange_params(Plater *p)
|
||||
{
|
||||
const arr2::ArrangeSettingsView *settings =
|
||||
p->canvas3D()->get_arrange_settings_view();
|
||||
|
||||
arrangement::ArrangeParams params;
|
||||
params.allow_rotations = settings->is_rotation_enabled();
|
||||
params.min_obj_distance = scaled(settings->get_distance_from_objects());
|
||||
params.min_bed_distance = scaled(settings->get_distance_from_bed());
|
||||
|
||||
arrangement::Pivots pivot = arrangement::Pivots::Center;
|
||||
|
||||
int pivot_max = static_cast<int>(arrangement::Pivots::TopRight);
|
||||
if (settings->get_xl_alignment() < 0) {
|
||||
pivot = arrangement::Pivots::Center;
|
||||
} else if (settings->get_xl_alignment() == 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, pivot_max);
|
||||
pivot = static_cast<arrangement::Pivots>(dist(rng));
|
||||
} else {
|
||||
pivot = static_cast<arrangement::Pivots>(settings->get_xl_alignment());
|
||||
}
|
||||
|
||||
params.alignment = pivot;
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
void assign_logical_beds(std::vector<arrangement::ArrangePolygon> &items,
|
||||
const arrangement::ArrangeBed &bed,
|
||||
double stride)
|
||||
{
|
||||
// The strides have to be removed from the fixed items. For the
|
||||
// arrangeable (selected) items bed_idx is ignored and the
|
||||
// translation is irrelevant.
|
||||
coord_t bedx = bounding_box(bed).min.x();
|
||||
for (auto &itm : items) {
|
||||
auto bedidx = std::max(arrangement::UNARRANGED,
|
||||
static_cast<int>(std::floor(
|
||||
(get_extents(itm.transformed_poly()).min.x() - bedx) /
|
||||
stride)));
|
||||
|
||||
itm.bed_idx = bedidx;
|
||||
|
||||
if (bedidx >= 0)
|
||||
itm.translation.x() -= bedidx * stride;
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
@ -1,122 +0,0 @@
|
||||
///|/ Copyright (c) Prusa Research 2020 - 2023 Tomáš Mészáros @tamasmeszaros, David Kocík @kocikdav
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef ARRANGEJOB_HPP
|
||||
#define ARRANGEJOB_HPP
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "Job.hpp"
|
||||
#include "libslic3r/Arrange.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ModelInstance;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class Plater;
|
||||
|
||||
class ArrangeJob : public Job
|
||||
{
|
||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||
using ArrangePolygons = arrangement::ArrangePolygons;
|
||||
|
||||
ArrangePolygons m_selected, m_unselected, m_unprintable;
|
||||
std::vector<ModelInstance*> m_unarranged;
|
||||
arrangement::ArrangeBed m_bed;
|
||||
coord_t m_min_bed_inset = 0.;
|
||||
|
||||
Plater *m_plater;
|
||||
bool m_selection_only = false;
|
||||
|
||||
// clear m_selected and m_unselected, reserve space for next usage
|
||||
void clear_input();
|
||||
|
||||
// Prepare all objects on the bed regardless of the selection
|
||||
void prepare_all();
|
||||
|
||||
// Prepare the selected and unselected items separately. If nothing is
|
||||
// selected, behaves as if everything would be selected.
|
||||
void prepare_selected();
|
||||
|
||||
ArrangePolygon get_arrange_poly_(ModelInstance *mi);
|
||||
|
||||
public:
|
||||
|
||||
enum Mode { Full, SelectionOnly };
|
||||
|
||||
void prepare();
|
||||
|
||||
void process(Ctl &ctl) override;
|
||||
|
||||
ArrangeJob(Mode mode = Full);
|
||||
|
||||
int status_range() const
|
||||
{
|
||||
return int(m_selected.size() + m_unprintable.size());
|
||||
}
|
||||
|
||||
void finalize(bool canceled, std::exception_ptr &e) override;
|
||||
};
|
||||
|
||||
std::optional<arrangement::ArrangePolygon> get_wipe_tower_arrangepoly(const Plater &);
|
||||
|
||||
// The gap between logical beds in the x axis expressed in ratio of
|
||||
// the current bed width.
|
||||
static const constexpr double LOGICAL_BED_GAP = 1. / 5.;
|
||||
|
||||
// Stride between logical beds
|
||||
double bed_stride(const Plater *plater);
|
||||
|
||||
template<class T> struct PtrWrapper
|
||||
{
|
||||
T *ptr;
|
||||
|
||||
explicit PtrWrapper(T *p) : ptr{p} {}
|
||||
|
||||
arrangement::ArrangePolygon get_arrange_polygon() const
|
||||
{
|
||||
return ptr->get_arrange_polygon();
|
||||
}
|
||||
|
||||
void apply_arrange_result(const Vec2d &t, double rot)
|
||||
{
|
||||
ptr->apply_arrange_result(t, rot);
|
||||
}
|
||||
};
|
||||
|
||||
// Set up arrange polygon for a ModelInstance and Wipe tower
|
||||
template<class T>
|
||||
arrangement::ArrangePolygon get_arrange_poly(T obj, const Plater *plater)
|
||||
{
|
||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||
|
||||
ArrangePolygon ap = obj.get_arrange_polygon();
|
||||
ap.setter = [obj, plater](const ArrangePolygon &p) {
|
||||
if (p.is_arranged()) {
|
||||
Vec2d t = p.translation.cast<double>();
|
||||
t.x() += p.bed_idx * bed_stride(plater);
|
||||
T{obj}.apply_arrange_result(t, p.rotation);
|
||||
}
|
||||
};
|
||||
|
||||
return ap;
|
||||
}
|
||||
|
||||
template<>
|
||||
arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst,
|
||||
const Plater * plater);
|
||||
|
||||
arrangement::ArrangeParams get_arrange_params(Plater *p);
|
||||
|
||||
coord_t get_skirt_offset(const Plater* plater);
|
||||
|
||||
void assign_logical_beds(std::vector<arrangement::ArrangePolygon> &items,
|
||||
const arrangement::ArrangeBed &bed,
|
||||
double stride);
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // ARRANGEJOB_HPP
|
@ -1,207 +0,0 @@
|
||||
///|/ Copyright (c) Prusa Research 2020 - 2023 Tomáš Mészáros @tamasmeszaros
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include "FillBedJob.hpp"
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
|
||||
#include <numeric>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
void FillBedJob::prepare()
|
||||
{
|
||||
m_selected.clear();
|
||||
m_unselected.clear();
|
||||
m_min_bed_inset = 0.;
|
||||
|
||||
m_object_idx = m_plater->get_selected_object_idx();
|
||||
if (m_object_idx == -1)
|
||||
return;
|
||||
|
||||
ModelObject *model_object = m_plater->model().objects[m_object_idx];
|
||||
if (model_object->instances.empty())
|
||||
return;
|
||||
|
||||
m_selected.reserve(model_object->instances.size());
|
||||
for (ModelInstance *inst : model_object->instances)
|
||||
if (inst->printable) {
|
||||
ArrangePolygon ap = get_arrange_poly(inst, m_plater);
|
||||
// Existing objects need to be included in the result. Only
|
||||
// the needed amount of object will be added, no more.
|
||||
++ap.priority;
|
||||
m_selected.emplace_back(ap);
|
||||
}
|
||||
|
||||
if (m_selected.empty())
|
||||
return;
|
||||
|
||||
Points bedpts = get_bed_shape(*m_plater->config());
|
||||
|
||||
auto &objects = m_plater->model().objects;
|
||||
BoundingBox bedbb = get_extents(bedpts);
|
||||
|
||||
for (size_t idx = 0; idx < objects.size(); ++idx)
|
||||
if (int(idx) != m_object_idx)
|
||||
for (ModelInstance *mi : objects[idx]->instances) {
|
||||
ArrangePolygon ap = get_arrange_poly(PtrWrapper{mi}, m_plater);
|
||||
auto ap_bb = ap.transformed_poly().contour.bounding_box();
|
||||
|
||||
if (ap.bed_idx == 0 && !bedbb.contains(ap_bb))
|
||||
ap.bed_idx = arrangement::UNARRANGED;
|
||||
|
||||
m_unselected.emplace_back(ap);
|
||||
}
|
||||
|
||||
if (auto wt = get_wipe_tower_arrangepoly(*m_plater))
|
||||
m_unselected.emplace_back(std::move(*wt));
|
||||
|
||||
double sc = scaled<double>(1.) * scaled(1.);
|
||||
|
||||
ExPolygon poly = m_selected.front().poly;
|
||||
double poly_area = poly.area() / sc;
|
||||
double unsel_area = std::accumulate(m_unselected.begin(),
|
||||
m_unselected.end(), 0.,
|
||||
[](double s, const auto &ap) {
|
||||
return s + (ap.bed_idx == 0) * ap.poly.area();
|
||||
}) / sc;
|
||||
|
||||
double fixed_area = unsel_area + m_selected.size() * poly_area;
|
||||
double bed_area = Polygon{bedpts}.area() / sc;
|
||||
|
||||
// This is the maximum number of items, the real number will always be close but less.
|
||||
int needed_items = (bed_area - fixed_area) / poly_area;
|
||||
|
||||
int sel_id = m_plater->get_selection().get_instance_idx();
|
||||
// if the selection is not a single instance, choose the first as template
|
||||
sel_id = std::max(sel_id, 0);
|
||||
ModelInstance *mi = model_object->instances[sel_id];
|
||||
ArrangePolygon template_ap = get_arrange_poly(PtrWrapper{mi}, m_plater);
|
||||
|
||||
for (int i = 0; i < needed_items; ++i) {
|
||||
ArrangePolygon ap = template_ap;
|
||||
ap.bed_idx = arrangement::UNARRANGED;
|
||||
auto m = mi->get_transformation();
|
||||
ap.setter = [this, m](const ArrangePolygon &p) {
|
||||
ModelObject *mo = m_plater->model().objects[m_object_idx];
|
||||
ModelInstance *inst = mo->add_instance(m);
|
||||
inst->apply_arrange_result(p.translation.cast<double>(), p.rotation);
|
||||
};
|
||||
m_selected.emplace_back(ap);
|
||||
}
|
||||
|
||||
m_status_range = m_selected.size();
|
||||
|
||||
coord_t min_offset = 0;
|
||||
for (auto &ap : m_selected) {
|
||||
min_offset = std::max(ap.inflation, min_offset);
|
||||
}
|
||||
|
||||
if (m_plater->printer_technology() == ptSLA) {
|
||||
// Apply the max offset for all the objects
|
||||
for (auto &ap : m_selected) {
|
||||
ap.inflation = min_offset;
|
||||
}
|
||||
} else { // it's fff, brims only need to be minded from bed edges
|
||||
for (auto &ap : m_selected) {
|
||||
ap.inflation = 0;
|
||||
}
|
||||
m_min_bed_inset = min_offset;
|
||||
}
|
||||
|
||||
// The strides have to be removed from the fixed items. For the
|
||||
// arrangeable (selected) items bed_idx is ignored and the
|
||||
// translation is irrelevant.
|
||||
double stride = bed_stride(m_plater);
|
||||
|
||||
m_bed = arrangement::to_arrange_bed(bedpts);
|
||||
assign_logical_beds(m_unselected, m_bed, stride);
|
||||
}
|
||||
|
||||
void FillBedJob::process(Ctl &ctl)
|
||||
{
|
||||
auto statustxt = _u8L("Filling bed");
|
||||
arrangement::ArrangeParams params;
|
||||
ctl.call_on_main_thread([this, ¶ms] {
|
||||
prepare();
|
||||
params = get_arrange_params(m_plater);
|
||||
coord_t min_inset = get_skirt_offset(m_plater) + m_min_bed_inset;
|
||||
params.min_bed_distance = std::max(params.min_bed_distance, min_inset);
|
||||
}).wait();
|
||||
ctl.update_status(0, statustxt);
|
||||
|
||||
if (m_object_idx == -1 || m_selected.empty())
|
||||
return;
|
||||
|
||||
bool do_stop = false;
|
||||
params.stopcondition = [&ctl, &do_stop]() {
|
||||
return ctl.was_canceled() || do_stop;
|
||||
};
|
||||
|
||||
params.progressind = [this, &ctl, &statustxt](unsigned st) {
|
||||
if (st > 0)
|
||||
ctl.update_status(int(m_status_range - st) * 100 / status_range(), statustxt);
|
||||
};
|
||||
|
||||
params.on_packed = [&do_stop] (const ArrangePolygon &ap) {
|
||||
do_stop = ap.bed_idx > 0 && ap.priority == 0;
|
||||
};
|
||||
|
||||
arrangement::arrange(m_selected, m_unselected, m_bed, params);
|
||||
|
||||
// finalize just here.
|
||||
ctl.update_status(100, ctl.was_canceled() ?
|
||||
_u8L("Bed filling canceled.") :
|
||||
_u8L("Bed filling done."));
|
||||
}
|
||||
|
||||
FillBedJob::FillBedJob() : m_plater{wxGetApp().plater()} {}
|
||||
|
||||
void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||
{
|
||||
// Ignore the arrange result if aborted.
|
||||
if (canceled || eptr)
|
||||
return;
|
||||
|
||||
if (m_object_idx == -1)
|
||||
return;
|
||||
|
||||
ModelObject *model_object = m_plater->model().objects[m_object_idx];
|
||||
if (model_object->instances.empty())
|
||||
return;
|
||||
|
||||
size_t inst_cnt = model_object->instances.size();
|
||||
|
||||
int added_cnt = std::accumulate(m_selected.begin(), m_selected.end(), 0, [](int s, auto &ap) {
|
||||
return s + int(ap.priority == 0 && ap.bed_idx == 0);
|
||||
});
|
||||
|
||||
if (added_cnt > 0) {
|
||||
for (ArrangePolygon &ap : m_selected) {
|
||||
if (ap.bed_idx != arrangement::UNARRANGED && (ap.priority != 0 || ap.bed_idx == 0))
|
||||
ap.apply();
|
||||
}
|
||||
|
||||
model_object->ensure_on_bed();
|
||||
|
||||
m_plater->update(static_cast<unsigned int>(
|
||||
Plater::UpdateParams::FORCE_FULL_SCREEN_REFRESH));
|
||||
|
||||
// FIXME: somebody explain why this is needed for increase_object_instances
|
||||
if (inst_cnt == 1)
|
||||
added_cnt++;
|
||||
|
||||
m_plater->sidebar()
|
||||
.obj_list()->increase_object_instances(m_object_idx, size_t(added_cnt));
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
@ -1,46 +0,0 @@
|
||||
///|/ Copyright (c) Prusa Research 2020 - 2023 Tomáš Mészáros @tamasmeszaros, David Kocík @kocikdav
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef FILLBEDJOB_HPP
|
||||
#define FILLBEDJOB_HPP
|
||||
|
||||
#include "ArrangeJob.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class Plater;
|
||||
|
||||
class FillBedJob : public Job
|
||||
{
|
||||
int m_object_idx = -1;
|
||||
|
||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||
using ArrangePolygons = arrangement::ArrangePolygons;
|
||||
|
||||
ArrangePolygons m_selected;
|
||||
ArrangePolygons m_unselected;
|
||||
coord_t m_min_bed_inset = 0.;
|
||||
|
||||
arrangement::ArrangeBed m_bed;
|
||||
|
||||
int m_status_range = 0;
|
||||
Plater *m_plater;
|
||||
|
||||
public:
|
||||
void prepare();
|
||||
void process(Ctl &ctl) override;
|
||||
|
||||
FillBedJob();
|
||||
|
||||
int status_range() const /*override*/
|
||||
{
|
||||
return m_status_range;
|
||||
}
|
||||
|
||||
void finalize(bool canceled, std::exception_ptr &e) override;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // FILLBEDJOB_HPP
|
Loading…
x
Reference in New Issue
Block a user