Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_custom_bed

This commit is contained in:
Enrico Turri 2019-07-22 11:26:02 +02:00
commit c1dee0e87d
28 changed files with 2042 additions and 2063 deletions

642
src/libslic3r/Arrange.cpp Normal file
View file

@ -0,0 +1,642 @@
#include "Arrange.hpp"
#include "Geometry.hpp"
#include "SVG.hpp"
#include "MTUtils.hpp"
#include <libnest2d/backends/clipper/geometries.hpp>
#include <libnest2d/optimizers/nlopt/subplex.hpp>
#include <libnest2d/placers/nfpplacer.hpp>
#include <libnest2d/selections/firstfit.hpp>
#include <numeric>
#include <ClipperUtils.hpp>
#include <boost/geometry/index/rtree.hpp>
#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 SLIC3R_CONSTEXPR Eigen::Matrix<Tout, 2, EigenArgs...> unscaled(
const ClipperLib::IntPoint &v) SLIC3R_NOEXCEPT
{
return Eigen::Matrix<Tout, 2, EigenArgs...>{unscaled<Tout>(v.X),
unscaled<Tout>(v.Y)};
}
namespace arrangement {
using namespace libnest2d;
namespace clppr = ClipperLib;
// Get the libnest2d types for clipper backend
using Item = _Item<clppr::Polygon>;
using Box = _Box<clppr::IntPoint>;
using Circle = _Circle<clppr::IntPoint>;
using Segment = _Segment<clppr::IntPoint>;
using MultiPolygon = TMultiShape<clppr::Polygon>;
// 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 fillConfig(PConf& pcfg) {
// Align the arranged pile into the center of the bin
pcfg.alignment = PConf::Alignment::CENTER;
// 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.
pcfg.rotations = { 0.0 };
// The accuracy of optimization.
// Goes from 0.0 to 1.0 and scales performance as well
pcfg.accuracy = 0.65f;
// Allow parallel execution.
pcfg.parallel = true;
}
// Apply penalty to object function result. This is used only when alignment
// after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN)
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<clppr::Polygon, TBin>;
using Selector = selections::_FirstFitSelection<clppr::Polygon>;
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;
SpatIndex m_rtree; // spatial index for the normal (bigger) objects
SpatIndex m_smallsrtree; // spatial index for only the smaller items
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 (m_items at the beginning)
ItemGroup m_items; // The 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 clppr::IntPoint &bincenter)
{
const double bin_area = m_bin_area;
const SpatIndex& spatindex = m_rtree;
const SpatIndex& smalls_spatindex = m_smallsrtree;
const ItemGroup& remaining = m_remaining;
// 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 && !remaining.empty()) compute_case = BIG_ITEM;
else if (bigitems && remaining.empty()) compute_case = LAST_BIG_ITEM;
else compute_case = SMALL_ITEM;
switch (compute_case) {
case BIG_ITEM: {
const clppr::IntPoint& minc = ibb.minCorner(); // bottom left corner
const clppr::IntPoint& maxc = ibb.maxCorner(); // top right corner
// top left and bottom right corners
clppr::IntPoint top_left{getX(minc), getY(maxc)};
clppr::IntPoint 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()));
// 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.5 * dist + 0.5 * density;
else
score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score;
break;
}
case LAST_BIG_ITEM: {
auto mp = m_merged_pile;
mp.emplace_back(item.transformedShape());
auto chull = sl::convexHull(mp);
placers::EdgeCache<clppr::Polygon> ec(chull);
double circ = norm(ec.circumference());
double bcirc = 2.0 * norm(fullbb.width() + fullbb.height());
score = 0.5 * circ + 0.5 * bcirc;
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,
Distance dist,
std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcond)
: m_pck(bin, dist)
, m_bin(bin)
, m_bin_area(sl::area(bin))
, m_norm(std::sqrt(m_bin_area))
{
fillConfig(m_pconf);
// 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();
if (progressind) m_pck.progressIndicator(progressind);
if (stopcond) m_pck.stopCondition(stopcond);
m_pck.configure(m_pconf);
}
template<class...Args> inline void operator()(Args&&...args) {
m_rtree.clear(); /*m_preload_idx.clear();*/
m_pck.execute(std::forward<Args>(args)...);
}
inline void preload(std::vector<Item>& fixeditems) {
m_pconf.alignment = PConfig::Alignment::DONT_ALIGN;
auto bb = sl::boundingBox(m_bin);
auto bbcenter = bb.center();
m_pconf.object_function = [this, bb, bbcenter](const Item &item) {
return fixed_overfit(objfunc(item, bbcenter), bb);
};
// Build the rtree for queries to work
for(unsigned idx = 0; idx < fixeditems.size(); ++idx) {
Item& itm = fixeditems[idx];
itm.markAsFixed();
}
m_pck.configure(m_pconf);
}
};
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);
auto isBig = [this](const Item& itm) {
return itm.area() / m_bin_area > BIG_ITEM_TRESHOLD ;
};
if(isBig(item)) {
auto mp = m_merged_pile;
mp.push_back(item.transformedShape());
auto chull = sl::convexHull(mp);
double miss = Placer::overfit(chull, m_bin);
if(miss < 0) miss = 0;
score += miss*miss;
}
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<clppr::Polygon>::get_objfn()
{
auto bincenter = sl::boundingBox(m_bin).center();
return [this, bincenter](const Item &item) {
return std::get<0>(objfunc(item, bincenter));
};
}
inline Circle to_lnCircle(const CircleBed& circ) {
return Circle({circ.center()(0), circ.center()(1)}, circ.radius());
}
// Get the type of bed geometry from a simple vector of points.
BedShapeHint::BedShapeHint(const Polyline &bed) {
auto x = [](const Point& p) { return p(X); };
auto y = [](const Point& p) { return p(Y); };
auto width = [x](const BoundingBox& box) {
return x(box.max) - x(box.min);
};
auto height = [y](const BoundingBox& box) {
return y(box.max) - y(box.min);
};
auto area = [&width, &height](const BoundingBox& box) {
double w = width(box);
double h = height(box);
return w * h;
};
auto poly_area = [](Polyline p) {
Polygon pp; pp.points.reserve(p.points.size() + 1);
pp.points = std::move(p.points);
pp.points.emplace_back(pp.points.front());
return std::abs(pp.area());
};
auto distance_to = [x, y](const Point& p1, const Point& p2) {
double dx = x(p2) - x(p1);
double dy = y(p2) - y(p1);
return std::sqrt(dx*dx + dy*dy);
};
auto bb = bed.bounding_box();
auto isCircle = [bb, distance_to](const Polyline& polygon) {
auto center = bb.center();
std::vector<double> vertex_distances;
double avg_dist = 0;
for (auto pt: polygon.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 = CircleBed();
break;
}
}
return ret;
};
auto parea = poly_area(bed);
if( (1.0 - parea/area(bb)) < 1e-3 ) {
m_type = BedShapes::bsBox;
m_bed.box = bb;
}
else if(auto c = isCircle(bed)) {
m_type = BedShapes::bsCircle;
m_bed.circ = c;
} else {
m_type = BedShapes::bsIrregular;
m_bed.polygon = bed;
}
}
template<class BinT> // Arrange for arbitrary bin type
void _arrange(
std::vector<Item> & shapes,
std::vector<Item> & excludes,
const BinT & bin,
coord_t minobjd,
std::function<void(unsigned)> prind,
std::function<bool()> stopfn)
{
// Integer ceiling the min distance from the bed perimeters
coord_t md = minobjd - 2 * scaled(0.1 + EPSILON);
md = (md % 2) ? md / 2 + 1 : md / 2;
auto corrected_bin = bin;
sl::offset(corrected_bin, md);
AutoArranger<BinT> arranger{corrected_bin, 0, prind, stopfn};
auto infl = coord_t(std::ceil(minobjd / 2.0));
for (Item& itm : shapes) itm.inflate(infl);
for (Item& itm : excludes) itm.inflate(infl);
auto it = excludes.begin();
while (it != excludes.end())
sl::isInside(it->transformedShape(), corrected_bin) ?
++it : it = excludes.erase(it);
// 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);
arranger(inp.begin(), inp.end());
for (Item &itm : inp) itm.inflate(-infl);
}
// The final client function for arrangement. A progress indicator and
// a stop predicate can be also be passed to control the process.
void arrange(ArrangePolygons & arrangables,
const ArrangePolygons & excludes,
coord_t min_obj_dist,
const BedShapeHint & bedhint,
std::function<void(unsigned)> progressind,
std::function<bool()> stopcondition)
{
namespace clppr = ClipperLib;
std::vector<Item> items, fixeditems;
items.reserve(arrangables.size());
// Create Item from Arrangeable
auto process_arrangeable =
[](const ArrangePolygon &arrpoly, std::vector<Item> &outp)
{
Polygon p = arrpoly.poly.contour;
const Vec2crd & offs = arrpoly.translation;
double rotation = arrpoly.rotation;
if (p.is_counter_clockwise()) p.reverse();
clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p));
auto firstp = clpath.Contour.front();
clpath.Contour.emplace_back(firstp);
outp.emplace_back(std::move(clpath));
outp.back().rotation(rotation);
outp.back().translation({offs.x(), offs.y()});
outp.back().binId(arrpoly.bed_idx);
outp.back().priority(arrpoly.priority);
};
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 = stopcondition;
auto &pri = progressind;
switch (bedhint.get_type()) {
case bsBox: {
// Create the arranger for the box shaped bed
BoundingBox bbb = bedhint.get_box();
Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}};
_arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn);
break;
}
case bsCircle: {
auto cc = to_lnCircle(bedhint.get_circle());
_arrange(items, fixeditems, cc, min_obj_dist, pri, cfn);
break;
}
case bsIrregular: {
auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.get_irregular());
auto irrbed = sl::create<clppr::Polygon>(std::move(ctour));
BoundingBox polybb(bedhint.get_irregular());
_arrange(items, fixeditems, irrbed, min_obj_dist, pri, cfn);
break;
}
case bsInfinite: {
const InfiniteBed& nobin = bedhint.get_infinite();
auto infbb = Box::infinite({nobin.center.x(), nobin.center.y()});
_arrange(items, fixeditems, infbb, min_obj_dist, pri, cfn);
break;
}
case bsUnknown: {
// We know nothing about the bed, let it be infinite and zero centered
_arrange(items, fixeditems, Box::infinite(), min_obj_dist, pri, cfn);
break;
}
};
for(size_t i = 0; i < items.size(); ++i) {
clppr::IntPoint 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();
}
}
// Arrange, without the fixed items (excludes)
void arrange(ArrangePolygons & inp,
coord_t min_d,
const BedShapeHint & bedhint,
std::function<void(unsigned)> prfn,
std::function<bool()> stopfn)
{
arrange(inp, {}, min_d, bedhint, prfn, stopfn);
}
} // namespace arr
} // namespace Slic3r

