Small objects can now fit inside free space surrounded by objects.
This commit is contained in:
parent
1a6fdb668f
commit
ed0f073ef3
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
auto first = ShapeLike::cbegin(sh);
|
||||
auto next = first + 1;
|
||||
auto endit = ShapeLike::cend(sh);
|
||||
{ // For the contour
|
||||
auto first = ShapeLike::cbegin(sh);
|
||||
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_);
|
||||
while(next != endit) {
|
||||
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 ) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user