mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-11 16:57:53 -06:00
Initial version of smart auto placement intended to replace autocenter.
This commit is contained in:
parent
a85db038be
commit
4628ba5767
14 changed files with 793 additions and 122 deletions
|
@ -70,7 +70,10 @@ if(TBB_FOUND)
|
||||||
# The Intel TBB library will use the std::exception_ptr feature of C++11.
|
# The Intel TBB library will use the std::exception_ptr feature of C++11.
|
||||||
target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0)
|
target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0)
|
||||||
|
|
||||||
target_link_libraries(libnest2d INTERFACE tbb)
|
find_package(Threads REQUIRED)
|
||||||
|
target_link_libraries(libnest2d INTERFACE ${TBB_LIBRARIES} ${CMAKE_DL_LIBS}
|
||||||
|
Threads::Threads
|
||||||
|
)
|
||||||
else()
|
else()
|
||||||
find_package(OpenMP QUIET)
|
find_package(OpenMP QUIET)
|
||||||
|
|
||||||
|
@ -88,7 +91,7 @@ endif()
|
||||||
add_subdirectory(${SRC_DIR}/libnest2d/backends/${LIBNEST2D_GEOMETRIES})
|
add_subdirectory(${SRC_DIR}/libnest2d/backends/${LIBNEST2D_GEOMETRIES})
|
||||||
add_subdirectory(${SRC_DIR}/libnest2d/optimizers/${LIBNEST2D_OPTIMIZER})
|
add_subdirectory(${SRC_DIR}/libnest2d/optimizers/${LIBNEST2D_OPTIMIZER})
|
||||||
|
|
||||||
#target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES})
|
target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES})
|
||||||
target_include_directories(libnest2d INTERFACE ${SRC_DIR})
|
target_include_directories(libnest2d INTERFACE ${SRC_DIR})
|
||||||
|
|
||||||
if(NOT LIBNEST2D_HEADER_ONLY)
|
if(NOT LIBNEST2D_HEADER_ONLY)
|
||||||
|
|
|
@ -62,9 +62,9 @@ if(NOT Boost_INCLUDE_DIRS_FOUND)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(ClipperBackend INTERFACE ${Boost_INCLUDE_DIRS} )
|
target_include_directories(ClipperBackend INTERFACE ${Boost_INCLUDE_DIRS} )
|
||||||
#target_sources(ClipperBackend INTERFACE
|
target_sources(ClipperBackend INTERFACE
|
||||||
# ${CMAKE_CURRENT_SOURCE_DIR}/geometries.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/geometries.hpp
|
||||||
# ${SRC_DIR}/libnest2d/utils/boost_alg.hpp )
|
${SRC_DIR}/libnest2d/utils/boost_alg.hpp )
|
||||||
|
|
||||||
target_compile_definitions(ClipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER)
|
target_compile_definitions(ClipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER)
|
||||||
|
|
||||||
|
|
|
@ -251,6 +251,460 @@ inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
|
||||||
return {rsh, top_nfp};
|
return {rsh, top_nfp};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
|
||||||
|
const RawShape& cother)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Algorithms are from the original algorithm proposed in paper:
|
||||||
|
// https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf
|
||||||
|
|
||||||
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
// Algorithm 1: Obtaining the minkowski sum
|
||||||
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// I guess this is not a full minkowski sum of the two input polygons by
|
||||||
|
// definition. This yields a subset that is compatible with the next 2
|
||||||
|
// algorithms.
|
||||||
|
|
||||||
|
using Result = NfpResult<RawShape>;
|
||||||
|
using Vertex = TPoint<RawShape>;
|
||||||
|
using Coord = TCoord<Vertex>;
|
||||||
|
using Edge = _Segment<Vertex>;
|
||||||
|
namespace sl = shapelike;
|
||||||
|
using std::signbit;
|
||||||
|
using std::sort;
|
||||||
|
using std::vector;
|
||||||
|
using std::ref;
|
||||||
|
using std::reference_wrapper;
|
||||||
|
|
||||||
|
// TODO The original algorithms expects the stationary polygon in
|
||||||
|
// counter clockwise and the orbiter in clockwise order.
|
||||||
|
// So for preventing any further complication, I will make the input
|
||||||
|
// the way it should be, than make my way around the orientations.
|
||||||
|
|
||||||
|
// Reverse the stationary contour to counter clockwise
|
||||||
|
auto stcont = sl::contour(cstationary);
|
||||||
|
{
|
||||||
|
std::reverse(sl::begin(stcont), sl::end(stcont));
|
||||||
|
stcont.pop_back();
|
||||||
|
auto it = std::min_element(sl::begin(stcont), sl::end(stcont),
|
||||||
|
[](const Vertex& v1, const Vertex& v2) {
|
||||||
|
return getY(v1) < getY(v2);
|
||||||
|
});
|
||||||
|
std::rotate(sl::begin(stcont), it, sl::end(stcont));
|
||||||
|
sl::addVertex(stcont, sl::front(stcont));
|
||||||
|
}
|
||||||
|
RawShape stationary;
|
||||||
|
sl::contour(stationary) = stcont;
|
||||||
|
|
||||||
|
// Reverse the orbiter contour to counter clockwise
|
||||||
|
auto orbcont = sl::contour(cother);
|
||||||
|
{
|
||||||
|
std::reverse(orbcont.begin(), orbcont.end());
|
||||||
|
|
||||||
|
// Step 1: Make the orbiter reverse oriented
|
||||||
|
|
||||||
|
orbcont.pop_back();
|
||||||
|
auto it = std::min_element(orbcont.begin(), orbcont.end(),
|
||||||
|
[](const Vertex& v1, const Vertex& v2) {
|
||||||
|
return getY(v1) < getY(v2);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::rotate(orbcont.begin(), it, orbcont.end());
|
||||||
|
orbcont.emplace_back(orbcont.front());
|
||||||
|
|
||||||
|
for(auto &v : orbcont) v = -v;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the orbiter (contour only), we will have to work on it
|
||||||
|
RawShape orbiter;
|
||||||
|
sl::contour(orbiter) = orbcont;
|
||||||
|
|
||||||
|
// An edge with additional data for marking it
|
||||||
|
struct MarkedEdge {
|
||||||
|
Edge e; Radians turn_angle = 0; bool is_turning_point = false;
|
||||||
|
MarkedEdge() = default;
|
||||||
|
MarkedEdge(const Edge& ed, Radians ta, bool tp):
|
||||||
|
e(ed), turn_angle(ta), is_turning_point(tp) {}
|
||||||
|
|
||||||
|
// debug
|
||||||
|
std::string label;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Container for marked edges
|
||||||
|
using EdgeList = vector<MarkedEdge>;
|
||||||
|
|
||||||
|
EdgeList A, B;
|
||||||
|
|
||||||
|
// This is how an edge list is created from the polygons
|
||||||
|
auto fillEdgeList = [](EdgeList& L, const RawShape& ppoly, int dir) {
|
||||||
|
auto& poly = sl::contour(ppoly);
|
||||||
|
|
||||||
|
L.reserve(sl::contourVertexCount(poly));
|
||||||
|
|
||||||
|
if(dir > 0) {
|
||||||
|
auto it = poly.begin();
|
||||||
|
auto nextit = std::next(it);
|
||||||
|
|
||||||
|
double turn_angle = 0;
|
||||||
|
bool is_turn_point = false;
|
||||||
|
|
||||||
|
while(nextit != poly.end()) {
|
||||||
|
L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
|
||||||
|
it++; nextit++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto it = sl::rbegin(poly);
|
||||||
|
auto nextit = std::next(it);
|
||||||
|
|
||||||
|
double turn_angle = 0;
|
||||||
|
bool is_turn_point = false;
|
||||||
|
|
||||||
|
while(nextit != sl::rend(poly)) {
|
||||||
|
L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
|
||||||
|
it++; nextit++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto getTurnAngle = [](const Edge& e1, const Edge& e2) {
|
||||||
|
auto phi = e1.angleToXaxis();
|
||||||
|
auto phi_prev = e2.angleToXaxis();
|
||||||
|
auto turn_angle = phi-phi_prev;
|
||||||
|
if(turn_angle > Pi) turn_angle -= TwoPi;
|
||||||
|
if(turn_angle < -Pi) turn_angle += TwoPi;
|
||||||
|
return turn_angle;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto eit = L.begin();
|
||||||
|
auto enext = std::next(eit);
|
||||||
|
|
||||||
|
eit->turn_angle = getTurnAngle(L.front().e, L.back().e);
|
||||||
|
|
||||||
|
while(enext != L.end()) {
|
||||||
|
enext->turn_angle = getTurnAngle( enext->e, eit->e);
|
||||||
|
eit->is_turning_point =
|
||||||
|
signbit(enext->turn_angle) != signbit(eit->turn_angle);
|
||||||
|
++eit; ++enext;
|
||||||
|
}
|
||||||
|
|
||||||
|
L.back().is_turning_point = signbit(L.back().turn_angle) !=
|
||||||
|
signbit(L.front().turn_angle);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Step 2: Fill the edgelists
|
||||||
|
fillEdgeList(A, stationary, 1);
|
||||||
|
fillEdgeList(B, orbiter, 1);
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
for(MarkedEdge& me : A) {
|
||||||
|
std::cout << "a" << i << ":\n\t"
|
||||||
|
<< getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
|
||||||
|
<< getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
|
||||||
|
<< "Turning point: " << (me.is_turning_point ? "yes" : "no")
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
me.label = "a"; me.label += std::to_string(i);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 1;
|
||||||
|
for(MarkedEdge& me : B) {
|
||||||
|
std::cout << "b" << i << ":\n\t"
|
||||||
|
<< getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
|
||||||
|
<< getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
|
||||||
|
<< "Turning point: " << (me.is_turning_point ? "yes" : "no")
|
||||||
|
<< std::endl;
|
||||||
|
me.label = "b"; me.label += std::to_string(i);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A reference to a marked edge that also knows its container
|
||||||
|
struct MarkedEdgeRef {
|
||||||
|
reference_wrapper<MarkedEdge> eref;
|
||||||
|
reference_wrapper<vector<MarkedEdgeRef>> container;
|
||||||
|
Coord dir = 1; // Direction modifier
|
||||||
|
|
||||||
|
inline Radians angleX() const { return eref.get().e.angleToXaxis(); }
|
||||||
|
inline const Edge& edge() const { return eref.get().e; }
|
||||||
|
inline Edge& edge() { return eref.get().e; }
|
||||||
|
inline bool isTurningPoint() const {
|
||||||
|
return eref.get().is_turning_point;
|
||||||
|
}
|
||||||
|
inline bool isFrom(const vector<MarkedEdgeRef>& cont ) {
|
||||||
|
return &(container.get()) == &cont;
|
||||||
|
}
|
||||||
|
inline bool eq(const MarkedEdgeRef& mr) {
|
||||||
|
return &(eref.get()) == &(mr.eref.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
|
||||||
|
reference_wrapper<vector<MarkedEdgeRef>> ec):
|
||||||
|
eref(er), container(ec), dir(1) {}
|
||||||
|
|
||||||
|
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
|
||||||
|
reference_wrapper<vector<MarkedEdgeRef>> ec,
|
||||||
|
Coord d):
|
||||||
|
eref(er), container(ec), dir(d) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
using EdgeRefList = vector<MarkedEdgeRef>;
|
||||||
|
|
||||||
|
// Comparing two marked edges
|
||||||
|
auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) {
|
||||||
|
return e1.angleX() < e2.angleX();
|
||||||
|
};
|
||||||
|
|
||||||
|
EdgeRefList Aref, Bref; // We create containers for the references
|
||||||
|
Aref.reserve(A.size()); Bref.reserve(B.size());
|
||||||
|
|
||||||
|
// Fill reference container for the stationary polygon
|
||||||
|
std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) {
|
||||||
|
Aref.emplace_back( ref(me), ref(Aref) );
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fill reference container for the orbiting polygon
|
||||||
|
std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) {
|
||||||
|
Bref.emplace_back( ref(me), ref(Bref) );
|
||||||
|
});
|
||||||
|
|
||||||
|
auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure
|
||||||
|
(const EdgeRefList& Q, const EdgeRefList& R, bool positive)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)"
|
||||||
|
// Sort the containers of edge references and merge them.
|
||||||
|
// Q could be sorted only once and be reused here but we would still
|
||||||
|
// need to merge it with sorted(R).
|
||||||
|
|
||||||
|
EdgeRefList merged;
|
||||||
|
EdgeRefList S, seq;
|
||||||
|
merged.reserve(Q.size() + R.size());
|
||||||
|
|
||||||
|
merged.insert(merged.end(), R.begin(), R.end());
|
||||||
|
std::stable_sort(merged.begin(), merged.end(), sortfn);
|
||||||
|
merged.insert(merged.end(), Q.begin(), Q.end());
|
||||||
|
std::stable_sort(merged.begin(), merged.end(), sortfn);
|
||||||
|
|
||||||
|
// Step 2 "set i = 1, k = 1, direction = 1, s1 = q1"
|
||||||
|
// we don't use i, instead, q is an iterator into Q. k would be an index
|
||||||
|
// into the merged sequence but we use "it" as an iterator for that
|
||||||
|
|
||||||
|
// here we obtain references for the containers for later comparisons
|
||||||
|
const auto& Rcont = R.begin()->container.get();
|
||||||
|
const auto& Qcont = Q.begin()->container.get();
|
||||||
|
|
||||||
|
// Set the initial direction
|
||||||
|
Coord dir = 1;
|
||||||
|
|
||||||
|
// roughly i = 1 (so q = Q.begin()) and s1 = q1 so S[0] = q;
|
||||||
|
if(positive) {
|
||||||
|
auto q = Q.begin();
|
||||||
|
S.emplace_back(*q);
|
||||||
|
|
||||||
|
// Roughly step 3
|
||||||
|
|
||||||
|
std::cout << "merged size: " << merged.size() << std::endl;
|
||||||
|
auto mit = merged.begin();
|
||||||
|
for(bool finish = false; !finish && q != Q.end();) {
|
||||||
|
++q; // "Set i = i + 1"
|
||||||
|
|
||||||
|
while(!finish && mit != merged.end()) {
|
||||||
|
if(mit->isFrom(Rcont)) {
|
||||||
|
auto s = *mit;
|
||||||
|
s.dir = dir;
|
||||||
|
S.emplace_back(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mit->eq(*q)) {
|
||||||
|
S.emplace_back(*q);
|
||||||
|
if(mit->isTurningPoint()) dir = -dir;
|
||||||
|
if(q == Q.begin()) finish = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mit += dir;
|
||||||
|
// __nfp::advance(mit, merged, dir > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto q = Q.rbegin();
|
||||||
|
S.emplace_back(*q);
|
||||||
|
|
||||||
|
// Roughly step 3
|
||||||
|
|
||||||
|
std::cout << "merged size: " << merged.size() << std::endl;
|
||||||
|
auto mit = merged.begin();
|
||||||
|
for(bool finish = false; !finish && q != Q.rend();) {
|
||||||
|
++q; // "Set i = i + 1"
|
||||||
|
|
||||||
|
while(!finish && mit != merged.end()) {
|
||||||
|
if(mit->isFrom(Rcont)) {
|
||||||
|
auto s = *mit;
|
||||||
|
s.dir = dir;
|
||||||
|
S.emplace_back(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mit->eq(*q)) {
|
||||||
|
S.emplace_back(*q);
|
||||||
|
S.back().dir = -1;
|
||||||
|
if(mit->isTurningPoint()) dir = -dir;
|
||||||
|
if(q == Q.rbegin()) finish = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mit += dir;
|
||||||
|
// __nfp::advance(mit, merged, dir > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Step 4:
|
||||||
|
|
||||||
|
// "Let starting edge r1 be in position si in sequence"
|
||||||
|
// whaaat? I guess this means the following:
|
||||||
|
auto it = S.begin();
|
||||||
|
while(!it->eq(*R.begin())) ++it;
|
||||||
|
|
||||||
|
// "Set j = 1, next = 2, direction = 1, seq1 = si"
|
||||||
|
// we don't use j, seq is expanded dynamically.
|
||||||
|
dir = 1;
|
||||||
|
auto next = std::next(R.begin()); seq.emplace_back(*it);
|
||||||
|
|
||||||
|
// Step 5:
|
||||||
|
// "If all si edges have been allocated to seqj" should mean that
|
||||||
|
// we loop until seq has equal size with S
|
||||||
|
auto send = it; //it == S.begin() ? it : std::prev(it);
|
||||||
|
while(it != S.end()) {
|
||||||
|
++it; if(it == S.end()) it = S.begin();
|
||||||
|
if(it == send) break;
|
||||||
|
|
||||||
|
if(it->isFrom(Qcont)) {
|
||||||
|
seq.emplace_back(*it); // "If si is from Q, j = j + 1, seqj = si"
|
||||||
|
|
||||||
|
// "If si is a turning point in Q,
|
||||||
|
// direction = - direction, next = next + direction"
|
||||||
|
if(it->isTurningPoint()) {
|
||||||
|
dir = -dir;
|
||||||
|
next += dir;
|
||||||
|
// __nfp::advance(next, R, dir > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(it->eq(*next) /*&& dir == next->dir*/) { // "If si = direction.rnext"
|
||||||
|
// "j = j + 1, seqj = si, next = next + direction"
|
||||||
|
seq.emplace_back(*it);
|
||||||
|
next += dir;
|
||||||
|
// __nfp::advance(next, R, dir > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return seq;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<EdgeRefList> seqlist;
|
||||||
|
seqlist.reserve(Bref.size());
|
||||||
|
|
||||||
|
EdgeRefList Bslope = Bref; // copy Bref, we will make a slope diagram
|
||||||
|
|
||||||
|
// make the slope diagram of B
|
||||||
|
std::sort(Bslope.begin(), Bslope.end(), sortfn);
|
||||||
|
|
||||||
|
auto slopeit = Bslope.begin(); // search for the first turning point
|
||||||
|
while(!slopeit->isTurningPoint() && slopeit != Bslope.end()) slopeit++;
|
||||||
|
|
||||||
|
if(slopeit == Bslope.end()) {
|
||||||
|
// no turning point means convex polygon.
|
||||||
|
seqlist.emplace_back(mink(Aref, Bref, true));
|
||||||
|
} else {
|
||||||
|
int dir = 1;
|
||||||
|
|
||||||
|
auto firstturn = Bref.begin();
|
||||||
|
while(!firstturn->eq(*slopeit)) ++firstturn;
|
||||||
|
|
||||||
|
assert(firstturn != Bref.end());
|
||||||
|
|
||||||
|
EdgeRefList bgroup; bgroup.reserve(Bref.size());
|
||||||
|
bgroup.emplace_back(*slopeit);
|
||||||
|
|
||||||
|
auto b_it = std::next(firstturn);
|
||||||
|
while(b_it != firstturn) {
|
||||||
|
if(b_it == Bref.end()) b_it = Bref.begin();
|
||||||
|
|
||||||
|
while(!slopeit->eq(*b_it)) {
|
||||||
|
__nfp::advance(slopeit, Bslope, dir > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!slopeit->isTurningPoint()) {
|
||||||
|
bgroup.emplace_back(*slopeit);
|
||||||
|
} else {
|
||||||
|
if(!bgroup.empty()) {
|
||||||
|
if(dir > 0) bgroup.emplace_back(*slopeit);
|
||||||
|
for(auto& me : bgroup) {
|
||||||
|
std::cout << me.eref.get().label << ", ";
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
seqlist.emplace_back(mink(Aref, bgroup, dir == 1 ? true : false));
|
||||||
|
bgroup.clear();
|
||||||
|
if(dir < 0) bgroup.emplace_back(*slopeit);
|
||||||
|
} else {
|
||||||
|
bgroup.emplace_back(*slopeit);
|
||||||
|
}
|
||||||
|
|
||||||
|
dir *= -1;
|
||||||
|
}
|
||||||
|
++b_it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// while(it != Bref.end()) // This is step 3 and step 4 in one loop
|
||||||
|
// if(it->isTurningPoint()) {
|
||||||
|
// R = {R.last, it++};
|
||||||
|
// auto seq = mink(Q, R, orientation);
|
||||||
|
|
||||||
|
// // TODO step 6 (should be 5 shouldn't it?): linking edges from A
|
||||||
|
// // I don't get this step
|
||||||
|
|
||||||
|
// seqlist.insert(seqlist.end(), seq.begin(), seq.end());
|
||||||
|
// orientation = !orientation;
|
||||||
|
// } else ++it;
|
||||||
|
|
||||||
|
// if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true);
|
||||||
|
|
||||||
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
// Algorithm 2: breaking Minkowski sums into track line trips
|
||||||
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
// Algorithm 3: finding the boundary of the NFP from track line trips
|
||||||
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
for(auto& seq : seqlist) {
|
||||||
|
std::cout << "seqlist size: " << seq.size() << std::endl;
|
||||||
|
for(auto& s : seq) {
|
||||||
|
std::cout << (s.dir > 0 ? "" : "-") << s.eref.get().label << ", ";
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& seq = seqlist.front();
|
||||||
|
RawShape rsh;
|
||||||
|
Vertex top_nfp;
|
||||||
|
std::vector<Edge> edgelist; edgelist.reserve(seq.size());
|
||||||
|
for(auto& s : seq) {
|
||||||
|
edgelist.emplace_back(s.eref.get().e);
|
||||||
|
}
|
||||||
|
|
||||||
|
__nfp::buildPolygon(edgelist, rsh, top_nfp);
|
||||||
|
|
||||||
|
return Result(rsh, top_nfp);
|
||||||
|
}
|
||||||
|
|
||||||
// Specializable NFP implementation class. Specialize it if you have a faster
|
// Specializable NFP implementation class. Specialize it if you have a faster
|
||||||
// or better NFP implementation
|
// or better NFP implementation
|
||||||
template<class RawShape, NfpLevel nfptype>
|
template<class RawShape, NfpLevel nfptype>
|
||||||
|
|
|
@ -490,9 +490,32 @@ _Item<RawShape>::isInside(const _Circle<TPoint<RawShape>>& circ) const {
|
||||||
return sl::isInside<RawShape>(transformedShape(), circ);
|
return sl::isInside<RawShape>(transformedShape(), circ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class RawShape> using _ItemRef = std::reference_wrapper<_Item<RawShape>>;
|
||||||
|
template<class RawShape> using _ItemGroup = std::vector<_ItemRef<RawShape>>;
|
||||||
|
|
||||||
template<class I> using _ItemRef = std::reference_wrapper<I>;
|
/**
|
||||||
template<class I> using _ItemGroup = std::vector<_ItemRef<I>>;
|
* \brief A list of packed item vectors. Each vector represents a bin.
|
||||||
|
*/
|
||||||
|
template<class RawShape>
|
||||||
|
using _PackGroup = std::vector<std::vector<_ItemRef<RawShape>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A list of packed (index, item) pair vectors. Each vector represents a
|
||||||
|
* bin.
|
||||||
|
*
|
||||||
|
* The index is points to the position of the item in the original input
|
||||||
|
* sequence. This way the caller can use the items as a transformation data
|
||||||
|
* carrier and transform the original objects manually.
|
||||||
|
*/
|
||||||
|
template<class RawShape>
|
||||||
|
using _IndexedPackGroup = std::vector<
|
||||||
|
std::vector<
|
||||||
|
std::pair<
|
||||||
|
unsigned,
|
||||||
|
_ItemRef<RawShape>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
|
||||||
template<class Iterator>
|
template<class Iterator>
|
||||||
struct ConstItemRange {
|
struct ConstItemRange {
|
||||||
|
@ -524,8 +547,10 @@ class PlacementStrategyLike {
|
||||||
PlacementStrategy impl_;
|
PlacementStrategy impl_;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
using RawShape = typename PlacementStrategy::ShapeType;
|
||||||
|
|
||||||
/// The item type that the placer works with.
|
/// The item type that the placer works with.
|
||||||
using Item = typename PlacementStrategy::Item;
|
using Item = _Item<RawShape>;
|
||||||
|
|
||||||
/// The placer's config type. Should be a simple struct but can be anything.
|
/// The placer's config type. Should be a simple struct but can be anything.
|
||||||
using Config = typename PlacementStrategy::Config;
|
using Config = typename PlacementStrategy::Config;
|
||||||
|
@ -544,8 +569,7 @@ public:
|
||||||
*/
|
*/
|
||||||
using PackResult = typename PlacementStrategy::PackResult;
|
using PackResult = typename PlacementStrategy::PackResult;
|
||||||
|
|
||||||
using ItemRef = _ItemRef<Item>;
|
using ItemGroup = _ItemGroup<RawShape>;
|
||||||
using ItemGroup = _ItemGroup<Item>;
|
|
||||||
using DefaultIterator = typename ItemGroup::const_iterator;
|
using DefaultIterator = typename ItemGroup::const_iterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -619,6 +643,16 @@ public:
|
||||||
return impl_.pack(item, remaining);
|
return impl_.pack(item, remaining);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method makes possible to "preload" some items into the placer. It
|
||||||
|
* will not move these items but will consider them as already packed.
|
||||||
|
*/
|
||||||
|
template<class Range = ConstItemRange<DefaultIterator>>
|
||||||
|
inline void preload(const Range& packeditems = Range())
|
||||||
|
{
|
||||||
|
impl_.preload(packeditems);
|
||||||
|
}
|
||||||
|
|
||||||
/// Unpack the last element (remove it from the list of packed items).
|
/// Unpack the last element (remove it from the list of packed items).
|
||||||
inline void unpackLast() { impl_.unpackLast(); }
|
inline void unpackLast() { impl_.unpackLast(); }
|
||||||
|
|
||||||
|
@ -649,11 +683,11 @@ template<class SelectionStrategy>
|
||||||
class SelectionStrategyLike {
|
class SelectionStrategyLike {
|
||||||
SelectionStrategy impl_;
|
SelectionStrategy impl_;
|
||||||
public:
|
public:
|
||||||
using Item = typename SelectionStrategy::Item;
|
using RawShape = typename SelectionStrategy::ShapeType;
|
||||||
|
using Item = _Item<RawShape>;
|
||||||
|
using PackGroup = _PackGroup<RawShape>;
|
||||||
using Config = typename SelectionStrategy::Config;
|
using Config = typename SelectionStrategy::Config;
|
||||||
|
|
||||||
using ItemRef = std::reference_wrapper<Item>;
|
|
||||||
using ItemGroup = std::vector<ItemRef>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Provide a different configuration for the selection strategy.
|
* @brief Provide a different configuration for the selection strategy.
|
||||||
|
@ -703,60 +737,29 @@ public:
|
||||||
std::forward<PConfig>(config));
|
std::forward<PConfig>(config));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief Get the number of bins opened by the selection algorithm.
|
|
||||||
*
|
|
||||||
* Initially it is zero and after the call to packItems it will return
|
|
||||||
* the number of bins opened by the packing procedure.
|
|
||||||
*
|
|
||||||
* \return The number of bins opened.
|
|
||||||
*/
|
|
||||||
inline size_t binCount() const { return impl_.binCount(); }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the items for a particular bin.
|
* @brief Get the items for a particular bin.
|
||||||
* @param binIndex The index of the requested bin.
|
* @param binIndex The index of the requested bin.
|
||||||
* @return Returns a list of all items packed into the requested bin.
|
* @return Returns a list of all items packed into the requested bin.
|
||||||
*/
|
*/
|
||||||
inline ItemGroup itemsForBin(size_t binIndex) {
|
inline const PackGroup& getResult() const {
|
||||||
return impl_.itemsForBin(binIndex);
|
return impl_.getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as itemsForBin but for a const context.
|
/**
|
||||||
inline const ItemGroup itemsForBin(size_t binIndex) const {
|
* @brief Loading a group of already packed bins. It is best to use a result
|
||||||
return impl_.itemsForBin(binIndex);
|
* from a previous packing. The algorithm will consider this input as if the
|
||||||
}
|
* objects are already packed and not move them. If any of these items are
|
||||||
|
* outside the bin, it is up to the placer algorithm what will happen.
|
||||||
|
* Packing additional items can fail for the bottom-left and nfp placers.
|
||||||
|
* @param pckgrp A packgroup which is a vector of item vectors. Each item
|
||||||
|
* vector corresponds to a packed bin.
|
||||||
|
*/
|
||||||
|
inline void preload(const PackGroup& pckgrp) { impl_.preload(pckgrp); }
|
||||||
|
|
||||||
|
void clear() { impl_.clear(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief A list of packed item vectors. Each vector represents a bin.
|
|
||||||
*/
|
|
||||||
template<class RawShape>
|
|
||||||
using _PackGroup = std::vector<
|
|
||||||
std::vector<
|
|
||||||
std::reference_wrapper<_Item<RawShape>>
|
|
||||||
>
|
|
||||||
>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief A list of packed (index, item) pair vectors. Each vector represents a
|
|
||||||
* bin.
|
|
||||||
*
|
|
||||||
* The index is points to the position of the item in the original input
|
|
||||||
* sequence. This way the caller can use the items as a transformation data
|
|
||||||
* carrier and transform the original objects manually.
|
|
||||||
*/
|
|
||||||
template<class RawShape>
|
|
||||||
using _IndexedPackGroup = std::vector<
|
|
||||||
std::vector<
|
|
||||||
std::pair<
|
|
||||||
unsigned,
|
|
||||||
std::reference_wrapper<_Item<RawShape>>
|
|
||||||
>
|
|
||||||
>
|
|
||||||
>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Arranger is the front-end class for the libnest2d library. It takes the
|
* The Arranger is the front-end class for the libnest2d library. It takes the
|
||||||
* input items and outputs the items with the proper transformations to be
|
* input items and outputs the items with the proper transformations to be
|
||||||
|
@ -868,17 +871,29 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a predicate to tell when to abort nesting.
|
/// Set a predicate to tell when to abort nesting.
|
||||||
inline Nester& stopCondition(StopCondition fn) {
|
inline Nester& stopCondition(StopCondition fn)
|
||||||
|
{
|
||||||
selector_.stopCondition(fn); return *this;
|
selector_.stopCondition(fn); return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline PackGroup lastResult() {
|
inline const PackGroup& lastResult() const
|
||||||
PackGroup ret;
|
{
|
||||||
for(size_t i = 0; i < selector_.binCount(); i++) {
|
return selector_.getResult();
|
||||||
auto items = selector_.itemsForBin(i);
|
}
|
||||||
ret.push_back(items);
|
|
||||||
|
inline void preload(const PackGroup& pgrp)
|
||||||
|
{
|
||||||
|
selector_.preload(pgrp);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void preload(const IndexedPackGroup& ipgrp)
|
||||||
|
{
|
||||||
|
PackGroup pgrp; pgrp.reserve(ipgrp.size());
|
||||||
|
for(auto& ig : ipgrp) {
|
||||||
|
pgrp.emplace_back(); pgrp.back().reserve(ig.size());
|
||||||
|
for(auto& r : ig) pgrp.back().emplace_back(r.second);
|
||||||
}
|
}
|
||||||
return ret;
|
preload(pgrp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -892,7 +907,7 @@ private:
|
||||||
// have to exist for the lifetime of this call.
|
// have to exist for the lifetime of this call.
|
||||||
class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT>
|
class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT>
|
||||||
>
|
>
|
||||||
inline PackGroup _execute(TIterator from, TIterator to, bool = false)
|
inline const PackGroup& _execute(TIterator from, TIterator to, bool = false)
|
||||||
{
|
{
|
||||||
__execute(from, to);
|
__execute(from, to);
|
||||||
return lastResult();
|
return lastResult();
|
||||||
|
@ -902,7 +917,7 @@ private:
|
||||||
class IT = remove_cvref_t<typename TIterator::value_type>,
|
class IT = remove_cvref_t<typename TIterator::value_type>,
|
||||||
class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT>
|
class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT>
|
||||||
>
|
>
|
||||||
inline PackGroup _execute(TIterator from, TIterator to, int = false)
|
inline const PackGroup& _execute(TIterator from, TIterator to, int = false)
|
||||||
{
|
{
|
||||||
item_cache_ = {from, to};
|
item_cache_ = {from, to};
|
||||||
|
|
||||||
|
@ -946,10 +961,12 @@ private:
|
||||||
TSel& selector)
|
TSel& selector)
|
||||||
{
|
{
|
||||||
IndexedPackGroup pg;
|
IndexedPackGroup pg;
|
||||||
pg.reserve(selector.binCount());
|
pg.reserve(selector.getResult().size());
|
||||||
|
|
||||||
for(size_t i = 0; i < selector.binCount(); i++) {
|
const PackGroup& pckgrp = selector.getResult();
|
||||||
auto items = selector.itemsForBin(i);
|
|
||||||
|
for(size_t i = 0; i < pckgrp.size(); i++) {
|
||||||
|
auto items = pckgrp[i];
|
||||||
pg.push_back({});
|
pg.push_back({});
|
||||||
pg[i].reserve(items.size());
|
pg[i].reserve(items.size());
|
||||||
|
|
||||||
|
|
|
@ -48,12 +48,12 @@ else()
|
||||||
target_link_libraries(NloptOptimizer INTERFACE Nlopt::Nlopt)
|
target_link_libraries(NloptOptimizer INTERFACE Nlopt::Nlopt)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
#target_sources( NloptOptimizer INTERFACE
|
target_sources( NloptOptimizer INTERFACE
|
||||||
#${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp
|
||||||
#${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp
|
||||||
#${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp
|
||||||
#${CMAKE_CURRENT_SOURCE_DIR}/nlopt_boilerplate.hpp
|
${CMAKE_CURRENT_SOURCE_DIR}/nlopt_boilerplate.hpp
|
||||||
#)
|
)
|
||||||
|
|
||||||
target_compile_definitions(NloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT)
|
target_compile_definitions(NloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT)
|
||||||
|
|
||||||
|
|
|
@ -130,7 +130,7 @@ namespace placers {
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
struct NfpPConfig {
|
struct NfpPConfig {
|
||||||
|
|
||||||
using ItemGroup = _ItemGroup<_Item<RawShape>>;
|
using ItemGroup = _ItemGroup<RawShape>;
|
||||||
|
|
||||||
enum class Alignment {
|
enum class Alignment {
|
||||||
CENTER,
|
CENTER,
|
||||||
|
@ -138,6 +138,8 @@ struct NfpPConfig {
|
||||||
BOTTOM_RIGHT,
|
BOTTOM_RIGHT,
|
||||||
TOP_LEFT,
|
TOP_LEFT,
|
||||||
TOP_RIGHT,
|
TOP_RIGHT,
|
||||||
|
DONT_ALIGN //!> Warning: parts may end up outside the bin with the
|
||||||
|
//! default object function.
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Which angles to try out for better results.
|
/// Which angles to try out for better results.
|
||||||
|
@ -545,8 +547,8 @@ public:
|
||||||
_NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default;
|
_NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default;
|
||||||
|
|
||||||
#ifndef BP2D_COMPILER_MSVC12 // MSVC2013 does not support default move ctors
|
#ifndef BP2D_COMPILER_MSVC12 // MSVC2013 does not support default move ctors
|
||||||
_NofitPolyPlacer(_NofitPolyPlacer&&) /*BP2D_NOEXCEPT*/ = default;
|
_NofitPolyPlacer(_NofitPolyPlacer&&) = default;
|
||||||
_NofitPolyPlacer& operator=(_NofitPolyPlacer&&) /*BP2D_NOEXCEPT*/ = default;
|
_NofitPolyPlacer& operator=(_NofitPolyPlacer&&) = default;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static inline double overfit(const Box& bb, const RawShape& bin) {
|
static inline double overfit(const Box& bb, const RawShape& bin) {
|
||||||
|
@ -905,26 +907,43 @@ private:
|
||||||
|
|
||||||
// This is the kernel part of the object function that is
|
// This is the kernel part of the object function that is
|
||||||
// customizable by the library client
|
// customizable by the library client
|
||||||
auto _objfunc = config_.object_function?
|
std::function<double(const Item&)> _objfunc;
|
||||||
config_.object_function :
|
if(config_.object_function) _objfunc = config_.object_function;
|
||||||
[norm, bin, binbb, pbb](const Item& item)
|
else {
|
||||||
{
|
|
||||||
auto ibb = item.boundingBox();
|
|
||||||
auto fullbb = boundingBox(pbb, ibb);
|
|
||||||
|
|
||||||
double score = pl::distance(ibb.center(), binbb.center());
|
// Inside check has to be strict if no alignment was enabled.
|
||||||
score /= norm;
|
std::function<double(const Box&)> ins_check;
|
||||||
|
if(config_.alignment == Config::Alignment::DONT_ALIGN)
|
||||||
|
ins_check = [&binbb, norm](const Box& fullbb) {
|
||||||
|
double ret = 0;
|
||||||
|
if(sl::isInside<RawShape>(fullbb, binbb)) ret += norm;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
else
|
||||||
|
ins_check = [&bin](const Box& fullbb) {
|
||||||
|
double miss = overfit(fullbb, bin);
|
||||||
|
miss = miss > 0? miss : 0;
|
||||||
|
return std::pow(miss, 2);
|
||||||
|
};
|
||||||
|
|
||||||
double miss = overfit(fullbb, bin);
|
_objfunc = [norm, binbb, pbb, ins_check](const Item& item)
|
||||||
miss = miss > 0? miss : 0;
|
{
|
||||||
score += std::pow(miss, 2);
|
auto ibb = item.boundingBox();
|
||||||
|
auto fullbb = boundingBox(pbb, ibb);
|
||||||
|
|
||||||
return score;
|
double score = pl::distance(ibb.center(),
|
||||||
};
|
binbb.center());
|
||||||
|
score /= norm;
|
||||||
|
|
||||||
|
score += ins_check(fullbb);
|
||||||
|
|
||||||
|
return score;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Our object function for placement
|
// Our object function for placement
|
||||||
auto rawobjfunc =
|
auto rawobjfunc = [_objfunc, iv, startpos]
|
||||||
[_objfunc, iv, startpos] (Vertex v, Item& itm)
|
(Vertex v, Item& itm)
|
||||||
{
|
{
|
||||||
auto d = v - iv;
|
auto d = v - iv;
|
||||||
d += startpos;
|
d += startpos;
|
||||||
|
@ -1101,7 +1120,9 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void finalAlign(_Circle<TPoint<RawShape>> cbin) {
|
inline void finalAlign(_Circle<TPoint<RawShape>> cbin) {
|
||||||
if(items_.empty()) return;
|
if(items_.empty() ||
|
||||||
|
config_.alignment == Config::Alignment::DONT_ALIGN) return;
|
||||||
|
|
||||||
nfp::Shapes<RawShape> m;
|
nfp::Shapes<RawShape> m;
|
||||||
m.reserve(items_.size());
|
m.reserve(items_.size());
|
||||||
for(Item& item : items_) m.emplace_back(item.transformedShape());
|
for(Item& item : items_) m.emplace_back(item.transformedShape());
|
||||||
|
@ -1113,7 +1134,9 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void finalAlign(Box bbin) {
|
inline void finalAlign(Box bbin) {
|
||||||
if(items_.empty()) return;
|
if(items_.empty() ||
|
||||||
|
config_.alignment == Config::Alignment::DONT_ALIGN) return;
|
||||||
|
|
||||||
nfp::Shapes<RawShape> m;
|
nfp::Shapes<RawShape> m;
|
||||||
m.reserve(items_.size());
|
m.reserve(items_.size());
|
||||||
for(Item& item : items_) m.emplace_back(item.transformedShape());
|
for(Item& item : items_) m.emplace_back(item.transformedShape());
|
||||||
|
@ -1147,6 +1170,7 @@ private:
|
||||||
cb = bbin.maxCorner();
|
cb = bbin.maxCorner();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
default: ; // DONT_ALIGN
|
||||||
}
|
}
|
||||||
|
|
||||||
auto d = cb - ci;
|
auto d = cb - ci;
|
||||||
|
@ -1184,6 +1208,7 @@ private:
|
||||||
cb = bbin.maxCorner();
|
cb = bbin.maxCorner();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
default:;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto d = cb - ci;
|
auto d = cb - ci;
|
||||||
|
|
|
@ -12,6 +12,7 @@ class PlacerBoilerplate {
|
||||||
mutable bool farea_valid_ = false;
|
mutable bool farea_valid_ = false;
|
||||||
mutable double farea_ = 0.0;
|
mutable double farea_ = 0.0;
|
||||||
public:
|
public:
|
||||||
|
using ShapeType = RawShape;
|
||||||
using Item = _Item<RawShape>;
|
using Item = _Item<RawShape>;
|
||||||
using Vertex = TPoint<RawShape>;
|
using Vertex = TPoint<RawShape>;
|
||||||
using Segment = _Segment<Vertex>;
|
using Segment = _Segment<Vertex>;
|
||||||
|
@ -19,7 +20,7 @@ public:
|
||||||
using Coord = TCoord<Vertex>;
|
using Coord = TCoord<Vertex>;
|
||||||
using Unit = Coord;
|
using Unit = Coord;
|
||||||
using Config = Cfg;
|
using Config = Cfg;
|
||||||
using ItemGroup = _ItemGroup<Item>;
|
using ItemGroup = _ItemGroup<RawShape>;
|
||||||
using DefaultIter = typename ItemGroup::const_iterator;
|
using DefaultIter = typename ItemGroup::const_iterator;
|
||||||
|
|
||||||
class PackResult {
|
class PackResult {
|
||||||
|
@ -69,6 +70,12 @@ public:
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class Range = ConstItemRange<DefaultIter>>
|
||||||
|
void preload(const Range& packeditems = Range()) {
|
||||||
|
items_.insert(items_.end(), packeditems.from, packeditems.to);
|
||||||
|
farea_valid_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
void accept(PackResult& r) {
|
void accept(PackResult& r) {
|
||||||
if(r) {
|
if(r) {
|
||||||
r.item_ptr_->translation(r.move_);
|
r.item_ptr_->translation(r.move_);
|
||||||
|
@ -117,6 +124,7 @@ using Base::bin_; \
|
||||||
using Base::items_; \
|
using Base::items_; \
|
||||||
using Base::config_; \
|
using Base::config_; \
|
||||||
public: \
|
public: \
|
||||||
|
using typename Base::ShapeType; \
|
||||||
using typename Base::Item; \
|
using typename Base::Item; \
|
||||||
using typename Base::ItemGroup; \
|
using typename Base::ItemGroup; \
|
||||||
using typename Base::BinType; \
|
using typename Base::BinType; \
|
||||||
|
|
|
@ -33,7 +33,7 @@ class _DJDHeuristic: public SelectionBoilerplate<RawShape> {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using typename Base::Item;
|
using typename Base::Item;
|
||||||
using typename Base::ItemRef;
|
using ItemRef = std::reference_wrapper<Item>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The Config for DJD heuristic.
|
* @brief The Config for DJD heuristic.
|
||||||
|
@ -126,6 +126,8 @@ public:
|
||||||
|
|
||||||
store_.clear();
|
store_.clear();
|
||||||
store_.reserve(last-first);
|
store_.reserve(last-first);
|
||||||
|
|
||||||
|
// TODO: support preloading
|
||||||
packed_bins_.clear();
|
packed_bins_.clear();
|
||||||
|
|
||||||
std::copy(first, last, std::back_inserter(store_));
|
std::copy(first, last, std::back_inserter(store_));
|
||||||
|
|
|
@ -34,6 +34,10 @@ public:
|
||||||
store_.clear();
|
store_.clear();
|
||||||
auto total = last-first;
|
auto total = last-first;
|
||||||
store_.reserve(total);
|
store_.reserve(total);
|
||||||
|
|
||||||
|
// TODO: support preloading
|
||||||
|
packed_bins_.clear();
|
||||||
|
|
||||||
packed_bins_.emplace_back();
|
packed_bins_.emplace_back();
|
||||||
|
|
||||||
auto makeProgress = [this, &total](
|
auto makeProgress = [this, &total](
|
||||||
|
|
|
@ -36,11 +36,19 @@ public:
|
||||||
|
|
||||||
store_.clear();
|
store_.clear();
|
||||||
store_.reserve(last-first);
|
store_.reserve(last-first);
|
||||||
packed_bins_.clear();
|
|
||||||
|
|
||||||
std::vector<Placer> placers;
|
std::vector<Placer> placers;
|
||||||
placers.reserve(last-first);
|
placers.reserve(last-first);
|
||||||
|
|
||||||
|
// If the packed_items array is not empty we have to create as many
|
||||||
|
// placers as there are elements in packed bins and preload each item
|
||||||
|
// into the appropriate placer
|
||||||
|
for(ItemGroup& ig : packed_bins_) {
|
||||||
|
placers.emplace_back(bin);
|
||||||
|
placers.back().configure(pconfig);
|
||||||
|
placers.back().preload({ig.begin(), ig.end()});
|
||||||
|
}
|
||||||
|
|
||||||
std::copy(first, last, std::back_inserter(store_));
|
std::copy(first, last, std::back_inserter(store_));
|
||||||
|
|
||||||
auto sortfunc = [](Item& i1, Item& i2) {
|
auto sortfunc = [](Item& i1, Item& i2) {
|
||||||
|
|
|
@ -9,27 +9,23 @@ namespace libnest2d { namespace selections {
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
class SelectionBoilerplate {
|
class SelectionBoilerplate {
|
||||||
public:
|
public:
|
||||||
|
using ShapeType = RawShape;
|
||||||
using Item = _Item<RawShape>;
|
using Item = _Item<RawShape>;
|
||||||
using ItemRef = std::reference_wrapper<Item>;
|
using ItemGroup = _ItemGroup<RawShape>;
|
||||||
using ItemGroup = std::vector<ItemRef>;
|
using PackGroup = _PackGroup<RawShape>;
|
||||||
using PackGroup = std::vector<ItemGroup>;
|
|
||||||
|
|
||||||
size_t binCount() const { return packed_bins_.size(); }
|
inline const PackGroup& getResult() const {
|
||||||
|
return packed_bins_;
|
||||||
ItemGroup itemsForBin(size_t binIndex) {
|
|
||||||
assert(binIndex < packed_bins_.size());
|
|
||||||
return packed_bins_[binIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const ItemGroup itemsForBin(size_t binIndex) const {
|
|
||||||
assert(binIndex < packed_bins_.size());
|
|
||||||
return packed_bins_[binIndex];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void progressIndicator(ProgressFunction fn) { progress_ = fn; }
|
inline void progressIndicator(ProgressFunction fn) { progress_ = fn; }
|
||||||
|
|
||||||
inline void stopCondition(StopCondition cond) { stopcond_ = cond; }
|
inline void stopCondition(StopCondition cond) { stopcond_ = cond; }
|
||||||
|
|
||||||
|
inline void preload(const PackGroup& pckgrp) { packed_bins_ = pckgrp; }
|
||||||
|
|
||||||
|
inline void clear() { packed_bins_.clear(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
PackGroup packed_bins_;
|
PackGroup packed_bins_;
|
||||||
|
|
|
@ -358,6 +358,28 @@ public:
|
||||||
m_rtree.clear();
|
m_rtree.clear();
|
||||||
return m_pck.executeIndexed(std::forward<Args>(args)...);
|
return m_pck.executeIndexed(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void preload(const PackGroup& pg) {
|
||||||
|
m_pconf.alignment = PConfig::Alignment::DONT_ALIGN;
|
||||||
|
m_pconf.object_function = nullptr; // drop the special objectfunction
|
||||||
|
m_pck.preload(pg);
|
||||||
|
|
||||||
|
// Build the rtree for queries to work
|
||||||
|
for(const ItemGroup& grp : pg)
|
||||||
|
for(unsigned idx = 0; idx < grp.size(); ++idx) {
|
||||||
|
Item& itm = grp[idx];
|
||||||
|
m_rtree.insert({itm.boundingBox(), idx});
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pck.configure(m_pconf);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_colliding(const Item& item) {
|
||||||
|
std::vector<SpatElement> result;
|
||||||
|
m_rtree.query(bgi::intersects(item.boundingBox()),
|
||||||
|
std::back_inserter(result));
|
||||||
|
return result.empty();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Arranger specialization for a Box shaped bin.
|
// Arranger specialization for a Box shaped bin.
|
||||||
|
@ -365,8 +387,8 @@ template<> class AutoArranger<Box>: public _ArrBase<Box> {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
AutoArranger(const Box& bin, Distance dist,
|
AutoArranger(const Box& bin, Distance dist,
|
||||||
std::function<void(unsigned)> progressind,
|
std::function<void(unsigned)> progressind = [](unsigned){},
|
||||||
std::function<bool(void)> stopcond):
|
std::function<bool(void)> stopcond = [](){return false;}):
|
||||||
_ArrBase<Box>(bin, dist, progressind, stopcond)
|
_ArrBase<Box>(bin, dist, progressind, stopcond)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -791,5 +813,114 @@ bool arrange(Model &model, // The model with the geometries
|
||||||
return ret && result.size() == 1;
|
return ret && result.size() == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void find_new_position(const Model &model,
|
||||||
|
ModelInstancePtrs toadd,
|
||||||
|
coord_t min_obj_distance,
|
||||||
|
const Polyline &bed)
|
||||||
|
{
|
||||||
|
// Get the 2D projected shapes with their 3D model instance pointers
|
||||||
|
auto shapemap = arr::projectModelFromTop(model);
|
||||||
|
|
||||||
|
// Copy the references for the shapes only as the arranger expects a
|
||||||
|
// sequence of objects convertible to Item or ClipperPolygon
|
||||||
|
PackGroup preshapes; preshapes.emplace_back();
|
||||||
|
ItemGroup shapes;
|
||||||
|
preshapes.front().reserve(shapemap.size());
|
||||||
|
|
||||||
|
std::vector<ModelInstance*> shapes_ptr; shapes_ptr.reserve(toadd.size());
|
||||||
|
IndexedPackGroup result;
|
||||||
|
|
||||||
|
// If there is no hint about the shape, we will try to guess
|
||||||
|
BedShapeHint bedhint = bedShape(bed);
|
||||||
|
|
||||||
|
BoundingBox bbb(bed);
|
||||||
|
|
||||||
|
auto binbb = Box({
|
||||||
|
static_cast<libnest2d::Coord>(bbb.min(0)),
|
||||||
|
static_cast<libnest2d::Coord>(bbb.min(1))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
static_cast<libnest2d::Coord>(bbb.max(0)),
|
||||||
|
static_cast<libnest2d::Coord>(bbb.max(1))
|
||||||
|
});
|
||||||
|
|
||||||
|
for(auto it = shapemap.begin(); it != shapemap.end(); ++it) {
|
||||||
|
if(std::find(toadd.begin(), toadd.end(), it->first) == toadd.end() &&
|
||||||
|
it->second.isInside(binbb)) { // just ignore items which are outside
|
||||||
|
preshapes.front().emplace_back(std::ref(it->second));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
shapes_ptr.emplace_back(it->first);
|
||||||
|
shapes.emplace_back(std::ref(it->second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(bedhint.type) {
|
||||||
|
case BedShapeType::BOX: {
|
||||||
|
|
||||||
|
// Create the arranger for the box shaped bed
|
||||||
|
AutoArranger<Box> arrange(binbb, min_obj_distance);
|
||||||
|
std::cout << "preload size: " << preshapes.front().size() << std::endl;
|
||||||
|
if(!preshapes.front().empty()) arrange.preload(preshapes);
|
||||||
|
|
||||||
|
// Arrange and return the items with their respective indices within the
|
||||||
|
// input sequence.
|
||||||
|
result = arrange(shapes.begin(), shapes.end());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BedShapeType::CIRCLE: {
|
||||||
|
|
||||||
|
// auto c = bedhint.shape.circ;
|
||||||
|
// auto cc = to_lnCircle(c);
|
||||||
|
|
||||||
|
// AutoArranger<lnCircle> arrange(cc, min_obj_distance, progressind, cfn);
|
||||||
|
// result = arrange(shapes.begin(), shapes.end());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BedShapeType::IRREGULAR:
|
||||||
|
case BedShapeType::WHO_KNOWS: {
|
||||||
|
|
||||||
|
// using P = libnest2d::PolygonImpl;
|
||||||
|
|
||||||
|
// auto ctour = Slic3rMultiPoint_to_ClipperPath(bed);
|
||||||
|
// P irrbed = sl::create<PolygonImpl>(std::move(ctour));
|
||||||
|
|
||||||
|
// AutoArranger<P> arrange(irrbed, min_obj_distance, progressind, cfn);
|
||||||
|
|
||||||
|
// // Arrange and return the items with their respective indices within the
|
||||||
|
// // input sequence.
|
||||||
|
// result = arrange(shapes.begin(), shapes.end());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Now we go through the result which will contain the fixed and the moving
|
||||||
|
// polygons as well. We will have to search for our item.
|
||||||
|
|
||||||
|
const auto STRIDE_PADDING = 1.2;
|
||||||
|
Coord stride = Coord(STRIDE_PADDING*binbb.width()*SCALING_FACTOR);
|
||||||
|
Coord batch_offset = 0;
|
||||||
|
|
||||||
|
for(auto& group : result) {
|
||||||
|
for(auto& r : group) if(r.first < shapes.size()) {
|
||||||
|
Item& resultitem = r.second;
|
||||||
|
unsigned idx = r.first;
|
||||||
|
auto offset = resultitem.translation();
|
||||||
|
Radians rot = resultitem.rotation();
|
||||||
|
ModelInstance *minst = shapes_ptr[idx];
|
||||||
|
Vec3d foffset(offset.X*SCALING_FACTOR + batch_offset,
|
||||||
|
offset.Y*SCALING_FACTOR,
|
||||||
|
minst->get_offset()(Z));
|
||||||
|
|
||||||
|
// write the transformation data into the model instance
|
||||||
|
minst->set_rotation(Z, rot);
|
||||||
|
minst->set_offset(foffset);
|
||||||
|
}
|
||||||
|
batch_offset += stride;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,13 @@ bool arrange(Model &model, coord_t min_obj_distance,
|
||||||
std::function<void(unsigned)> progressind,
|
std::function<void(unsigned)> progressind,
|
||||||
std::function<bool(void)> stopcondition);
|
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);
|
||||||
|
|
||||||
}
|
} // arr
|
||||||
|
} // Slic3r
|
||||||
#endif // MODELARRANGE_HPP
|
#endif // MODELARRANGE_HPP
|
||||||
|
|
|
@ -1399,6 +1399,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||||
if (one_by_one) {
|
if (one_by_one) {
|
||||||
auto loaded_idxs = load_model_objects(model.objects);
|
auto loaded_idxs = load_model_objects(model.objects);
|
||||||
obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
|
obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
|
||||||
|
|
||||||
|
std::cout << "New model objects added..." << std::endl;
|
||||||
} else {
|
} else {
|
||||||
// This must be an .stl or .obj file, which may contain a maximum of one volume.
|
// This must be an .stl or .obj file, which may contain a maximum of one volume.
|
||||||
for (const ModelObject* model_object : model.objects) {
|
for (const ModelObject* model_object : model.objects) {
|
||||||
|
@ -1448,11 +1450,12 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
|
||||||
const BoundingBoxf bed_shape = bed_shape_bb();
|
const BoundingBoxf bed_shape = bed_shape_bb();
|
||||||
const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast<double>(), 1.0) - 2.0 * Vec3d::Ones();
|
const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast<double>(), 1.0) - 2.0 * Vec3d::Ones();
|
||||||
|
|
||||||
bool need_arrange = false;
|
// bool need_arrange = false;
|
||||||
bool scaled_down = false;
|
bool scaled_down = false;
|
||||||
std::vector<size_t> obj_idxs;
|
std::vector<size_t> obj_idxs;
|
||||||
unsigned int obj_count = model.objects.size();
|
unsigned int obj_count = model.objects.size();
|
||||||
|
|
||||||
|
ModelInstancePtrs new_instances;
|
||||||
for (ModelObject *model_object : model_objects) {
|
for (ModelObject *model_object : model_objects) {
|
||||||
auto *object = model.add_object(*model_object);
|
auto *object = model.add_object(*model_object);
|
||||||
std::string object_name = object->name.empty() ? fs::path(object->input_file).filename().string() : object->name;
|
std::string object_name = object->name.empty() ? fs::path(object->input_file).filename().string() : object->name;
|
||||||
|
@ -1460,12 +1463,15 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
|
||||||
|
|
||||||
if (model_object->instances.empty()) {
|
if (model_object->instances.empty()) {
|
||||||
// if object has no defined position(s) we need to rearrange everything after loading
|
// if object has no defined position(s) we need to rearrange everything after loading
|
||||||
need_arrange = true;
|
// need_arrange = true;
|
||||||
|
|
||||||
// add a default instance and center object around origin
|
object->center_around_origin();
|
||||||
object->center_around_origin(); // also aligns object to Z = 0
|
new_instances.emplace_back(object->add_instance());
|
||||||
ModelInstance* instance = object->add_instance();
|
|
||||||
instance->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -object->origin_translation(2)));
|
// // add a default instance and center object around origin
|
||||||
|
// object->center_around_origin(); // also aligns object to Z = 0
|
||||||
|
// ModelInstance* instance = object->add_instance();
|
||||||
|
// instance->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -object->origin_translation(2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
const Vec3d size = object->bounding_box().size();
|
const Vec3d size = object->bounding_box().size();
|
||||||
|
@ -1492,6 +1498,17 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
|
||||||
// print.add_model_object(object);
|
// print.add_model_object(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME distance should be a config value
|
||||||
|
auto min_obj_distance = static_cast<coord_t>(6/SCALING_FACTOR);
|
||||||
|
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
|
||||||
|
assert(bed_shape_opt);
|
||||||
|
auto& bedpoints = bed_shape_opt->values;
|
||||||
|
Polyline bed; bed.points.reserve(bedpoints.size());
|
||||||
|
for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
||||||
|
|
||||||
|
arr::find_new_position(model, new_instances,
|
||||||
|
min_obj_distance, bed);
|
||||||
|
|
||||||
if (scaled_down) {
|
if (scaled_down) {
|
||||||
GUI::show_info(q,
|
GUI::show_info(q,
|
||||||
_(L("Your object appears to be too large, so it was automatically scaled down to fit your print bed.")),
|
_(L("Your object appears to be too large, so it was automatically scaled down to fit your print bed.")),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue