2015-07-03 20:58:29 +00:00
# include "PerimeterGenerator.hpp"
2015-07-23 13:53:02 +00:00
# include "ClipperUtils.hpp"
# include "ExtrusionEntityCollection.hpp"
2019-09-26 07:44:38 +00:00
# include "ShortestPath.hpp"
2016-03-19 14:33:58 +00:00
# include <cmath>
2016-03-20 00:50:27 +00:00
# include <cassert>
2021-01-22 16:51:26 +00:00
# include <chrono>
2015-07-03 20:58:29 +00:00
namespace Slic3r {
2019-09-11 09:37:48 +00:00
static ExtrusionPaths thick_polyline_to_extrusion_paths ( const ThickPolyline & thick_polyline , ExtrusionRole role , Flow & flow , const float tolerance )
{
ExtrusionPaths paths ;
ExtrusionPath path ( role ) ;
ThickLines lines = thick_polyline . thicklines ( ) ;
for ( int i = 0 ; i < ( int ) lines . size ( ) ; + + i ) {
const ThickLine & line = lines [ i ] ;
const coordf_t line_len = line . length ( ) ;
if ( line_len < SCALED_EPSILON ) continue ;
double thickness_delta = fabs ( line . a_width - line . b_width ) ;
if ( thickness_delta > tolerance ) {
const unsigned int segments = ( unsigned int ) ceil ( thickness_delta / tolerance ) ;
const coordf_t seg_len = line_len / segments ;
Points pp ;
std : : vector < coordf_t > width ;
{
pp . push_back ( line . a ) ;
width . push_back ( line . a_width ) ;
for ( size_t j = 1 ; j < segments ; + + j ) {
pp . push_back ( ( line . a . cast < double > ( ) + ( line . b - line . a ) . cast < double > ( ) . normalized ( ) * ( j * seg_len ) ) . cast < coord_t > ( ) ) ;
coordf_t w = line . a_width + ( j * seg_len ) * ( line . b_width - line . a_width ) / line_len ;
width . push_back ( w ) ;
width . push_back ( w ) ;
}
pp . push_back ( line . b ) ;
width . push_back ( line . b_width ) ;
assert ( pp . size ( ) = = segments + 1u ) ;
assert ( width . size ( ) = = segments * 2 ) ;
}
// delete this line and insert new ones
lines . erase ( lines . begin ( ) + i ) ;
for ( size_t j = 0 ; j < segments ; + + j ) {
ThickLine new_line ( pp [ j ] , pp [ j + 1 ] ) ;
new_line . a_width = width [ 2 * j ] ;
new_line . b_width = width [ 2 * j + 1 ] ;
lines . insert ( lines . begin ( ) + i + j , new_line ) ;
}
- - i ;
continue ;
}
const double w = fmax ( line . a_width , line . b_width ) ;
if ( path . polyline . points . empty ( ) ) {
path . polyline . append ( line . a ) ;
path . polyline . append ( line . b ) ;
// Convert from spacing to extrusion width based on the extrusion model
// of a square extrusion ended with semi circles.
flow . width = unscale < float > ( w ) + flow . height * float ( 1. - 0.25 * PI ) ;
# ifdef SLIC3R_DEBUG
printf ( " filling %f gap \n " , flow . width ) ;
# endif
path . mm3_per_mm = flow . mm3_per_mm ( ) ;
path . width = flow . width ;
path . height = flow . height ;
} else {
thickness_delta = fabs ( scale_ ( flow . width ) - w ) ;
if ( thickness_delta < = tolerance ) {
// the width difference between this line and the current flow width is
// within the accepted tolerance
path . polyline . append ( line . b ) ;
} else {
// we need to initialize a new line
paths . emplace_back ( std : : move ( path ) ) ;
path = ExtrusionPath ( role ) ;
- - i ;
}
}
}
if ( path . polyline . is_valid ( ) )
paths . emplace_back ( std : : move ( path ) ) ;
return paths ;
}
2019-09-26 07:44:38 +00:00
static void variable_width ( const ThickPolylines & polylines , ExtrusionRole role , Flow flow , std : : vector < ExtrusionEntity * > & out )
2019-09-11 09:37:48 +00:00
{
// This value determines granularity of adaptive width, as G-code does not allow
// variable extrusion within a single move; this value shall only affect the amount
// of segments, and any pruning shall be performed before we apply this tolerance.
const float tolerance = float ( scale_ ( 0.05 ) ) ;
for ( const ThickPolyline & p : polylines ) {
ExtrusionPaths paths = thick_polyline_to_extrusion_paths ( p , role , flow , tolerance ) ;
// Append paths to collection.
if ( ! paths . empty ( ) ) {
if ( paths . front ( ) . first_point ( ) = = paths . back ( ) . last_point ( ) )
2019-09-26 07:44:38 +00:00
out . emplace_back ( new ExtrusionLoop ( std : : move ( paths ) ) ) ;
else {
for ( ExtrusionPath & path : paths )
out . emplace_back ( new ExtrusionPath ( std : : move ( path ) ) ) ;
}
2019-09-11 09:37:48 +00:00
}
}
}
// Hierarchy of perimeters.
class PerimeterGeneratorLoop {
public :
// Polygon of this contour.
Polygon polygon ;
// Is it a contour or a hole?
// Contours are CCW oriented, holes are CW oriented.
bool is_contour ;
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
unsigned short depth ;
// Children contour, may be both CCW and CW oriented (outer contours or holes).
std : : vector < PerimeterGeneratorLoop > children ;
PerimeterGeneratorLoop ( Polygon polygon , unsigned short depth , bool is_contour ) :
polygon ( polygon ) , is_contour ( is_contour ) , depth ( depth ) { }
// External perimeter. It may be CCW or CW oriented (outer contour or hole contour).
bool is_external ( ) const { return this - > depth = = 0 ; }
// An island, which may have holes, but it does not have another internal island.
bool is_internal_contour ( ) const ;
} ;
typedef std : : vector < PerimeterGeneratorLoop > PerimeterGeneratorLoops ;
static ExtrusionEntityCollection traverse_loops ( const PerimeterGenerator & perimeter_generator , const PerimeterGeneratorLoops & loops , ThickPolylines & thin_walls )
{
// loops is an arrayref of ::Loop objects
// turn each one into an ExtrusionLoop object
ExtrusionEntityCollection coll ;
for ( const PerimeterGeneratorLoop & loop : loops ) {
bool is_external = loop . is_external ( ) ;
ExtrusionRole role ;
ExtrusionLoopRole loop_role ;
role = is_external ? erExternalPerimeter : erPerimeter ;
if ( loop . is_internal_contour ( ) ) {
// Note that we set loop role to ContourInternalPerimeter
// also when loop is both internal and external (i.e.
// there's only one contour loop).
loop_role = elrContourInternalPerimeter ;
} else {
loop_role = elrDefault ;
}
// detect overhanging/bridging perimeters
ExtrusionPaths paths ;
if ( perimeter_generator . config - > overhangs & & perimeter_generator . layer_id > 0
& & ! ( perimeter_generator . object_config - > support_material & & perimeter_generator . object_config - > support_material_contact_distance . value = = 0 ) ) {
// get non-overhang paths by intersecting this loop with the grown lower slices
extrusion_paths_append (
paths ,
2020-11-24 15:00:46 +00:00
intersection_pl ( ( Polygons ) loop . polygon , perimeter_generator . lower_slices_polygons ( ) ) ,
2019-09-11 09:37:48 +00:00
role ,
is_external ? perimeter_generator . ext_mm3_per_mm ( ) : perimeter_generator . mm3_per_mm ( ) ,
is_external ? perimeter_generator . ext_perimeter_flow . width : perimeter_generator . perimeter_flow . width ,
( float ) perimeter_generator . layer_height ) ;
// get overhang paths by checking what parts of this loop fall
// outside the grown lower slices (thus where the distance between
// the loop centerline and original lower slices is >= half nozzle diameter
extrusion_paths_append (
paths ,
2020-11-24 15:00:46 +00:00
diff_pl ( ( Polygons ) loop . polygon , perimeter_generator . lower_slices_polygons ( ) ) ,
2019-09-11 09:37:48 +00:00
erOverhangPerimeter ,
perimeter_generator . mm3_per_mm_overhang ( ) ,
perimeter_generator . overhang_flow . width ,
perimeter_generator . overhang_flow . height ) ;
2019-09-27 16:17:21 +00:00
// Reapply the nearest point search for starting point.
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
chain_and_reorder_extrusion_paths ( paths , & paths . front ( ) . first_point ( ) ) ;
2019-09-11 09:37:48 +00:00
} else {
ExtrusionPath path ( role ) ;
path . polyline = loop . polygon . split_at_first_point ( ) ;
path . mm3_per_mm = is_external ? perimeter_generator . ext_mm3_per_mm ( ) : perimeter_generator . mm3_per_mm ( ) ;
path . width = is_external ? perimeter_generator . ext_perimeter_flow . width : perimeter_generator . perimeter_flow . width ;
path . height = ( float ) perimeter_generator . layer_height ;
paths . push_back ( path ) ;
}
2019-09-26 07:44:38 +00:00
coll . append ( ExtrusionLoop ( std : : move ( paths ) , loop_role ) ) ;
2019-09-11 09:37:48 +00:00
}
2019-09-11 11:25:50 +00:00
// Append thin walls to the nearest-neighbor search (only for first iteration)
if ( ! thin_walls . empty ( ) ) {
2019-09-26 07:44:38 +00:00
variable_width ( thin_walls , erExternalPerimeter , perimeter_generator . ext_perimeter_flow , coll . entities ) ;
2019-09-11 09:37:48 +00:00
thin_walls . clear ( ) ;
}
2019-09-26 07:44:38 +00:00
// Traverse children and build the final collection.
Point zero_point ( 0 , 0 ) ;
std : : vector < std : : pair < size_t , bool > > chain = chain_extrusion_entities ( coll . entities , & zero_point ) ;
ExtrusionEntityCollection out ;
for ( const std : : pair < size_t , bool > & idx : chain ) {
assert ( coll . entities [ idx . first ] ! = nullptr ) ;
if ( idx . first > = loops . size ( ) ) {
// This is a thin wall.
out . entities . reserve ( out . entities . size ( ) + 1 ) ;
out . entities . emplace_back ( coll . entities [ idx . first ] ) ;
coll . entities [ idx . first ] = nullptr ;
if ( idx . second )
out . entities . back ( ) - > reverse ( ) ;
2019-09-11 09:37:48 +00:00
} else {
2019-09-26 07:44:38 +00:00
const PerimeterGeneratorLoop & loop = loops [ idx . first ] ;
assert ( thin_walls . empty ( ) ) ;
2019-09-11 09:37:48 +00:00
ExtrusionEntityCollection children = traverse_loops ( perimeter_generator , loop . children , thin_walls ) ;
2019-09-26 07:44:38 +00:00
out . entities . reserve ( out . entities . size ( ) + children . entities . size ( ) + 1 ) ;
ExtrusionLoop * eloop = static_cast < ExtrusionLoop * > ( coll . entities [ idx . first ] ) ;
coll . entities [ idx . first ] = nullptr ;
2019-09-11 09:37:48 +00:00
if ( loop . is_contour ) {
2019-09-26 07:44:38 +00:00
eloop - > make_counter_clockwise ( ) ;
out . append ( std : : move ( children . entities ) ) ;
out . entities . emplace_back ( eloop ) ;
2019-09-11 09:37:48 +00:00
} else {
2019-09-26 07:44:38 +00:00
eloop - > make_clockwise ( ) ;
out . entities . emplace_back ( eloop ) ;
out . append ( std : : move ( children . entities ) ) ;
2019-09-11 09:37:48 +00:00
}
}
}
2019-09-26 07:44:38 +00:00
return out ;
2019-09-11 09:37:48 +00:00
}
2021-01-27 08:36:39 +00:00
/*
2021-01-22 16:51:26 +00:00
enum class FuzzyShape {
Triangle ,
Sawtooth ,
Random
} ;
2021-01-27 08:36:39 +00:00
*/
2021-01-22 16:51:26 +00:00
2021-01-27 08:36:39 +00:00
static void fuzzy_polygon ( Polygon & poly , /* FuzzyShape shape, */ double fuzzy_skin_thickness , double fuzzy_skin_point_dist )
2021-01-22 16:51:26 +00:00
{
#if 0
Point last = poly . points . at ( poly . points . size ( ) - 1 ) ;
Point last_processed = last ;
double max_length = scale_ ( 2 ) ;
double min_length = scale_ ( 1 ) ;
if ( poly . length ( ) < scale_ ( 5 ) )
return ;
deepness * = 3 ;
bool triangle_or_sawtooth = shape = = FuzzyShape : : Sawtooth ;
double length_sum = 0 ;
Points : : iterator it = poly . points . begin ( ) ;
while ( it ! = poly . points . end ( ) ) {
Point & pt = * it ;
Line line ( last , pt ) ;
double length = line . length ( ) ;
// split long line
if ( length > max_length ) {
auto parts = int ( ceil ( length / max_length ) ) ;
if ( parts = = 2 ) {
Point point_to_insert ( line . midpoint ( ) ) ;
it = poly . points . insert ( it , point_to_insert ) ;
}
else {
Vector part_vector = line . vector ( ) / parts ;
Points points_to_insert ;
Point point_to_insert ( last ) ;
while ( - - parts ) {
point_to_insert + = part_vector ;
Point point_to_insert_2 ( point_to_insert ) ;
points_to_insert . push_back ( point_to_insert_2 ) ;
}
it = poly . points . insert ( it , points_to_insert . begin ( ) , points_to_insert . end ( ) ) ;
}
continue ;
}
length_sum + = length ;
// join short lines
if ( length_sum < min_length ) {
last = pt ;
it = poly . points . erase ( it ) ;
continue ;
}
line = Line ( last_processed , pt ) ;
last = pt ;
last_processed = pt ;
if ( shape = = FuzzyShape : : Random ) {
triangle_or_sawtooth = ! ( rand ( ) % 2 ) ;
}
Point point_to_insert ( triangle_or_sawtooth ? pt : line . midpoint ( ) ) ;
int scale = ( rand ( ) % deepness ) + 1 ;
Vec2d normal = line . normal ( ) . cast < double > ( ) ;
normal / = line . length ( ) / scale_ ( 1. ) / ( ( double ) scale / 20. ) ;
it = poly . points . insert ( it , point_to_insert + normal . cast < coord_t > ( ) ) + 2 ;
length_sum = 0 ;
}
# else
const double min_dist_between_points = fuzzy_skin_point_dist * 3. / 4. ; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value
const double range_random_point_dist = fuzzy_skin_point_dist / 2. ;
double dist_left_over = double ( rand ( ) ) * ( min_dist_between_points / 2 ) / double ( RAND_MAX ) ; // the distance to be traversed on the line before making the first new point
Point * p0 = & poly . points . back ( ) ;
Points out ;
out . reserve ( poly . points . size ( ) ) ;
for ( Point & p1 : poly . points )
{ // 'a' is the (next) new point between p0 and p1
Vec2d p0p1 = ( p1 - * p0 ) . cast < double > ( ) ;
double p0p1_size = p0p1 . norm ( ) ;
// so that p0p1_size - dist_last_point evaulates to dist_left_over - p0p1_size
double dist_last_point = dist_left_over + p0p1_size * 2. ;
for ( double p0pa_dist = dist_left_over ; p0pa_dist < p0p1_size ;
p0pa_dist + = min_dist_between_points + double ( rand ( ) ) * range_random_point_dist / double ( RAND_MAX ) )
{
double r = double ( rand ( ) ) * ( fuzzy_skin_thickness * 2. ) / double ( RAND_MAX ) - fuzzy_skin_thickness ;
out . emplace_back ( * p0 + ( p0p1 * ( p0pa_dist / p0p1_size ) + perp ( p0p1 ) . cast < double > ( ) . normalized ( ) * r ) . cast < coord_t > ( ) ) ;
dist_last_point = p0pa_dist ;
}
dist_left_over = p0p1_size - dist_last_point ;
p0 = & p1 ;
}
while ( out . size ( ) < 3 ) {
size_t point_idx = poly . size ( ) - 2 ;
out . emplace_back ( poly [ point_idx ] ) ;
if ( point_idx = = 0 )
break ;
- - point_idx ;
}
if ( out . size ( ) > = 3 )
poly . points = std : : move ( out ) ;
# endif
}
2018-05-18 07:52:09 +00:00
void PerimeterGenerator : : process ( )
2015-07-03 20:58:29 +00:00
{
2021-01-22 16:51:26 +00:00
// nasty hack! initialize random generator
auto time_us = std : : chrono : : duration_cast < std : : chrono : : microseconds > ( std : : chrono : : time_point_cast < std : : chrono : : microseconds > ( std : : chrono : : high_resolution_clock : : now ( ) ) . time_since_epoch ( ) ) . count ( ) ;
srand ( this - > layer_id * time_us ) ;
2015-07-06 23:17:31 +00:00
// other perimeters
2019-09-11 09:37:48 +00:00
m_mm3_per_mm = this - > perimeter_flow . mm3_per_mm ( ) ;
2017-07-19 13:42:49 +00:00
coord_t perimeter_width = this - > perimeter_flow . scaled_width ( ) ;
coord_t perimeter_spacing = this - > perimeter_flow . scaled_spacing ( ) ;
2015-07-06 23:17:31 +00:00
// external perimeters
2019-09-11 09:37:48 +00:00
m_ext_mm3_per_mm = this - > ext_perimeter_flow . mm3_per_mm ( ) ;
2017-07-19 13:42:49 +00:00
coord_t ext_perimeter_width = this - > ext_perimeter_flow . scaled_width ( ) ;
coord_t ext_perimeter_spacing = this - > ext_perimeter_flow . scaled_spacing ( ) ;
coord_t ext_perimeter_spacing2 = this - > ext_perimeter_flow . scaled_spacing ( this - > perimeter_flow ) ;
2015-07-06 23:17:31 +00:00
// overhang perimeters
2019-09-11 09:37:48 +00:00
m_mm3_per_mm_overhang = this - > overhang_flow . mm3_per_mm ( ) ;
2015-07-06 23:17:31 +00:00
// solid infill
2017-07-19 13:42:49 +00:00
coord_t solid_infill_spacing = this - > solid_infill_flow . scaled_spacing ( ) ;
2015-07-06 23:17:31 +00:00
// Calculate the minimum required spacing between two adjacent traces.
// This should be equal to the nominal flow spacing but we experiment
// with some tolerance in order to avoid triggering medial axis when
// some squishing might work. Loops are still spaced by the entire
// flow spacing; this only applies to collapsing parts.
2017-07-19 13:42:49 +00:00
// For ext_min_spacing we use the ext_perimeter_spacing calculated for two adjacent
// external loops (which is the correct way) instead of using ext_perimeter_spacing2
2015-12-19 15:46:56 +00:00
// which is the spacing between external and internal, which is not correct
// and would make the collapsing (thus the details resolution) dependent on
// internal flow which is unrelated.
2019-09-11 09:37:48 +00:00
coord_t min_spacing = coord_t ( perimeter_spacing * ( 1 - INSET_OVERLAP_TOLERANCE ) ) ;
coord_t ext_min_spacing = coord_t ( ext_perimeter_spacing * ( 1 - INSET_OVERLAP_TOLERANCE ) ) ;
2019-09-10 17:03:37 +00:00
bool has_gap_fill = this - > config - > gap_fill_speed . value > 0 ;
2015-07-06 23:17:31 +00:00
// prepare grown lower layer slices for overhang detection
if ( this - > lower_slices ! = NULL & & this - > config - > overhangs ) {
// We consider overhang any part where the entire nozzle diameter is not supported by the
// lower layer, so we take lower slices and offset them by half the nozzle diameter used
// in the current layer
double nozzle_diameter = this - > print_config - > nozzle_diameter . get_at ( this - > config - > perimeter_extruder - 1 ) ;
2019-09-11 09:37:48 +00:00
m_lower_slices_polygons = offset ( * this - > lower_slices , float ( scale_ ( + nozzle_diameter / 2 ) ) ) ;
2015-07-06 23:17:31 +00:00
}
2020-12-09 13:07:22 +00:00
2021-01-22 16:51:26 +00:00
// fuzzy skin configuration
double fuzzy_skin_thickness ;
double fuzzy_skin_point_dist ;
2021-01-27 08:36:39 +00:00
//FuzzyShape fuzzy_skin_shape;
2021-01-22 16:51:26 +00:00
if ( this - > object_config - > fuzzy_skin_perimeter_mode ! = FuzzySkinPerimeterMode : : None ) {
2021-01-27 08:36:39 +00:00
/*
2021-01-22 16:51:26 +00:00
switch ( this - > object_config - > fuzzy_skin_shape ) {
case FuzzySkinShape : : Triangle1 :
case FuzzySkinShape : : Triangle2 :
case FuzzySkinShape : : Triangle3 :
fuzzy_skin_shape = FuzzyShape : : Triangle ;
break ;
case FuzzySkinShape : : Sawtooth1 :
case FuzzySkinShape : : Sawtooth2 :
case FuzzySkinShape : : Sawtooth3 :
fuzzy_skin_shape = FuzzyShape : : Sawtooth ;
break ;
case FuzzySkinShape : : Random1 :
case FuzzySkinShape : : Random2 :
case FuzzySkinShape : : Random3 :
fuzzy_skin_shape = FuzzyShape : : Random ;
break ;
}
2021-01-27 08:36:39 +00:00
*/
2021-01-22 16:51:26 +00:00
fuzzy_skin_thickness = scale_ ( this - > object_config - > fuzzy_skin_thickness ) ;
fuzzy_skin_point_dist = scale_ ( this - > object_config - > fuzzy_skin_point_dist ) ;
}
2015-07-06 23:17:31 +00:00
// we need to process each island separately because we might have different
// extra perimeters for each one
2017-07-19 14:06:29 +00:00
for ( const Surface & surface : this - > slices - > surfaces ) {
2015-07-06 23:17:31 +00:00
// detect how many perimeters must be generated for this island
2018-05-18 07:52:09 +00:00
int loop_number = this - > config - > perimeters + surface . extra_perimeters - 1 ; // 0-indexed loops
ExPolygons last = union_ex ( surface . expolygon . simplify_p ( SCALED_RESOLUTION ) ) ;
ExPolygons gaps ;
if ( loop_number > = 0 ) {
// In case no perimeters are to be generated, loop_number will equal to -1.
2015-07-23 13:53:02 +00:00
std : : vector < PerimeterGeneratorLoops > contours ( loop_number + 1 ) ; // depth => loops
std : : vector < PerimeterGeneratorLoops > holes ( loop_number + 1 ) ; // depth => loops
2016-03-19 18:40:11 +00:00
ThickPolylines thin_walls ;
2015-07-06 23:17:31 +00:00
// we loop one time more than needed in order to find gaps after the last perimeter was applied
2018-05-18 07:52:09 +00:00
for ( int i = 0 ; ; + + i ) { // outer loop is 0
// Calculate next onion shell of perimeters.
ExPolygons offsets ;
2015-07-06 23:17:31 +00:00
if ( i = = 0 ) {
// the minimum thickness of a single loop is:
// ext_width/2 + ext_spacing/2 + spacing/2 + width/2
2018-05-18 07:52:09 +00:00
offsets = this - > config - > thin_walls ?
offset2_ex (
2015-07-23 13:53:02 +00:00
last ,
2019-09-11 09:37:48 +00:00
- float ( ext_perimeter_width / 2. + ext_min_spacing / 2. - 1 ) ,
+ float ( ext_min_spacing / 2. - 1 ) ) :
offset_ex ( last , - float ( ext_perimeter_width / 2. ) ) ;
2015-07-06 23:17:31 +00:00
// look for thin walls
if ( this - > config - > thin_walls ) {
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
// (actually, something larger than that still may exist due to mitering or other causes)
2019-09-11 09:37:48 +00:00
coord_t min_width = coord_t ( scale_ ( this - > ext_perimeter_flow . nozzle_diameter / 3 ) ) ;
2018-05-18 07:52:09 +00:00
ExPolygons expp = offset2_ex (
// medial axis requires non-overlapping geometry
diff_ex ( to_polygons ( last ) ,
2019-09-11 09:37:48 +00:00
offset ( offsets , float ( ext_perimeter_width / 2. ) ) ,
2018-05-18 07:52:09 +00:00
true ) ,
2019-09-11 09:37:48 +00:00
- float ( min_width / 2. ) , float ( min_width / 2. ) ) ;
2015-07-06 23:17:31 +00:00
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
2018-05-18 07:52:09 +00:00
for ( ExPolygon & ex : expp )
ex . medial_axis ( ext_perimeter_width + ext_perimeter_spacing2 , min_width , & thin_walls ) ;
2015-07-06 23:17:31 +00:00
}
2020-12-09 13:07:22 +00:00
if ( m_spiral_vase & & offsets . size ( ) > 1 ) {
2020-02-08 20:36:29 +00:00
// Remove all but the largest area polygon.
keep_largest_contour_only ( offsets ) ;
}
2015-07-06 23:17:31 +00:00
} else {
2016-09-14 09:22:41 +00:00
//FIXME Is this offset correct if the line width of the inner perimeters differs
// from the line width of the infill?
2017-07-19 13:42:49 +00:00
coord_t distance = ( i = = 1 ) ? ext_perimeter_spacing2 : perimeter_spacing ;
2018-05-18 07:52:09 +00:00
offsets = this - > config - > thin_walls ?
2016-11-12 18:04:40 +00:00
// This path will ensure, that the perimeters do not overfill, as in
// prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters
// excessively, creating gaps, which then need to be filled in by the not very
// reliable gap fill algorithm.
// Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than
// the original.
2018-05-18 07:52:09 +00:00
offset2_ex ( last ,
2019-09-11 09:37:48 +00:00
- float ( distance + min_spacing / 2. - 1. ) ,
float ( min_spacing / 2. - 1. ) ) :
2016-11-12 18:04:40 +00:00
// If "detect thin walls" is not enabled, this paths will be entered, which
// leads to overflows, as in prusa3d/Slic3r GH #32
2019-09-11 09:37:48 +00:00
offset_ex ( last , - float ( distance ) ) ;
2015-07-06 23:17:31 +00:00
// look for gaps
2019-09-10 17:03:37 +00:00
if ( has_gap_fill )
2015-07-06 23:17:31 +00:00
// not using safety offset here would "detect" very narrow gaps
// (but still long enough to escape the area threshold) that gap fill
// won't be able to fill but we'd still remove from infill area
2018-05-18 07:52:09 +00:00
append ( gaps , diff_ex (
2019-09-11 09:37:48 +00:00
offset ( last , - float ( 0.5 * distance ) ) ,
offset ( offsets , float ( 0.5 * distance + 10 ) ) ) ) ; // safety offset
2015-07-06 23:17:31 +00:00
}
2018-05-18 07:52:09 +00:00
if ( offsets . empty ( ) ) {
// Store the number of loops actually generated.
loop_number = i - 1 ;
// No region left to be filled in.
last . clear ( ) ;
break ;
} else if ( i > loop_number ) {
// If i > loop_number, we were looking just for gaps.
break ;
}
2021-01-22 16:51:26 +00:00
for ( ExPolygon & expolygon : offsets ) {
2019-09-11 09:37:48 +00:00
// Outer contour may overlap with an inner contour,
// inner contour may overlap with another inner contour,
// outer contour may overlap with itself.
//FIXME evaluate the overlaps, annotate each point with an overlap depth,
2021-01-22 16:51:26 +00:00
bool skip_polygon = false ;
if ( this - > object_config - > fuzzy_skin_perimeter_mode ! = FuzzySkinPerimeterMode : : None ) {
if ( i = = 0 & & ( this - > object_config - > fuzzy_skin_perimeter_mode ! = FuzzySkinPerimeterMode : : ExternalSkipFirst | | this - > layer_id > 0 ) ) {
if (
this - > object_config - > fuzzy_skin_perimeter_mode = = FuzzySkinPerimeterMode : : External | |
this - > object_config - > fuzzy_skin_perimeter_mode = = FuzzySkinPerimeterMode : : ExternalSkipFirst
) {
ExPolygon expolygon_fuzzy ( expolygon ) ;
2021-01-27 08:36:39 +00:00
fuzzy_polygon ( expolygon_fuzzy . contour , /* fuzzy_skin_shape, */ fuzzy_skin_thickness , fuzzy_skin_point_dist ) ;
2021-01-22 16:51:26 +00:00
// compensate for the depth of intersection.
contours [ i ] . emplace_back ( PerimeterGeneratorLoop ( expolygon_fuzzy . contour , i , true ) ) ;
skip_polygon = true ;
} else
2021-01-27 08:36:39 +00:00
fuzzy_polygon ( expolygon . contour , /* fuzzy_skin_shape, */ fuzzy_skin_thickness , fuzzy_skin_point_dist ) ;
2021-01-22 16:51:26 +00:00
}
}
if ( ! skip_polygon ) {
// compensate for the depth of intersection.
contours [ i ] . emplace_back ( PerimeterGeneratorLoop ( expolygon . contour , i , true ) ) ;
}
2018-05-18 07:52:09 +00:00
if ( ! expolygon . holes . empty ( ) ) {
holes [ i ] . reserve ( holes [ i ] . size ( ) + expolygon . holes . size ( ) ) ;
for ( const Polygon & hole : expolygon . holes )
holes [ i ] . emplace_back ( PerimeterGeneratorLoop ( hole , i , false ) ) ;
2015-07-06 23:17:31 +00:00
}
}
2018-05-18 07:52:09 +00:00
last = std : : move ( offsets ) ;
2019-09-10 17:03:37 +00:00
if ( i = = loop_number & & ( ! has_gap_fill | | this - > config - > fill_density . value = = 0 ) ) {
// The last run of this loop is executed to collect gaps for gap fill.
// As the gap fill is either disabled or not
break ;
}
2015-07-06 23:17:31 +00:00
}
2018-05-18 07:52:09 +00:00
2015-07-06 23:17:31 +00:00
// nest loops: holes first
2018-05-18 07:52:09 +00:00
for ( int d = 0 ; d < = loop_number ; + + d ) {
2015-07-06 23:17:31 +00:00
PerimeterGeneratorLoops & holes_d = holes [ d ] ;
// loop through all holes having depth == d
2018-05-18 07:52:09 +00:00
for ( int i = 0 ; i < ( int ) holes_d . size ( ) ; + + i ) {
2015-07-06 23:17:31 +00:00
const PerimeterGeneratorLoop & loop = holes_d [ i ] ;
// find the hole loop that contains this one, if any
2018-05-18 07:52:09 +00:00
for ( int t = d + 1 ; t < = loop_number ; + + t ) {
for ( int j = 0 ; j < ( int ) holes [ t ] . size ( ) ; + + j ) {
2015-07-06 23:17:31 +00:00
PerimeterGeneratorLoop & candidate_parent = holes [ t ] [ j ] ;
if ( candidate_parent . polygon . contains ( loop . polygon . first_point ( ) ) ) {
2015-07-23 13:53:02 +00:00
candidate_parent . children . push_back ( loop ) ;
2015-07-06 23:17:31 +00:00
holes_d . erase ( holes_d . begin ( ) + i ) ;
2018-05-18 07:52:09 +00:00
- - i ;
2015-07-23 13:53:02 +00:00
goto NEXT_LOOP ;
2015-07-06 23:17:31 +00:00
}
}
}
// if no hole contains this hole, find the contour loop that contains it
2018-05-18 07:52:09 +00:00
for ( int t = loop_number ; t > = 0 ; - - t ) {
for ( int j = 0 ; j < ( int ) contours [ t ] . size ( ) ; + + j ) {
2015-07-06 23:17:31 +00:00
PerimeterGeneratorLoop & candidate_parent = contours [ t ] [ j ] ;
if ( candidate_parent . polygon . contains ( loop . polygon . first_point ( ) ) ) {
2015-07-23 13:53:02 +00:00
candidate_parent . children . push_back ( loop ) ;
2015-07-06 23:17:31 +00:00
holes_d . erase ( holes_d . begin ( ) + i ) ;
2018-05-18 07:52:09 +00:00
- - i ;
2015-07-23 13:53:02 +00:00
goto NEXT_LOOP ;
2015-07-06 23:17:31 +00:00
}
}
}
2015-07-23 13:53:02 +00:00
NEXT_LOOP : ;
2015-07-06 23:17:31 +00:00
}
}
// nest contour loops
2018-05-18 07:52:09 +00:00
for ( int d = loop_number ; d > = 1 ; - - d ) {
2015-07-06 23:17:31 +00:00
PerimeterGeneratorLoops & contours_d = contours [ d ] ;
// loop through all contours having depth == d
2018-05-18 07:52:09 +00:00
for ( int i = 0 ; i < ( int ) contours_d . size ( ) ; + + i ) {
2015-07-06 23:17:31 +00:00
const PerimeterGeneratorLoop & loop = contours_d [ i ] ;
// find the contour loop that contains it
2018-05-18 07:52:09 +00:00
for ( int t = d - 1 ; t > = 0 ; - - t ) {
2019-06-25 11:06:04 +00:00
for ( size_t j = 0 ; j < contours [ t ] . size ( ) ; + + j ) {
2015-07-06 23:17:31 +00:00
PerimeterGeneratorLoop & candidate_parent = contours [ t ] [ j ] ;
if ( candidate_parent . polygon . contains ( loop . polygon . first_point ( ) ) ) {
2015-07-23 13:53:02 +00:00
candidate_parent . children . push_back ( loop ) ;
2015-07-06 23:17:31 +00:00
contours_d . erase ( contours_d . begin ( ) + i ) ;
2018-05-18 07:52:09 +00:00
- - i ;
2015-07-06 23:17:31 +00:00
goto NEXT_CONTOUR ;
}
}
}
2015-07-23 13:53:02 +00:00
NEXT_CONTOUR : ;
2015-07-06 23:17:31 +00:00
}
}
// at this point, all loops should be in contours[0]
2019-09-11 09:37:48 +00:00
ExtrusionEntityCollection entities = traverse_loops ( * this , contours . front ( ) , thin_walls ) ;
2015-07-06 23:17:31 +00:00
// if brim will be printed, reverse the order of perimeters so that
// we continue inwards after having finished the brim
// TODO: add test for perimeter order
2018-05-18 07:52:09 +00:00
if ( this - > config - > external_perimeters_first | |
( this - > layer_id = = 0 & & this - > print_config - > brim_width . value > 0 ) )
entities . reverse ( ) ;
2015-07-06 23:17:31 +00:00
// append perimeters for this slice as a collection
2018-05-18 07:52:09 +00:00
if ( ! entities . empty ( ) )
2015-07-06 23:17:31 +00:00
this - > loops - > append ( entities ) ;
2016-09-12 14:25:15 +00:00
} // for each loop of an island
2016-09-26 11:44:23 +00:00
2015-07-06 23:17:31 +00:00
// fill gaps
2018-05-18 07:52:09 +00:00
if ( ! gaps . empty ( ) ) {
2016-03-19 14:33:58 +00:00
// collapse
2017-07-19 13:42:49 +00:00
double min = 0.2 * perimeter_width * ( 1 - INSET_OVERLAP_TOLERANCE ) ;
double max = 2. * perimeter_spacing ;
2016-03-19 14:33:58 +00:00
ExPolygons gaps_ex = diff_ex (
2018-05-18 07:52:09 +00:00
//FIXME offset2 would be enough and cheaper.
2019-09-11 09:37:48 +00:00
offset2_ex ( gaps , - float ( min / 2. ) , float ( min / 2. ) ) ,
offset2_ex ( gaps , - float ( max / 2. ) , float ( max / 2. ) ) ,
2018-05-18 07:52:09 +00:00
true ) ;
2016-03-19 14:33:58 +00:00
ThickPolylines polylines ;
2018-05-18 07:52:09 +00:00
for ( const ExPolygon & ex : gaps_ex )
ex . medial_axis ( max , min , & polylines ) ;
if ( ! polylines . empty ( ) ) {
2019-09-26 07:44:38 +00:00
ExtrusionEntityCollection gap_fill ;
variable_width ( polylines , erGapFill , this - > solid_infill_flow , gap_fill . entities ) ;
2016-03-19 14:33:58 +00:00
/* Make sure we don't infill narrow parts that are already gap-filled
( we only consider this surface ' s gaps to reduce the diff ( ) complexity ) .
Growing actual extrusions ensures that gaps not filled by medial axis
are not subtracted from fill surfaces ( they might be too short gaps
that medial axis skips but infill might join with other infill regions
and use zigzag ) . */
2016-11-03 09:24:32 +00:00
//FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing,
// therefore it may cover the area, but no the volume.
2018-05-18 07:52:09 +00:00
last = diff_ex ( to_polygons ( last ) , gap_fill . polygons_covered_by_width ( 10.f ) ) ;
2019-09-26 07:44:38 +00:00
this - > gap_fill - > append ( std : : move ( gap_fill . entities ) ) ;
}
2015-07-06 23:17:31 +00:00
}
2016-09-26 11:44:23 +00:00
2015-07-06 23:17:31 +00:00
// create one more offset to be used as boundary for fill
// we offset by half the perimeter spacing (to get to the actual infill boundary)
// and then we offset back and forth by half the infill spacing to only consider the
// non-collapsing regions
2018-05-18 07:52:09 +00:00
coord_t inset =
( loop_number < 0 ) ? 0 :
( loop_number = = 0 ) ?
// one loop
ext_perimeter_spacing / 2 :
// two or more loops?
perimeter_spacing / 2 ;
2015-07-06 23:17:31 +00:00
// only apply infill overlap if we actually have one perimeter
if ( inset > 0 )
2019-09-11 09:37:48 +00:00
inset - = coord_t ( scale_ ( this - > config - > get_abs_value ( " infill_overlap " , unscale < double > ( inset + solid_infill_spacing / 2 ) ) ) ) ;
2017-04-05 07:51:03 +00:00
// simplify infill contours according to resolution
Polygons pp ;
2018-05-18 07:52:09 +00:00
for ( ExPolygon & ex : last )
2017-04-05 07:51:03 +00:00
ex . simplify_p ( SCALED_RESOLUTION , & pp ) ;
// collapse too narrow infill areas
2019-09-11 09:37:48 +00:00
coord_t min_perimeter_infill_spacing = coord_t ( solid_infill_spacing * ( 1. - INSET_OVERLAP_TOLERANCE ) ) ;
2017-04-05 07:51:03 +00:00
// append infill areas to fill_surfaces
this - > fill_surfaces - > append (
offset2_ex (
2018-05-18 07:52:09 +00:00
union_ex ( pp ) ,
2019-09-11 09:37:48 +00:00
float ( - inset - min_perimeter_infill_spacing / 2. ) ,
float ( min_perimeter_infill_spacing / 2. ) ) ,
2017-04-05 07:51:03 +00:00
stInternal ) ;
2016-09-12 14:25:15 +00:00
} // for each island
2015-07-03 20:58:29 +00:00
}
2018-05-18 07:52:09 +00:00
bool PerimeterGeneratorLoop : : is_internal_contour ( ) const
2015-07-03 20:58:29 +00:00
{
2018-05-18 07:52:09 +00:00
// An internal contour is a contour containing no other contours
if ( ! this - > is_contour )
return false ;
for ( const PerimeterGeneratorLoop & loop : this - > children )
if ( loop . is_contour )
return false ;
return true ;
2015-07-03 20:58:29 +00:00
}
}