Small objects can now fit inside free space surrounded by objects.

This commit is contained in:
tamasmeszaros 2018-07-18 16:37:44 +02:00 committed by bubnikv
parent 1a6fdb668f
commit ed0f073ef3
5 changed files with 238 additions and 83 deletions

View File

@ -519,20 +519,28 @@ void arrangeRectangles() {
std::vector<Item> proba = {
{
{ {0, 0}, {20, 20}, {40, 0}, {0, 0} }
Rectangle(100, 2)
},
{
{ {0, 100}, {50, 60}, {100, 100}, {50, 0}, {0, 100} }
Rectangle(100, 2)
},
{
Rectangle(100, 2)
},
{
Rectangle(10, 10)
},
};
proba[0].rotate(Pi/3);
proba[1].rotate(Pi-Pi/3);
std::vector<Item> input;
input.insert(input.end(), prusaParts().begin(), prusaParts().end());
// input.insert(input.end(), prusaExParts().begin(), prusaExParts().end());
// input.insert(input.end(), stegoParts().begin(), stegoParts().end());
input.insert(input.end(), stegoParts().begin(), stegoParts().end());
// input.insert(input.end(), rects.begin(), rects.end());
// input.insert(input.end(), proba.begin(), proba.end());
input.insert(input.end(), proba.begin(), proba.end());
// input.insert(input.end(), crasher.begin(), crasher.end());
Box bin(250*SCALE, 210*SCALE);
@ -569,9 +577,9 @@ void arrangeRectangles() {
Packer::SelectionConfig sconf;
// sconf.allow_parallel = false;
// sconf.force_parallel = false;
// sconf.try_triplets = true;
// sconf.try_triplets = false;
// sconf.try_reverse_order = true;
// sconf.waste_increment = 0.1;
// sconf.waste_increment = 0.005;
arrange.configure(pconf, sconf);

View File

@ -25,9 +25,60 @@ using Shapes = typename ShapeLike::Shapes<RawShape>;
/// Minkowski addition (not used yet)
template<class RawShape>
static RawShape minkowskiDiff(const RawShape& sh, const RawShape& /*other*/)
static RawShape minkowskiDiff(const RawShape& sh, const RawShape& cother)
{
using Vertex = TPoint<RawShape>;
//using Coord = TCoord<Vertex>;
using Edge = _Segment<Vertex>;
using sl = ShapeLike;
using std::signbit;
// Copy the orbiter (controur only), we will have to work on it
RawShape orbiter = sl::create(sl::getContour(cother));
// Make the orbiter reverse oriented
for(auto &v : sl::getContour(orbiter)) v = -v;
// An egde with additional data for marking it
struct MarkedEdge { Edge e; Radians turn_angle; bool is_turning_point; };
// Container for marked edges
using EdgeList = std::vector<MarkedEdge>;
EdgeList A, B;
auto fillEdgeList = [](EdgeList& L, const RawShape& poly) {
L.reserve(sl::contourVertexCount(poly));
auto it = sl::cbegin(poly);
auto nextit = std::next(it);
L.emplace_back({Edge(*it, *nextit), 0, false});
it++; nextit++;
while(nextit != sl::cend(poly)) {
Edge e(*it, *nextit);
auto& L_prev = L.back();
auto phi = L_prev.e.angleToXaxis();
auto phi_prev = e.angleToXaxis();
auto turn_angle = phi-phi_prev;
if(turn_angle > Pi) turn_angle -= 2*Pi;
L.emplace_back({
e,
turn_angle,
signbit(turn_angle) != signbit(L_prev.turn_angle)
});
it++; nextit++;
}
L.front().turn_angle = L.front().e.angleToXaxis() -
L.back().e.angleToXaxis();
if(L.front().turn_angle > Pi) L.front().turn_angle -= 2*Pi;
};
fillEdgeList(A, sh);
fillEdgeList(B, orbiter);
return sh;
}
@ -193,6 +244,9 @@ static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother)
// Lindmark's reasoning about the reference vertex of nfp in his thesis
// ("No fit polygon problem" - section 2.1.9)
// TODO: dont do this here. Cache the rmu and lmd in Item and get translate
// the nfp after this call
auto csh = sh; // Copy sh, we will sort the verices in the copy
auto& cmp = _vsort<RawShape>;
std::sort(ShapeLike::begin(csh), ShapeLike::end(csh), cmp);

View File

@ -48,32 +48,89 @@ template<class RawShape> class EdgeCache {
using Coord = TCoord<Vertex>;
using Edge = _Segment<Vertex>;
mutable std::vector<double> corners_;
struct ContourCache {
mutable std::vector<double> corners;
std::vector<Edge> emap;
std::vector<double> distances;
double full_distance = 0;
} contour_;
std::vector<Edge> emap_;
std::vector<double> distances_;
double full_distance_ = 0;
std::vector<ContourCache> holes_;
void createCache(const RawShape& sh) {
{ // For the contour
auto first = ShapeLike::cbegin(sh);
auto next = first + 1;
auto next = std::next(first);
auto endit = ShapeLike::cend(sh);
distances_.reserve(ShapeLike::contourVertexCount(sh));
contour_.distances.reserve(ShapeLike::contourVertexCount(sh));
while(next != endit) {
emap_.emplace_back(*(first++), *(next++));
full_distance_ += emap_.back().length();
distances_.push_back(full_distance_);
contour_.emap.emplace_back(*(first++), *(next++));
contour_.full_distance += contour_.emap.back().length();
contour_.distances.push_back(contour_.full_distance);
}
}
for(auto& h : ShapeLike::holes(sh)) { // For the holes
auto first = h.begin();
auto next = std::next(first);
auto endit = h.end();
ContourCache hc;
hc.distances.reserve(endit - first);
while(next != endit) {
hc.emap.emplace_back(*(first++), *(next++));
hc.full_distance += hc.emap.back().length();
hc.distances.push_back(hc.full_distance);
}
holes_.push_back(hc);
}
}
void fetchCorners() const {
if(!corners_.empty()) return;
if(!contour_.corners.empty()) return;
// TODO Accuracy
corners_ = distances_;
for(auto& d : corners_) d /= full_distance_;
contour_.corners = contour_.distances;
for(auto& d : contour_.corners) d /= contour_.full_distance;
}
void fetchHoleCorners(unsigned hidx) const {
auto& hc = holes_[hidx];
if(!hc.corners.empty()) return;
// TODO Accuracy
hc.corners = hc.distances;
for(auto& d : hc.corners) d /= hc.full_distance;
}
inline Vertex coords(const ContourCache& cache, double distance) const {
assert(distance >= .0 && distance <= 1.0);
// distance is from 0.0 to 1.0, we scale it up to the full length of
// the circumference
double d = distance*cache.full_distance;
auto& distances = cache.distances;
// Magic: we find the right edge in log time
auto it = std::lower_bound(distances.begin(), distances.end(), d);
auto idx = it - distances.begin(); // get the index of the edge
auto edge = cache.emap[idx]; // extrac the edge
// Get the remaining distance on the target edge
auto ed = d - (idx > 0 ? *std::prev(it) : 0 );
auto angle = edge.angleToXaxis();
Vertex ret = edge.first();
// Get the point on the edge which lies in ed distance from the start
ret += { static_cast<Coord>(std::round(ed*std::cos(angle))),
static_cast<Coord>(std::round(ed*std::sin(angle))) };
return ret;
}
public:
@ -102,37 +159,36 @@ public:
* @return Returns the coordinates of the point lying on the polygon
* circumference.
*/
inline Vertex coords(double distance) {
assert(distance >= .0 && distance <= 1.0);
// distance is from 0.0 to 1.0, we scale it up to the full length of
// the circumference
double d = distance*full_distance_;
// Magic: we find the right edge in log time
auto it = std::lower_bound(distances_.begin(), distances_.end(), d);
auto idx = it - distances_.begin(); // get the index of the edge
auto edge = emap_[idx]; // extrac the edge
// Get the remaining distance on the target edge
auto ed = d - (idx > 0 ? *std::prev(it) : 0 );
auto angle = edge.angleToXaxis();
Vertex ret = edge.first();
// Get the point on the edge which lies in ed distance from the start
ret += { static_cast<Coord>(std::round(ed*std::cos(angle))),
static_cast<Coord>(std::round(ed*std::sin(angle))) };
return ret;
inline Vertex coords(double distance) const {
return coords(contour_, distance);
}
inline double circumference() const BP2D_NOEXCEPT { return full_distance_; }
inline Vertex coords(unsigned hidx, double distance) const {
assert(hidx < holes_.size());
return coords(holes_[hidx], distance);
}
inline double circumference() const BP2D_NOEXCEPT {
return contour_.full_distance;
}
inline double circumference(unsigned hidx) const BP2D_NOEXCEPT {
return holes_[hidx].full_distance;
}
inline const std::vector<double>& corners() const BP2D_NOEXCEPT {
fetchCorners();
return corners_;
return contour_.corners;
}
inline const std::vector<double>&
corners(unsigned holeidx) const BP2D_NOEXCEPT {
fetchHoleCorners(holeidx);
return holes_[holeidx].corners;
}
inline unsigned holeCount() const BP2D_NOEXCEPT { return holes_.size(); }
};
template<NfpLevel lvl>
@ -294,12 +350,20 @@ public:
for(auto& nfp : nfps ) ecache.emplace_back(nfp);
auto getNfpPoint = [&ecache](double relpos) {
auto relpfloor = std::floor(relpos);
auto nfp_idx = static_cast<unsigned>(relpfloor);
if(nfp_idx >= ecache.size()) nfp_idx--;
auto p = relpos - relpfloor;
return ecache[nfp_idx].coords(p);
struct Optimum {
double relpos;
unsigned nfpidx;
int hidx;
Optimum(double pos, unsigned nidx):
relpos(pos), nfpidx(nidx), hidx(-1) {}
Optimum(double pos, unsigned nidx, int holeidx):
relpos(pos), nfpidx(nidx), hidx(holeidx) {}
};
auto getNfpPoint = [&ecache](const Optimum& opt)
{
return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) :
ecache[opt.nfpidx].coords(opt.nfpidx, opt.relpos);
};
Nfp::Shapes<RawShape> pile;
@ -310,6 +374,8 @@ public:
pile_area += mitem.area();
}
// This is the kernel part of the object function that is
// customizable by the library client
auto _objfunc = config_.object_function?
config_.object_function :
[this](const Nfp::Shapes<RawShape>& pile, double occupied_area,
@ -334,9 +400,8 @@ public:
};
// Our object function for placement
auto objfunc = [&] (double relpos)
auto rawobjfunc = [&] (Vertex v)
{
Vertex v = getNfpPoint(relpos);
auto d = v - iv;
d += startpos;
item.translation(d);
@ -359,46 +424,74 @@ public:
stopcr.type = opt::StopLimitType::RELATIVE;
opt::TOptimizer<opt::Method::L_SIMPLEX> solver(stopcr);
double optimum = 0;
Optimum optimum(0, 0);
double best_score = penality_;
// double max_bound = 1.0*nfps.size();
// Genetic should look like this:
/*auto result = solver.optimize_min(objfunc,
opt::initvals<double>(0.0),
opt::bound(0.0, max_bound)
);
if(result.score < penality_) {
best_score = result.score;
optimum = std::get<0>(result.optimum);
}*/
// Local optimization with the four polygon corners as
// starting points
for(unsigned ch = 0; ch < ecache.size(); ch++) {
auto& cache = ecache[ch];
auto contour_ofn = [&rawobjfunc, &getNfpPoint, ch]
(double relpos)
{
return rawobjfunc(getNfpPoint(Optimum(relpos, ch)));
};
std::for_each(cache.corners().begin(),
cache.corners().end(),
[ch, &solver, &objfunc,
&best_score, &optimum]
(double pos)
[ch, &contour_ofn, &solver, &best_score,
&optimum] (double pos)
{
try {
auto result = solver.optimize_min(objfunc,
opt::initvals<double>(ch+pos),
opt::bound<double>(ch, 1.0 + ch)
auto result = solver.optimize_min(contour_ofn,
opt::initvals<double>(pos),
opt::bound<double>(0, 1.0)
);
if(result.score < best_score) {
best_score = result.score;
optimum = std::get<0>(result.optimum);
optimum.relpos = std::get<0>(result.optimum);
optimum.nfpidx = ch;
optimum.hidx = -1;
}
} catch(std::exception& e) {
derr() << "ERROR: " << e.what() << "\n";
}
});
for(unsigned hidx = 0; hidx < cache.holeCount(); ++hidx) {
auto hole_ofn =
[&rawobjfunc, &getNfpPoint, ch, hidx]
(double pos)
{
Optimum opt(pos, ch, hidx);
return rawobjfunc(getNfpPoint(opt));
};
std::for_each(cache.corners(hidx).begin(),
cache.corners(hidx).end(),
[&hole_ofn, &solver, &best_score,
&optimum, ch, hidx]
(double pos)
{
try {
auto result = solver.optimize_min(hole_ofn,
opt::initvals<double>(pos),
opt::bound<double>(0, 1.0)
);
if(result.score < best_score) {
best_score = result.score;
Optimum o(std::get<0>(result.optimum),
ch, hidx);
optimum = o;
}
} catch(std::exception& e) {
derr() << "ERROR: " << e.what() << "\n";
}
});
}
}
if( best_score < global_score ) {

View File

@ -56,7 +56,7 @@ public:
};
// Safety test: try to pack each item into an empty bin. If it fails
// then it should be removed from the not_packed list
// then it should be removed from the list
{ auto it = store_.begin();
while (it != store_.end()) {
Placer p(bin);
@ -72,7 +72,7 @@ public:
while(!was_packed) {
for(size_t j = 0; j < placers.size() && !was_packed; j++) {
if(was_packed = placers[j].pack(item))
if((was_packed = placers[j].pack(item)))
makeProgress(placers[j], j);
}

View File

@ -530,12 +530,12 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb,
// arranger.useMinimumBoundigBoxRotation();
pcfg.rotations = { 0.0 };
// Magic: we will specify what is the goal of arrangement...
// In this case we override the default object to make the larger items go
// into the center of the pile and smaller items orbit it so the resulting
// pile has a circle-like shape. This is good for the print bed's heat
// profile. We alse sacrafice a bit of pack efficiency for this to work. As
// a side effect, the arrange procedure is a lot faster (we do not need to
// 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
// has a circle-like shape. This is good for the print bed's heat profile.
// We alse sacrafice a bit of pack efficiency for this to work. As a side
// effect, the arrange procedure is a lot faster (we do not need to
// calculate the convex hulls)
pcfg.object_function = [bin, hasbin](
NfpPlacer::Pile pile, // The currently arranged pile