2013-07-16 21:04:14 +02:00
#include "ClipperUtils.hpp"
2013-11-23 23:21:59 +01:00
#include "Geometry.hpp"
2019-09-26 17:30:03 +02:00
#include "ShortestPath.hpp"
2013-07-16 21:04:14 +02:00
2016-09-13 13:30:00 +02:00
// #define CLIPPER_UTILS_DEBUG
#ifdef CLIPPER_UTILS_DEBUG
#include "SVG.hpp"
#endif /* CLIPPER_UTILS_DEBUG */
2016-11-16 22:12:58 +01:00
#include <Shiny/Shiny.h>
2017-04-04 11:17:25 +02:00
#define CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR (0.005f)
2013-07-16 21:04:14 +02:00
namespace Slic3r {
2016-11-28 17:33:17 +01:00
#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 )
{
2016-12-13 19:22:23 +01:00
FILE * pfile = fopen ( path , "wb" );
if ( pfile == NULL )
return false ;
2016-11-28 17:33:17 +01:00
2016-12-13 19:22:23 +01:00
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 ;
2016-11-28 17:33:17 +01:00
err :
2016-12-13 19:22:23 +01:00
:: fclose ( pfile );
return false ;
2016-11-28 17:33:17 +01:00
}
#endif /* CLIPPER_UTILS_DEBUG */
void scaleClipperPolygon ( ClipperLib :: Path & polygon )
2016-10-20 13:04:23 +02:00
{
2016-11-16 22:12:58 +01:00
PROFILE_FUNC ();
2016-10-20 13:04:23 +02:00
for ( ClipperLib :: Path :: iterator pit = polygon . begin (); pit != polygon . end (); ++ pit ) {
2016-11-28 17:33:17 +01:00
pit -> X <<= CLIPPER_OFFSET_POWER_OF_2 ;
pit -> Y <<= CLIPPER_OFFSET_POWER_OF_2 ;
2016-10-20 13:04:23 +02:00
}
}
2016-11-28 17:33:17 +01:00
void scaleClipperPolygons ( ClipperLib :: Paths & polygons )
2013-07-16 21:04:14 +02:00
{
2016-11-16 22:12:58 +01:00
PROFILE_FUNC ();
2016-11-28 17:33:17 +01:00
for ( ClipperLib :: Paths :: iterator it = polygons . begin (); it != polygons . end (); ++ it )
2013-11-20 15:59:19 +01:00
for ( ClipperLib :: Path :: iterator pit = ( * it ). begin (); pit != ( * it ). end (); ++ pit ) {
2016-11-28 17:33:17 +01:00
pit -> X <<= CLIPPER_OFFSET_POWER_OF_2 ;
pit -> Y <<= CLIPPER_OFFSET_POWER_OF_2 ;
2013-07-16 21:04:14 +02:00
}
2016-11-28 17:33:17 +01:00
}
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 ;
2013-07-16 21:04:14 +02:00
}
}
2016-11-28 17:33:17 +01:00
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 ;
}
}
2016-12-13 19:22:23 +01:00
//-----------------------------------------------------------
// legacy code from Clipper documentation
void AddOuterPolyNodeToExPolygons ( ClipperLib :: PolyNode & polynode , ExPolygons * expolygons )
{
size_t cnt = expolygons -> size ();
expolygons -> resize ( cnt + 1 );
2016-12-13 22:13:02 +01:00
( * expolygons )[ cnt ]. contour = ClipperPath_to_Slic3rPolygon ( polynode . Contour );
2016-12-13 19:22:23 +01:00
( * expolygons )[ cnt ]. holes . resize ( polynode . ChildCount ());
for ( int i = 0 ; i < polynode . ChildCount (); ++ i )
{
2016-12-13 22:13:02 +01:00
( * expolygons )[ cnt ]. holes [ i ] = ClipperPath_to_Slic3rPolygon ( polynode . Childs [ i ] -> Contour );
2016-12-13 19:22:23 +01:00
//Add outer polygons contained by (nested within) holes ...
for ( int j = 0 ; j < polynode . Childs [ i ] -> ChildCount (); ++ j )
AddOuterPolyNodeToExPolygons ( * polynode . Childs [ i ] -> Childs [ j ], expolygons );
}
}
2019-10-25 13:34:37 +02:00
ExPolygons PolyTreeToExPolygons ( ClipperLib :: PolyTree & polytree )
2013-07-16 21:04:14 +02:00
{
2016-12-13 19:22:23 +01:00
ExPolygons retval ;
for ( int i = 0 ; i < polytree . ChildCount (); ++ i )
AddOuterPolyNodeToExPolygons ( * polytree . Childs [ i ], & retval );
return retval ;
}
//-----------------------------------------------------------
2016-12-13 22:13:02 +01:00
Slic3r :: Polygon ClipperPath_to_Slic3rPolygon ( const ClipperLib :: Path & input )
2016-12-13 19:22:23 +01:00
{
2016-12-13 22:13:02 +01:00
Polygon retval ;
for ( ClipperLib :: Path :: const_iterator pit = input . begin (); pit != input . end (); ++ pit )
2019-04-03 10:17:57 +02:00
retval . points . emplace_back ( pit -> X , pit -> Y );
2016-12-13 22:13:02 +01:00
return retval ;
}
Slic3r :: Polyline ClipperPath_to_Slic3rPolyline ( const ClipperLib :: Path & input )
{
Polyline retval ;
2016-12-13 19:22:23 +01:00
for ( ClipperLib :: Path :: const_iterator pit = input . begin (); pit != input . end (); ++ pit )
2019-04-03 10:17:57 +02:00
retval . points . emplace_back ( pit -> X , pit -> Y );
2016-12-13 19:22:23 +01:00
return retval ;
}
2016-12-13 21:30:56 +01:00
Slic3r :: Polygons ClipperPaths_to_Slic3rPolygons ( const ClipperLib :: Paths & input )
2016-12-13 19:22:23 +01:00
{
2016-12-13 21:30:56 +01:00
Slic3r :: Polygons retval ;
retval . reserve ( input . size ());
2016-12-13 19:22:23 +01:00
for ( ClipperLib :: Paths :: const_iterator it = input . begin (); it != input . end (); ++ it )
2019-04-03 10:17:57 +02:00
retval . emplace_back ( ClipperPath_to_Slic3rPolygon ( * it ));
2016-12-13 21:30:56 +01:00
return retval ;
}
Slic3r :: Polylines ClipperPaths_to_Slic3rPolylines ( const ClipperLib :: Paths & input )
{
Slic3r :: Polylines retval ;
retval . reserve ( input . size ());
for ( ClipperLib :: Paths :: const_iterator it = input . begin (); it != input . end (); ++ it )
2019-04-03 10:17:57 +02:00
retval . emplace_back ( ClipperPath_to_Slic3rPolyline ( * it ));
2016-12-13 19:22:23 +01:00
return retval ;
}
2019-10-25 13:34:37 +02:00
ExPolygons ClipperPaths_to_Slic3rExPolygons ( const ClipperLib :: Paths & input )
2016-12-13 19:22:23 +01:00
{
// init Clipper
ClipperLib :: Clipper clipper ;
clipper . Clear ();
2013-07-16 21:04:14 +02:00
2016-12-13 19:22:23 +01:00
// perform union
clipper . AddPaths ( input , ClipperLib :: ptSubject , true );
ClipperLib :: PolyTree polytree ;
clipper . Execute ( ClipperLib :: ctUnion , polytree , ClipperLib :: pftEvenOdd , ClipperLib :: pftEvenOdd ); // offset results work with both EvenOdd and NonZero
// write to ExPolygons object
return PolyTreeToExPolygons ( polytree );
}
2019-10-25 13:34:37 +02:00
ClipperLib :: Path Slic3rMultiPoint_to_ClipperPath ( const MultiPoint & input )
2016-12-13 19:22:23 +01:00
{
ClipperLib :: Path retval ;
for ( Points :: const_iterator pit = input . points . begin (); pit != input . points . end (); ++ pit )
2019-04-03 10:17:57 +02:00
retval . emplace_back (( * pit )( 0 ), ( * pit )( 1 ));
2016-12-13 19:22:23 +01:00
return retval ;
}
2019-10-25 13:34:37 +02:00
ClipperLib :: Path Slic3rMultiPoint_to_ClipperPath_reversed ( const Slic3r :: MultiPoint & input )
2016-12-13 19:22:23 +01:00
{
ClipperLib :: Path output ;
output . reserve ( input . points . size ());
for ( Slic3r :: Points :: const_reverse_iterator pit = input . points . rbegin (); pit != input . points . rend (); ++ pit )
2019-04-03 10:17:57 +02:00
output . emplace_back (( * pit )( 0 ), ( * pit )( 1 ));
2016-12-13 19:22:23 +01:00
return output ;
}
2016-12-13 21:30:56 +01:00
ClipperLib :: Paths Slic3rMultiPoints_to_ClipperPaths ( const Polygons & input )
2016-12-13 19:22:23 +01:00
{
ClipperLib :: Paths retval ;
2016-12-13 21:30:56 +01:00
for ( Polygons :: const_iterator it = input . begin (); it != input . end (); ++ it )
2019-04-03 10:17:57 +02:00
retval . emplace_back ( Slic3rMultiPoint_to_ClipperPath ( * it ));
2016-12-13 21:30:56 +01:00
return retval ;
}
2019-09-24 14:54:33 +02:00
ClipperLib :: Paths Slic3rMultiPoints_to_ClipperPaths ( const ExPolygons & input )
{
ClipperLib :: Paths retval ;
for ( auto & ep : input ) {
retval . emplace_back ( Slic3rMultiPoint_to_ClipperPath ( ep . contour ));
for ( auto & h : ep . holes )
retval . emplace_back ( Slic3rMultiPoint_to_ClipperPath ( h ));
}
return retval ;
}
2016-12-13 21:30:56 +01:00
ClipperLib :: Paths Slic3rMultiPoints_to_ClipperPaths ( const Polylines & input )
{
ClipperLib :: Paths retval ;
for ( Polylines :: const_iterator it = input . begin (); it != input . end (); ++ it )
2019-04-03 10:17:57 +02:00
retval . emplace_back ( Slic3rMultiPoint_to_ClipperPath ( * it ));
2016-12-13 19:22:23 +01:00
return retval ;
}
2016-12-13 19:39:20 +01:00
ClipperLib :: Paths _offset ( ClipperLib :: Paths && input , ClipperLib :: EndType endType , const float delta , ClipperLib :: JoinType joinType , double miterLimit )
2016-12-13 19:22:23 +01:00
{
2013-07-16 21:04:14 +02:00
// scale input
2016-11-28 17:33:17 +01:00
scaleClipperPolygons ( input );
2013-07-16 21:04:14 +02:00
// perform offset
2013-12-24 12:40:46 +01:00
ClipperLib :: ClipperOffset co ;
2016-12-13 19:22:23 +01:00
if ( joinType == jtRound )
co . ArcTolerance = miterLimit ;
else
2013-12-24 12:40:46 +01:00
co . MiterLimit = miterLimit ;
2017-04-04 11:17:25 +02:00
float delta_scaled = delta * float ( CLIPPER_OFFSET_SCALE );
co . ShortestEdgeLength = double ( std :: abs ( delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR ));
2016-12-13 19:22:23 +01:00
co . AddPaths ( input , joinType , endType );
ClipperLib :: Paths retval ;
2017-04-04 11:17:25 +02:00
co . Execute ( retval , delta_scaled );
2013-07-16 21:04:14 +02:00
// unscale output
2016-12-13 19:22:23 +01:00
unscaleClipperPolygons ( retval );
return retval ;
2013-07-17 00:48:29 +02:00
}
2016-12-13 19:39:20 +01:00
ClipperLib :: Paths _offset ( ClipperLib :: Path && input , ClipperLib :: EndType endType , const float delta , ClipperLib :: JoinType joinType , double miterLimit )
2013-07-17 00:48:29 +02:00
{
2016-12-13 19:22:23 +01:00
ClipperLib :: Paths paths ;
2019-04-03 10:17:57 +02:00
paths . emplace_back ( std :: move ( input ));
2016-12-13 19:39:20 +01:00
return _offset ( std :: move ( paths ), endType , delta , joinType , miterLimit );
2013-07-16 21:04:14 +02:00
}
2016-10-20 13:04:23 +02:00
// This is a safe variant of the polygon offset, tailored for a single ExPolygon:
// a single polygon with multiple non-overlapping holes.
// Each contour and hole is offsetted separately, then the holes are subtracted from the outer contours.
2016-12-13 19:22:23 +01:00
ClipperLib :: Paths _offset ( const Slic3r :: ExPolygon & expolygon , const float delta ,
2016-11-28 17:33:17 +01:00
ClipperLib :: JoinType joinType , double miterLimit )
2016-10-20 13:04:23 +02:00
{
// printf("new ExPolygon offset\n");
// 1) Offset the outer contour.
2016-11-28 17:33:17 +01:00
const float delta_scaled = delta * float ( CLIPPER_OFFSET_SCALE );
2016-10-20 13:04:23 +02:00
ClipperLib :: Paths contours ;
{
2016-12-13 19:22:23 +01:00
ClipperLib :: Path input = Slic3rMultiPoint_to_ClipperPath ( expolygon . contour );
2016-11-28 17:33:17 +01:00
scaleClipperPolygon ( input );
2016-10-20 13:04:23 +02:00
ClipperLib :: ClipperOffset co ;
if ( joinType == jtRound )
2016-11-28 17:33:17 +01:00
co . ArcTolerance = miterLimit * double ( CLIPPER_OFFSET_SCALE );
2016-10-20 13:04:23 +02:00
else
co . MiterLimit = miterLimit ;
2017-04-04 11:17:25 +02:00
co . ShortestEdgeLength = double ( std :: abs ( delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR ));
2016-10-20 13:04:23 +02:00
co . AddPath ( input , joinType , ClipperLib :: etClosedPolygon );
co . Execute ( contours , delta_scaled );
}
// 2) Offset the holes one by one, collect the results.
ClipperLib :: Paths holes ;
{
holes . reserve ( expolygon . holes . size ());
for ( Polygons :: const_iterator it_hole = expolygon . holes . begin (); it_hole != expolygon . holes . end (); ++ it_hole ) {
2016-12-13 19:22:23 +01:00
ClipperLib :: Path input = Slic3rMultiPoint_to_ClipperPath_reversed ( * it_hole );
2016-11-28 17:33:17 +01:00
scaleClipperPolygon ( input );
2016-10-20 13:04:23 +02:00
ClipperLib :: ClipperOffset co ;
if ( joinType == jtRound )
2016-11-28 17:33:17 +01:00
co . ArcTolerance = miterLimit * double ( CLIPPER_OFFSET_SCALE );
2016-10-20 13:04:23 +02:00
else
co . MiterLimit = miterLimit ;
2017-04-04 11:17:25 +02:00
co . ShortestEdgeLength = double ( std :: abs ( delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR ));
2016-10-20 13:04:23 +02:00
co . AddPath ( input , joinType , ClipperLib :: etClosedPolygon );
ClipperLib :: Paths out ;
co . Execute ( out , - delta_scaled );
holes . insert ( holes . end (), out . begin (), out . end ());
}
}
// 3) Subtract holes from the contours.
ClipperLib :: Paths output ;
2017-01-02 16:51:43 +01:00
if ( holes . empty ()) {
output = std :: move ( contours );
} else {
2016-10-20 13:04:23 +02:00
ClipperLib :: Clipper clipper ;
clipper . Clear ();
clipper . AddPaths ( contours , ClipperLib :: ptSubject , true );
clipper . AddPaths ( holes , ClipperLib :: ptClip , true );
2016-12-13 19:22:23 +01:00
clipper . Execute ( ClipperLib :: ctDifference , output , ClipperLib :: pftNonZero , ClipperLib :: pftNonZero );
2016-10-20 13:04:23 +02:00
}
// 4) Unscale the output.
2016-12-13 19:22:23 +01:00
unscaleClipperPolygons ( output );
return output ;
2016-11-07 22:49:11 +01:00
}
2017-01-02 16:51:43 +01:00
// This is a safe variant of the polygons offset, tailored for multiple ExPolygons.
// It is required, that the input expolygons do not overlap and that the holes of each ExPolygon don't intersect with their respective outer contours.
// Each ExPolygon is offsetted separately, then the offsetted ExPolygons are united.
2016-12-13 19:22:23 +01:00
ClipperLib :: Paths _offset ( const Slic3r :: ExPolygons & expolygons , const float delta ,
2016-11-28 17:33:17 +01:00
ClipperLib :: JoinType joinType , double miterLimit )
2016-11-07 22:49:11 +01:00
{
2016-11-28 17:33:17 +01:00
const float delta_scaled = delta * float ( CLIPPER_OFFSET_SCALE );
2017-01-02 16:51:43 +01:00
// Offsetted ExPolygons before they are united.
ClipperLib :: Paths contours_cummulative ;
contours_cummulative . reserve ( expolygons . size ());
// How many non-empty offsetted expolygons were actually collected into contours_cummulative?
// If only one, then there is no need to do a final union.
size_t expolygons_collected = 0 ;
2016-11-07 22:49:11 +01:00
for ( Slic3r :: ExPolygons :: const_iterator it_expoly = expolygons . begin (); it_expoly != expolygons . end (); ++ it_expoly ) {
// 1) Offset the outer contour.
2017-01-02 16:51:43 +01:00
ClipperLib :: Paths contours ;
2016-11-07 22:49:11 +01:00
{
2016-12-13 19:22:23 +01:00
ClipperLib :: Path input = Slic3rMultiPoint_to_ClipperPath ( it_expoly -> contour );
2016-11-28 17:33:17 +01:00
scaleClipperPolygon ( input );
2016-11-07 22:49:11 +01:00
ClipperLib :: ClipperOffset co ;
if ( joinType == jtRound )
2016-11-28 17:33:17 +01:00
co . ArcTolerance = miterLimit * double ( CLIPPER_OFFSET_SCALE );
2016-11-07 22:49:11 +01:00
else
co . MiterLimit = miterLimit ;
2017-04-04 11:17:25 +02:00
co . ShortestEdgeLength = double ( std :: abs ( delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR ));
2016-11-07 22:49:11 +01:00
co . AddPath ( input , joinType , ClipperLib :: etClosedPolygon );
2017-01-02 16:51:43 +01:00
co . Execute ( contours , delta_scaled );
2016-11-07 22:49:11 +01:00
}
2017-01-02 16:51:43 +01:00
if ( contours . empty ())
// No need to try to offset the holes.
continue ;
2016-11-07 22:49:11 +01:00
2017-01-02 16:51:43 +01:00
if ( it_expoly -> holes . empty ()) {
// No need to subtract holes from the offsetted expolygon, we are done.
contours_cummulative . insert ( contours_cummulative . end (), contours . begin (), contours . end ());
++ expolygons_collected ;
} else {
// 2) Offset the holes one by one, collect the offsetted holes.
ClipperLib :: Paths holes ;
{
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 );
scaleClipperPolygon ( input );
ClipperLib :: ClipperOffset co ;
if ( joinType == jtRound )
co . ArcTolerance = miterLimit * double ( CLIPPER_OFFSET_SCALE );
else
co . MiterLimit = miterLimit ;
2017-04-04 11:17:25 +02:00
co . ShortestEdgeLength = double ( std :: abs ( delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR ));
2017-01-02 16:51:43 +01:00
co . AddPath ( input , joinType , ClipperLib :: etClosedPolygon );
ClipperLib :: Paths out ;
co . Execute ( out , - delta_scaled );
holes . insert ( holes . end (), out . begin (), out . end ());
}
}
// 3) Subtract holes from the contours.
if ( holes . empty ()) {
// No hole remaining after an offset. Just copy the outer contour.
contours_cummulative . insert ( contours_cummulative . end (), contours . begin (), contours . end ());
++ expolygons_collected ;
} else if ( delta < 0 ) {
// Negative offset. There is a chance, that the offsetted hole intersects the outer contour.
// Subtract the offsetted holes from the offsetted contours.
ClipperLib :: Clipper clipper ;
clipper . Clear ();
clipper . AddPaths ( contours , ClipperLib :: ptSubject , true );
clipper . AddPaths ( holes , ClipperLib :: ptClip , true );
ClipperLib :: Paths output ;
clipper . Execute ( ClipperLib :: ctDifference , output , ClipperLib :: pftNonZero , ClipperLib :: pftNonZero );
if ( ! output . empty ()) {
contours_cummulative . insert ( contours_cummulative . end (), output . begin (), output . end ());
++ expolygons_collected ;
} else {
// The offsetted holes have eaten up the offsetted outer contour.
}
} else {
// Positive offset. As long as the Clipper offset does what one expects it to do, the offsetted hole will have a smaller
// area than the original hole or even disappear, therefore there will be no new intersections.
// Just collect the reversed holes.
contours_cummulative . reserve ( contours . size () + holes . size ());
contours_cummulative . insert ( contours_cummulative . end (), contours . begin (), contours . end ());
// Reverse the holes in place.
for ( size_t i = 0 ; i < holes . size (); ++ i )
std :: reverse ( holes [ i ]. begin (), holes [ i ]. end ());
contours_cummulative . insert ( contours_cummulative . end (), holes . begin (), holes . end ());
++ expolygons_collected ;
2016-11-07 22:49:11 +01:00
}
}
}
2017-01-02 16:51:43 +01:00
// 4) Unite the offsetted expolygons.
2016-11-07 22:49:11 +01:00
ClipperLib :: Paths output ;
2017-01-02 16:51:43 +01:00
if ( expolygons_collected > 1 && delta > 0 ) {
// There is a chance that the outwards offsetted expolygons may intersect. Perform a union.
2016-11-07 22:49:11 +01:00
ClipperLib :: Clipper clipper ;
2017-01-02 16:51:43 +01:00
clipper . Clear ();
clipper . AddPaths ( contours_cummulative , ClipperLib :: ptSubject , true );
clipper . Execute ( ClipperLib :: ctUnion , output , ClipperLib :: pftNonZero , ClipperLib :: pftNonZero );
} else {
// Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output.
output = std :: move ( contours_cummulative );
2016-11-07 22:49:11 +01:00
}
// 4) Unscale the output.
2016-12-13 19:22:23 +01:00
unscaleClipperPolygons ( output );
return output ;
2016-11-07 22:49:11 +01:00
}
2016-12-13 19:22:23 +01:00
ClipperLib :: Paths
_offset2 ( const Polygons & polygons , const float delta1 , const float delta2 ,
const ClipperLib :: JoinType joinType , const double miterLimit )
2016-11-07 22:49:11 +01:00
{
2013-07-16 21:04:14 +02:00
// read input
2016-12-13 19:22:23 +01:00
ClipperLib :: Paths input = Slic3rMultiPoints_to_ClipperPaths ( polygons );
2013-07-16 21:04:14 +02:00
// scale input
2016-11-28 17:33:17 +01:00
scaleClipperPolygons ( input );
2013-12-24 12:40:46 +01:00
// prepare ClipperOffset object
ClipperLib :: ClipperOffset co ;
if ( joinType == jtRound ) {
2016-12-13 19:22:23 +01:00
co . ArcTolerance = miterLimit ;
2013-12-24 12:40:46 +01:00
} else {
co . MiterLimit = miterLimit ;
}
2017-04-04 11:17:25 +02:00
float delta_scaled1 = delta1 * float ( CLIPPER_OFFSET_SCALE );
float delta_scaled2 = delta2 * float ( CLIPPER_OFFSET_SCALE );
co . ShortestEdgeLength = double ( std :: max ( std :: abs ( delta_scaled1 ), std :: abs ( delta_scaled2 )) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR );
2013-07-16 21:04:14 +02:00
// perform first offset
2013-12-24 12:40:46 +01:00
ClipperLib :: Paths output1 ;
co . AddPaths ( input , joinType , ClipperLib :: etClosedPolygon );
2017-04-04 11:17:25 +02:00
co . Execute ( output1 , delta_scaled1 );
2013-07-16 21:04:14 +02:00
// perform second offset
2013-12-24 12:40:46 +01:00
co . Clear ();
co . AddPaths ( output1 , joinType , ClipperLib :: etClosedPolygon );
2016-12-13 19:22:23 +01:00
ClipperLib :: Paths retval ;
2017-04-04 11:17:25 +02:00
co . Execute ( retval , delta_scaled2 );
2016-12-13 19:22:23 +01:00
2013-07-16 21:04:14 +02:00
// unscale output
2016-12-13 19:22:23 +01:00
unscaleClipperPolygons ( retval );
return retval ;
2013-07-17 00:48:29 +02:00
}
2016-12-13 19:22:23 +01:00
Polygons
offset2 ( const Polygons & polygons , const float delta1 , const float delta2 ,
const ClipperLib :: JoinType joinType , const double miterLimit )
2013-07-17 00:48:29 +02:00
{
// perform offset
2016-12-13 19:22:23 +01:00
ClipperLib :: Paths output = _offset2 ( polygons , delta1 , delta2 , joinType , miterLimit );
2013-07-16 21:04:14 +02:00
// convert into ExPolygons
2016-12-13 21:30:56 +01:00
return ClipperPaths_to_Slic3rPolygons ( output );
2013-07-17 00:48:29 +02:00
}
2016-12-13 19:22:23 +01:00
ExPolygons
offset2_ex ( const Polygons & polygons , const float delta1 , const float delta2 ,
const ClipperLib :: JoinType joinType , const double miterLimit )
2013-07-17 00:48:29 +02:00
{
// perform offset
2016-12-13 19:22:23 +01:00
ClipperLib :: Paths output = _offset2 ( polygons , delta1 , delta2 , joinType , miterLimit );
2013-07-17 00:48:29 +02:00
// convert into ExPolygons
2016-12-13 19:22:23 +01:00
return ClipperPaths_to_Slic3rExPolygons ( output );
2015-07-23 15:53:02 +02:00
}
2018-05-18 09:52:09 +02:00
//FIXME Vojtech: This functon may likely be optimized to avoid some of the Slic3r to Clipper
// conversions and unnecessary Clipper calls.
ExPolygons offset2_ex ( const ExPolygons & expolygons , const float delta1 ,
const float delta2 , ClipperLib :: JoinType joinType , double miterLimit )
{
Polygons polys ;
for ( const ExPolygon & expoly : expolygons )
append ( polys ,
offset ( offset_ex ( expoly , delta1 , joinType , miterLimit ),
delta2 , joinType , miterLimit ));
return union_ex ( polys );
}
2019-09-24 14:54:33 +02:00
template < class T , class TSubj , class TClip >
T _clipper_do ( const ClipperLib :: ClipType clipType ,
TSubj && subject ,
TClip && clip ,
const ClipperLib :: PolyFillType fillType ,
const bool safety_offset_ )
2013-07-16 21:04:14 +02:00
{
// read input
2019-09-24 14:54:33 +02:00
ClipperLib :: Paths input_subject = Slic3rMultiPoints_to_ClipperPaths ( std :: forward < TSubj > ( subject ));
ClipperLib :: Paths input_clip = Slic3rMultiPoints_to_ClipperPaths ( std :: forward < TClip > ( clip ));
2013-07-16 21:04:14 +02:00
// perform safety offset
2013-08-26 17:58:37 +02:00
if ( safety_offset_ ) {
2013-07-17 00:29:09 +02:00
if ( clipType == ClipperLib :: ctUnion ) {
2014-05-08 15:00:49 +02:00
safety_offset ( & input_subject );
2013-07-17 00:29:09 +02:00
} else {
2014-05-08 15:00:49 +02:00
safety_offset ( & input_clip );
2013-07-17 00:29:09 +02:00
}
2013-07-16 21:04:14 +02:00
}
// init Clipper
ClipperLib :: Clipper clipper ;
clipper . Clear ();
// add polygons
2016-12-13 19:22:23 +01:00
clipper . AddPaths ( input_subject , ClipperLib :: ptSubject , true );
clipper . AddPaths ( input_clip , ClipperLib :: ptClip , true );
2013-07-16 21:04:14 +02:00
2013-07-17 01:21:30 +02:00
// perform operation
2016-12-13 19:22:23 +01:00
T retval ;
clipper . Execute ( clipType , retval , fillType , fillType );
return retval ;
2013-07-17 01:21:30 +02:00
}
2017-03-02 16:39:43 +01:00
// Fix of #117: A large fractal pyramid takes ages to slice
// The Clipper library has difficulties processing overlapping polygons.
2019-10-25 13:34:37 +02:00
// Namely, the function ClipperLib::JoinCommonEdges() has potentially a terrible time complexity if the output
2017-03-02 16:39:43 +01:00
// of the operation is of the PolyTree type.
// This function implmenets a following workaround:
// 1) Peform the Clipper operation with the output to Paths. This method handles overlaps in a reasonable time.
// 2) Run Clipper Union once again to extract the PolyTree from the result of 1).
inline ClipperLib :: PolyTree _clipper_do_polytree2 ( const ClipperLib :: ClipType clipType , const Polygons & subject ,
const Polygons & clip , const ClipperLib :: PolyFillType fillType , const bool safety_offset_ )
{
// read input
ClipperLib :: Paths input_subject = Slic3rMultiPoints_to_ClipperPaths ( subject );
ClipperLib :: Paths input_clip = Slic3rMultiPoints_to_ClipperPaths ( clip );
// perform safety offset
2017-08-02 14:24:32 +02:00
if ( safety_offset_ )
safety_offset (( clipType == ClipperLib :: ctUnion ) ? & input_subject : & input_clip );
2017-03-02 16:39:43 +01:00
ClipperLib :: Clipper clipper ;
clipper . AddPaths ( input_subject , ClipperLib :: ptSubject , true );
clipper . AddPaths ( input_clip , ClipperLib :: ptClip , true );
// 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.
clipper . Execute ( clipType , input_subject , fillType , fillType );
// Perform an additional Union operation to generate the PolyTree ordering.
clipper . Clear ();
clipper . AddPaths ( input_subject , ClipperLib :: ptSubject , true );
ClipperLib :: PolyTree retval ;
clipper . Execute ( ClipperLib :: ctUnion , retval , fillType , fillType );
return retval ;
}
2017-08-02 14:24:32 +02:00
ClipperLib :: PolyTree _clipper_do_pl ( const ClipperLib :: ClipType clipType , const Polylines & subject ,
2016-12-13 19:22:23 +01:00
const Polygons & clip , const ClipperLib :: PolyFillType fillType ,
2014-05-08 11:07:37 +02:00
const bool safety_offset_ )
2013-11-21 14:15:38 +01:00
{
// read input
2016-12-13 19:22:23 +01:00
ClipperLib :: Paths input_subject = Slic3rMultiPoints_to_ClipperPaths ( subject );
ClipperLib :: Paths input_clip = Slic3rMultiPoints_to_ClipperPaths ( clip );
2014-05-08 11:07:37 +02:00
// perform safety offset
if ( safety_offset_ ) safety_offset ( & input_clip );
2013-11-21 14:15:38 +01:00
// init Clipper
ClipperLib :: Clipper clipper ;
clipper . Clear ();
// add polygons
2016-12-13 19:22:23 +01:00
clipper . AddPaths ( input_subject , ClipperLib :: ptSubject , false );
clipper . AddPaths ( input_clip , ClipperLib :: ptClip , true );
2013-11-21 14:15:38 +01:00
// perform operation
2016-12-13 19:22:23 +01:00
ClipperLib :: PolyTree retval ;
clipper . Execute ( clipType , retval , fillType , fillType );
return retval ;
2013-11-21 14:15:38 +01:00
}
2017-08-02 14:24:32 +02:00
Polygons _clipper ( ClipperLib :: ClipType clipType , const Polygons & subject , const Polygons & clip , bool safety_offset_ )
2016-12-13 19:22:23 +01:00
{
2016-12-13 21:30:56 +01:00
return ClipperPaths_to_Slic3rPolygons ( _clipper_do < ClipperLib :: Paths > ( clipType , subject , clip , ClipperLib :: pftNonZero , safety_offset_ ));
2016-12-13 19:22:23 +01:00
}
2017-08-02 14:24:32 +02:00
ExPolygons _clipper_ex ( ClipperLib :: ClipType clipType , const Polygons & subject , const Polygons & clip , bool safety_offset_ )
2016-12-13 19:22:23 +01:00
{
2017-03-02 16:39:43 +01:00
ClipperLib :: PolyTree polytree = _clipper_do_polytree2 ( clipType , subject , clip , ClipperLib :: pftNonZero , safety_offset_ );
2016-12-13 19:52:28 +01:00
return PolyTreeToExPolygons ( polytree );
2016-12-13 19:22:23 +01:00
}
2017-08-02 14:24:32 +02:00
Polylines _clipper_pl ( ClipperLib :: ClipType clipType , const Polylines & subject , const Polygons & clip , bool safety_offset_ )
2013-07-17 01:21:30 +02:00
{
2014-05-08 15:00:49 +02:00
ClipperLib :: Paths output ;
2016-12-13 22:13:02 +01:00
ClipperLib :: PolyTreeToPaths ( _clipper_do_pl ( clipType , subject , clip , ClipperLib :: pftNonZero , safety_offset_ ), output );
2016-12-13 21:30:56 +01:00
return ClipperPaths_to_Slic3rPolylines ( output );
2013-07-17 01:21:30 +02:00
}
2017-08-02 14:24:32 +02:00
Polylines _clipper_pl ( ClipperLib :: ClipType clipType , const Polygons & subject , const Polygons & clip , bool safety_offset_ )
2014-05-21 15:03:31 +02:00
{
// transform input polygons into polylines
2016-12-13 19:22:23 +01:00
Polylines polylines ;
2014-05-21 15:03:31 +02:00
polylines . reserve ( subject . size ());
2016-12-13 19:22:23 +01:00
for ( Polygons :: const_iterator polygon = subject . begin (); polygon != subject . end (); ++ polygon )
2019-04-03 10:17:57 +02:00
polylines . emplace_back ( polygon -> operator Polyline ()); // implicit call to split_at_first_point()
2014-05-21 15:03:31 +02:00
// perform clipping
2016-12-13 19:22:23 +01:00
Polylines retval = _clipper_pl ( clipType , polylines , clip , safety_offset_ );
2014-05-08 11:07:37 +02:00
/* If the split_at_first_point() call above happens to split the polygon inside the clipping area
we would get two consecutive polylines instead of a single one, so we go through them in order
to recombine continuous polylines. */
2016-12-13 19:22:23 +01:00
for ( size_t i = 0 ; i < retval . size (); ++ i ) {
for ( size_t j = i + 1 ; j < retval . size (); ++ j ) {
2018-08-15 13:51:40 +02:00
if ( retval [ i ]. points . back () == retval [ j ]. points . front ()) {
2014-05-08 11:07:37 +02:00
/* If last point of i coincides with first point of j,
append points of j to i and delete j */
2016-12-13 19:22:23 +01:00
retval [ i ]. points . insert ( retval [ i ]. points . end (), retval [ j ]. points . begin () + 1 , retval [ j ]. points . end ());
retval . erase ( retval . begin () + j );
2014-05-08 11:07:37 +02:00
-- j ;
2018-08-15 13:51:40 +02:00
} else if ( retval [ i ]. points . front () == retval [ j ]. points . back ()) {
2014-05-08 11:07:37 +02:00
/* If first point of i coincides with last point of j,
prepend points of j to i and delete j */
2016-12-13 19:22:23 +01:00
retval [ i ]. points . insert ( retval [ i ]. points . begin (), retval [ j ]. points . begin (), retval [ j ]. points . end () - 1 );
retval . erase ( retval . begin () + j );
2014-05-08 11:07:37 +02:00
-- j ;
2018-08-15 13:51:40 +02:00
} else if ( retval [ i ]. points . front () == retval [ j ]. points . front ()) {
2014-05-08 11:07:37 +02:00
/* Since Clipper does not preserve orientation of polylines,
also check the case when first point of i coincides with first point of j. */
2016-12-13 19:22:23 +01:00
retval [ j ]. reverse ();
retval [ i ]. points . insert ( retval [ i ]. points . begin (), retval [ j ]. points . begin (), retval [ j ]. points . end () - 1 );
retval . erase ( retval . begin () + j );
2014-05-08 11:07:37 +02:00
-- j ;
2018-08-15 13:51:40 +02:00
} else if ( retval [ i ]. points . back () == retval [ j ]. points . back ()) {
2014-05-08 11:07:37 +02:00
/* Since Clipper does not preserve orientation of polylines,
also check the case when last point of i coincides with last point of j. */
2016-12-13 19:22:23 +01:00
retval [ j ]. reverse ();
retval [ i ]. points . insert ( retval [ i ]. points . end (), retval [ j ]. points . begin () + 1 , retval [ j ]. points . end ());
retval . erase ( retval . begin () + j );
2014-05-08 11:07:37 +02:00
-- j ;
}
}
2014-12-24 10:20:55 +01:00
}
2016-10-24 14:07:38 +02:00
return retval ;
}
2016-12-13 19:22:23 +01:00
Lines
_clipper_ln ( ClipperLib :: ClipType clipType , const Lines & subject , const Polygons & clip ,
bool safety_offset_ )
2013-08-26 18:37:19 +02:00
{
2016-12-13 19:22:23 +01:00
// convert Lines to Polylines
Polylines polylines ;
polylines . reserve ( subject . size ());
2018-08-17 14:14:24 +02:00
for ( const Line & line : subject )
polylines . emplace_back ( Polyline ( line . a , line . b ));
2016-12-13 19:22:23 +01:00
// perform operation
polylines = _clipper_pl ( clipType , polylines , clip , safety_offset_ );
// convert Polylines to Lines
Lines retval ;
for ( Polylines :: const_iterator polyline = polylines . begin (); polyline != polylines . end (); ++ polyline )
2019-04-03 11:17:15 +02:00
retval . emplace_back ( polyline -> operator Line ());
2016-12-13 19:22:23 +01:00
return retval ;
2013-08-26 18:37:19 +02:00
}
2019-09-24 14:54:33 +02:00
ClipperLib :: PolyTree union_pt ( const Polygons & subject , bool safety_offset_ )
2013-11-23 23:21:59 +01:00
{
2016-12-13 19:22:23 +01:00
return _clipper_do < ClipperLib :: PolyTree > ( ClipperLib :: ctUnion , subject , Polygons (), ClipperLib :: pftEvenOdd , safety_offset_ );
2013-11-23 23:21:59 +01:00
}
2019-09-24 14:54:33 +02:00
ClipperLib :: PolyTree union_pt ( const ExPolygons & subject , bool safety_offset_ )
{
return _clipper_do < ClipperLib :: PolyTree > ( ClipperLib :: ctUnion , subject , Polygons (), ClipperLib :: pftEvenOdd , safety_offset_ );
}
ClipperLib :: PolyTree union_pt ( Polygons && subject , bool safety_offset_ )
{
return _clipper_do < ClipperLib :: PolyTree > ( ClipperLib :: ctUnion , std :: move ( subject ), Polygons (), ClipperLib :: pftEvenOdd , safety_offset_ );
}
ClipperLib :: PolyTree union_pt ( ExPolygons && subject , bool safety_offset_ )
{
return _clipper_do < ClipperLib :: PolyTree > ( ClipperLib :: ctUnion , std :: move ( subject ), Polygons (), ClipperLib :: pftEvenOdd , safety_offset_ );
}
2019-12-19 11:27:01 +01:00
// Simple spatial ordering of Polynodes
ClipperLib :: PolyNodes order_nodes ( const ClipperLib :: PolyNodes & nodes )
2019-09-24 14:54:33 +02:00
{
// collect ordering points
Points ordering_points ;
ordering_points . reserve ( nodes . size ());
2019-12-19 11:27:01 +01:00
2019-09-24 14:54:33 +02:00
for ( const ClipperLib :: PolyNode * node : nodes )
2019-12-19 11:27:01 +01:00
ordering_points . emplace_back (
Point ( node -> Contour . front (). X , node -> Contour . front (). Y ));
2019-09-24 14:54:33 +02:00
// perform the ordering
2019-12-19 11:27:01 +01:00
ClipperLib :: PolyNodes ordered_nodes =
chain_clipper_polynodes ( ordering_points , nodes );
2019-09-24 14:54:33 +02:00
return ordered_nodes ;
}
2019-12-19 11:27:01 +01:00
static void traverse_pt_noholes ( const ClipperLib :: PolyNodes & nodes , Polygons * out )
2019-09-24 14:54:33 +02:00
{
2019-12-19 11:27:01 +01:00
foreach_node < e_ordering :: ON > ( nodes , [ & out ]( const ClipperLib :: PolyNode * node )
{
traverse_pt_noholes ( node -> Childs , out );
out -> emplace_back ( ClipperPath_to_Slic3rPolygon ( node -> Contour ));
if ( node -> IsHole ()) out -> back (). reverse (); // ccw
});
2019-09-24 14:54:33 +02:00
}
2019-12-19 11:27:01 +01:00
static void traverse_pt_old ( ClipperLib :: PolyNodes & nodes , Polygons * retval )
2013-11-23 23:21:59 +01:00
{
/* use a nearest neighbor search to order these children
TODO: supply start_near to chained_path() too? */
2019-12-19 11:27:01 +01:00
// collect ordering points
Points ordering_points ;
ordering_points . reserve ( nodes . size ());
for ( ClipperLib :: PolyNodes :: const_iterator it = nodes . begin (); it != nodes . end (); ++ it ) {
Point p (( * it ) -> Contour . front (). X , ( * it ) -> Contour . front (). Y );
ordering_points . push_back ( p );
}
// perform the ordering
ClipperLib :: PolyNodes ordered_nodes = chain_clipper_polynodes ( ordering_points , nodes );
2013-11-23 23:21:59 +01:00
// push results recursively
2019-12-19 11:27:01 +01:00
for ( ClipperLib :: PolyNodes :: iterator it = ordered_nodes . begin (); it != ordered_nodes . end (); ++ it ) {
2013-11-23 23:21:59 +01:00
// traverse the next depth
2019-12-19 11:27:01 +01:00
traverse_pt_old (( * it ) -> Childs , retval );
retval -> push_back ( ClipperPath_to_Slic3rPolygon (( * it ) -> Contour ));
if (( * it ) -> IsHole ()) retval -> back (). reverse (); // ccw
}
2019-09-24 14:54:33 +02:00
}
2019-12-19 11:27:01 +01:00
Polygons union_pt_chained ( const Polygons & subject , bool safety_offset_ )
2019-09-24 14:54:33 +02:00
{
2019-12-19 11:27:01 +01:00
ClipperLib :: PolyTree polytree = union_pt ( subject , safety_offset_ );
2019-09-24 14:54:33 +02:00
2019-12-19 11:27:01 +01:00
Polygons retval ;
traverse_pt_old ( polytree . Childs , & retval );
return retval ;
2019-09-24 14:54:33 +02:00
2019-12-19 11:27:01 +01:00
// TODO: This needs to be tested:
// ClipperLib::PolyTree polytree = union_pt(subject, safety_offset_);
2019-09-24 14:54:33 +02:00
2019-12-19 11:27:01 +01:00
// Polygons retval;
// traverse_pt_noholes(polytree.Childs, &retval);
// return retval;
2013-11-23 23:21:59 +01:00
}
2018-05-18 09:52:09 +02:00
Polygons simplify_polygons ( const Polygons & subject , bool preserve_collinear )
2013-08-08 02:10:34 +02:00
{
// convert into Clipper polygons
2016-12-13 19:22:23 +01:00
ClipperLib :: Paths input_subject = Slic3rMultiPoints_to_ClipperPaths ( subject );
2014-05-01 10:37:38 +02:00
2016-12-13 19:22:23 +01:00
ClipperLib :: Paths output ;
2014-05-01 10:37:38 +02:00
if ( preserve_collinear ) {
ClipperLib :: Clipper c ;
c . PreserveCollinear ( true );
c . StrictlySimple ( true );
2014-05-08 15:00:49 +02:00
c . AddPaths ( input_subject , ClipperLib :: ptSubject , true );
c . Execute ( ClipperLib :: ctUnion , output , ClipperLib :: pftNonZero , ClipperLib :: pftNonZero );
2014-05-01 10:37:38 +02:00
} else {
2014-05-08 15:00:49 +02:00
ClipperLib :: SimplifyPolygons ( input_subject , output , ClipperLib :: pftNonZero );
2014-05-01 10:37:38 +02:00
}
2013-08-08 02:10:34 +02:00
// convert into Slic3r polygons
2016-12-13 21:30:56 +01:00
return ClipperPaths_to_Slic3rPolygons ( output );
2013-08-08 02:10:34 +02:00
}
2018-05-18 09:52:09 +02:00
ExPolygons simplify_polygons_ex ( const Polygons & subject , bool preserve_collinear )
2014-05-01 12:07:11 +02:00
{
2018-05-18 09:52:09 +02:00
if ( ! preserve_collinear )
return union_ex ( simplify_polygons ( subject , false ));
2014-05-01 12:07:11 +02:00
// convert into Clipper polygons
2016-12-13 19:22:23 +01:00
ClipperLib :: Paths input_subject = Slic3rMultiPoints_to_ClipperPaths ( subject );
2014-05-01 12:07:11 +02:00
ClipperLib :: PolyTree polytree ;
ClipperLib :: Clipper c ;
c . PreserveCollinear ( true );
c . StrictlySimple ( true );
c . AddPaths ( input_subject , ClipperLib :: ptSubject , true );
c . Execute ( ClipperLib :: ctUnion , polytree , ClipperLib :: pftNonZero , ClipperLib :: pftNonZero );
// convert into ExPolygons
2016-12-13 19:22:23 +01:00
return PolyTreeToExPolygons ( polytree );
2014-05-01 12:07:11 +02:00
}
2014-05-08 11:07:37 +02:00
void safety_offset ( ClipperLib :: Paths * paths )
2013-08-26 17:58:37 +02:00
{
2016-11-16 22:12:58 +01:00
PROFILE_FUNC ();
2013-08-26 17:58:37 +02:00
// scale input
2016-11-28 17:33:17 +01:00
scaleClipperPolygons ( * paths );
2013-08-26 17:58:37 +02:00
// perform offset (delta = scale 1e-05)
2013-12-24 12:40:46 +01:00
ClipperLib :: ClipperOffset co ;
2016-11-28 17:33:17 +01:00
#ifdef CLIPPER_UTILS_DEBUG
2016-12-13 19:22:23 +01:00
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 ());
}
2016-11-28 17:33:17 +01:00
#endif /* CLIPPER_UTILS_DEBUG */
ClipperLib :: Paths out ;
for ( size_t i = 0 ; i < paths -> size (); ++ i ) {
2016-11-29 19:26:26 +01:00
ClipperLib :: Path & path = ( * paths )[ i ];
2016-12-13 19:22:23 +01:00
co . Clear ();
2016-11-28 17:33:17 +01:00
co . MiterLimit = 2 ;
2016-11-29 19:26:26 +01:00
bool ccw = ClipperLib :: Orientation ( path );
if ( ! ccw )
std :: reverse ( path . begin (), path . end ());
2016-11-28 17:33:17 +01:00
{
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 ;
2016-11-29 19:26:26 +01:00
co . Execute ( out_this , ccw ? 10.f * float ( CLIPPER_OFFSET_SCALE ) : - 10.f * float ( CLIPPER_OFFSET_SCALE ));
if ( ! ccw ) {
// Reverse the resulting contours once again.
for ( ClipperLib :: Paths :: iterator it = out_this . begin (); it != out_this . end (); ++ it )
std :: reverse ( it -> begin (), it -> end ());
}
2016-11-28 17:33:17 +01:00
if ( out . empty ())
out = std :: move ( out_this );
else
std :: move ( std :: begin ( out_this ), std :: end ( out_this ), std :: back_inserter ( out ));
}
2016-11-16 22:12:58 +01:00
}
2016-11-28 17:33:17 +01:00
* paths = std :: move ( out );
2013-08-26 17:58:37 +02:00
// unscale output
2016-11-28 17:33:17 +01:00
unscaleClipperPolygons ( * paths );
2013-08-26 17:58:37 +02:00
}
2016-11-23 15:51:47 +01:00
Polygons top_level_islands ( const Slic3r :: Polygons & polygons )
{
// init Clipper
ClipperLib :: Clipper clipper ;
clipper . Clear ();
// perform union
2016-12-13 19:22:23 +01:00
clipper . AddPaths ( Slic3rMultiPoints_to_ClipperPaths ( polygons ), ClipperLib :: ptSubject , true );
2016-11-23 15:51:47 +01:00
ClipperLib :: PolyTree polytree ;
clipper . Execute ( ClipperLib :: ctUnion , polytree , ClipperLib :: pftEvenOdd , ClipperLib :: pftEvenOdd );
// Convert only the top level islands to the output.
Polygons out ;
out . reserve ( polytree . ChildCount ());
2016-12-13 19:22:23 +01:00
for ( int i = 0 ; i < polytree . ChildCount (); ++ i )
2019-04-03 10:17:57 +02:00
out . emplace_back ( ClipperPath_to_Slic3rPolygon ( polytree . Childs [ i ] -> Contour ));
2016-11-23 15:51:47 +01:00
return out ;
2013-07-16 21:04:14 +02:00
}
2016-11-23 15:51:47 +01:00
2019-10-25 13:34:37 +02:00
// Outer offset shall not split the input contour into multiples. It is expected, that the solution will be non empty and it will contain just a single polygon.
2020-02-19 12:57:54 +01:00
ClipperLib :: Paths fix_after_outer_offset (
const ClipperLib :: Path & input ,
// combination of default prameters to correspond to void ClipperOffset::Execute(Paths& solution, double delta)
// to produce a CCW output contour from CCW input contour for a positive offset.
ClipperLib :: PolyFillType filltype , // = ClipperLib::pftPositive
bool reverse_result ) // = false
2019-10-25 13:34:37 +02:00
{
ClipperLib :: Paths solution ;
if ( ! input . empty ()) {
ClipperLib :: Clipper clipper ;
clipper . AddPath ( input , ClipperLib :: ptSubject , true );
clipper . ReverseSolution ( reverse_result );
clipper . Execute ( ClipperLib :: ctUnion , solution , filltype , filltype );
}
return solution ;
}
2020-02-19 12:57:54 +01:00
// Inner offset may split the source contour into multiple contours, but one resulting contour shall not lie inside the other.
ClipperLib :: Paths fix_after_inner_offset (
const ClipperLib :: Path & input ,
// combination of default prameters to correspond to void ClipperOffset::Execute(Paths& solution, double delta)
// to produce a CCW output contour from CCW input contour for a negative offset.
ClipperLib :: PolyFillType filltype , // = ClipperLib::pftNegative
bool reverse_result ) // = true
2019-10-25 13:34:37 +02:00
{
ClipperLib :: Paths solution ;
if ( ! input . empty ()) {
ClipperLib :: Clipper clipper ;
clipper . AddPath ( input , ClipperLib :: ptSubject , true );
ClipperLib :: IntRect r = clipper . GetBounds ();
r . left -= 10 ; r . top -= 10 ; r . right += 10 ; r . bottom += 10 ;
if ( filltype == ClipperLib :: pftPositive )
clipper . AddPath ({ ClipperLib :: IntPoint ( r . left , r . bottom ), ClipperLib :: IntPoint ( r . left , r . top ), ClipperLib :: IntPoint ( r . right , r . top ), ClipperLib :: IntPoint ( r . right , r . bottom ) }, ClipperLib :: ptSubject , true );
else
clipper . AddPath ({ ClipperLib :: IntPoint ( r . left , r . bottom ), ClipperLib :: IntPoint ( r . right , r . bottom ), ClipperLib :: IntPoint ( r . right , r . top ), ClipperLib :: IntPoint ( r . left , r . top ) }, ClipperLib :: ptSubject , true );
clipper . ReverseSolution ( reverse_result );
clipper . Execute ( ClipperLib :: ctUnion , solution , filltype , filltype );
if ( ! solution . empty ())
solution . erase ( solution . begin ());
}
return solution ;
}
ClipperLib :: Path mittered_offset_path_scaled ( const Points & contour , const std :: vector < float > & deltas , double miter_limit )
{
assert ( contour . size () == deltas . size ());
2019-11-04 11:26:36 +01:00
2019-10-25 13:34:37 +02:00
#ifndef NDEBUG
// Verify that the deltas are either all positive, or all negative.
bool positive = false ;
bool negative = false ;
for ( float delta : deltas )
if ( delta < 0.f )
negative = true ;
else if ( delta > 0.f )
positive = true ;
assert ( ! ( negative && positive ));
#endif /* NDEBUG */
ClipperLib :: Path out ;
if ( deltas . size () > 2 )
{
out . reserve ( contour . size () * 2 );
// Clamp miter limit to 2.
miter_limit = ( miter_limit > 2. ) ? 2. / ( miter_limit * miter_limit ) : 0.5 ;
// perpenduclar vector
auto perp = []( const Vec2d & v ) -> Vec2d { return Vec2d ( v . y (), - v . x ()); };
// Add a new point to the output, scale by CLIPPER_OFFSET_SCALE and round to ClipperLib::cInt.
auto add_offset_point = [ & out ]( Vec2d pt ) {
pt *= double ( CLIPPER_OFFSET_SCALE );
pt += Vec2d ( 0.5 - ( pt . x () < 0 ), 0.5 - ( pt . y () < 0 ));
out . emplace_back ( ClipperLib :: cInt ( pt . x ()), ClipperLib :: cInt ( pt . y ()));
};
// Minimum edge length, squared.
double lmin = * std :: max_element ( deltas . begin (), deltas . end ()) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR ;
double l2min = lmin * lmin ;
// Minimum angle to consider two edges to be parallel.
2019-11-04 11:26:36 +01:00
// Vojtech's estimate.
// const double sin_min_parallel = EPSILON + 1. / double(CLIPPER_OFFSET_SCALE);
// Implementation equal to Clipper.
const double sin_min_parallel = 1. ;
2019-10-25 13:34:37 +02:00
// Find the last point further from pt by l2min.
Vec2d pt = contour . front (). cast < double > ();
size_t iprev = contour . size () - 1 ;
Vec2d ptprev ;
for (; iprev > 0 ; -- iprev ) {
ptprev = contour [ iprev ]. cast < double > ();
if (( ptprev - pt ). squaredNorm () > l2min )
break ;
}
if ( iprev != 0 ) {
size_t ilast = iprev ;
// Normal to the (pt - ptprev) segment.
Vec2d nprev = perp ( pt - ptprev ). normalized ();
for ( size_t i = 0 ; ; ) {
// Find the next point further from pt by l2min.
size_t j = i + 1 ;
Vec2d ptnext ;
for (; j <= ilast ; ++ j ) {
ptnext = contour [ j ]. cast < double > ();
double l2 = ( ptnext - pt ). squaredNorm ();
if ( l2 > l2min )
break ;
}
2019-11-04 11:26:36 +01:00
if ( j > ilast ) {
assert ( i <= ilast );
// If the last edge is too short, merge it with the previous edge.
i = ilast ;
2019-10-25 13:34:37 +02:00
ptnext = contour . front (). cast < double > ();
2019-11-04 11:26:36 +01:00
}
2019-10-25 13:34:37 +02:00
// Normal to the (ptnext - pt) segment.
Vec2d nnext = perp ( ptnext - pt ). normalized ();
double delta = deltas [ i ];
double sin_a = clamp ( - 1. , 1. , cross2 ( nprev , nnext ));
double convex = sin_a * delta ;
if ( convex <= - sin_min_parallel ) {
// Concave corner.
add_offset_point ( pt + nprev * delta );
add_offset_point ( pt );
add_offset_point ( pt + nnext * delta );
} else {
double dot = nprev . dot ( nnext );
2019-11-04 11:26:36 +01:00
if ( convex < sin_min_parallel && dot > 0. ) {
// Nearly parallel.
add_offset_point (( nprev . dot ( nnext ) > 0. ) ? ( pt + nprev * delta ) : pt );
} else {
// Convex corner, possibly extremely sharp if convex < sin_min_parallel.
double r = 1. + dot ;
if ( r >= miter_limit )
add_offset_point ( pt + ( nprev + nnext ) * ( delta / r ));
else {
double dx = std :: tan ( std :: atan2 ( sin_a , dot ) / 4. );
Vec2d newpt1 = pt + ( nprev - perp ( nprev ) * dx ) * delta ;
Vec2d newpt2 = pt + ( nnext + perp ( nnext ) * dx ) * delta ;
2019-10-25 13:34:37 +02:00
#ifndef NDEBUG
2019-11-04 11:26:36 +01:00
Vec2d vedge = 0.5 * ( newpt1 + newpt2 ) - pt ;
double dist_norm = vedge . norm ();
assert ( std :: abs ( dist_norm - std :: abs ( delta )) < SCALED_EPSILON );
2019-10-25 13:34:37 +02:00
#endif /* NDEBUG */
2019-11-04 11:26:36 +01:00
add_offset_point ( newpt1 );
add_offset_point ( newpt2 );
}
}
2019-10-25 13:34:37 +02:00
}
if ( i == ilast )
break ;
ptprev = pt ;
nprev = nnext ;
pt = ptnext ;
i = j ;
}
}
}
2019-11-01 19:59:09 +01:00
#if 0
{
ClipperLib::Path polytmp(out);
unscaleClipperPolygon(polytmp);
Slic3r::Polygon offsetted = ClipperPath_to_Slic3rPolygon(polytmp);
BoundingBox bbox = get_extents(contour);
bbox.merge(get_extents(offsetted));
static int iRun = 0;
SVG svg(debug_out_path("mittered_offset_path_scaled-%d.svg", iRun ++).c_str(), bbox);
svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
svg.draw_outline(offsetted, "red", scale_(0.01));
svg.draw(contour, "blue", scale_(0.03));
svg.draw((Points)offsetted, "blue", scale_(0.03));
}
#endif
2019-10-25 13:34:37 +02:00
return out ;
}
Polygons variable_offset_inner ( const ExPolygon & expoly , const std :: vector < std :: vector < float >> & deltas , double miter_limit )
{
#ifndef NDEBUG
// Verify that the deltas are all non positive.
for ( const std :: vector < float > & ds : deltas )
for ( float delta : ds )
assert ( delta <= 0. );
assert ( expoly . holes . size () + 1 == deltas . size ());
#endif /* NDEBUG */
// 1) Offset the outer contour.
ClipperLib :: Paths contours = fix_after_inner_offset ( mittered_offset_path_scaled ( expoly . contour . points , deltas . front (), miter_limit ), ClipperLib :: pftNegative , true );
2020-02-19 12:57:54 +01:00
#ifndef NDEBUG
for ( auto & c : contours )
assert ( ClipperLib :: Area ( c ) > 0. );
#endif /* NDEBUG */
2019-10-25 13:34:37 +02:00
// 2) Offset the holes one by one, collect the results.
ClipperLib :: Paths holes ;
holes . reserve ( expoly . holes . size ());
for ( const Polygon & hole : expoly . holes )
2020-02-19 12:57:54 +01:00
append ( holes , fix_after_outer_offset ( mittered_offset_path_scaled ( hole , deltas [ 1 + & hole - expoly . holes . data ()], miter_limit ), ClipperLib :: pftNegative , false ));
#ifndef NDEBUG
for ( auto & c : holes )
assert ( ClipperLib :: Area ( c ) > 0. );
#endif /* NDEBUG */
2019-10-25 13:34:37 +02:00
// 3) Subtract holes from the contours.
ClipperLib :: Paths output ;
if ( holes . empty ())
output = std :: move ( contours );
else {
ClipperLib :: Clipper clipper ;
clipper . Clear ();
clipper . AddPaths ( contours , ClipperLib :: ptSubject , true );
clipper . AddPaths ( holes , ClipperLib :: ptClip , true );
clipper . Execute ( ClipperLib :: ctDifference , output , ClipperLib :: pftNonZero , ClipperLib :: pftNonZero );
}
// 4) Unscale the output.
unscaleClipperPolygons ( output );
return ClipperPaths_to_Slic3rPolygons ( output );
}
Polygons variable_offset_outer ( const ExPolygon & expoly , const std :: vector < std :: vector < float >> & deltas , double miter_limit )
{
#ifndef NDEBUG
// Verify that the deltas are all non positive.
for ( const std :: vector < float >& ds : deltas )
for ( float delta : ds )
assert ( delta >= 0. );
assert ( expoly . holes . size () + 1 == deltas . size ());
#endif /* NDEBUG */
// 1) Offset the outer contour.
ClipperLib :: Paths contours = fix_after_outer_offset ( mittered_offset_path_scaled ( expoly . contour . points , deltas . front (), miter_limit ), ClipperLib :: pftPositive , false );
2020-02-19 12:57:54 +01:00
#ifndef NDEBUG
for ( auto & c : contours )
assert ( ClipperLib :: Area ( c ) > 0. );
#endif /* NDEBUG */
2019-10-25 13:34:37 +02:00
// 2) Offset the holes one by one, collect the results.
ClipperLib :: Paths holes ;
holes . reserve ( expoly . holes . size ());
for ( const Polygon & hole : expoly . holes )
append ( holes , fix_after_inner_offset ( mittered_offset_path_scaled ( hole , deltas [ 1 + & hole - expoly . holes . data ()], miter_limit ), ClipperLib :: pftPositive , true ));
2020-02-19 12:57:54 +01:00
#ifndef NDEBUG
for ( auto & c : holes )
assert ( ClipperLib :: Area ( c ) > 0. );
#endif /* NDEBUG */
2019-10-25 13:34:37 +02:00
// 3) Subtract holes from the contours.
ClipperLib :: Paths output ;
if ( holes . empty ())
output = std :: move ( contours );
else {
ClipperLib :: Clipper clipper ;
clipper . Clear ();
clipper . AddPaths ( contours , ClipperLib :: ptSubject , true );
clipper . AddPaths ( holes , ClipperLib :: ptClip , true );
clipper . Execute ( ClipperLib :: ctDifference , output , ClipperLib :: pftNonZero , ClipperLib :: pftNonZero );
}
// 4) Unscale the output.
unscaleClipperPolygons ( output );
return ClipperPaths_to_Slic3rPolygons ( output );
}
ExPolygons variable_offset_outer_ex ( const ExPolygon & expoly , const std :: vector < std :: vector < float >> & deltas , double miter_limit )
{
#ifndef NDEBUG
// Verify that the deltas are all non positive.
for ( const std :: vector < float >& ds : deltas )
for ( float delta : ds )
assert ( delta >= 0. );
assert ( expoly . holes . size () + 1 == deltas . size ());
#endif /* NDEBUG */
// 1) Offset the outer contour.
ClipperLib :: Paths contours = fix_after_outer_offset ( mittered_offset_path_scaled ( expoly . contour . points , deltas . front (), miter_limit ), ClipperLib :: pftPositive , false );
2020-02-19 12:57:54 +01:00
#ifndef NDEBUG
for ( auto & c : contours )
assert ( ClipperLib :: Area ( c ) > 0. );
#endif /* NDEBUG */
2019-10-25 13:34:37 +02:00
// 2) Offset the holes one by one, collect the results.
ClipperLib :: Paths holes ;
holes . reserve ( expoly . holes . size ());
for ( const Polygon & hole : expoly . holes )
append ( holes , fix_after_inner_offset ( mittered_offset_path_scaled ( hole , deltas [ 1 + & hole - expoly . holes . data ()], miter_limit ), ClipperLib :: pftPositive , true ));
2020-02-19 12:57:54 +01:00
#ifndef NDEBUG
for ( auto & c : holes )
assert ( ClipperLib :: Area ( c ) > 0. );
#endif /* NDEBUG */
2019-10-25 13:34:37 +02:00
// 3) Subtract holes from the contours.
unscaleClipperPolygons ( contours );
ExPolygons output ;
if ( holes . empty ()) {
output . reserve ( contours . size ());
for ( ClipperLib :: Path & path : contours )
output . emplace_back ( ClipperPath_to_Slic3rPolygon ( path ));
} else {
ClipperLib :: Clipper clipper ;
unscaleClipperPolygons ( holes );
clipper . AddPaths ( contours , ClipperLib :: ptSubject , true );
clipper . AddPaths ( holes , ClipperLib :: ptClip , true );
ClipperLib :: PolyTree polytree ;
clipper . Execute ( ClipperLib :: ctDifference , polytree , ClipperLib :: pftNonZero , ClipperLib :: pftNonZero );
output = PolyTreeToExPolygons ( polytree );
}
return output ;
}
ExPolygons variable_offset_inner_ex ( const ExPolygon & expoly , const std :: vector < std :: vector < float >> & deltas , double miter_limit )
{
#ifndef NDEBUG
// Verify that the deltas are all non positive.
2019-11-14 17:02:32 +01:00
for ( const std :: vector < float >& ds : deltas )
2019-10-25 13:34:37 +02:00
for ( float delta : ds )
assert ( delta <= 0. );
assert ( expoly . holes . size () + 1 == deltas . size ());
#endif /* NDEBUG */
// 1) Offset the outer contour.
2020-02-19 12:57:54 +01:00
ClipperLib :: Paths contours = fix_after_inner_offset ( mittered_offset_path_scaled ( expoly . contour . points , deltas . front (), miter_limit ), ClipperLib :: pftNegative , true );
#ifndef NDEBUG
for ( auto & c : contours )
assert ( ClipperLib :: Area ( c ) > 0. );
#endif /* NDEBUG */
2019-10-25 13:34:37 +02:00
// 2) Offset the holes one by one, collect the results.
ClipperLib :: Paths holes ;
holes . reserve ( expoly . holes . size ());
for ( const Polygon & hole : expoly . holes )
2020-02-19 12:57:54 +01:00
append ( holes , fix_after_outer_offset ( mittered_offset_path_scaled ( hole , deltas [ 1 + & hole - expoly . holes . data ()], miter_limit ), ClipperLib :: pftNegative , false ));
#ifndef NDEBUG
for ( auto & c : holes )
assert ( ClipperLib :: Area ( c ) > 0. );
#endif /* NDEBUG */
2019-10-25 13:34:37 +02:00
// 3) Subtract holes from the contours.
unscaleClipperPolygons ( contours );
ExPolygons output ;
if ( holes . empty ()) {
output . reserve ( contours . size ());
for ( ClipperLib :: Path & path : contours )
output . emplace_back ( ClipperPath_to_Slic3rPolygon ( path ));
} else {
ClipperLib :: Clipper clipper ;
unscaleClipperPolygons ( holes );
clipper . AddPaths ( contours , ClipperLib :: ptSubject , true );
clipper . AddPaths ( holes , ClipperLib :: ptClip , true );
ClipperLib :: PolyTree polytree ;
clipper . Execute ( ClipperLib :: ctDifference , polytree , ClipperLib :: pftNonZero , ClipperLib :: pftNonZero );
output = PolyTreeToExPolygons ( polytree );
}
return output ;
}
2019-04-03 10:17:57 +02:00
}