201
src/libslic3r/Arrange.hpp Normal file
View file

@ -0,0 +1,201 @@
#ifndef MODELARRANGE_HPP
#define MODELARRANGE_HPP
#include "ExPolygon.hpp"
#include "BoundingBox.hpp"
namespace Slic3r {
namespace arrangement {
/// A geometry abstraction for a circular print bed. Similarly to BoundingBox.
class CircleBed {
Point center_;
double radius_;
public:
inline CircleBed(): center_(0, 0), radius_(std::nan("")) {}
inline CircleBed(const Point& c, double r): center_(c), radius_(r) {}
inline double radius() const { return radius_; }
inline const Point& center() const { return center_; }
inline operator bool() { return !std::isnan(radius_); }
};
/// Representing an unbounded bed.
struct InfiniteBed { Point center; };
/// Types of print bed shapes.
enum BedShapes {
bsBox,
bsCircle,
bsIrregular,
bsInfinite,
bsUnknown
};
/// Info about the print bed for the arrange() function. This is a variant
/// holding one of the four shapes a bed can be.
class BedShapeHint {
BedShapes m_type = BedShapes::bsInfinite;
union BedShape_u { // TODO: use variant from cpp17?
CircleBed circ;
BoundingBox box;
Polyline polygon;
InfiniteBed infbed{};
~BedShape_u() {}
BedShape_u() {};
} m_bed;
public:
BedShapeHint(){};
/// Get a bed shape hint for arrange() from a naked Polyline.
explicit BedShapeHint(const Polyline &polyl);
explicit BedShapeHint(const BoundingBox &bb)
{
m_type = bsBox; m_bed.box = bb;
}
explicit BedShapeHint(const CircleBed &c)
{
m_type = bsCircle; m_bed.circ = c;
}
explicit BedShapeHint(const InfiniteBed &ibed)
{
m_type = bsInfinite; m_bed.infbed = ibed;
}
~BedShapeHint()
{
if (m_type == BedShapes::bsIrregular)
m_bed.polygon.Slic3r::Polyline::~Polyline();
};
BedShapeHint(const BedShapeHint &cpy) { *this = cpy; }
BedShapeHint(BedShapeHint &&cpy) { *this = std::move(cpy); }
BedShapeHint &operator=(const BedShapeHint &cpy)
{
m_type = cpy.m_type;
switch(m_type) {
case bsBox: m_bed.box = cpy.m_bed.box; break;
case bsCircle: m_bed.circ = cpy.m_bed.circ; break;
case bsIrregular: m_bed.polygon = cpy.m_bed.polygon; break;
case bsInfinite: m_bed.infbed = cpy.m_bed.infbed; break;
case bsUnknown: break;
}
return *this;
}
BedShapeHint& operator=(BedShapeHint &&cpy)
{
m_type = cpy.m_type;
switch(m_type) {
case bsBox: m_bed.box = std::move(cpy.m_bed.box); break;
case bsCircle: m_bed.circ = std::move(cpy.m_bed.circ); break;
case bsIrregular: m_bed.polygon = std::move(cpy.m_bed.polygon); break;
case bsInfinite: m_bed.infbed = std::move(cpy.m_bed.infbed); break;
case bsUnknown: break;
}
return *this;
}
BedShapes get_type() const { return m_type; }
const BoundingBox &get_box() const
{
assert(m_type == bsBox); return m_bed.box;
}
const CircleBed &get_circle() const
{
assert(m_type == bsCircle); return m_bed.circ;
}
const Polyline &get_irregular() const
{
assert(m_type == bsIrregular); return m_bed.polygon;
}
const InfiniteBed &get_infinite() const
{
assert(m_type == bsInfinite); return m_bed.infbed;
}
};
/// 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
int bed_idx{UNARRANGED}; /// To which logical bed does poly belong...
int priority{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; }
};
using ArrangePolygons = std::vector<ArrangePolygon>;
/**
* \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.
*
* \param min_obj_distance The minimum distance which is allowed for any
* pair of items on the print bed in any direction.
*
* \param bedhint Info about the shape and type of the bed.
*
* \param progressind Progress indicator callback called when
* an object gets packed. The unsigned argument is the number of items
* remaining to pack.
*
* \param stopcondition A predicate returning true if abort is needed.
*/
void arrange(ArrangePolygons & items,
coord_t min_obj_distance,
const BedShapeHint & bedhint,
std::function<void(unsigned)> progressind = nullptr,
std::function<bool(void)> stopcondition = nullptr);
/// Same as the previous, only that it takes unmovable items as an
/// additional argument. Those will be considered as already arranged objects.
void arrange(ArrangePolygons & items,
const ArrangePolygons & excludes,
coord_t min_obj_distance,
const BedShapeHint & bedhint,
std::function<void(unsigned)> progressind = nullptr,
std::function<bool(void)> stopcondition = nullptr);
} // arr
} // Slic3r
#endif // MODELARRANGE_HPP

