Fix arrange crash with incorrect geometries. Guard the case with tests.
This commit is contained in:
parent
4aec14ddab
commit
a6f5fe7bea
@ -81,17 +81,16 @@ inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance, const PolygonTag
|
|||||||
using ClipperLib::etClosedPolygon;
|
using ClipperLib::etClosedPolygon;
|
||||||
using ClipperLib::Paths;
|
using ClipperLib::Paths;
|
||||||
|
|
||||||
// If the input is not at least a triangle, we can not do this algorithm
|
|
||||||
if(sh.Contour.size() <= 3 ||
|
|
||||||
std::any_of(sh.Holes.begin(), sh.Holes.end(),
|
|
||||||
[](const PathImpl& p) { return p.size() <= 3; })
|
|
||||||
) throw GeometryException(GeomErr::OFFSET);
|
|
||||||
|
|
||||||
ClipperOffset offs;
|
|
||||||
Paths result;
|
Paths result;
|
||||||
offs.AddPath(sh.Contour, jtMiter, etClosedPolygon);
|
|
||||||
offs.AddPaths(sh.Holes, jtMiter, etClosedPolygon);
|
try {
|
||||||
offs.Execute(result, static_cast<double>(distance));
|
ClipperOffset offs;
|
||||||
|
offs.AddPath(sh.Contour, jtMiter, etClosedPolygon);
|
||||||
|
offs.AddPaths(sh.Holes, jtMiter, etClosedPolygon);
|
||||||
|
offs.Execute(result, static_cast<double>(distance));
|
||||||
|
} catch (ClipperLib::clipperException &) {
|
||||||
|
throw GeometryException(GeomErr::OFFSET);
|
||||||
|
}
|
||||||
|
|
||||||
// Offsetting reverts the orientation and also removes the last vertex
|
// Offsetting reverts the orientation and also removes the last vertex
|
||||||
// so boost will not have a closed polygon.
|
// so boost will not have a closed polygon.
|
||||||
|
@ -1144,7 +1144,7 @@ inline bool isInside(const TBGuest& ibb, const TBHost& box,
|
|||||||
auto minY = getY(box.minCorner());
|
auto minY = getY(box.minCorner());
|
||||||
auto maxY = getY(box.maxCorner());
|
auto maxY = getY(box.maxCorner());
|
||||||
|
|
||||||
return iminX > minX && imaxX < maxX && iminY > minY && imaxY < maxY;
|
return iminX >= minX && imaxX <= maxX && iminY >= minY && imaxY <= maxY;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class S, class TB>
|
template<class S, class TB>
|
||||||
|
@ -3,9 +3,6 @@
|
|||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
// For caching nfps
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
// For parallel for
|
// For parallel for
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
@ -76,55 +73,6 @@ inline void enumerate(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace __itemhash {
|
|
||||||
|
|
||||||
using Key = size_t;
|
|
||||||
|
|
||||||
template<class S>
|
|
||||||
Key hash(const _Item<S>& item) {
|
|
||||||
using Point = TPoint<S>;
|
|
||||||
using Segment = _Segment<Point>;
|
|
||||||
|
|
||||||
static const int N = 26;
|
|
||||||
static const int M = N*N - 1;
|
|
||||||
|
|
||||||
std::string ret;
|
|
||||||
auto& rhs = item.rawShape();
|
|
||||||
auto& ctr = sl::contour(rhs);
|
|
||||||
auto it = ctr.begin();
|
|
||||||
auto nx = std::next(it);
|
|
||||||
|
|
||||||
double circ = 0;
|
|
||||||
while(nx != ctr.end()) {
|
|
||||||
Segment seg(*it++, *nx++);
|
|
||||||
Radians a = seg.angleToXaxis();
|
|
||||||
double deg = Degrees(a);
|
|
||||||
int ms = 'A', ls = 'A';
|
|
||||||
while(deg > N) { ms++; deg -= N; }
|
|
||||||
ls += int(deg);
|
|
||||||
ret.push_back(char(ms)); ret.push_back(char(ls));
|
|
||||||
circ += std::sqrt(seg.template sqlength<double>());
|
|
||||||
}
|
|
||||||
|
|
||||||
it = ctr.begin(); nx = std::next(it);
|
|
||||||
|
|
||||||
while(nx != ctr.end()) {
|
|
||||||
Segment seg(*it++, *nx++);
|
|
||||||
auto l = int(M * std::sqrt(seg.template sqlength<double>()) / circ);
|
|
||||||
int ms = 'A', ls = 'A';
|
|
||||||
while(l > N) { ms++; l -= N; }
|
|
||||||
ls += l;
|
|
||||||
ret.push_back(char(ms)); ret.push_back(char(ls));
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::hash<std::string>()(ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class S>
|
|
||||||
using Hash = std::unordered_map<Key, nfp::NfpResult<S>>;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace placers {
|
namespace placers {
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
@ -529,17 +477,9 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin
|
|||||||
|
|
||||||
using MaxNfpLevel = nfp::MaxNfpLevel<RawShape>;
|
using MaxNfpLevel = nfp::MaxNfpLevel<RawShape>;
|
||||||
|
|
||||||
using ItemKeys = std::vector<__itemhash::Key>;
|
|
||||||
|
|
||||||
// Norming factor for the optimization function
|
// Norming factor for the optimization function
|
||||||
const double norm_;
|
const double norm_;
|
||||||
|
|
||||||
// Caching calculated nfps
|
|
||||||
__itemhash::Hash<RawShape> nfpcache_;
|
|
||||||
|
|
||||||
// Storing item hash keys
|
|
||||||
ItemKeys item_keys_;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
using Pile = nfp::Shapes<RawShape>;
|
using Pile = nfp::Shapes<RawShape>;
|
||||||
@ -636,15 +576,12 @@ public:
|
|||||||
private:
|
private:
|
||||||
|
|
||||||
using Shapes = TMultiShape<RawShape>;
|
using Shapes = TMultiShape<RawShape>;
|
||||||
using ItemRef = std::reference_wrapper<Item>;
|
|
||||||
using ItemWithHash = const std::pair<ItemRef, __itemhash::Key>;
|
|
||||||
|
|
||||||
Shapes calcnfp(const ItemWithHash itsh, Lvl<nfp::NfpLevel::CONVEX_ONLY>)
|
Shapes calcnfp(const Item &trsh, Lvl<nfp::NfpLevel::CONVEX_ONLY>)
|
||||||
{
|
{
|
||||||
using namespace nfp;
|
using namespace nfp;
|
||||||
|
|
||||||
Shapes nfps(items_.size());
|
Shapes nfps(items_.size());
|
||||||
const Item& trsh = itsh.first;
|
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////
|
||||||
// TODO: this is a workaround and should be solved in Item with mutexes
|
// TODO: this is a workaround and should be solved in Item with mutexes
|
||||||
@ -678,12 +615,11 @@ private:
|
|||||||
|
|
||||||
|
|
||||||
template<class Level>
|
template<class Level>
|
||||||
Shapes calcnfp( const ItemWithHash itsh, Level)
|
Shapes calcnfp(const Item &trsh, Level)
|
||||||
{ // Function for arbitrary level of nfp implementation
|
{ // Function for arbitrary level of nfp implementation
|
||||||
using namespace nfp;
|
using namespace nfp;
|
||||||
|
|
||||||
Shapes nfps;
|
Shapes nfps;
|
||||||
const Item& trsh = itsh.first;
|
|
||||||
|
|
||||||
auto& orb = trsh.transformedShape();
|
auto& orb = trsh.transformedShape();
|
||||||
bool orbconvex = trsh.isContourConvex();
|
bool orbconvex = trsh.isContourConvex();
|
||||||
@ -849,8 +785,6 @@ private:
|
|||||||
remlist.insert(remlist.end(), remaining.from, remaining.to);
|
remlist.insert(remlist.end(), remaining.from, remaining.to);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t itemhash = __itemhash::hash(item);
|
|
||||||
|
|
||||||
if(items_.empty()) {
|
if(items_.empty()) {
|
||||||
setInitialPosition(item);
|
setInitialPosition(item);
|
||||||
best_overfit = overfit(item.transformedShape(), bin_);
|
best_overfit = overfit(item.transformedShape(), bin_);
|
||||||
@ -875,7 +809,7 @@ private:
|
|||||||
// it is disjunct from the current merged pile
|
// it is disjunct from the current merged pile
|
||||||
placeOutsideOfBin(item);
|
placeOutsideOfBin(item);
|
||||||
|
|
||||||
nfps = calcnfp({item, itemhash}, Lvl<MaxNfpLevel::value>());
|
nfps = calcnfp(item, Lvl<MaxNfpLevel::value>());
|
||||||
|
|
||||||
auto iv = item.referenceVertex();
|
auto iv = item.referenceVertex();
|
||||||
|
|
||||||
@ -1112,7 +1046,6 @@ private:
|
|||||||
|
|
||||||
if(can_pack) {
|
if(can_pack) {
|
||||||
ret = PackResult(item);
|
ret = PackResult(item);
|
||||||
item_keys_.emplace_back(itemhash);
|
|
||||||
} else {
|
} else {
|
||||||
ret = PackResult(best_overfit);
|
ret = PackResult(best_overfit);
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ protected:
|
|||||||
|
|
||||||
Placer p{bin};
|
Placer p{bin};
|
||||||
p.configure(pcfg);
|
p.configure(pcfg);
|
||||||
if (!p.pack(cpy)) it = c.erase(it);
|
if (itm.area() <= 0 || !p.pack(cpy)) it = c.erase(it);
|
||||||
else it++;
|
else it++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<libnest2d::Item>& prusaParts() {
|
static std::vector<libnest2d::Item>& prusaParts() {
|
||||||
static std::vector<libnest2d::Item> ret;
|
static std::vector<libnest2d::Item> ret;
|
||||||
|
|
||||||
if(ret.empty()) {
|
if(ret.empty()) {
|
||||||
@ -51,7 +51,7 @@ std::vector<libnest2d::Item>& prusaParts() {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BasicFunctionality, Angles)
|
TEST(GeometryAlgorithms, Angles)
|
||||||
{
|
{
|
||||||
|
|
||||||
using namespace libnest2d;
|
using namespace libnest2d;
|
||||||
@ -109,7 +109,7 @@ TEST(BasicFunctionality, Angles)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Simple test, does not use gmock
|
// Simple test, does not use gmock
|
||||||
TEST(BasicFunctionality, creationAndDestruction)
|
TEST(Nesting, ItemCreationAndDestruction)
|
||||||
{
|
{
|
||||||
using namespace libnest2d;
|
using namespace libnest2d;
|
||||||
|
|
||||||
@ -572,26 +572,74 @@ TEST(GeometryAlgorithms, convexHull) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST(GeometryAlgorithms, NestTest) {
|
TEST(Nesting, NestPrusaPartsShouldFitIntoTwoBins) {
|
||||||
|
|
||||||
|
// Get the input items and define the bin.
|
||||||
std::vector<Item> input = prusaParts();
|
std::vector<Item> input = prusaParts();
|
||||||
|
auto bin = Box(250000000, 210000000);
|
||||||
libnest2d::nest(input, Box(250000000, 210000000), [](unsigned cnt) {
|
|
||||||
std::cout << "parts left: " << cnt << std::endl;
|
// Do the nesting. Check in each step if the remaining items are less than
|
||||||
|
// in the previous step. (Some algorithms can place more items in one step)
|
||||||
|
size_t pcount = input.size();
|
||||||
|
libnest2d::nest(input, bin, [&pcount](unsigned cnt) {
|
||||||
|
ASSERT_TRUE(cnt < pcount);
|
||||||
|
pcount = cnt;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Get the number of logical bins: search for the max binId...
|
||||||
auto max_binid_it = std::max_element(input.begin(), input.end(),
|
auto max_binid_it = std::max_element(input.begin(), input.end(),
|
||||||
[](const Item &i1, const Item &i2) {
|
[](const Item &i1, const Item &i2) {
|
||||||
return i1.binId() < i2.binId();
|
return i1.binId() < i2.binId();
|
||||||
});
|
});
|
||||||
|
|
||||||
size_t bins = max_binid_it == input.end() ? 0 : max_binid_it->binId() + 1;
|
|
||||||
|
|
||||||
ASSERT_EQ(bins, 2u);
|
auto bins = size_t(max_binid_it == input.end() ? 0 :
|
||||||
|
max_binid_it->binId() + 1);
|
||||||
|
|
||||||
|
// For prusa parts, 2 bins should be enough...
|
||||||
|
ASSERT_LE(bins, 2u);
|
||||||
|
|
||||||
|
// All parts should be processed by the algorithm
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
std::all_of(input.begin(), input.end(), [](const Item &itm) {
|
std::all_of(input.begin(), input.end(), [](const Item &itm) {
|
||||||
return itm.binId() != BIN_ID_UNSET;
|
return itm.binId() != BIN_ID_UNSET;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Gather the items into piles of arranged polygons...
|
||||||
|
using Pile = TMultiShape<ClipperLib::Polygon>;
|
||||||
|
std::vector<Pile> piles(bins);
|
||||||
|
|
||||||
|
for (auto &itm : input)
|
||||||
|
piles[size_t(itm.binId())].emplace_back(itm.transformedShape());
|
||||||
|
|
||||||
|
// Now check all the piles, the bounding box of each pile should be inside
|
||||||
|
// the defined bin.
|
||||||
|
for (auto &pile : piles) {
|
||||||
|
auto bb = sl::boundingBox(pile);
|
||||||
|
ASSERT_TRUE(sl::isInside(bb, bin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Nesting, NestEmptyItemShouldBeUntouched) {
|
||||||
|
auto bin = Box(250000000, 210000000); // dummy bin
|
||||||
|
|
||||||
|
std::vector<Item> items;
|
||||||
|
items.emplace_back(Item{}); // Emplace empty item
|
||||||
|
items.emplace_back(Item{0, 200, 0}); // Emplace zero area item
|
||||||
|
|
||||||
|
libnest2d::nest(items, bin);
|
||||||
|
|
||||||
|
for (auto &itm : items) ASSERT_EQ(itm.binId(), BIN_ID_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Nesting, NestLargeItemShouldBeUntouched) {
|
||||||
|
auto bin = Box(250000000, 210000000); // dummy bin
|
||||||
|
|
||||||
|
std::vector<Item> items;
|
||||||
|
items.emplace_back(Rectangle{250000001, 210000001}); // Emplace large item
|
||||||
|
|
||||||
|
libnest2d::nest(items, bin);
|
||||||
|
|
||||||
|
ASSERT_EQ(items.front().binId(), BIN_ID_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -966,26 +1014,20 @@ using Ratio = boost::rational<boost::multiprecision::int128_t>;
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RotatingCalipers, MinAreaBBCClk) {
|
//TEST(GeometryAlgorithms, MinAreaBBCClk) {
|
||||||
auto u = [](ClipperLib::cInt n) { return n*1000000; };
|
// auto u = [](ClipperLib::cInt n) { return n*1000000; };
|
||||||
PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}});
|
// PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}});
|
||||||
|
|
||||||
long double arearef = refMinAreaBox(poly);
|
// long double arearef = refMinAreaBox(poly);
|
||||||
long double area = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly).area();
|
// long double area = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly).area();
|
||||||
|
|
||||||
ASSERT_LE(std::abs(area - arearef), 500e6 );
|
// ASSERT_LE(std::abs(area - arearef), 500e6 );
|
||||||
}
|
//}
|
||||||
|
|
||||||
TEST(RotatingCalipers, AllPrusaMinBB) {
|
TEST(GeometryAlgorithms, MinAreaBBWithRotatingCalipers) {
|
||||||
// /size_t idx = 0;
|
|
||||||
long double err_epsilon = 500e6l;
|
long double err_epsilon = 500e6l;
|
||||||
|
|
||||||
for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) {
|
for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) {
|
||||||
// ClipperLib::Path rinput = PRINTER_PART_POLYGONS[idx];
|
|
||||||
// rinput.pop_back();
|
|
||||||
// std::reverse(rinput.begin(), rinput.end());
|
|
||||||
|
|
||||||
// PolygonImpl poly(removeCollinearPoints<PathImpl, PointImpl, Unit>(rinput, 1000000));
|
|
||||||
PolygonImpl poly(rinput);
|
PolygonImpl poly(rinput);
|
||||||
|
|
||||||
long double arearef = refMinAreaBox(poly);
|
long double arearef = refMinAreaBox(poly);
|
||||||
@ -993,8 +1035,6 @@ TEST(RotatingCalipers, AllPrusaMinBB) {
|
|||||||
long double area = cast<long double>(bb.area());
|
long double area = cast<long double>(bb.area());
|
||||||
|
|
||||||
bool succ = std::abs(arearef - area) < err_epsilon;
|
bool succ = std::abs(arearef - area) < err_epsilon;
|
||||||
// std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: "
|
|
||||||
// << arearef << " actual: " << area << std::endl;
|
|
||||||
|
|
||||||
ASSERT_TRUE(succ);
|
ASSERT_TRUE(succ);
|
||||||
}
|
}
|
||||||
@ -1011,8 +1051,6 @@ TEST(RotatingCalipers, AllPrusaMinBB) {
|
|||||||
|
|
||||||
|
|
||||||
bool succ = std::abs(arearef - area) < err_epsilon;
|
bool succ = std::abs(arearef - area) < err_epsilon;
|
||||||
// std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: "
|
|
||||||
// << arearef << " actual: " << area << std::endl;
|
|
||||||
|
|
||||||
ASSERT_TRUE(succ);
|
ASSERT_TRUE(succ);
|
||||||
}
|
}
|
||||||
|
@ -618,19 +618,21 @@ void arrange(ArrangePolygons & arrangables,
|
|||||||
items.reserve(arrangables.size());
|
items.reserve(arrangables.size());
|
||||||
|
|
||||||
// Create Item from Arrangeable
|
// Create Item from Arrangeable
|
||||||
auto process_arrangeable =
|
auto process_arrangeable = [](const ArrangePolygon &arrpoly,
|
||||||
[](const ArrangePolygon &arrpoly, std::vector<Item> &outp)
|
std::vector<Item> & outp)
|
||||||
{
|
{
|
||||||
Polygon p = arrpoly.poly.contour;
|
Polygon p = arrpoly.poly.contour;
|
||||||
const Vec2crd & offs = arrpoly.translation;
|
const Vec2crd &offs = arrpoly.translation;
|
||||||
double rotation = arrpoly.rotation;
|
double rotation = arrpoly.rotation;
|
||||||
|
|
||||||
if (p.is_counter_clockwise()) p.reverse();
|
if (p.is_counter_clockwise()) p.reverse();
|
||||||
|
|
||||||
clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p));
|
clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p));
|
||||||
|
|
||||||
auto firstp = clpath.Contour.front();
|
if (!clpath.Contour.empty()) {
|
||||||
clpath.Contour.emplace_back(firstp);
|
auto firstp = clpath.Contour.front();
|
||||||
|
clpath.Contour.emplace_back(firstp);
|
||||||
|
}
|
||||||
|
|
||||||
outp.emplace_back(std::move(clpath));
|
outp.emplace_back(std::move(clpath));
|
||||||
outp.back().rotation(rotation);
|
outp.back().rotation(rotation);
|
||||||
|
Loading…
Reference in New Issue
Block a user