ClipperLib sometimes hangs forever on a single union / diff / offset task.
Implemented optional time limit on ClipperLib execution.
This commit is contained in:
parent
b3b44681a9
commit
29719a7ab9
5 changed files with 126 additions and 2 deletions
|
@ -116,7 +116,8 @@ using DoublePoint = Eigen::Matrix<double, 2, 1, Eigen::DontAlign>;
|
|||
|
||||
template<typename BaseType>
|
||||
using Allocator = tbb::scalable_allocator<BaseType>;
|
||||
using Path = std::vector<IntPoint, Allocator<IntPoint>>;
|
||||
//using Allocator = std::allocator<BaseType>;
|
||||
using Path = std::vector<IntPoint, Allocator<IntPoint>>;
|
||||
using Paths = std::vector<Path, Allocator<Path>>;
|
||||
|
||||
inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;}
|
||||
|
|
|
@ -3,6 +3,20 @@
|
|||
#include "ShortestPath.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
// #define CLIPPER_UTILS_TIMING
|
||||
|
||||
#ifdef CLIPPER_UTILS_TIMING
|
||||
// time limit for one ClipperLib operation (union / diff / offset), in ms
|
||||
#define CLIPPER_UTILS_TIME_LIMIT_DEFAULT 50
|
||||
#include <boost/current_function.hpp>
|
||||
#include "Timer.hpp"
|
||||
#define CLIPPER_UTILS_TIME_LIMIT_SECONDS(limit) Timing::TimeLimitAlarm time_limit_alarm(uint64_t(limit) * 1000000000l, BOOST_CURRENT_FUNCTION)
|
||||
#define CLIPPER_UTILS_TIME_LIMIT_MILLIS(limit) Timing::TimeLimitAlarm time_limit_alarm(uint64_t(limit) * 1000000l, BOOST_CURRENT_FUNCTION)
|
||||
#else
|
||||
#define CLIPPER_UTILS_TIME_LIMIT_SECONDS(limit) do {} while(false)
|
||||
#define CLIPPER_UTILS_TIME_LIMIT_MILLIS(limit) do {} while(false)
|
||||
#endif // CLIPPER_UTILS_TIMING
|
||||
|
||||
// #define CLIPPER_UTILS_DEBUG
|
||||
|
||||
#ifdef CLIPPER_UTILS_DEBUG
|
||||
|
@ -260,6 +274,8 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree)
|
|||
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
|
||||
static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
ClipperLib::ClipperOffset co;
|
||||
ClipperLib::Paths out;
|
||||
out.reserve(paths.size());
|
||||
|
@ -301,6 +317,8 @@ TResult clipper_do(
|
|||
TClip && clip,
|
||||
const ClipperLib::PolyFillType fillType)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.AddPaths(std::forward<TSubj>(subject), ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(std::forward<TClip>(clip), ClipperLib::ptClip, true);
|
||||
|
@ -330,6 +348,8 @@ TResult clipper_union(
|
|||
// fillType pftNonZero and pftPositive "should" produce the same result for "normalized with implicit union" set of polygons
|
||||
const ClipperLib::PolyFillType fillType = ClipperLib::pftNonZero)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.AddPaths(std::forward<TSubj>(subject), ClipperLib::ptSubject, true);
|
||||
TResult retval;
|
||||
|
@ -368,6 +388,8 @@ template<> void remove_outermost_polygon<ClipperLib::PolyTree>(ClipperLib::PolyT
|
|||
template<class TResult, typename PathsProvider>
|
||||
static TResult shrink_paths(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
assert(offset > 0);
|
||||
TResult out;
|
||||
if (auto raw = raw_offset(std::forward<PathsProvider>(paths), - offset, joinType, miterLimit); ! raw.empty()) {
|
||||
|
@ -407,6 +429,8 @@ Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, C
|
|||
// returns number of expolygons collected (0 or 1).
|
||||
static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
// 1) Offset the outer contour.
|
||||
ClipperLib::Paths contours;
|
||||
{
|
||||
|
@ -615,6 +639,8 @@ inline ClipperLib::PolyTree clipper_do_polytree(
|
|||
PathProvider2 &&clip,
|
||||
const ClipperLib::PolyFillType fillType)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
// Perform the operation with the output to input_subject.
|
||||
// This pass does not generate a PolyTree, which is a very expensive operation with the current Clipper library
|
||||
// if there are overapping edges.
|
||||
|
@ -753,6 +779,8 @@ Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject)
|
|||
template<typename PathsProvider1, typename PathsProvider2>
|
||||
Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.AddPaths(std::forward<PathsProvider1>(subject), ClipperLib::ptSubject, false);
|
||||
clipper.AddPaths(std::forward<PathsProvider2>(clip), ClipperLib::ptClip, true);
|
||||
|
@ -938,6 +966,8 @@ Polygons union_pt_chained_outside_in(const Polygons &subject)
|
|||
|
||||
Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
ClipperLib::Paths output;
|
||||
if (preserve_collinear) {
|
||||
ClipperLib::Clipper c;
|
||||
|
@ -955,6 +985,8 @@ Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear)
|
|||
|
||||
ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
if (! preserve_collinear)
|
||||
return union_ex(simplify_polygons(subject, false));
|
||||
|
||||
|
@ -971,6 +1003,8 @@ ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear
|
|||
|
||||
Polygons top_level_islands(const Slic3r::Polygons &polygons)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
// init Clipper
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.Clear();
|
||||
|
@ -994,6 +1028,8 @@ ClipperLib::Paths fix_after_outer_offset(
|
|||
ClipperLib::PolyFillType filltype, // = ClipperLib::pftPositive
|
||||
bool reverse_result) // = false
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
ClipperLib::Paths solution;
|
||||
if (! input.empty()) {
|
||||
ClipperLib::Clipper clipper;
|
||||
|
@ -1012,6 +1048,8 @@ ClipperLib::Paths fix_after_inner_offset(
|
|||
ClipperLib::PolyFillType filltype, // = ClipperLib::pftNegative
|
||||
bool reverse_result) // = true
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
ClipperLib::Paths solution;
|
||||
if (! input.empty()) {
|
||||
ClipperLib::Clipper clipper;
|
||||
|
@ -1032,6 +1070,8 @@ ClipperLib::Paths fix_after_inner_offset(
|
|||
|
||||
ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::vector<float> &deltas, double miter_limit)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
assert(contour.size() == deltas.size());
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
@ -1172,6 +1212,8 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v
|
|||
|
||||
static void variable_offset_inner_raw(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit, ClipperLib::Paths &contours, ClipperLib::Paths &holes)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Verify that the deltas are all non positive.
|
||||
for (const std::vector<float> &ds : deltas)
|
||||
|
@ -1205,6 +1247,8 @@ static void variable_offset_inner_raw(const ExPolygon &expoly, const std::vector
|
|||
|
||||
Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
ClipperLib::Paths contours, holes;
|
||||
variable_offset_inner_raw(expoly, deltas, miter_limit, contours, holes);
|
||||
|
||||
|
@ -1227,6 +1271,8 @@ Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector<std::v
|
|||
|
||||
ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
ClipperLib::Paths contours, holes;
|
||||
variable_offset_inner_raw(expoly, deltas, miter_limit, contours, holes);
|
||||
|
||||
|
@ -1253,6 +1299,8 @@ ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector<s
|
|||
|
||||
static void variable_offset_outer_raw(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit, ClipperLib::Paths &contours, ClipperLib::Paths &holes)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Verify that the deltas are all non positive.
|
||||
for (const std::vector<float> &ds : deltas)
|
||||
|
@ -1288,6 +1336,8 @@ static void variable_offset_outer_raw(const ExPolygon &expoly, const std::vector
|
|||
|
||||
Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
ClipperLib::Paths contours, holes;
|
||||
variable_offset_outer_raw(expoly, deltas, miter_limit, contours, holes);
|
||||
|
||||
|
@ -1309,6 +1359,8 @@ Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector<std::v
|
|||
|
||||
ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit)
|
||||
{
|
||||
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
|
||||
|
||||
ClipperLib::Paths contours, holes;
|
||||
variable_offset_outer_raw(expoly, deltas, miter_limit, contours, holes);
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ using Vec4d = Eigen::Matrix<double, 4, 1, Eigen::DontAlign>;
|
|||
|
||||
template<typename BaseType>
|
||||
using PointsAllocator = tbb::scalable_allocator<BaseType>;
|
||||
//using PointsAllocator = std::allocator<BaseType>;
|
||||
using Points = std::vector<Point, PointsAllocator<Point>>;
|
||||
using PointPtrs = std::vector<Point*>;
|
||||
using PointConstPtrs = std::vector<const Point*>;
|
||||
|
|
|
@ -10,3 +10,12 @@ Slic3r::Timer::~Timer()
|
|||
BOOST_LOG_TRIVIAL(debug) << "Timer '" << m_name << "' spend " <<
|
||||
duration_cast<milliseconds>(steady_clock::now() - m_start).count() << "ms";
|
||||
}
|
||||
|
||||
|
||||
namespace Slic3r::Timing {
|
||||
|
||||
void TimeLimitAlarm::report_time_exceeded() const {
|
||||
BOOST_LOG_TRIVIAL(error) << "Time limit exceeded for " << m_limit_exceeded_message << ": " << m_timer.elapsed_seconds() << "s";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,5 +27,66 @@ public:
|
|||
~Timer();
|
||||
};
|
||||
|
||||
namespace Timing {
|
||||
|
||||
// Timing code from Catch2 unit testing library
|
||||
static inline uint64_t nanoseconds_since_epoch() {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
// Timing code from Catch2 unit testing library
|
||||
class Timer {
|
||||
public:
|
||||
void start() {
|
||||
m_nanoseconds = nanoseconds_since_epoch();
|
||||
}
|
||||
uint64_t elapsed_nanoseconds() const {
|
||||
return nanoseconds_since_epoch() - m_nanoseconds;
|
||||
}
|
||||
uint64_t elapsed_microseconds() const {
|
||||
return elapsed_nanoseconds() / 1000;
|
||||
}
|
||||
unsigned int elapsed_milliseconds() const {
|
||||
return static_cast<unsigned int>(elapsed_microseconds()/1000);
|
||||
}
|
||||
double elapsed_seconds() const {
|
||||
return elapsed_microseconds() / 1000000.0;
|
||||
}
|
||||
private:
|
||||
uint64_t m_nanoseconds = 0;
|
||||
};
|
||||
|
||||
// Emits a Boost::log error if the life time of this timing object exceeds a limit.
|
||||
class TimeLimitAlarm {
|
||||
public:
|
||||
TimeLimitAlarm(uint64_t time_limit_nanoseconds, std::string_view limit_exceeded_message) :
|
||||
m_time_limit_nanoseconds(time_limit_nanoseconds), m_limit_exceeded_message(limit_exceeded_message) {
|
||||
m_timer.start();
|
||||
}
|
||||
~TimeLimitAlarm() {
|
||||
auto elapsed = m_timer.elapsed_nanoseconds();
|
||||
if (elapsed > m_time_limit_nanoseconds)
|
||||
this->report_time_exceeded();
|
||||
}
|
||||
static TimeLimitAlarm new_nanos(uint64_t time_limit_nanoseconds, std::string_view limit_exceeded_message) {
|
||||
return TimeLimitAlarm(time_limit_nanoseconds, limit_exceeded_message);
|
||||
}
|
||||
static TimeLimitAlarm new_milis(uint64_t time_limit_milis, std::string_view limit_exceeded_message) {
|
||||
return TimeLimitAlarm(uint64_t(time_limit_milis) * 1000000l, limit_exceeded_message);
|
||||
}
|
||||
static TimeLimitAlarm new_seconds(uint64_t time_limit_seconds, std::string_view limit_exceeded_message) {
|
||||
return TimeLimitAlarm(uint64_t(time_limit_seconds) * 1000000000l, limit_exceeded_message);
|
||||
}
|
||||
private:
|
||||
void report_time_exceeded() const;
|
||||
|
||||
Timer m_timer;
|
||||
uint64_t m_time_limit_nanoseconds;
|
||||
std::string_view m_limit_exceeded_message;
|
||||
};
|
||||
|
||||
} // namespace Catch
|
||||
|
||||
} // namespace Slic3r
|
||||
#endif // libslic3r_Timer_hpp_
|
||||
|
||||
#endif // libslic3r_Timer_hpp_
|
||||
|
|
Loading…
Reference in a new issue