View file

@ -106,8 +106,8 @@ add_library(libslic3r STATIC
Line.hpp
Model.cpp
Model.hpp
ModelArrange.hpp
ModelArrange.cpp
Arrange.hpp
Arrange.cpp
MotionPlanner.cpp
MotionPlanner.hpp
MultiPoint.cpp

View file

@ -126,6 +126,7 @@ void GCodeAnalyzer::reset()
_set_start_position(DEFAULT_START_POSITION);
_set_start_extrusion(DEFAULT_START_EXTRUSION);
_reset_axes_position();
_reset_cached_position();
m_moves_map.clear();
m_extruder_offsets.clear();
@ -262,6 +263,16 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi
_processM108orM135(line);
break;
}
case 401: // Repetier: Store x, y and z position
{
_processM401(line);
break;
}
case 402: // Repetier: Go to stored position
{
_processM402(line);
break;
}
}
break;
@ -433,12 +444,6 @@ void GCodeAnalyzer::_processM83(const GCodeReader::GCodeLine& line)
_set_e_local_positioning_type(Relative);
}
void GCodeAnalyzer::_processM600(const GCodeReader::GCodeLine& line)
{
m_state.cur_cp_color_id++;
_set_cp_color_id(m_state.cur_cp_color_id);
}
void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line)
{
// These M-codes are used by MakerWare and Sailfish to change active tool.
@ -447,7 +452,7 @@ void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line)
size_t code = ::atoi(&(line.cmd()[1]));
if ((code == 108 && m_gcode_flavor == gcfSailfish)
|| (code == 135 && m_gcode_flavor == gcfMakerWare)) {
|| (code == 135 && m_gcode_flavor == gcfMakerWare)) {
std::string cmd = line.raw();
size_t T_pos = cmd.find("T");
@ -458,6 +463,66 @@ void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line)
}
}
void GCodeAnalyzer::_processM401(const GCodeReader::GCodeLine& line)
{
if (m_gcode_flavor != gcfRepetier)
return;
for (unsigned char a = 0; a <= 3; ++a)
{
_set_cached_position(a, _get_axis_position((EAxis)a));
}
_set_cached_position(4, _get_feedrate());
}
void GCodeAnalyzer::_processM402(const GCodeReader::GCodeLine& line)
{
if (m_gcode_flavor != gcfRepetier)
return;
// see for reference:
// https://github.com/repetier/Repetier-Firmware/blob/master/src/ArduinoAVR/Repetier/Printer.cpp
// void Printer::GoToMemoryPosition(bool x, bool y, bool z, bool e, float feed)
bool has_xyz = !(line.has_x() || line.has_y() || line.has_z());
float p = FLT_MAX;
for (unsigned char a = X; a <= Z; ++a)
{
if (has_xyz || line.has(a))
{
p = _get_cached_position(a);
if (p != FLT_MAX)
_set_axis_position((EAxis)a, p);
}
}
p = _get_cached_position(E);
if (p != FLT_MAX)
_set_axis_position(E, p);
p = FLT_MAX;
if (!line.has_value(4, p))
p = _get_cached_position(4);
if (p != FLT_MAX)
_set_feedrate(p);
}
void GCodeAnalyzer::_reset_cached_position()
{
for (unsigned char a = 0; a <= 4; ++a)
{
m_state.cached_position[a] = FLT_MAX;
}
}
void GCodeAnalyzer::_processM600(const GCodeReader::GCodeLine& line)
{
m_state.cur_cp_color_id++;
_set_cp_color_id(m_state.cur_cp_color_id);
}
void GCodeAnalyzer::_processT(const std::string& cmd)
{
if (cmd.length() > 1)
@ -668,6 +733,17 @@ const Vec3d& GCodeAnalyzer::_get_start_position() const
return m_state.start_position;
}
void GCodeAnalyzer::_set_cached_position(unsigned char axis, float position)
{
if ((0 <= axis) || (axis <= 4))
m_state.cached_position[axis] = position;
}
float GCodeAnalyzer::_get_cached_position(unsigned char axis) const
{
return ((0 <= axis) || (axis <= 4)) ? m_state.cached_position[axis] : FLT_MAX;
}
void GCodeAnalyzer::_set_start_extrusion(float extrusion)
{
m_state.start_extrusion = extrusion;

View file

@ -96,6 +96,7 @@ private:
EPositioningType e_local_positioning_type;
Metadata data;
Vec3d start_position = Vec3d::Zero();
float cached_position[5]{ FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX };
float start_extrusion;
float position[Num_Axis];
unsigned int cur_cp_color_id = 0;
@ -170,6 +171,12 @@ private:
// Set tool (MakerWare and Sailfish flavor)
void _processM108orM135(const GCodeReader::GCodeLine& line);
// Repetier: Store x, y and z position
void _processM401(const GCodeReader::GCodeLine& line);
// Repetier: Go to stored position
void _processM402(const GCodeReader::GCodeLine& line);
// Set color change
void _processM600(const GCodeReader::GCodeLine& line);
@ -232,6 +239,11 @@ private:
void _set_start_position(const Vec3d& position);
const Vec3d& _get_start_position() const;
void _set_cached_position(unsigned char axis, float position);
float _get_cached_position(unsigned char axis) const;
void _reset_cached_position();
void _set_start_extrusion(float extrusion);
float _get_start_extrusion() const;
float _get_delta_extrusion() const;

View file

@ -314,49 +314,48 @@ template<class I> struct is_scaled_coord
};
// Meta predicates for floating, 'scaled coord' and generic arithmetic types
template<class T>
using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, T>;
template<class T, class O = T>
using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, O>;
template<class T>
using ScaledCoordOnly = enable_if_t<is_scaled_coord<T>::value, T>;
template<class T, class O = T>
using ScaledCoordOnly = enable_if_t<is_scaled_coord<T>::value, O>;
template<class T>
using ArithmeticOnly = enable_if_t<std::is_arithmetic<T>::value, T>;
// A shorter form for a generic Eigen vector which is widely used in PrusaSlicer
template<class T, int N>
using EigenVec = Eigen::Matrix<T, N, 1, Eigen::DontAlign>;
template<class T, class O = T>
using ArithmeticOnly = enable_if_t<std::is_arithmetic<T>::value, O>;
// Semantics are the following:
// Upscaling (scaled()): only from floating point types (or Vec) to either
// floating point or integer 'scaled coord' coordinates.
// Downscaling (unscaled()): from arithmetic types (or Vec) to either
// floating point only
// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only
// Conversion definition from unscaled to floating point scaled
template<class Tout,
class Tin,
class = FloatingOnly<Tin>,
class = FloatingOnly<Tout>>
inline SLIC3R_CONSTEXPR Tout scaled(const Tin &v) SLIC3R_NOEXCEPT
class = FloatingOnly<Tin>>
inline constexpr FloatingOnly<Tout> scaled(const Tin &v) noexcept
{
return static_cast<Tout>(v / static_cast<Tin>(SCALING_FACTOR));
return Tout(v / Tin(SCALING_FACTOR));
}
// Conversion definition from unscaled to integer 'scaled coord'.
// TODO: is the rounding necessary ? Here it is to show that it can be different
// but it does not have to be. Using std::round means loosing noexcept and
// constexpr modifiers
// TODO: is the rounding necessary? Here it is commented out to show that
// it can be different for integers but it does not have to be. Using
// std::round means loosing noexcept and constexpr modifiers
template<class Tout = coord_t, class Tin, class = FloatingOnly<Tin>>
inline SLIC3R_CONSTEXPR ScaledCoordOnly<Tout> scaled(const Tin &v) SLIC3R_NOEXCEPT
inline constexpr ScaledCoordOnly<Tout> scaled(const Tin &v) noexcept
{
//return static_cast<Tout>(std::round(v / SCALING_FACTOR));
return static_cast<Tout>(v / static_cast<Tin>(SCALING_FACTOR));
return Tout(v / Tin(SCALING_FACTOR));
}
// Conversion for Eigen vectors (N dimensional points)
template<class Tout = coord_t, class Tin, int N, class = FloatingOnly<Tin>>
inline EigenVec<ArithmeticOnly<Tout>, N> scaled(const EigenVec<Tin, N> &v)
template<class Tout = coord_t,
class Tin,
int N,
class = FloatingOnly<Tin>,
int...EigenArgs>
inline Eigen::Matrix<ArithmeticOnly<Tout>, N, EigenArgs...>
scaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v)
{
return (v / SCALING_FACTOR).template cast<Tout>();
}
@ -366,9 +365,9 @@ template<class Tout = double,
class Tin,
class = ArithmeticOnly<Tin>,
class = FloatingOnly<Tout>>
inline SLIC3R_CONSTEXPR Tout unscaled(const Tin &v) SLIC3R_NOEXCEPT
inline constexpr Tout unscaled(const Tin &v) noexcept
{
return static_cast<Tout>(v * static_cast<Tout>(SCALING_FACTOR));
return Tout(v * Tout(SCALING_FACTOR));
}
// Unscaling for Eigen vectors. Input base type can be arithmetic, output base
@ -377,9 +376,10 @@ template<class Tout = double,
class Tin,
int N,
class = ArithmeticOnly<Tin>,
class = FloatingOnly<Tout>>
inline SLIC3R_CONSTEXPR EigenVec<Tout, N> unscaled(
const EigenVec<Tin, N> &v) SLIC3R_NOEXCEPT
class = FloatingOnly<Tout>,
int...EigenArgs>
inline constexpr Eigen::Matrix<Tout, N, EigenArgs...>
unscaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v) noexcept
{
return v.template cast<Tout>() * SCALING_FACTOR;
}

