CLIPPER_OFFSET_SCALE was made a power of two, the scaling functions

inside ClipperUtils are now using bit shifts instead of multiplication
by doubles, which makes the scaling precise.

Removed the scale parameter from all offset functions.

Modified the safety offset to calculate offset per polygon instead
of over all polygons at once. The old way was not safe and very slow,
sometimes this meant a kiss of death for supports for example.
This commit is contained in:
bubnikv 2016-11-28 17:33:17 +01:00
parent e93253e270
commit 695c92fb00
14 changed files with 214 additions and 152 deletions

View file

@ -9,8 +9,51 @@
#include <Shiny/Shiny.h>
// Factor to convert from coord_t (which is int32) to an int64 type used by the Clipper library
// for general offsetting (the offset(), offset2(), offset_ex() functions) and for the safety offset,
// which is optionally executed by other functions (union, intersection, diff).
// By the way, is the scalling for offset needed at all?
#define CLIPPER_OFFSET_POWER_OF_2 17
// 2^17=131072
#define CLIPPER_OFFSET_SCALE (1 << CLIPPER_OFFSET_POWER_OF_2)
#define CLIPPER_OFFSET_SCALE_ROUNDING_DELTA ((1 << (CLIPPER_OFFSET_POWER_OF_2 - 1)) - 1)
namespace Slic3r {
#ifdef CLIPPER_UTILS_DEBUG
bool clipper_export_enabled = false;
// For debugging the Clipper library, for providing bug reports to the Clipper author.
bool export_clipper_input_polygons_bin(const char *path, const ClipperLib::Paths &input_subject, const ClipperLib::Paths &input_clip)
{
FILE *pfile = fopen(path, "wb");
if (pfile == NULL)
return false;
uint32_t sz = uint32_t(input_subject.size());
fwrite(&sz, 1, sizeof(sz), pfile);
for (size_t i = 0; i < input_subject.size(); ++i) {
const ClipperLib::Path &path = input_subject[i];
sz = uint32_t(path.size());
::fwrite(&sz, 1, sizeof(sz), pfile);
::fwrite(path.data(), sizeof(ClipperLib::IntPoint), sz, pfile);
}
sz = uint32_t(input_clip.size());
::fwrite(&sz, 1, sizeof(sz), pfile);
for (size_t i = 0; i < input_clip.size(); ++i) {
const ClipperLib::Path &path = input_clip[i];
sz = uint32_t(path.size());
::fwrite(&sz, 1, sizeof(sz), pfile);
::fwrite(path.data(), sizeof(ClipperLib::IntPoint), sz, pfile);
}
::fclose(pfile);
return true;
err:
::fclose(pfile);
return false;
}
#endif /* CLIPPER_UTILS_DEBUG */
//-----------------------------------------------------------
// legacy code from Clipper documentation
void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, Slic3r::ExPolygons* expolygons)
@ -120,37 +163,51 @@ Slic3rMultiPoints_to_ClipperPaths(const T &input, ClipperLib::Paths* output)
}
}
void
scaleClipperPolygon(ClipperLib::Path &polygon, const double scale)
void scaleClipperPolygon(ClipperLib::Path &polygon)
{
PROFILE_FUNC();
for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) {
//FIXME multiplication of int64_t by double!
// Replace by bit shifts?
(*pit).X *= scale;
(*pit).Y *= scale;
pit->X <<= CLIPPER_OFFSET_POWER_OF_2;
pit->Y <<= CLIPPER_OFFSET_POWER_OF_2;
}
}
void
scaleClipperPolygons(ClipperLib::Paths &polygons, const double scale)
void scaleClipperPolygons(ClipperLib::Paths &polygons)
{
PROFILE_FUNC();
for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it) {
for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it)
for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) {
//FIXME multiplication of int64_t by double!
// Replace by bit shifts?
(*pit).X *= scale;
(*pit).Y *= scale;
pit->X <<= CLIPPER_OFFSET_POWER_OF_2;
pit->Y <<= CLIPPER_OFFSET_POWER_OF_2;
}
}
void unscaleClipperPolygon(ClipperLib::Path &polygon)
{
PROFILE_FUNC();
for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) {
pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
pit->Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
pit->X >>= CLIPPER_OFFSET_POWER_OF_2;
pit->Y >>= CLIPPER_OFFSET_POWER_OF_2;
}
}
void unscaleClipperPolygons(ClipperLib::Paths &polygons)
{
PROFILE_FUNC();
for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it)
for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) {
pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
pit->Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
pit->X >>= CLIPPER_OFFSET_POWER_OF_2;
pit->Y >>= CLIPPER_OFFSET_POWER_OF_2;
}
}
void
offset(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::JoinType joinType, double miterLimit)
{
PROFILE_FUNC();
// read input
@ -158,12 +215,12 @@ offset(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float
Slic3rMultiPoints_to_ClipperPaths(polygons, &input);
// scale input
scaleClipperPolygons(input, scale);
scaleClipperPolygons(input);
// perform offset
ClipperLib::ClipperOffset co;
if (joinType == jtRound) {
co.ArcTolerance = miterLimit;
co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE);
} else {
co.MiterLimit = miterLimit;
}
@ -173,20 +230,20 @@ offset(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float
}
{
PROFILE_BLOCK(offset_Execute);
co.Execute(*retval, (delta*scale));
co.Execute(*retval, delta * float(CLIPPER_OFFSET_SCALE));
}
// unscale output
scaleClipperPolygons(*retval, 1/scale);
unscaleClipperPolygons(*retval);
}
void
offset(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::JoinType joinType, double miterLimit)
{
// perform offset
ClipperLib::Paths output;
offset(polygons, &output, delta, scale, joinType, miterLimit);
offset(polygons, &output, delta, joinType, miterLimit);
// convert into ExPolygons
ClipperPaths_to_Slic3rMultiPoints(output, retval);
@ -194,45 +251,45 @@ offset(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float d
Slic3r::Polygons
offset(const Slic3r::Polygons &polygons, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::JoinType joinType, double miterLimit)
{
Slic3r::Polygons pp;
offset(polygons, &pp, delta, scale, joinType, miterLimit);
offset(polygons, &pp, delta, joinType, miterLimit);
return pp;
}
void
offset(const Slic3r::Polylines &polylines, ClipperLib::Paths* retval, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::JoinType joinType, double miterLimit)
{
// read input
ClipperLib::Paths input;
Slic3rMultiPoints_to_ClipperPaths(polylines, &input);
// scale input
scaleClipperPolygons(input, scale);
scaleClipperPolygons(input);
// perform offset
ClipperLib::ClipperOffset co;
if (joinType == jtRound) {
co.ArcTolerance = miterLimit;
co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE);
} else {
co.MiterLimit = miterLimit;
}
co.AddPaths(input, joinType, ClipperLib::etOpenButt);
co.Execute(*retval, (delta*scale));
co.Execute(*retval, delta * float(CLIPPER_OFFSET_SCALE));
// unscale output
scaleClipperPolygons(*retval, 1/scale);
unscaleClipperPolygons(*retval);
}
void
offset(const Slic3r::Polylines &polylines, Slic3r::Polygons* retval, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::JoinType joinType, double miterLimit)
{
// perform offset
ClipperLib::Paths output;
offset(polylines, &output, delta, scale, joinType, miterLimit);
offset(polylines, &output, delta, joinType, miterLimit);
// convert into ExPolygons
ClipperPaths_to_Slic3rMultiPoints(output, retval);
@ -240,11 +297,11 @@ offset(const Slic3r::Polylines &polylines, Slic3r::Polygons* retval, const float
void
offset(const Slic3r::Surface &surface, Slic3r::Surfaces* retval, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::JoinType joinType, double miterLimit)
{
// perform offset
Slic3r::ExPolygons expp;
offset(surface.expolygon, &expp, delta, scale, joinType, miterLimit);
offset(surface.expolygon, &expp, delta, joinType, miterLimit);
// clone the input surface for each expolygon we got
retval->clear();
@ -258,11 +315,11 @@ offset(const Slic3r::Surface &surface, Slic3r::Surfaces* retval, const float del
void
offset(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::JoinType joinType, double miterLimit)
{
// perform offset
ClipperLib::Paths output;
offset(polygons, &output, delta, scale, joinType, miterLimit);
offset(polygons, &output, delta, joinType, miterLimit);
// convert into ExPolygons
ClipperPaths_to_Slic3rExPolygons(output, retval);
@ -272,19 +329,19 @@ offset(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float
// a single polygon with multiple non-overlapping holes.
// Each contour and hole is offsetted separately, then the holes are subtracted from the outer contours.
void offset(const Slic3r::ExPolygon &expolygon, ClipperLib::Paths* retval, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::JoinType joinType, double miterLimit)
{
// printf("new ExPolygon offset\n");
// 1) Offset the outer contour.
const float delta_scaled = float(delta * scale);
const float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE);
ClipperLib::Paths contours;
{
ClipperLib::Path input;
Slic3rMultiPoint_to_ClipperPath(expolygon.contour, &input);
scaleClipperPolygon(input, scale);
scaleClipperPolygon(input);
ClipperLib::ClipperOffset co;
if (joinType == jtRound)
co.ArcTolerance = miterLimit;
co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE);
else
co.MiterLimit = miterLimit;
co.AddPath(input, joinType, ClipperLib::etClosedPolygon);
@ -298,10 +355,10 @@ void offset(const Slic3r::ExPolygon &expolygon, ClipperLib::Paths* retval, const
for (Polygons::const_iterator it_hole = expolygon.holes.begin(); it_hole != expolygon.holes.end(); ++ it_hole) {
ClipperLib::Path input;
Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole, &input);
scaleClipperPolygon(input, scale);
scaleClipperPolygon(input);
ClipperLib::ClipperOffset co;
if (joinType == jtRound)
co.ArcTolerance = miterLimit;
co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE);
else
co.MiterLimit = miterLimit;
co.AddPath(input, joinType, ClipperLib::etClosedPolygon);
@ -322,15 +379,15 @@ void offset(const Slic3r::ExPolygon &expolygon, ClipperLib::Paths* retval, const
}
// 4) Unscale the output.
scaleClipperPolygons(*retval, 1/scale);
unscaleClipperPolygons(*retval);
}
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::JoinType joinType, double miterLimit)
{
// perform offset
ClipperLib::Paths output;
offset(expolygon, &output, delta, scale, joinType, miterLimit);
offset(expolygon, &output, delta, joinType, miterLimit);
// convert into ExPolygons
Slic3r::Polygons retval;
@ -339,11 +396,11 @@ Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta,
}
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::JoinType joinType, double miterLimit)
{
// perform offset
ClipperLib::Paths output;
offset(expolygon, &output, delta, scale, joinType, miterLimit);
offset(expolygon, &output, delta, joinType, miterLimit);
// convert into ExPolygons
Slic3r::ExPolygons retval;
@ -355,10 +412,10 @@ Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float del
// a single polygon with multiple non-overlapping holes.
// Each contour and hole is offsetted separately, then the holes are subtracted from the outer contours.
void offset(const Slic3r::ExPolygons &expolygons, ClipperLib::Paths* retval, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::JoinType joinType, double miterLimit)
{
// printf("new ExPolygon offset\n");
const float delta_scaled = float(delta * scale);
const float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE);
ClipperLib::Paths contours;
ClipperLib::Paths holes;
contours.reserve(expolygons.size());
@ -374,10 +431,10 @@ void offset(const Slic3r::ExPolygons &expolygons, ClipperLib::Paths* retval, con
{
ClipperLib::Path input;
Slic3rMultiPoint_to_ClipperPath(it_expoly->contour, &input);
scaleClipperPolygon(input, scale);
scaleClipperPolygon(input);
ClipperLib::ClipperOffset co;
if (joinType == jtRound)
co.ArcTolerance = miterLimit;
co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE);
else
co.MiterLimit = miterLimit;
co.AddPath(input, joinType, ClipperLib::etClosedPolygon);
@ -391,10 +448,10 @@ void offset(const Slic3r::ExPolygons &expolygons, ClipperLib::Paths* retval, con
for (Polygons::const_iterator it_hole = it_expoly->holes.begin(); it_hole != it_expoly->holes.end(); ++ it_hole) {
ClipperLib::Path input;
Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole, &input);
scaleClipperPolygon(input, scale);
scaleClipperPolygon(input);
ClipperLib::ClipperOffset co;
if (joinType == jtRound)
co.ArcTolerance = miterLimit;
co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE);
else
co.MiterLimit = miterLimit;
co.AddPath(input, joinType, ClipperLib::etClosedPolygon);
@ -416,15 +473,15 @@ void offset(const Slic3r::ExPolygons &expolygons, ClipperLib::Paths* retval, con
}
// 4) Unscale the output.
scaleClipperPolygons(*retval, 1/scale);
unscaleClipperPolygons(*retval);
}
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::JoinType joinType, double miterLimit)
{
// perform offset
ClipperLib::Paths output;
offset(expolygons, &output, delta, scale, joinType, miterLimit);
offset(expolygons, &output, delta, joinType, miterLimit);
// convert into ExPolygons
Slic3r::Polygons retval;
@ -433,11 +490,11 @@ Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta,
}
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::JoinType joinType, double miterLimit)
{
// perform offset
ClipperLib::Paths output;
offset(expolygons, &output, delta, scale, joinType, miterLimit);
offset(expolygons, &output, delta, joinType, miterLimit);
// convert into ExPolygons
Slic3r::ExPolygons retval;
@ -447,20 +504,20 @@ Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float d
Slic3r::ExPolygons
offset_ex(const Slic3r::Polygons &polygons, const float delta,
double scale, ClipperLib::JoinType joinType, double miterLimit)
ClipperLib::JoinType joinType, double miterLimit)
{
Slic3r::ExPolygons expp;
offset(polygons, &expp, delta, scale, joinType, miterLimit);
offset(polygons, &expp, delta, joinType, miterLimit);
return expp;
}
void
offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta1,
const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit)
const float delta2, const ClipperLib::JoinType joinType, const double miterLimit)
{
if (delta1 * delta2 >= 0) {
// Both deltas are the same signum
offset(polygons, retval, delta1 + delta2, scale, joinType, miterLimit);
offset(polygons, retval, delta1 + delta2, joinType, miterLimit);
return;
}
#ifdef CLIPPER_UTILS_DEBUG
@ -479,12 +536,12 @@ offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float
Slic3rMultiPoints_to_ClipperPaths(polygons, &input);
// scale input
scaleClipperPolygons(input, scale);
scaleClipperPolygons(input);
// prepare ClipperOffset object
ClipperLib::ClipperOffset co;
if (joinType == jtRound) {
co.ArcTolerance = miterLimit;
co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE);
} else {
co.MiterLimit = miterLimit;
}
@ -492,30 +549,30 @@ offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float
// perform first offset
ClipperLib::Paths output1;
co.AddPaths(input, joinType, ClipperLib::etClosedPolygon);
co.Execute(output1, (delta1*scale));
co.Execute(output1, delta1 * float(CLIPPER_OFFSET_SCALE));
#ifdef CLIPPER_UTILS_DEBUG
svg.draw(output1, 1./CLIPPER_OFFSET_SCALE, "red", stroke_width);
svg.draw(output1, 1. / double(CLIPPER_OFFSET_SCALE), "red", stroke_width);
#endif /* CLIPPER_UTILS_DEBUG */
// perform second offset
co.Clear();
co.AddPaths(output1, joinType, ClipperLib::etClosedPolygon);
co.Execute(*retval, (delta2*scale));
co.Execute(*retval, delta2 * float(CLIPPER_OFFSET_SCALE));
#ifdef CLIPPER_UTILS_DEBUG
svg.draw(*retval, 1./CLIPPER_OFFSET_SCALE, "green", stroke_width);
svg.draw(*retval, 1. / double(CLIPPER_OFFSET_SCALE), "green", stroke_width);
#endif /* CLIPPER_UTILS_DEBUG */
// unscale output
scaleClipperPolygons(*retval, 1/scale);
unscaleClipperPolygons(*retval);
}
void
offset2(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta1,
const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit)
const float delta2, const ClipperLib::JoinType joinType, const double miterLimit)
{
// perform offset
ClipperLib::Paths output;
offset2(polygons, &output, delta1, delta2, scale, joinType, miterLimit);
offset2(polygons, &output, delta1, delta2, joinType, miterLimit);
// convert into ExPolygons
ClipperPaths_to_Slic3rMultiPoints(output, retval);
@ -523,20 +580,20 @@ offset2(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float
Slic3r::Polygons
offset2(const Slic3r::Polygons &polygons, const float delta1,
const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit)
const float delta2, const ClipperLib::JoinType joinType, const double miterLimit)
{
Slic3r::Polygons pp;
offset2(polygons, &pp, delta1, delta2, scale, joinType, miterLimit);
offset2(polygons, &pp, delta1, delta2, joinType, miterLimit);
return pp;
}
void
offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta1,
const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit)
const float delta2, const ClipperLib::JoinType joinType, const double miterLimit)
{
// perform offset
ClipperLib::Paths output;
offset2(polygons, &output, delta1, delta2, scale, joinType, miterLimit);
offset2(polygons, &output, delta1, delta2, joinType, miterLimit);
// convert into ExPolygons
ClipperPaths_to_Slic3rExPolygons(output, retval);
@ -544,10 +601,10 @@ offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const floa
Slic3r::ExPolygons
offset2_ex(const Slic3r::Polygons &polygons, const float delta1,
const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit)
const float delta2, const ClipperLib::JoinType joinType, const double miterLimit)
{
Slic3r::ExPolygons expp;
offset2(polygons, &expp, delta1, delta2, scale, joinType, miterLimit);
offset2(polygons, &expp, delta1, delta2, joinType, miterLimit);
return expp;
}
@ -580,6 +637,13 @@ void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polygons &su
PROFILE_BLOCK(_clipper_do_polygons_AddPaths);
clipper.AddPaths(input_subject, ClipperLib::ptSubject, true);
clipper.AddPaths(input_clip, ClipperLib::ptClip, true);
#ifdef CLIPPER_UTILS_DEBUG
if (clipper_export_enabled) {
static int iRun = 0;
export_clipper_input_polygons_bin(debug_out_path("_clipper_do_polygons_AddPaths-polygons-%d", ++iRun).c_str(), input_subject, input_clip);
}
#endif /* CLIPPER_UTILS_DEBUG */
}
// perform operation
@ -986,22 +1050,39 @@ void safety_offset(ClipperLib::Paths* paths)
PROFILE_FUNC();
// scale input
scaleClipperPolygons(*paths, CLIPPER_OFFSET_SCALE);
scaleClipperPolygons(*paths);
// perform offset (delta = scale 1e-05)
ClipperLib::ClipperOffset co;
co.MiterLimit = 2;
{
PROFILE_BLOCK(safety_offset_AddPaths);
co.AddPaths(*paths, ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
}
{
PROFILE_BLOCK(safety_offset_Execute);
co.Execute(*paths, 10.0 * CLIPPER_OFFSET_SCALE);
#ifdef CLIPPER_UTILS_DEBUG
if (clipper_export_enabled) {
static int iRun = 0;
export_clipper_input_polygons_bin(debug_out_path("safety_offset-polygons-%d", ++iRun).c_str(), *paths, ClipperLib::Paths());
}
#endif /* CLIPPER_UTILS_DEBUG */
ClipperLib::Paths out;
for (size_t i = 0; i < paths->size(); ++ i) {
co.Clear();
co.MiterLimit = 2;
{
PROFILE_BLOCK(safety_offset_AddPaths);
co.AddPath((*paths)[i], ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
}
{
PROFILE_BLOCK(safety_offset_Execute);
// offset outside by 10um
ClipperLib::Paths out_this;
co.Execute(out_this, 10.f * float(CLIPPER_OFFSET_SCALE));
if (out.empty())
out = std::move(out_this);
else
std::move(std::begin(out_this), std::end(out_this), std::back_inserter(out));
}
}
*paths = std::move(out);
// unscale output
scaleClipperPolygons(*paths, 1.0/CLIPPER_OFFSET_SCALE);
unscaleClipperPolygons(*paths);
}
Polygons top_level_islands(const Slic3r::Polygons &polygons)