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>
2015-07-03 20:58:29 +00:00
namespace Slic3r {
2021-03-08 12:44:00 +00:00
static ExtrusionPaths thick_polyline_to_extrusion_paths ( const ThickPolyline & thick_polyline , ExtrusionRole role , const Flow & flow , const float tolerance )
2019-09-11 09:37:48 +00:00
{
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.
2021-03-08 12:44:00 +00:00
Flow new_flow = flow . with_width ( unscale < float > ( w ) + flow . height ( ) * float ( 1. - 0.25 * PI ) ) ;
2019-09-11 09:37:48 +00:00
# ifdef SLIC3R_DEBUG
printf ( " filling %f gap \n " , flow . width ) ;
# endif
2021-03-08 12:44:00 +00:00
path . mm3_per_mm = new_flow . mm3_per_mm ( ) ;
path . width = new_flow . width ( ) ;
path . height = new_flow . height ( ) ;
2019-09-11 09:37:48 +00:00
} else {
2021-03-08 12:44:00 +00:00
thickness_delta = fabs ( scale_ ( flow . width ( ) ) - w ) ;
2019-09-11 09:37:48 +00:00
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 ;
}
2021-03-08 12:44:00 +00:00
static void variable_width ( const ThickPolylines & polylines , ExtrusionRole role , const 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.
2021-02-10 15:02:32 +00:00
Polygon polygon ;
2019-09-11 09:37:48 +00:00
// Is it a contour or a hole?
// Contours are CCW oriented, holes are CW oriented.
2021-02-10 15:02:32 +00:00
bool is_contour ;
2019-09-11 09:37:48 +00:00
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
2021-02-10 15:02:32 +00:00
unsigned short depth ;
// Should this contur be fuzzyfied on path generation?
bool fuzzify ;
2019-09-11 09:37:48 +00:00
// Children contour, may be both CCW and CW oriented (outer contours or holes).
std : : vector < PerimeterGeneratorLoop > children ;
2021-02-10 15:02:32 +00:00
PerimeterGeneratorLoop ( const Polygon & polygon , unsigned short depth , bool is_contour , bool fuzzify ) :
polygon ( polygon ) , is_contour ( is_contour ) , depth ( depth ) , fuzzify ( fuzzify ) { }
2019-09-11 09:37:48 +00:00
// 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 ;
} ;
2021-02-10 15:02:32 +00:00
// Thanks Cura developers for this function.
static void fuzzy_polygon ( Polygon & poly , double fuzzy_skin_thickness , double fuzzy_skin_point_dist )
{
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 ) ;
}
using PerimeterGeneratorLoops = std : : vector < PerimeterGeneratorLoop > ;
2019-09-11 09:37:48 +00:00
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
2021-02-10 15:02:32 +00:00
ExtrusionEntityCollection coll ;
Polygon fuzzified ;
2019-09-11 09:37:48 +00:00
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 ;
2021-02-10 15:02:32 +00:00
const Polygon & polygon = loop . fuzzify ? fuzzified : loop . polygon ;
if ( loop . fuzzify ) {
fuzzified = loop . polygon ;
fuzzy_polygon ( fuzzified , scaled < float > ( perimeter_generator . config - > fuzzy_skin_thickness . value ) , scaled < float > ( perimeter_generator . config - > fuzzy_skin_point_dist . value ) ) ;
}
2021-02-24 08:22:31 +00:00
if ( perimeter_generator . config - > overhangs & & perimeter_generator . layer_id > perimeter_generator . object_config - > raft_layers
2021-02-23 14:31:08 +00:00
& & ! ( ( perimeter_generator . object_config - > support_material | | perimeter_generator . object_config - > support_material_enforce_layers > 0 ) & &
perimeter_generator . object_config - > support_material_contact_distance . value = = 0 ) ) {
2019-09-11 09:37:48 +00:00
// get non-overhang paths by intersecting this loop with the grown lower slices
extrusion_paths_append (
paths ,
2021-02-10 15:02:32 +00:00
intersection_pl ( { polygon } , perimeter_generator . lower_slices_polygons ( ) ) ,
2019-09-11 09:37:48 +00:00
role ,
2021-03-08 12:44:00 +00:00
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 ( ) ,
2019-09-11 09:37:48 +00:00
( float ) perimeter_generator . layer_height ) ;
// get overhang paths by checking what parts of this loop fall
2021-01-14 12:00:03 +00:00
// outside the grown lower slices (thus where the distance between
2019-09-11 09:37:48 +00:00
// the loop centerline and original lower slices is >= half nozzle diameter
extrusion_paths_append (
paths ,
2021-02-10 15:02:32 +00:00
diff_pl ( { polygon } , perimeter_generator . lower_slices_polygons ( ) ) ,
2019-09-11 09:37:48 +00:00
erOverhangPerimeter ,
perimeter_generator . mm3_per_mm_overhang ( ) ,
2021-03-08 12:44:00 +00:00
perimeter_generator . overhang_flow . width ( ) ,
perimeter_generator . overhang_flow . height ( ) ) ;
2019-09-11 09:37:48 +00:00
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 ) ;
2021-02-10 15:02:32 +00:00
path . polyline = polygon . split_at_first_point ( ) ;
2021-03-08 12:44:00 +00:00
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 ( ) ;
2019-09-11 09:37:48 +00:00
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
}
2018-05-18 07:52:09 +00:00
void PerimeterGenerator : : process ( )
2015-07-03 20:58:29 +00:00
{
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 ( ) ;
2021-03-09 11:30:31 +00:00
coord_t ext_perimeter_spacing2 = scaled < coord_t > ( 0.5f * ( this - > ext_perimeter_flow . spacing ( ) + this - > perimeter_flow . spacing ( ) ) ) ;
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 ) ) ;
2021-02-10 16:28:56 +00:00
bool has_gap_fill = this - > config - > gap_fill_enabled . value & & this - > config - > gap_fill_speed . value > 0 ;
2019-09-10 17:03:37 +00:00
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
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)
2021-03-08 12:44:00 +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
2021-05-05 10:16:40 +00:00
diff_ex ( last , offset ( offsets , float ( ext_perimeter_width / 2. ) + ClipperSafetyOffset ) ) ,
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-02-10 15:02:32 +00:00
{
const bool fuzzify_contours = this - > config - > fuzzy_skin ! = FuzzySkinType : : None & & i = = 0 & & this - > layer_id > 0 ;
const bool fuzzify_holes = fuzzify_contours & & this - > config - > fuzzy_skin = = FuzzySkinType : : All ;
for ( const ExPolygon & expolygon : offsets ) {
// 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
// compensate for the depth of intersection.
2021-02-10 15:02:32 +00:00
contours [ i ] . emplace_back ( expolygon . contour , i , true , fuzzify_contours ) ;
2021-01-22 16:51:26 +00:00
2021-02-10 15:02:32 +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 ( hole , i , false , fuzzify_holes ) ;
}
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 | |
2021-02-03 14:12:53 +00:00
( this - > layer_id = = 0 & & this - > object_config - > brim_width . value > 0 ) )
2018-05-18 07:52:09 +00:00
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. ) ) ,
2021-05-05 10:16:40 +00:00
offset2_ex ( gaps , - float ( max / 2. ) , float ( max / 2. + ClipperSafetyOffset ) ) ) ;
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.
2021-05-03 09:39:53 +00:00
last = diff_ex ( 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
}
}