View file

@ -1,5 +1,6 @@
#include "Model.hpp"
#include "Geometry.hpp"
#include "MTUtils.hpp"
#include "Format/AMF.hpp"
#include "Format/OBJ.hpp"
@ -369,34 +370,44 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb
/* arrange objects preserving their instance count
but altering their instance positions */
bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
{
// get the (transformed) size of each instance so that we take
// into account their different transformations when packing
Pointfs instance_sizes;
Pointfs instance_centers;
for (const ModelObject *o : this->objects)
for (size_t i = 0; i < o->instances.size(); ++ i) {
// an accurate snug bounding box around the transformed mesh.
BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
instance_sizes.emplace_back(to_2d(bbox.size()));
instance_centers.emplace_back(to_2d(bbox.center()));
{
size_t count = 0;
for (auto obj : objects) count += obj->instances.size();
arrangement::ArrangePolygons input;
ModelInstancePtrs instances;
input.reserve(count);
instances.reserve(count);
for (ModelObject *mo : objects)
for (ModelInstance *minst : mo->instances) {
input.emplace_back(minst->get_arrange_polygon());
instances.emplace_back(minst);
}
Pointfs positions;
if (! _arrange(instance_sizes, dist, bb, positions))
return false;
size_t idx = 0;
for (ModelObject *o : this->objects) {
for (ModelInstance *i : o->instances) {
Vec2d offset_xy = positions[idx] - instance_centers[idx];
i->set_offset(Vec3d(offset_xy(0), offset_xy(1), i->get_offset(Z)));
++idx;
}
o->invalidate_bounding_box();
arrangement::BedShapeHint bedhint;
coord_t bedwidth = 0;
if (bb) {
bedwidth = scaled(bb->size().x());
bedhint = arrangement::BedShapeHint(
BoundingBox(scaled(bb->min), scaled(bb->max)));
}
return true;
arrangement::arrange(input, scaled(dist), bedhint);
bool ret = true;
coord_t stride = bedwidth + bedwidth / 5;
for(size_t i = 0; i < input.size(); ++i) {
if (input[i].bed_idx != 0) ret = false;
if (input[i].bed_idx >= 0) {
input[i].translation += Vec2crd{input[i].bed_idx * stride, 0};
instances[i]->apply_arrange_result(input[i].translation,
input[i].rotation);
}
}
return ret;
}
// Duplicate the entire model preserving instance relative positions.
@ -1814,6 +1825,36 @@ void ModelInstance::transform_polygon(Polygon* polygon) const
polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin
}
arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const
{
static const double SIMPLIFY_TOLERANCE_MM = 0.1;
Vec3d rotation = get_rotation();
rotation.z() = 0.;
Transform3d trafo_instance =
Geometry::assemble_transform(Vec3d::Zero(), rotation,
get_scaling_factor(), get_mirror());
Polygon p = get_object()->convex_hull_2d(trafo_instance);
assert(!p.points.empty());
// this may happen for malformed models, see:
// https://github.com/prusa3d/PrusaSlicer/issues/2209
if (!p.points.empty()) {
Polygons pp{p};
pp = p.simplify(scaled<double>(SIMPLIFY_TOLERANCE_MM));
if (!pp.empty()) p = pp.front();
}
arrangement::ArrangePolygon ret;
ret.poly.contour = std::move(p);
ret.translation = Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))};
ret.rotation = get_rotation(Z);
return ret;
}
// Test whether the two models contain the same number of ModelObjects with the same set of IDs
// ordered in the same order. In that case it is not necessary to kill the background processing.
bool model_object_list_equal(const Model &model_old, const Model &model_new)

