linest2d ready for arbitrary shaped beds.

This commit is contained in:
tamasmeszaros 2018-07-30 15:16:44 +02:00
parent 4e901a9db7
commit d136d61edd
8 changed files with 275 additions and 77 deletions

View file

@ -544,57 +544,126 @@ void arrangeRectangles() {
// input.insert(input.end(), proba.begin(), proba.end());
// input.insert(input.end(), crasher.begin(), crasher.end());
Box bin(250*SCALE, 210*SCALE);
// Box bin(250*SCALE, 210*SCALE);
PolygonImpl bin = {
{
{25*SCALE, 0},
{0, 25*SCALE},
{0, 225*SCALE},
{25*SCALE, 250*SCALE},
{225*SCALE, 250*SCALE},
{250*SCALE, 225*SCALE},
{250*SCALE, 25*SCALE},
{225*SCALE, 0},
{25*SCALE, 0}
},
{}
};
auto min_obj_distance = static_cast<Coord>(0*SCALE);
using Placer = NfpPlacer;
using Placer = strategies::_NofitPolyPlacer<PolygonImpl, PolygonImpl>;
using Packer = Arranger<Placer, FirstFitSelection>;
Packer arrange(bin, min_obj_distance);
Packer::PlacementConfig pconf;
pconf.alignment = Placer::Config::Alignment::CENTER;
pconf.starting_point = Placer::Config::Alignment::BOTTOM_LEFT;
pconf.starting_point = Placer::Config::Alignment::CENTER;
pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/};
pconf.accuracy = 1.0;
double norm_2 = std::nan("");
pconf.object_function = [&bin, &norm_2](Placer::Pile pile, const Item& item,
auto bincenter = ShapeLike::boundingBox(bin).center();
pconf.object_function = [&bin, bincenter](
Placer::Pile pile, const Item& item,
double /*area*/, double norm, double penality) {
using pl = PointLike;
auto bb = ShapeLike::boundingBox(pile);
static const double BIG_ITEM_TRESHOLD = 0.2;
static const double GRAVITY_RATIO = 0.5;
static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO;
// We will treat big items (compared to the print bed) differently
NfpPlacer::Pile bigs;
bigs.reserve(pile.size());
for(auto& p : pile) {
auto pbb = ShapeLike::boundingBox(p);
auto na = std::sqrt(pbb.width()*pbb.height())/norm;
if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p);
}
// Candidate item bounding box
auto ibb = item.boundingBox();
auto minc = ibb.minCorner();
auto maxc = ibb.maxCorner();
if(std::isnan(norm_2)) norm_2 = pow(norm, 2);
// Calculate the full bounding box of the pile with the candidate item
pile.emplace_back(item.transformedShape());
auto fullbb = ShapeLike::boundingBox(pile);
pile.pop_back();
// We get the distance of the reference point from the center of the
// heat bed
auto cc = bb.center();
auto top_left = PointImpl{getX(minc), getY(maxc)};
auto bottom_right = PointImpl{getX(maxc), getY(minc)};
// The bounding box of the big items (they will accumulate in the center
// of the pile
auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs);
auto a = pl::distance(ibb.maxCorner(), cc);
auto b = pl::distance(ibb.minCorner(), cc);
auto c = pl::distance(ibb.center(), cc);
auto d = pl::distance(top_left, cc);
auto e = pl::distance(bottom_right, cc);
// The size indicator of the candidate item. This is not the area,
// but almost...
auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm;
auto area = bb.width() * bb.height() / norm_2;
// Will hold the resulting score
double score = 0;
auto min_dist = std::min({a, b, c, d, e}) / norm;
if(itemnormarea > BIG_ITEM_TRESHOLD) {
// This branch is for the bigger items..
// Here we will use the closest point of the item bounding box to
// the already arranged pile. So not the bb center nor the a choosen
// corner but whichever is the closest to the center. This will
// prevent unwanted strange arrangements.
// The score will be the normalized distance which will be minimized,
// effectively creating a circle shaped pile of items
double score = 0.8*min_dist + 0.2*area;
auto minc = ibb.minCorner(); // bottom left corner
auto maxc = ibb.maxCorner(); // top right corner
// top left and bottom right corners
auto top_left = PointImpl{getX(minc), getY(maxc)};
auto bottom_right = PointImpl{getX(maxc), getY(minc)};
auto cc = fullbb.center(); // The gravity center
// Now the distnce of the gravity center will be calculated to the
// five anchor points and the smallest will be chosen.
std::array<double, 5> dists;
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);
auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
// Density is the pack density: how big is the arranged pile
auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
// The score is a weighted sum of the distance from pile center
// and the pile size
score = GRAVITY_RATIO * dist + DENSITY_RATIO * density;
} else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) {
// If there are no big items, only small, we should consider the
// density here as well to not get silly results
auto bindist = pl::distance(ibb.center(), bincenter) / norm;
auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density;
} else {
// 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 = pl::distance(ibb.center(), bigbb.center()) / norm;
}
// If it does not fit into the print bed we will beat it
// with a large penality. If we would not do this, there would be only
// one big pile that doesn't care whether it fits onto the print bed.
if(!NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score;
if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score;
return score;
};
@ -638,7 +707,7 @@ void arrangeRectangles() {
std::vector<double> eff;
eff.reserve(result.size());
auto bin_area = double(bin.height()*bin.width());
auto bin_area = ShapeLike::area(bin);
for(auto& r : result) {
double a = 0;
std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); });
@ -673,7 +742,7 @@ void arrangeRectangles() {
SVGWriter::Config conf;
conf.mm_in_coord_units = SCALE;
SVGWriter svgw(conf);
svgw.setSize(bin);
svgw.setSize(Box(250*SCALE, 210*SCALE));
svgw.writePackGroup(result);
// std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);});
svgw.save("out");

View file

@ -358,7 +358,7 @@ inline double ShapeLike::area(const PolygonImpl& shape)
#endif
template<>
inline bool ShapeLike::isInside(const PointImpl& point,
inline bool ShapeLike::isInside<PolygonImpl>(const PointImpl& point,
const PolygonImpl& shape)
{
return boost::geometry::within(point, shape);

View file

@ -3,6 +3,7 @@
#include <string>
#include <type_traits>
#include <algorithm>
#include <array>
#include <vector>
#include <numeric>
@ -85,6 +86,31 @@ public:
inline TCoord<RawPoint> height() const BP2D_NOEXCEPT;
inline RawPoint center() const BP2D_NOEXCEPT;
inline double area() const BP2D_NOEXCEPT {
return double(width()*height());
}
};
template<class RawPoint>
class _Circle {
RawPoint center_;
double radius_ = 0;
public:
_Circle() = default;
_Circle(const RawPoint& center, double r): center_(center), radius_(r) {}
inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; }
inline const void center(const RawPoint& c) { center_ = c; }
inline double radius() const BP2D_NOEXCEPT { return radius_; }
inline void radius(double r) { radius_ = r; }
inline double area() const BP2D_NOEXCEPT {
return 2.0*Pi*radius_;
}
};
/**
@ -288,8 +314,8 @@ inline RawPoint _Box<RawPoint>::center() const BP2D_NOEXCEPT {
using Coord = TCoord<RawPoint>;
RawPoint ret = {
static_cast<Coord>( std::round((getX(minc) + getX(maxc))/2.0) ),
static_cast<Coord>( std::round((getY(minc) + getY(maxc))/2.0) )
static_cast<Coord>( (getX(minc) + getX(maxc))/2.0 ),
static_cast<Coord>( (getY(minc) + getY(maxc))/2.0 )
};
return ret;
@ -614,12 +640,34 @@ struct ShapeLike {
return box;
}
template<class RawShape>
static inline _Box<TPoint<RawShape>> boundingBox(
const _Circle<TPoint<RawShape>>& circ)
{
using Coord = TCoord<TPoint<RawShape>>;
TPoint<RawShape> pmin = {
static_cast<Coord>(getX(circ.center()) - circ.radius()),
static_cast<Coord>(getY(circ.center()) - circ.radius()) };
TPoint<RawShape> pmax = {
static_cast<Coord>(getX(circ.center()) + circ.radius()),
static_cast<Coord>(getY(circ.center()) + circ.radius()) };
return {pmin, pmax};
}
template<class RawShape>
static inline double area(const _Box<TPoint<RawShape>>& box)
{
return static_cast<double>(box.width() * box.height());
}
template<class RawShape>
static inline double area(const _Circle<TPoint<RawShape>>& circ)
{
return circ.area();
}
template<class RawShape>
static inline double area(const Shapes<RawShape>& shapes)
{
@ -629,6 +677,31 @@ struct ShapeLike {
});
}
template<class RawShape>
static bool isInside(const TPoint<RawShape>& point,
const _Circle<TPoint<RawShape>>& circ)
{
return PointLike::distance(point, circ.center()) < circ.radius();
}
template<class RawShape>
static bool isInside(const RawShape& sh,
const _Circle<TPoint<RawShape>>& circ)
{
return std::all_of(cbegin(sh), cend(sh),
[&circ](const TPoint<RawShape>& p){
return isInside<RawShape>(p, circ);
});
}
template<class RawShape>
static bool isInside(const _Box<TPoint<RawShape>>& box,
const _Circle<TPoint<RawShape>>& circ)
{
return isInside<RawShape>(box.minCorner(), circ) &&
isInside<RawShape>(box.maxCorner(), circ);
}
template<class RawShape> // Potential O(1) implementation may exist
static inline TPoint<RawShape>& vertex(RawShape& sh, unsigned long idx)
{

View file

@ -254,7 +254,13 @@ public:
return sl::isInside(transformedShape(), sh.transformedShape());
}
inline bool isInside(const RawShape& sh) const
{
return sl::isInside(transformedShape(), sh);
}
inline bool isInside(const _Box<TPoint<RawShape>>& box) const;
inline bool isInside(const _Circle<TPoint<RawShape>>& box) const;
inline void translate(const Vertex& d) BP2D_NOEXCEPT
{
@ -471,6 +477,11 @@ inline bool _Item<RawShape>::isInside(const _Box<TPoint<RawShape>>& box) const {
return _Item<RawShape>::isInside(rect);
}
template<class RawShape> inline bool
_Item<RawShape>::isInside(const _Circle<TPoint<RawShape>>& circ) const {
return ShapeLike::isInside<RawShape>(transformedShape(), circ);
}
/**
* \brief A wrapper interface (trait) class for any placement strategy provider.
*

View file

@ -46,14 +46,12 @@ struct NfpPConfig {
* function you can e.g. influence the shape of the arranged pile.
*
* \param shapes The first parameter is a container with all the placed
* polygons including the current candidate. You can calculate a bounding
* box or convex hull on this pile of polygons.
* polygons excluding the current candidate. You can calculate a bounding
* box or convex hull on this pile of polygons without the candidate item
* or push back the candidate item into the container and then calculate
* some features.
*
* \param item The second parameter is the candidate item. Note that
* calling transformedShape() on this second argument returns an identical
* shape as calling shapes.back(). These would not be the same objects only
* identical shapes! Using the second parameter is a lot faster due to
* caching some properties of the polygon (area, etc...)
* \param item The second parameter is the candidate item.
*
* \param occupied_area The third parameter is the sum of areas of the
* items in the first parameter so you don't have to iterate through them
@ -127,6 +125,8 @@ template<class RawShape> class EdgeCache {
std::vector<ContourCache> holes_;
double accuracy_ = 1.0;
void createCache(const RawShape& sh) {
{ // For the contour
auto first = ShapeLike::cbegin(sh);
@ -160,11 +160,25 @@ template<class RawShape> class EdgeCache {
}
}
size_t stride(const size_t N) const {
using std::ceil;
using std::round;
using std::pow;
return static_cast<Coord>(
round( N/(ceil(pow(accuracy_, 2)*(N-1)) + 1) )
);
}
void fetchCorners() const {
if(!contour_.corners.empty()) return;
contour_.corners.reserve(contour_.distances.size() / 3 + 1);
for(size_t i = 0; i < contour_.distances.size() - 1; i += 3) {
const auto N = contour_.distances.size();
const auto S = stride(N);
contour_.corners.reserve(N / S + 1);
auto N_1 = N-1;
for(size_t i = 0; i < N_1; i += S) {
contour_.corners.emplace_back(
contour_.distances.at(i) / contour_.full_distance);
}
@ -174,8 +188,11 @@ template<class RawShape> class EdgeCache {
auto& hc = holes_[hidx];
if(!hc.corners.empty()) return;
hc.corners.reserve(hc.distances.size() / 3 + 1);
for(size_t i = 0; i < hc.distances.size() - 1; i += 3) {
const auto N = hc.distances.size();
const auto S = stride(N);
auto N_1 = N-1;
hc.corners.reserve(N / S + 1);
for(size_t i = 0; i < N_1; i += S) {
hc.corners.emplace_back(
hc.distances.at(i) / hc.full_distance);
}
@ -224,6 +241,9 @@ public:
createCache(sh);
}
/// Resolution of returned corners. The stride is derived from this value.
void accuracy(double a /* within <0.0, 1.0>*/) { accuracy_ = a; }
/**
* @brief Get a point on the circumference of a polygon.
* @param distance A relative distance from the starting point to the end.
@ -419,12 +439,12 @@ Nfp::Shapes<RawShape> nfp( const Container& polygons,
// return nfps;
}
template<class RawShape>
class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
RawShape, _Box<TPoint<RawShape>>, NfpPConfig<RawShape>> {
template<class RawShape, class TBin = _Box<TPoint<RawShape>>>
class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin>,
RawShape, TBin, NfpPConfig<RawShape>> {
using Base = PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
RawShape, _Box<TPoint<RawShape>>, NfpPConfig<RawShape>>;
using Base = PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin>,
RawShape, TBin, NfpPConfig<RawShape>>;
DECLARE_PLACER(Base)
@ -434,6 +454,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
const double penality_;
using MaxNfpLevel = Nfp::MaxNfpLevel<RawShape>;
using sl = ShapeLike;
public:
@ -441,7 +462,7 @@ public:
inline explicit _NofitPolyPlacer(const BinType& bin):
Base(bin),
norm_(std::sqrt(ShapeLike::area<RawShape>(bin))),
norm_(std::sqrt(sl::area<RawShape>(bin))),
penality_(1e6*norm_) {}
_NofitPolyPlacer(const _NofitPolyPlacer&) = default;
@ -452,18 +473,26 @@ public:
_NofitPolyPlacer& operator=(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default;
#endif
bool static inline wouldFit(const Box& bb, const RawShape& bin) {
auto bbin = sl::boundingBox<RawShape>(bin);
auto d = bbin.center() - bb.center();
_Rectangle<RawShape> rect(bb.width(), bb.height());
rect.translate(bb.minCorner() + d);
return sl::isInside<RawShape>(rect.transformedShape(), bin);
}
bool static inline wouldFit(const RawShape& chull, const RawShape& bin) {
auto bbch = ShapeLike::boundingBox<RawShape>(chull);
auto bbin = ShapeLike::boundingBox<RawShape>(bin);
auto d = bbin.minCorner() - bbch.minCorner();
auto bbch = sl::boundingBox<RawShape>(chull);
auto bbin = sl::boundingBox<RawShape>(bin);
auto d = bbin.center() - bbch.center();
auto chullcpy = chull;
ShapeLike::translate(chullcpy, d);
return ShapeLike::isInside<RawShape>(chullcpy, bbin);
sl::translate(chullcpy, d);
return sl::isInside<RawShape>(chullcpy, bin);
}
bool static inline wouldFit(const RawShape& chull, const Box& bin)
{
auto bbch = ShapeLike::boundingBox<RawShape>(chull);
auto bbch = sl::boundingBox<RawShape>(chull);
return wouldFit(bbch, bin);
}
@ -472,6 +501,17 @@ public:
return bb.width() <= bin.width() && bb.height() <= bin.height();
}
bool static inline wouldFit(const Box& bb, const _Circle<Vertex>& bin)
{
return sl::isInside<RawShape>(bb, bin);
}
bool static inline wouldFit(const RawShape& chull,
const _Circle<Vertex>& bin)
{
return sl::isInside<RawShape>(chull, bin);
}
PackResult trypack(Item& item) {
PackResult ret;
@ -510,7 +550,10 @@ public:
std::vector<EdgeCache<RawShape>> ecache;
ecache.reserve(nfps.size());
for(auto& nfp : nfps ) ecache.emplace_back(nfp);
for(auto& nfp : nfps ) {
ecache.emplace_back(nfp);
ecache.back().accuracy(config_.accuracy);
}
struct Optimum {
double relpos;
@ -540,14 +583,16 @@ public:
// customizable by the library client
auto _objfunc = config_.object_function?
config_.object_function :
[this](Nfp::Shapes<RawShape>& pile, Item,
[this](Nfp::Shapes<RawShape>& pile, const Item& item,
double occupied_area, double /*norm*/,
double penality)
{
auto ch = ShapeLike::convexHull(pile);
pile.emplace_back(item.transformedShape());
auto ch = sl::convexHull(pile);
pile.pop_back();
// The pack ratio -- how much is the convex hull occupied
double pack_rate = occupied_area/ShapeLike::area(ch);
double pack_rate = occupied_area/sl::area(ch);
// ratio of waste
double waste = 1.0 - pack_rate;
@ -569,22 +614,17 @@ public:
d += startpos;
item.translation(d);
// pile.emplace_back(item.transformedShape());
double occupied_area = pile_area + item.area();
double score = _objfunc(pile, item, occupied_area,
norm_, penality_);
// pile.pop_back();
return score;
};
opt::StopCriteria stopcr;
stopcr.max_iterations = 1000;
stopcr.absolute_score_difference = 1e-20*norm_;
// stopcr.relative_score_difference = 1e-20;
opt::TOptimizer<opt::Method::L_SIMPLEX> solver(stopcr);
Optimum optimum(0, 0);
@ -702,34 +742,35 @@ public:
m.reserve(items_.size());
for(Item& item : items_) m.emplace_back(item.transformedShape());
auto&& bb = ShapeLike::boundingBox<RawShape>(m);
auto&& bb = sl::boundingBox<RawShape>(m);
Vertex ci, cb;
auto bbin = sl::boundingBox<RawShape>(bin_);
switch(config_.alignment) {
case Config::Alignment::CENTER: {
ci = bb.center();
cb = bin_.center();
cb = bbin.center();
break;
}
case Config::Alignment::BOTTOM_LEFT: {
ci = bb.minCorner();
cb = bin_.minCorner();
cb = bbin.minCorner();
break;
}
case Config::Alignment::BOTTOM_RIGHT: {
ci = {getX(bb.maxCorner()), getY(bb.minCorner())};
cb = {getX(bin_.maxCorner()), getY(bin_.minCorner())};
cb = {getX(bbin.maxCorner()), getY(bbin.minCorner())};
break;
}
case Config::Alignment::TOP_LEFT: {
ci = {getX(bb.minCorner()), getY(bb.maxCorner())};
cb = {getX(bin_.minCorner()), getY(bin_.maxCorner())};
cb = {getX(bbin.minCorner()), getY(bbin.maxCorner())};
break;
}
case Config::Alignment::TOP_RIGHT: {
ci = bb.maxCorner();
cb = bin_.maxCorner();
cb = bbin.maxCorner();
break;
}
}
@ -745,31 +786,32 @@ private:
void setInitialPosition(Item& item) {
Box&& bb = item.boundingBox();
Vertex ci, cb;
auto bbin = sl::boundingBox<RawShape>(bin_);
switch(config_.starting_point) {
case Config::Alignment::CENTER: {
ci = bb.center();
cb = bin_.center();
cb = bbin.center();
break;
}
case Config::Alignment::BOTTOM_LEFT: {
ci = bb.minCorner();
cb = bin_.minCorner();
cb = bbin.minCorner();
break;
}
case Config::Alignment::BOTTOM_RIGHT: {
ci = {getX(bb.maxCorner()), getY(bb.minCorner())};
cb = {getX(bin_.maxCorner()), getY(bin_.minCorner())};
cb = {getX(bbin.maxCorner()), getY(bbin.minCorner())};
break;
}
case Config::Alignment::TOP_LEFT: {
ci = {getX(bb.minCorner()), getY(bb.maxCorner())};
cb = {getX(bin_.minCorner()), getY(bin_.maxCorner())};
cb = {getX(bbin.minCorner()), getY(bbin.maxCorner())};
break;
}
case Config::Alignment::TOP_RIGHT: {
ci = bb.maxCorner();
cb = bin_.maxCorner();
cb = bbin.maxCorner();
break;
}
}
@ -780,7 +822,7 @@ private:
void placeOutsideOfBin(Item& item) {
auto&& bb = item.boundingBox();
Box binbb = ShapeLike::boundingBox<RawShape>(bin_);
Box binbb = sl::boundingBox<RawShape>(bin_);
Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) };