View file

@ -10,6 +10,7 @@
#include "Slicing.hpp"
#include "SLA/SLACommon.hpp"
#include "TriangleMesh.hpp"
#include "Arrange.hpp"
#include <map>
#include <memory>
@ -602,7 +603,7 @@ public:
const Vec3d& get_offset() const { return m_transformation.get_offset(); }
double get_offset(Axis axis) const { return m_transformation.get_offset(axis); }
void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); }
void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); }
@ -621,7 +622,7 @@ public:
const Vec3d& get_mirror() const { return m_transformation.get_mirror(); }
double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
bool is_left_handed() const { return m_transformation.is_left_handed(); }
void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); }
void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); }
@ -639,6 +640,18 @@ public:
const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
bool is_printable() const { return print_volume_state == PVS_Inside; }
// Getting the input polygon for arrange
arrangement::ArrangePolygon get_arrange_polygon() const;
// Apply the arrange result on the ModelInstance
void apply_arrange_result(const Vec2crd& offs, double rotation)
{
// write the transformation data into the model instance
set_rotation(Z, rotation);
set_offset(X, unscale<double>(offs(X)));
set_offset(Y, unscale<double>(offs(Y)));
}
protected:
friend class Print;
@ -654,10 +667,10 @@ private:
ModelObject* object;
// Constructor, which assigns a new unique ID.
explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) { assert(this->id().valid()); }
explicit ModelInstance(ModelObject *object) : print_volume_state(PVS_Inside), object(object) { assert(this->id().valid()); }
// Constructor, which assigns a new unique ID.
explicit ModelInstance(ModelObject *object, const ModelInstance &other) :
m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) { assert(this->id().valid() && this->id() != other.id()); }
m_transformation(other.m_transformation), print_volume_state(PVS_Inside), object(object) { assert(this->id().valid() && this->id() != other.id()); }
explicit ModelInstance(ModelInstance &&rhs) = delete;
ModelInstance& operator=(const ModelInstance &rhs) = delete;

File diff suppressed because it is too large Load diff

View file

@ -1,95 +0,0 @@
#ifndef MODELARRANGE_HPP
#define MODELARRANGE_HPP
#include "Model.hpp"
namespace Slic3r {
class Model;
namespace arr {
class Circle {
Point center_;
double radius_;
public:
inline Circle(): center_(0, 0), radius_(std::nan("")) {}
inline Circle(const Point& c, double r): center_(c), radius_(r) {}
inline double radius() const { return radius_; }
inline const Point& center() const { return center_; }
inline operator bool() { return !std::isnan(radius_); }
};
enum class BedShapeType {
BOX,
CIRCLE,
IRREGULAR,
WHO_KNOWS
};
struct BedShapeHint {
BedShapeType type;
/*union*/ struct { // I know but who cares...
Circle circ;
BoundingBox box;
Polyline polygon;
} shape;
};
BedShapeHint bedShape(const Polyline& bed);
struct WipeTowerInfo {
bool is_wipe_tower = false;
Vec2d pos;
Vec2d bb_size;
double rotation;
};
/**
* \brief Arranges the model objects on the screen.
*
* The arrangement considers multiple bins (aka. print beds) for placing all
* the items provided in the model argument. If the items don't fit on one
* print bed, the remaining will be placed onto newly created print beds.
* The first_bin_only parameter, if set to true, disables this behavior and
* makes sure that only one print bed is filled and the remaining items will be
* untouched. When set to false, the items which could not fit onto the
* print bed will be placed next to the print bed so the user should see a
* pile of items on the print bed and some other piles outside the print
* area that can be dragged later onto the print bed as a group.
*
* \param model The model object with the 3D content.
* \param dist The minimum distance which is allowed for any pair of items
* on the print bed in any direction.
* \param bb The bounding box of the print bed. It corresponds to the 'bin'
* for bin packing.
* \param first_bin_only This parameter controls whether to place the
* remaining items which do not fit onto the print area next to the print
* bed or leave them untouched (let the user arrange them by hand or remove
* them).
* \param progressind Progress indicator callback called when an object gets
* packed. The unsigned argument is the number of items remaining to pack.
* \param stopcondition A predicate returning true if abort is needed.
*/
bool arrange(Model &model,
WipeTowerInfo& wipe_tower_info,
coord_t min_obj_distance,
const Slic3r::Polyline& bed,
BedShapeHint bedhint,
bool first_bin_only,
std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcondition);
/// This will find a suitable position for a new object instance and leave the
/// old items untouched.
void find_new_position(const Model& model,
ModelInstancePtrs instances_to_add,
coord_t min_obj_distance,
const Slic3r::Polyline& bed,
WipeTowerInfo& wti);
} // arr
} // Slic3r
#endif // MODELARRANGE_HPP