View file

@ -535,7 +535,7 @@ public:
// then it should be removed from the not_packed list
{ auto it = store_.begin();
while (it != store_.end()) {
Placer p(bin);
Placer p(bin); p.configure(pconfig);
if(!p.pack(*it)) {
it = store_.erase(it);
} else it++;

View file

@ -59,7 +59,7 @@ public:
// then it should be removed from the list
{ auto it = store_.begin();
while (it != store_.end()) {
Placer p(bin);
Placer p(bin); p.configure(pconfig);
if(!p.pack(*it)) {
it = store_.erase(it);
} else it++;

View file

@ -530,6 +530,9 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb,
// arranger.useMinimumBoundigBoxRotation();
pcfg.rotations = { 0.0 };
// The accuracy of optimization. Goes from 0.0 to 1.0 and scales performance
pcfg.accuracy = 0.8;
// Magic: we will specify what is the goal of arrangement... In this case
// we override the default object function to make the larger items go into
// the center of the pile and smaller items orbit it so the resulting pile
@ -539,7 +542,7 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb,
// calculate the convex hulls)
pcfg.object_function = [bin, hasbin](
NfpPlacer::Pile& pile, // The currently arranged pile
Item item,
const Item &item,
double /*area*/, // Sum area of items (not needed)
double norm, // A norming factor for physical dimensions
double penality) // Min penality in case of bad arrangement