2012-04-29 12:51:20 +02:00
package Slic3r::Print::Object ;
2016-09-13 11:24:55 +02:00
# extends c++ class Slic3r::PrintObject (Print.xsp)
2014-06-10 16:01:57 +02:00
use strict ;
use warnings ;
2012-04-29 12:51:20 +02:00
2013-07-29 20:49:54 +02:00
use List::Util qw(min max sum first) ;
2013-12-31 14:33:03 +01:00
use Slic3r::Flow ':roles' ;
2016-03-13 12:24:03 +01:00
use Slic3r::Geometry qw(X Y Z PI scale unscale chained_path epsilon) ;
2013-07-29 20:49:54 +02:00
use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex
2015-02-15 21:58:14 +01:00
offset offset_ex offset2 offset2_ex intersection_ppl CLIPPER_OFFSET_SCALE JT_MITER) ;
2013-12-19 18:54:24 +01:00
use Slic3r::Print::State ':steps' ;
2012-05-19 16:04:33 +02:00
use Slic3r::Surface ':types' ;
2012-04-29 12:51:20 +02:00
2016-09-26 13:56:24 +02:00
# If enabled, phases of prepare_infill will be written into SVG files to an "out" directory.
our $SLIC3R_DEBUG_SLICE_PROCESSING = 0 ;
2013-12-15 16:17:12 +01:00
2014-05-06 11:07:18 +03:00
sub region_volumes {
my $self = shift ;
return [ map $self -> get_region_volumes ( $_ ), 0 .. ( $self -> region_count - 1 ) ];
}
sub layers {
my $self = shift ;
return [ map $self -> get_layer ( $_ ), 0 .. ( $self -> layer_count - 1 ) ];
}
sub support_layers {
my $self = shift ;
return [ map $self -> get_support_layer ( $_ ), 0 .. ( $self -> support_layer_count - 1 ) ];
}
2016-09-13 11:24:55 +02:00
# 1) Decides Z positions of the layers,
# 2) Initializes layers and their regions
# 3) Slices the object meshes
# 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes
# 5) Applies size compensation (offsets the slices in XY plane)
# 6) Replaces bad slices by the slices reconstructed from the upper/lower layer
# Resulting expolygons of layer regions are marked as Internal.
#
2013-12-15 16:17:12 +01:00
# this should be idempotent
2012-04-30 14:56:01 +02:00
sub slice {
my $self = shift ;
2014-06-13 20:05:18 +02:00
return if $self -> step_done ( STEP_SLICE );
$self -> set_step_started ( STEP_SLICE );
$self -> print -> status_cb -> ( 10 , "Processing triangulated mesh" );
2012-04-30 14:56:01 +02:00
2013-12-15 16:17:12 +01:00
# init layers
{
2014-05-06 11:07:18 +03:00
$self -> clear_layers ;
2013-12-15 16:17:12 +01:00
# make layers taking custom heights into account
2015-06-02 19:44:29 +02:00
my $id = 0 ;
my $print_z = 0 ;
my $first_object_layer_height = - 1 ;
2014-06-11 19:58:11 +02:00
my $first_object_layer_distance = - 1 ;
2013-12-15 16:17:12 +01:00
# add raft layers
if ( $self -> config -> raft_layers > 0 ) {
2016-10-16 16:30:56 +02:00
# Reserve object layers for the raft. Last layer of the raft is the contact layer.
2014-01-11 23:26:48 +01:00
$id += $self -> config -> raft_layers ;
2016-10-16 16:30:56 +02:00
# Raise first object layer Z by the thickness of the raft itself
# plus the extra distance required by the support material logic.
#FIXME The last raft layer is the contact layer, which shall be printed with a bridging flow for ease of separation. Currently it is not the case.
2015-01-19 09:52:24 +01:00
my $first_layer_height = $self -> config -> get_value ( 'first_layer_height' );
$print_z += $first_layer_height ;
2015-03-09 19:36:23 +01:00
2016-10-16 16:30:56 +02:00
# Use as large as possible layer height for the intermediate raft layers.
2015-03-09 19:36:23 +01:00
my $support_material_layer_height ;
{
my @nozzle_diameters = (
2015-03-09 20:00:55 +01:00
map $self -> print -> config -> get_at ( 'nozzle_diameter' , $_ ),
2015-06-02 19:44:29 +02:00
$self -> config -> support_material_extruder - 1 ,
$self -> config -> support_material_interface_extruder - 1 ,
2015-03-09 19:36:23 +01:00
);
$support_material_layer_height = 0.75 * min ( @nozzle_diameters );
}
$print_z += $support_material_layer_height * ( $self -> config -> raft_layers - 1 );
2014-01-11 23:26:48 +01:00
2015-03-09 19:27:57 +01:00
# compute the average of all nozzles used for printing the object
2016-10-16 16:30:56 +02:00
#FIXME It is expected, that the 1st layer of the object is printed with a bridging flow over a full raft. Shall it not be vice versa?
2015-03-09 19:27:57 +01:00
my $nozzle_diameter ;
{
my @nozzle_diameters = (
map $self -> print -> config -> get_at ( 'nozzle_diameter' , $_ ), @ { $self -> print -> object_extruders }
);
$nozzle_diameter = sum ( @nozzle_diameters ) / @nozzle_diameters ;
}
2015-06-02 19:44:29 +02:00
$first_object_layer_distance = $self -> _support_material -> contact_distance ( $self -> config -> layer_height , $nozzle_diameter );
2014-01-11 23:26:48 +01:00
# force first layer print_z according to the contact distance
# (the loop below will raise print_z by such height)
2015-06-02 19:44:29 +02:00
$first_object_layer_height = $first_object_layer_distance - $self -> config -> support_material_contact_distance ;
2013-12-15 16:17:12 +01:00
}
# loop until we have at least one layer and the max slice_z reaches the object height
2015-06-02 19:44:29 +02:00
my $slice_z = 0 ;
my $height = 0 ;
my $max_z = unscale ( $self -> size -> z );
2014-01-11 23:26:48 +01:00
while (( $slice_z - $height ) <= $max_z ) {
2013-12-15 16:17:12 +01:00
# assign the default height to the layer according to the general settings
$height = ( $id == 0 )
2013-12-24 11:29:31 +01:00
? $self -> config -> get_value ( 'first_layer_height' )
: $self -> config -> layer_height ;
2013-12-15 16:17:12 +01:00
# look for an applicable custom range
if ( my $range = first { $_ -> [ 0 ] <= $slice_z && $_ -> [ 1 ] > $slice_z } @ { $self -> layer_height_ranges }) {
$height = $range -> [ 2 ];
# if user set custom height to zero we should just skip the range and resume slicing over it
if ( $height == 0 ) {
$slice_z += $range -> [ 1 ] - $range -> [ 0 ];
next ;
}
}
2014-01-11 23:26:48 +01:00
if ( $first_object_layer_height != - 1 && ! @ { $self -> layers }) {
$height = $first_object_layer_height ;
2014-06-11 19:58:11 +02:00
$print_z += ( $first_object_layer_distance - $height );
2014-01-11 23:26:48 +01:00
}
2013-12-15 16:17:12 +01:00
$print_z += $height ;
$slice_z += $height / 2 ;
### Slic3r::debugf "Layer %d: height = %s; slice_z = %s; print_z = %s\n", $id, $height, $slice_z, $print_z;
2014-05-06 11:07:18 +03:00
$self -> add_layer ( $id , $height , $print_z , $slice_z );
2014-06-13 18:45:44 +03:00
if ( $self -> layer_count >= 2 ) {
my $lc = $self -> layer_count ;
$self -> get_layer ( $lc - 2 ) -> set_upper_layer ( $self -> get_layer ( $lc - 1 ));
$self -> get_layer ( $lc - 1 ) -> set_lower_layer ( $self -> get_layer ( $lc - 2 ));
2013-12-15 16:17:12 +01:00
}
$id ++ ;
$slice_z += $height / 2 ; # add the other half layer
}
}
2013-06-22 20:37:15 +02:00
# make sure all layers contain layer region objects for all regions
2014-08-03 18:41:09 +02:00
my $regions_count = $self -> print -> region_count ;
2013-06-22 20:37:15 +02:00
foreach my $layer ( @ { $self -> layers }) {
$layer -> region ( $_ ) for 0 .. ( $regions_count - 1 );
}
2014-01-07 15:40:38 +01:00
# get array of Z coordinates for slicing
my @z = map $_ -> slice_z , @ { $self -> layers };
# slice all non-modifier volumes
2014-05-06 11:07:18 +03:00
for my $region_id ( 0 .. ( $self -> region_count - 1 )) {
2014-01-07 15:40:38 +01:00
my $expolygons_by_layer = $self -> _slice_region ( $region_id , \ @z , 0 );
for my $layer_id ( 0 .. $#$expolygons_by_layer ) {
2014-06-13 18:45:44 +03:00
my $layerm = $self -> get_layer ( $layer_id ) -> regions -> [ $region_id ];
2014-01-07 15:40:38 +01:00
$layerm -> slices -> clear ;
foreach my $expolygon ( @ { $expolygons_by_layer -> [ $layer_id ] }) {
$layerm -> slices -> append ( Slic3r::Surface -> new (
expolygon => $expolygon ,
surface_type => S_TYPE_INTERNAL ,
));
2013-12-18 00:13:41 +01:00
}
}
2014-01-07 15:40:38 +01:00
}
# then slice all modifier volumes
2014-05-06 11:07:18 +03:00
if ( $self -> region_count > 1 ) {
for my $region_id ( 0 .. $self -> region_count ) {
2014-01-07 15:40:38 +01:00
my $expolygons_by_layer = $self -> _slice_region ( $region_id , \ @z , 1 );
# loop through the other regions and 'steal' the slices belonging to this one
2014-05-06 11:07:18 +03:00
for my $other_region_id ( 0 .. $self -> region_count ) {
2014-01-07 15:40:38 +01:00
next if $other_region_id == $region_id ;
for my $layer_id ( 0 .. $#$expolygons_by_layer ) {
2014-06-13 18:45:44 +03:00
my $layerm = $self -> get_layer ( $layer_id ) -> regions -> [ $region_id ];
my $other_layerm = $self -> get_layer ( $layer_id ) -> regions -> [ $other_region_id ];
2014-07-12 10:38:19 +02:00
next if ! defined $other_layerm ;
2014-01-07 15:40:38 +01:00
my $other_slices = [ map $_ -> p , @ { $other_layerm -> slices } ]; # Polygons
my $my_parts = intersection_ex (
$other_slices ,
[ map @$_ , @ { $expolygons_by_layer -> [ $layer_id ] } ],
);
next if ! @$my_parts ;
# append new parts to our region
foreach my $expolygon ( @$my_parts ) {
$layerm -> slices -> append ( Slic3r::Surface -> new (
expolygon => $expolygon ,
surface_type => S_TYPE_INTERNAL ,
));
}
# remove such parts from original region
$other_layerm -> slices -> clear ;
2014-03-23 16:56:41 +01:00
$other_layerm -> slices -> append ( Slic3r::Surface -> new (
expolygon => $_ ,
surface_type => S_TYPE_INTERNAL ,
)) for @ { diff_ex ( $other_slices , [ map @$_ , @$my_parts ]) };
2014-01-07 15:40:38 +01:00
}
2012-04-30 14:56:01 +02:00
}
2013-09-09 18:21:10 +02:00
}
2012-04-30 14:56:01 +02:00
}
2013-03-24 15:26:55 +01:00
# remove last layer(s) if empty
2014-05-06 11:07:18 +03:00
$self -> delete_layer ( $self -> layer_count - 1 )
2014-06-13 18:45:44 +03:00
while $self -> layer_count && ( ! map @ { $_ -> slices }, @ { $self -> get_layer ( $self -> layer_count - 1 ) -> regions });
2012-04-30 14:56:01 +02:00
foreach my $layer ( @ { $self -> layers }) {
2014-06-10 13:28:57 +02:00
# apply size compensation
if ( $self -> config -> xy_size_compensation != 0 ) {
my $delta = scale ( $self -> config -> xy_size_compensation );
if ( @ { $layer -> regions } == 1 ) {
# single region
my $layerm = $layer -> regions -> [ 0 ];
my $slices = [ map $_ -> p , @ { $layerm -> slices } ];
$layerm -> slices -> clear ;
$layerm -> slices -> append ( Slic3r::Surface -> new (
expolygon => $_ ,
surface_type => S_TYPE_INTERNAL ,
)) for @ { offset_ex ( $slices , $delta )};
} else {
if ( $delta < 0 ) {
# multiple regions, shrinking
# we apply the offset to the combined shape, then intersect it
# with the original slices for each region
my $slices = union ([ map $_ -> p , map @ { $_ -> slices }, @ { $layer -> regions } ]);
$slices = offset ( $slices , $delta );
foreach my $layerm ( @ { $layer -> regions }) {
my $this_slices = intersection_ex (
$slices ,
[ map $_ -> p , @ { $layerm -> slices } ],
);
$layerm -> slices -> clear ;
$layerm -> slices -> append ( Slic3r::Surface -> new (
expolygon => $_ ,
surface_type => S_TYPE_INTERNAL ,
)) for @$this_slices ;
}
} else {
# multiple regions, growing
# this is an ambiguous case, since it's not clear how to grow regions where they are going to overlap
# so we give priority to the first one and so on
for my $i ( 0 .. $# { $layer -> regions }) {
my $layerm = $layer -> regions -> [ $i ];
my $slices = offset_ex ([ map $_ -> p , @ { $layerm -> slices } ], $delta );
if ( $i > 0 ) {
$slices = diff_ex (
[ map @$_ , @$slices ],
[ map $_ -> p , map @ { $_ -> slices }, map $layer -> regions -> [ $_ ], 0 .. ( $i - 1 ) ], # slices of already processed regions
);
}
$layerm -> slices -> clear ;
$layerm -> slices -> append ( Slic3r::Surface -> new (
expolygon => $_ ,
surface_type => S_TYPE_INTERNAL ,
)) for @$slices ;
}
}
}
}
2012-09-23 02:52:31 +02:00
# merge all regions' slices to get islands
2012-09-22 19:04:36 +02:00
$layer -> make_slices ;
2012-04-30 14:56:01 +02:00
}
# detect slicing errors
my $warning_thrown = 0 ;
2014-05-06 11:07:18 +03:00
for my $i ( 0 .. ( $self -> layer_count - 1 )) {
2014-06-13 18:45:44 +03:00
my $layer = $self -> get_layer ( $i );
2012-04-30 14:56:01 +02:00
next unless $layer -> slicing_errors ;
if ( ! $warning_thrown ) {
warn "The model has overlapping or self-intersecting facets. I tried to repair it, "
. "however you might want to check the results or repair the input file and retry.\n" ;
$warning_thrown = 1 ;
}
# try to repair the layer surfaces by merging all contours and all holes from
# neighbor layers
Slic3r:: debugf "Attempting to repair layer %d\n" , $i ;
2014-05-06 11:07:18 +03:00
foreach my $region_id ( 0 .. ( $layer -> region_count - 1 )) {
2012-09-23 02:52:31 +02:00
my $layerm = $layer -> region ( $region_id );
2012-09-22 19:04:36 +02:00
my ( @upper_surfaces , @lower_surfaces );
2014-05-06 11:07:18 +03:00
for ( my $j = $i + 1 ; $j < $self -> layer_count ; $j ++ ) {
2014-06-13 18:45:44 +03:00
if ( ! $self -> get_layer ( $j ) -> slicing_errors ) {
@upper_surfaces = @ { $self -> get_layer ( $j ) -> region ( $region_id ) -> slices };
2012-09-22 19:04:36 +02:00
last ;
}
2012-04-30 14:56:01 +02:00
}
2012-09-22 19:04:36 +02:00
for ( my $j = $i - 1 ; $j >= 0 ; $j -- ) {
2014-06-13 18:45:44 +03:00
if ( ! $self -> get_layer ( $j ) -> slicing_errors ) {
@lower_surfaces = @ { $self -> get_layer ( $j ) -> region ( $region_id ) -> slices };
2012-09-22 19:04:36 +02:00
last ;
}
2012-04-30 14:56:01 +02:00
}
2012-09-22 19:04:36 +02:00
my $union = union_ex ([
map $_ -> expolygon -> contour , @upper_surfaces , @lower_surfaces ,
]);
my $diff = diff_ex (
[ map @$_ , @$union ],
2013-08-26 23:09:18 +02:00
[ map @ { $_ -> expolygon -> holes }, @upper_surfaces , @lower_surfaces , ],
2012-09-22 19:04:36 +02:00
);
2013-07-14 15:03:45 +02:00
$layerm -> slices -> clear ;
2014-11-09 16:23:50 +01:00
$layerm -> slices -> append ( $_ )
for map Slic3r::Surface -> new
2013-07-14 15:03:45 +02:00
( expolygon => $_ , surface_type => S_TYPE_INTERNAL ),
2014-11-09 16:23:50 +01:00
@$diff ;
2012-04-30 14:56:01 +02:00
}
2012-09-22 19:04:36 +02:00
2012-09-23 02:52:31 +02:00
# update layer slices after repairing the single regions
2012-09-22 19:04:36 +02:00
$layer -> make_slices ;
2012-04-30 14:56:01 +02:00
}
# remove empty layers from bottom
2014-06-13 18:45:44 +03:00
while ( @ { $self -> layers } && ! @ { $self -> get_layer ( 0 ) -> slices }) {
2015-11-04 19:19:45 +01:00
$self -> delete_layer ( 0 );
2014-05-25 23:17:00 +02:00
for ( my $i = 0 ; $i <= $# { $self -> layers }; $i ++ ) {
2015-01-18 12:35:05 +01:00
$self -> get_layer ( $i ) -> set_id ( $self -> get_layer ( $i ) -> id - 1 );
2012-04-30 14:56:01 +02:00
}
}
2013-12-19 15:23:10 +01:00
# simplify slices if required
2014-01-02 10:44:54 +01:00
if ( $self -> print -> config -> resolution ) {
$self -> _simplify_slices ( scale ( $self -> print -> config -> resolution ));
2013-12-19 15:23:10 +01:00
}
2014-06-13 20:05:18 +02:00
2015-11-04 19:27:58 +01:00
die "No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"
2014-06-13 20:05:18 +02:00
if ! @ { $self -> layers };
2014-07-15 19:07:38 +02:00
$self -> set_typed_slices ( 0 );
2014-06-13 20:05:18 +02:00
$self -> set_step_done ( STEP_SLICE );
2012-04-30 14:56:01 +02:00
}
2016-09-13 11:24:55 +02:00
# called from slice()
2014-01-07 15:40:38 +01:00
sub _slice_region {
my ( $self , $region_id , $z , $modifier ) = @_ ;
2014-05-06 11:07:18 +03:00
return [] if ! @ { $self -> get_region_volumes ( $region_id )};
2014-01-07 15:40:38 +01:00
# compose mesh
my $mesh ;
2014-05-06 11:07:18 +03:00
foreach my $volume_id ( @ { $self -> get_region_volumes ( $region_id ) }) {
2014-01-07 15:40:38 +01:00
my $volume = $self -> model_object -> volumes -> [ $volume_id ];
next if $volume -> modifier && ! $modifier ;
next if ! $volume -> modifier && $modifier ;
if ( defined $mesh ) {
$mesh -> merge ( $volume -> mesh );
} else {
$mesh = $volume -> mesh -> clone ;
}
}
2014-03-22 17:44:42 +01:00
return if ! defined $mesh ;
2014-01-07 15:40:38 +01:00
# transform mesh
# we ignore the per-instance transformations currently and only
# consider the first one
$self -> model_object -> instances -> [ 0 ] -> transform_mesh ( $mesh , 1 );
2014-12-12 22:43:04 +01:00
# align mesh to Z = 0 (it should be already aligned actually) and apply XY shift
2014-01-07 15:40:38 +01:00
$mesh -> translate (( map unscale ( - $_ ), @ { $self -> _copies_shift }), - $self -> model_object -> bounding_box -> z_min );
# perform actual slicing
return $mesh -> slice ( $z );
}
2012-05-05 16:36:10 +02:00
sub make_perimeters {
my $self = shift ;
2014-06-13 20:05:18 +02:00
# prerequisites
$self -> slice ;
return if $self -> step_done ( STEP_PERIMETERS );
$self -> set_step_started ( STEP_PERIMETERS );
$self -> print -> status_cb -> ( 20 , "Generating perimeters" );
2014-07-15 19:07:38 +02:00
# merge slices if they were split into types
if ( $self -> typed_slices ) {
$_ -> merge_slices for @ { $self -> layers };
$self -> set_typed_slices ( 0 );
$self -> invalidate_step ( STEP_PREPARE_INFILL );
}
2012-05-05 16:36:10 +02:00
# compare each layer to the one below, and mark those slices needing
# one additional inner perimeter, like the top of domed objects-
2013-02-18 11:37:34 +01:00
# this algorithm makes sure that at least one perimeter is overlapping
# but we don't generate any extra perimeter if fill density is zero, as they would be floating
# inside the object - infill_only_where_needed should be the method of choice for printing
# hollow objects
2014-08-03 18:41:09 +02:00
for my $region_id ( 0 .. ( $self -> print -> region_count - 1 )) {
2014-01-02 10:44:54 +01:00
my $region = $self -> print -> regions -> [ $region_id ];
my $region_perimeters = $region -> config -> perimeters ;
2015-02-15 21:58:14 +01:00
next if ! $region -> config -> extra_perimeters ;
next if $region_perimeters == 0 ;
next if $region -> config -> fill_density == 0 ;
for my $i ( 0 .. ( $self -> layer_count - 2 )) {
my $layerm = $self -> get_layer ( $i ) -> get_region ( $region_id );
my $upper_layerm = $self -> get_layer ( $i + 1 ) -> get_region ( $region_id );
2016-06-01 20:58:05 +02:00
my $upper_layerm_polygons = [ map $_ -> p , @ { $upper_layerm -> slices } ];
# Filter upper layer polygons in intersection_ppl by their bounding boxes?
# my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ];
my $total_loop_length = sum ( map $_ -> length , @$upper_layerm_polygons ) // 0 ;
2015-02-15 21:58:14 +01:00
my $perimeter_spacing = $layerm -> flow ( FLOW_ROLE_PERIMETER ) -> scaled_spacing ;
my $ext_perimeter_flow = $layerm -> flow ( FLOW_ROLE_EXTERNAL_PERIMETER );
my $ext_perimeter_width = $ext_perimeter_flow -> scaled_width ;
my $ext_perimeter_spacing = $ext_perimeter_flow -> scaled_spacing ;
foreach my $slice ( @ { $layerm -> slices }) {
while ( 1 ) {
# compute the total thickness of perimeters
my $perimeters_thickness = $ext_perimeter_width /2 + $ext_perimeter_spacing/ 2
+ ( $region_perimeters - 1 + $slice -> extra_perimeters ) * $perimeter_spacing ;
# define a critical area where we don't want the upper slice to fall into
# (it should either lay over our perimeters or outside this area)
my $critical_area_depth = $perimeter_spacing * 1.5 ;
my $critical_area = diff (
offset ( $slice -> expolygon -> arrayref , - $perimeters_thickness ),
offset ( $slice -> expolygon -> arrayref , - ( $perimeters_thickness + $critical_area_depth )),
);
# check whether a portion of the upper slices falls inside the critical area
my $intersection = intersection_ppl (
2016-06-01 20:58:05 +02:00
$upper_layerm_polygons ,
2015-02-15 21:58:14 +01:00
$critical_area ,
);
2015-02-24 00:34:43 +01:00
# only add an additional loop if at least 30% of the slice loop would benefit from it
my $total_intersection_length = sum ( map $_ -> length , @$intersection ) // 0 ;
last unless $total_intersection_length > $total_loop_length * 0.3 ;
if ( 0 ) {
require "Slic3r/SVG.pm" ;
Slic3r::SVG:: output (
"extra.svg" ,
no_arrows => 1 ,
expolygons => union_ex ( $critical_area ),
polylines => [ map $_ -> split_at_first_point , map $_ -> p , @ { $upper_layerm -> slices } ],
);
}
2015-02-15 21:58:14 +01:00
$slice -> extra_perimeters ( $slice -> extra_perimeters + 1 );
2012-05-05 16:36:10 +02:00
}
2015-02-15 21:58:14 +01:00
Slic3r:: debugf " adding %d more perimeter(s) at layer %d\n" ,
2015-11-01 19:03:11 +01:00
$slice -> extra_perimeters , $layerm -> layer -> id
2015-02-15 21:58:14 +01:00
if $slice -> extra_perimeters > 0 ;
2012-05-05 16:36:10 +02:00
}
}
}
2013-01-31 15:44:55 +01:00
Slic3r:: parallelize (
2014-01-02 10:44:54 +01:00
threads => $self -> print -> config -> threads ,
2014-05-06 11:07:18 +03:00
items => sub { 0 .. ( $self -> layer_count - 1 ) },
2013-01-31 15:44:55 +01:00
thread_cb => sub {
my $q = shift ;
2014-01-11 17:40:09 +01:00
while ( defined ( my $i = $q -> dequeue )) {
2014-06-13 18:45:44 +03:00
$self -> get_layer ( $i ) -> make_perimeters ;
2013-01-31 15:44:55 +01:00
}
},
no_threads_cb => sub {
$_ -> make_perimeters for @ { $self -> layers };
},
);
2013-12-19 15:23:10 +01:00
# simplify slices (both layer and region slices),
# we only need the max resolution for perimeters
### This makes this method not-idempotent, so we keep it disabled for now.
###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION);
2014-06-13 20:05:18 +02:00
$self -> set_step_done ( STEP_PERIMETERS );
}
sub prepare_infill {
my ( $self ) = @_ ;
# prerequisites
$self -> make_perimeters ;
return if $self -> step_done ( STEP_PREPARE_INFILL );
$self -> set_step_started ( STEP_PREPARE_INFILL );
$self -> print -> status_cb -> ( 30 , "Preparing infill" );
# this will assign a type (top/bottom/internal) to $layerm->slices
# and transform $layerm->fill_surfaces from expolygon
# to typed top/bottom/internal surfaces;
$self -> detect_surfaces_type ;
2016-09-26 13:56:24 +02:00
# Mark the object to have the slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.)
2014-07-15 19:07:38 +02:00
$self -> set_typed_slices ( 1 );
2016-09-26 13:56:24 +02:00
# Decide what surfaces are to be filled.
# Here the S_TYPE_TOP / S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is turned to just S_TYPE_INTERNAL if zero top / bottom infill layers are configured.
# Also tiny S_TYPE_INTERNAL surfaces are turned to S_TYPE_INTERNAL_SOLID.
2014-06-13 20:05:18 +02:00
$_ -> prepare_fill_surfaces for map @ { $_ -> regions }, @ { $self -> layers };
# this will detect bridges and reverse bridges
# and rearrange top/bottom/internal surfaces
2016-09-26 13:56:24 +02:00
# It produces enlarged overlapping bridging areas.
#
# 1) S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is grown by 3mm and clipped by the total infill area. Bridges are detected. The areas may overlap.
# 2) S_TYPE_TOP is grown by 3mm and clipped by the grown bottom areas. The areas may overlap.
# 3) Clip the internal surfaces by the grown top/bottom surfaces.
# 4) Merge surfaces with the same style. This will mostly get rid of the overlaps.
#FIXME This does not likely merge surfaces, which are supported by a material with different colors, but same properties.
2014-06-13 20:05:18 +02:00
$self -> process_external_surfaces ;
2016-09-26 13:56:24 +02:00
# Add solid fills to ensure the shell vertical thickness.
$self -> discover_vertical_shells ;
# Debugging output.
if ( $SLIC3R_DEBUG_SLICE_PROCESSING ) {
for my $region_id ( 0 .. ( $self -> print -> region_count - 1 )) {
for ( my $i = 0 ; $i < $self -> layer_count ; $i ++ ) {
my $layerm = $self -> get_layer ( $i ) -> regions -> [ $region_id ];
$layerm -> export_region_slices_to_svg_debug ( "6_discover_vertical_shells-final" );
$layerm -> export_region_fill_surfaces_to_svg_debug ( "6_discover_vertical_shells-final" );
} # for each layer
} # for each region
}
# Detect, which fill surfaces are near external layers.
# They will be split in internal and internal-solid surfaces.
# The purpose is to add a configurable number of solid layers to support the TOP surfaces
# and to add a configurable number of solid layers above the BOTTOM / BOTTOMBRIDGE surfaces
# to close these surfaces reliably.
#FIXME Vojtech: Is this a good place to add supporting infills below sloping perimeters?
2014-06-13 20:05:18 +02:00
$self -> discover_horizontal_shells ;
2016-09-26 13:56:24 +02:00
if ( $SLIC3R_DEBUG_SLICE_PROCESSING ) {
# Debugging output.
for my $region_id ( 0 .. ( $self -> print -> region_count - 1 )) {
for ( my $i = 0 ; $i < $self -> layer_count ; $i ++ ) {
my $layerm = $self -> get_layer ( $i ) -> regions -> [ $region_id ];
$layerm -> export_region_slices_to_svg_debug ( "7_discover_horizontal_shells-final" );
$layerm -> export_region_fill_surfaces_to_svg_debug ( "7_discover_horizontal_shells-final" );
} # for each layer
} # for each region
}
# Only active if config->infill_only_where_needed. This step trims the sparse infill,
# so it acts as an internal support. It maintains all other infill types intact.
# Here the internal surfaces and perimeters have to be supported by the sparse infill.
#FIXME The surfaces are supported by a sparse infill, but the sparse infill is only as large as the area to support.
# Likely the sparse infill will not be anchored correctly, so it will not work as intended.
# Also one wishes the perimeters to be supported by a full infill.
2014-06-13 20:05:18 +02:00
$self -> clip_fill_surfaces ;
2016-09-26 13:56:24 +02:00
if ( $SLIC3R_DEBUG_SLICE_PROCESSING ) {
# Debugging output.
for my $region_id ( 0 .. ( $self -> print -> region_count - 1 )) {
for ( my $i = 0 ; $i < $self -> layer_count ; $i ++ ) {
my $layerm = $self -> get_layer ( $i ) -> regions -> [ $region_id ];
$layerm -> export_region_slices_to_svg_debug ( "8_clip_surfaces-final" );
$layerm -> export_region_fill_surfaces_to_svg_debug ( "8_clip_surfaces-final" );
} # for each layer
} # for each region
}
2014-06-13 20:05:18 +02:00
# the following step needs to be done before combination because it may need
# to remove only half of the combined infill
$self -> bridge_over_infill ;
# combine fill surfaces to honor the "infill every N layers" option
$self -> combine_infill ;
2016-09-26 13:56:24 +02:00
# Debugging output.
if ( $SLIC3R_DEBUG_SLICE_PROCESSING ) {
for my $region_id ( 0 .. ( $self -> print -> region_count - 1 )) {
for ( my $i = 0 ; $i < $self -> layer_count ; $i ++ ) {
my $layerm = $self -> get_layer ( $i ) -> regions -> [ $region_id ];
$layerm -> export_region_slices_to_svg_debug ( "9_prepare_infill-final" );
$layerm -> export_region_fill_surfaces_to_svg_debug ( "9_prepare_infill-final" );
} # for each layer
} # for each region
for ( my $i = 0 ; $i < $self -> layer_count ; $i ++ ) {
my $layer = $self -> get_layer ( $i );
$layer -> export_region_slices_to_svg_debug ( "9_prepare_infill-final" );
$layer -> export_region_fill_surfaces_to_svg_debug ( "9_prepare_infill-final" );
} # for each layer
}
2014-06-13 20:05:18 +02:00
$self -> set_step_done ( STEP_PREPARE_INFILL );
}
sub infill {
my ( $self ) = @_ ;
# prerequisites
$self -> prepare_infill ;
return if $self -> step_done ( STEP_INFILL );
$self -> set_step_started ( STEP_INFILL );
$self -> print -> status_cb -> ( 70 , "Infilling layers" );
Slic3r:: parallelize (
threads => $self -> print -> config -> threads ,
2016-02-20 20:50:40 +01:00
items => sub { 0 .. $# { $self -> layers } },
2014-06-13 20:05:18 +02:00
thread_cb => sub {
my $q = shift ;
2016-02-20 20:50:40 +01:00
while ( defined ( my $i = $q -> dequeue )) {
2016-11-02 10:47:00 +01:00
$self -> get_layer ( $i ) -> make_fills ;
2014-06-13 20:05:18 +02:00
}
},
no_threads_cb => sub {
2016-02-20 20:50:40 +01:00
foreach my $layer ( @ { $self -> layers }) {
2016-11-02 10:47:00 +01:00
$layer -> make_fills ;
2014-06-13 20:05:18 +02:00
}
},
);
### we could free memory now, but this would make this step not idempotent
### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers};
$self -> set_step_done ( STEP_INFILL );
}
sub generate_support_material {
my $self = shift ;
# prerequisites
$self -> slice ;
return if $self -> step_done ( STEP_SUPPORTMATERIAL );
$self -> set_step_started ( STEP_SUPPORTMATERIAL );
$self -> clear_support_layers ;
2014-06-13 20:18:34 +02:00
if (( ! $self -> config -> support_material && $self -> config -> raft_layers == 0 ) || scalar ( @ { $self -> layers }) < 2 ) {
$self -> set_step_done ( STEP_SUPPORTMATERIAL );
return ;
}
$self -> print -> status_cb -> ( 85 , "Generating support material" );
2014-06-13 20:05:18 +02:00
2015-01-19 09:52:24 +01:00
$self -> _support_material -> generate ( $self );
$self -> set_step_done ( STEP_SUPPORTMATERIAL );
}
sub _support_material {
my ( $self ) = @_ ;
2014-06-13 20:05:18 +02:00
my $first_layer_flow = Slic3r::Flow -> new_from_width (
2015-02-17 13:59:39 +08:00
width => ( $self -> print -> config -> first_layer_extrusion_width || $self -> config -> support_material_extrusion_width ),
2014-06-13 20:05:18 +02:00
role => FLOW_ROLE_SUPPORT_MATERIAL ,
nozzle_diameter => $self -> print -> config -> nozzle_diameter -> [ $self -> config -> support_material_extruder - 1 ]
// $self -> print -> config -> nozzle_diameter -> [ 0 ],
layer_height => $self -> config -> get_abs_value ( 'first_layer_height' ),
bridge_flow_ratio => 0 ,
);
2016-10-16 16:30:56 +02:00
if ( 1 ) {
# Old supports, Perl implementation.
return Slic3r::Print::SupportMaterial -> new (
print_config => $self -> print -> config ,
object_config => $self -> config ,
first_layer_flow => $first_layer_flow ,
flow => $self -> support_material_flow ,
interface_flow => $self -> support_material_flow ( FLOW_ROLE_SUPPORT_MATERIAL_INTERFACE ),
);
} else {
# New supports, C++ implementation.
2016-11-02 10:47:00 +01:00
return Slic3r::Print::SupportMaterial2 -> new ( $self );
2016-10-16 16:30:56 +02:00
}
2012-05-05 16:36:10 +02:00
}
2016-09-26 13:56:24 +02:00
# This function analyzes slices of a region (SurfaceCollection slices).
# Each slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface.
# Initially all slices are of type S_TYPE_INTERNAL.
# Slices are compared against the top / bottom slices and regions and classified to the following groups:
# S_TYPE_TOP - Part of a region, which is not covered by any upper layer. This surface will be filled with a top solid infill.
# S_TYPE_BOTTOMBRIDGE - Part of a region, which is not fully supported, but it hangs in the air, or it hangs losely on a support or a raft.
# S_TYPE_BOTTOM - Part of a region, which is not supported by the same region, but it is supported either by another region, or by a soluble interface layer.
# S_TYPE_INTERNAL - Part of a region, which is supported by the same region type.
# If a part of a region is of S_TYPE_BOTTOM and S_TYPE_TOP, the S_TYPE_BOTTOM wins.
2012-04-29 12:51:20 +02:00
sub detect_surfaces_type {
my $self = shift ;
Slic3r:: debugf "Detecting solid surfaces...\n" ;
2014-08-03 18:41:09 +02:00
for my $region_id ( 0 .. ( $self -> print -> region_count - 1 )) {
2014-05-06 11:07:18 +03:00
for my $i ( 0 .. ( $self -> layer_count - 1 )) {
2014-06-13 18:45:44 +03:00
my $layerm = $self -> get_layer ( $i ) -> regions -> [ $region_id ];
2013-09-16 12:36:04 +02:00
# prepare a reusable subroutine to make surface differences
my $difference = sub {
my ( $subject , $clip , $result_type ) = @_ ;
my $diff = diff (
[ map @$_ , @$subject ],
[ map @$_ , @$clip ],
2015-01-30 10:12:31 +01:00
1 ,
2013-09-16 12:36:04 +02:00
);
# collapse very narrow parts (using the safety offset in the diff is not enough)
2014-06-09 21:14:48 +02:00
my $offset = $layerm -> flow ( FLOW_ROLE_EXTERNAL_PERIMETER ) -> scaled_width / 10 ;
2013-09-16 12:36:04 +02:00
return map Slic3r::Surface -> new ( expolygon => $_ , surface_type => $result_type ),
@ { offset2_ex ( $diff , - $offset , + $offset ) };
};
2012-09-22 19:04:36 +02:00
2012-09-23 02:52:31 +02:00
# comparison happens against the *full* slices (considering all regions)
2014-03-25 01:11:28 +01:00
# unless internal shells are requested
2014-06-13 18:45:44 +03:00
my $upper_layer = $i < $self -> layer_count - 1 ? $self -> get_layer ( $i + 1 ) : undef ;
my $lower_layer = $i > 0 ? $self -> get_layer ( $i - 1 ) : undef ;
2012-09-22 19:04:36 +02:00
# find top surfaces (difference between current surfaces
# of current layer and upper one)
2014-03-25 01:11:28 +01:00
my @top = ();
2012-09-22 19:04:36 +02:00
if ( $upper_layer ) {
2016-09-26 13:56:24 +02:00
# Config value $self->config->interface_shells is true, if a support is separated from the object
# by a soluble material (for example a PVA plastic).
2014-03-25 01:11:28 +01:00
my $upper_slices = $self -> config -> interface_shells
? [ map $_ -> expolygon , @ { $upper_layer -> regions -> [ $region_id ] -> slices } ]
: $upper_layer -> slices ;
2013-09-07 00:42:56 +02:00
@top = $difference -> (
2013-02-26 20:52:13 +01:00
[ map $_ -> expolygon , @ { $layerm -> slices } ],
2014-03-25 01:11:28 +01:00
$upper_slices ,
2013-02-26 20:52:13 +01:00
S_TYPE_TOP ,
);
2012-09-22 19:04:36 +02:00
} else {
# if no upper layer, all surfaces of this one are solid
2013-07-15 15:26:56 +02:00
# we clone surfaces because we're going to clear the slices collection
@top = map $_ -> clone , @ { $layerm -> slices };
2012-09-22 19:04:36 +02:00
$_ -> surface_type ( S_TYPE_TOP ) for @top ;
}
# find bottom surfaces (difference between current surfaces
# of current layer and lower one)
2014-03-25 01:11:28 +01:00
my @bottom = ();
2012-09-22 19:04:36 +02:00
if ( $lower_layer ) {
2016-09-26 13:56:24 +02:00
# Any surface lying on the void is a true bottom bridge (an overhang)
2014-03-25 01:11:28 +01:00
push @bottom , $difference -> (
2013-02-26 20:52:13 +01:00
[ map $_ -> expolygon , @ { $layerm -> slices } ],
$lower_layer -> slices ,
2014-03-25 01:11:28 +01:00
S_TYPE_BOTTOMBRIDGE ,
2013-02-26 20:52:13 +01:00
);
2014-03-25 01:11:28 +01:00
2016-09-26 13:56:24 +02:00
# If we have soluble support material, don't bridge. The overhang will be squished against a soluble layer separating
# the support from the print.
2015-01-19 09:52:24 +01:00
if ( $self -> config -> support_material && $self -> config -> support_material_contact_distance == 0 ) {
$_ -> surface_type ( S_TYPE_BOTTOM ) for @bottom ;
}
2014-03-25 01:11:28 +01:00
# if user requested internal shells, we need to identify surfaces
# lying on other slices not belonging to this region
if ( $self -> config -> interface_shells ) {
# non-bridging bottom surfaces: any part of this layer lying
# on something else, excluding those lying on our own region
my $supported = intersection_ex (
[ map @ { $_ -> expolygon }, @ { $layerm -> slices } ],
[ map @$_ , @ { $lower_layer -> slices } ],
);
push @bottom , $difference -> (
$supported ,
[ map $_ -> expolygon , @ { $lower_layer -> regions -> [ $region_id ] -> slices } ],
S_TYPE_BOTTOM ,
);
}
2012-09-22 19:04:36 +02:00
} else {
# if no lower layer, all surfaces of this one are solid
2013-07-15 15:26:56 +02:00
# we clone surfaces because we're going to clear the slices collection
@bottom = map $_ -> clone , @ { $layerm -> slices };
2014-11-07 17:17:20 +01:00
# if we have raft layers, consider bottom layer as a bridge
# just like any other bottom surface lying on the void
2015-02-14 12:47:33 +01:00
if ( $self -> config -> raft_layers > 0 && $self -> config -> support_material_contact_distance > 0 ) {
2014-11-07 17:17:20 +01:00
$_ -> surface_type ( S_TYPE_BOTTOMBRIDGE ) for @bottom ;
} else {
$_ -> surface_type ( S_TYPE_BOTTOM ) for @bottom ;
}
2012-09-22 19:04:36 +02:00
}
# now, if the object contained a thin membrane, we could have overlapping bottom
# and top surfaces; let's do an intersection to discover them and consider them
# as bottom surfaces (to allow for bridge detection)
if ( @top && @bottom ) {
my $overlapping = intersection_ex ([ map $_ -> p , @top ], [ map $_ -> p , @bottom ]);
2015-11-08 09:12:59 +01:00
Slic3r:: debugf " layer %d contains %d membrane(s)\n" , $layerm -> layer -> id , scalar ( @$overlapping )
2013-09-16 12:36:04 +02:00
if $ Slic3r:: debug ;
2013-09-07 00:42:56 +02:00
@top = $difference -> ([ map $_ -> expolygon , @top ], $overlapping , S_TYPE_TOP );
2012-09-22 19:04:36 +02:00
}
# find internal surfaces (difference between top/bottom surfaces and others)
2014-03-25 01:11:28 +01:00
my @internal = $difference -> (
2013-02-26 20:52:13 +01:00
[ map $_ -> expolygon , @ { $layerm -> slices } ],
[ map $_ -> expolygon , @top , @bottom ],
S_TYPE_INTERNAL ,
);
2012-09-22 19:04:36 +02:00
# save surfaces to layer
2013-07-14 15:03:45 +02:00
$layerm -> slices -> clear ;
2014-11-09 16:23:50 +01:00
$layerm -> slices -> append ( $_ ) for ( @bottom , @top , @internal );
2012-09-22 19:04:36 +02:00
Slic3r:: debugf " layer %d has %d bottom, %d top and %d internal surfaces\n" ,
2015-11-08 09:12:59 +01:00
$layerm -> layer -> id , scalar ( @bottom ), scalar ( @top ), scalar ( @internal ) if $ Slic3r:: debug ;
2016-09-26 13:56:24 +02:00
if ( $SLIC3R_DEBUG_SLICE_PROCESSING ) {
$layerm -> export_region_slices_to_svg_debug ( "detect_surfaces_type-final" );
}
} # for each layer of a region
2012-04-29 12:51:20 +02:00
2012-09-22 19:04:36 +02:00
# clip surfaces to the fill boundaries
foreach my $layer ( @ { $self -> layers }) {
2012-09-23 02:52:31 +02:00
my $layerm = $layer -> regions -> [ $region_id ];
2014-06-11 17:51:18 +02:00
# Note: this method should be idempotent, but fill_surfaces gets modified
# in place. However we're now only using its boundaries (which are invariant)
2014-12-08 21:23:42 +01:00
# so we're safe. This guarantees idempotence of prepare_infill() also in case
# that combine_infill() turns some fill_surface into VOID surfaces.
2013-07-15 15:26:56 +02:00
my $fill_boundaries = [ map $_ -> clone -> p , @ { $layerm -> fill_surfaces } ];
2013-07-14 14:56:43 +02:00
$layerm -> fill_surfaces -> clear ;
2012-09-22 19:04:36 +02:00
foreach my $surface ( @ { $layerm -> slices }) {
my $intersection = intersection_ex (
[ $surface -> p ],
$fill_boundaries ,
);
2014-11-09 16:23:50 +01:00
$layerm -> fill_surfaces -> append ( $_ )
for map Slic3r::Surface -> new ( expolygon => $_ , surface_type => $surface -> surface_type ),
@$intersection ;
2012-09-22 19:04:36 +02:00
}
2016-09-26 13:56:24 +02:00
if ( $SLIC3R_DEBUG_SLICE_PROCESSING ) {
$layerm -> export_region_fill_surfaces_to_svg_debug ( "1_detect_surfaces_type-final" );
}
} # for each layer of a region
} # for each $self->print->region_count
2012-04-29 12:51:20 +02:00
}
2014-12-24 01:29:36 +01:00
# Idempotence of this method is guaranteed by the fact that we don't remove things from
# fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries.
2013-02-09 23:36:32 +01:00
sub clip_fill_surfaces {
my $self = shift ;
2013-08-25 14:37:50 +02:00
return unless $self -> config -> infill_only_where_needed ;
2013-02-09 23:36:32 +01:00
# We only want infill under ceilings; this is almost like an
# internal support material.
2015-02-23 00:44:51 +01:00
# proceed top-down skipping bottom layer
my $upper_internal = [] ;
for my $layer_id ( reverse 1 .. ( $self -> layer_count - 1 )) {
my $layer = $self -> get_layer ( $layer_id );
my $lower_layer = $self -> get_layer ( $layer_id - 1 );
2013-02-09 23:36:32 +01:00
2015-02-23 00:44:51 +01:00
# detect things that we need to support
my $overhangs = [] ; # Polygons
# we need to support any solid surface
push @$overhangs , map $_ -> p ,
grep $_ -> is_solid , map @ { $_ -> fill_surfaces }, @ { $layer -> regions };
# we also need to support perimeters when there's at least one full
# unsupported loop
{
# get perimeters area as the difference between slices and fill_surfaces
my $perimeters = diff (
[ map @$_ , @ { $layer -> slices } ],
[ map $_ -> p , map @ { $_ -> fill_surfaces }, @ { $layer -> regions } ],
);
# only consider the area that is not supported by lower perimeters
$perimeters = intersection (
$perimeters ,
[ map $_ -> p , map @ { $_ -> fill_surfaces }, @ { $lower_layer -> regions } ],
1 ,
);
# only consider perimeter areas that are at least one extrusion width thick
2016-09-26 13:56:24 +02:00
#FIXME Offset2 eats out from both sides, while the perimeters are create outside in.
#Should the $pw not be half of the current value?
2015-02-23 00:44:51 +01:00
my $pw = min ( map $_ -> flow ( FLOW_ROLE_PERIMETER ) -> scaled_width , @ { $layer -> regions });
$perimeters = offset2 ( $perimeters , - $pw , + $pw );
# append such thick perimeters to the areas that need support
push @$overhangs , @$perimeters ;
}
# find new internal infill
$upper_internal = my $new_internal = intersection (
[
@$overhangs ,
@$upper_internal ,
],
[
# our current internal fill boundaries
map $_ -> p ,
grep $_ -> surface_type == S_TYPE_INTERNAL || $_ -> surface_type == S_TYPE_INTERNALVOID ,
map @ { $_ -> fill_surfaces }, @ { $lower_layer -> regions }
],
);
# apply new internal infill to regions
foreach my $layerm ( @ { $lower_layer -> regions }) {
2013-09-07 00:02:58 +02:00
my ( @internal , @other ) = ();
foreach my $surface ( map $_ -> clone , @ { $layerm -> fill_surfaces }) {
2015-02-23 00:44:51 +01:00
if ( $surface -> surface_type == S_TYPE_INTERNAL || $surface -> surface_type == S_TYPE_INTERNALVOID ) {
2014-12-24 01:29:36 +01:00
push @internal , $surface ;
} else {
push @other , $surface ;
}
2013-09-07 00:02:58 +02:00
}
2015-02-23 00:44:51 +01:00
my @new = map Slic3r::Surface -> new (
2013-09-07 00:02:58 +02:00
expolygon => $_ ,
surface_type => S_TYPE_INTERNAL ,
),
2014-12-24 01:29:36 +01:00
@ { intersection_ex (
[ map $_ -> p , @internal ],
2015-02-23 00:44:51 +01:00
$new_internal ,
1 ,
2014-12-24 01:29:36 +01:00
)};
2015-02-23 00:44:51 +01:00
push @other , map Slic3r::Surface -> new (
2014-12-24 01:29:36 +01:00
expolygon => $_ ,
surface_type => S_TYPE_INTERNALVOID ,
),
@ { diff_ex (
[ map $_ -> p , @internal ],
2015-02-23 00:44:51 +01:00
$new_internal ,
1 ,
2014-12-24 01:29:36 +01:00
)};
2015-02-23 00:55:00 +01:00
# If there are voids it means that our internal infill is not adjacent to
# perimeters. In this case it would be nice to add a loop around infill to
# make it more robust and nicer. TODO.
2013-07-14 14:56:43 +02:00
$layerm -> fill_surfaces -> clear ;
2014-11-09 16:23:50 +01:00
$layerm -> fill_surfaces -> append ( $_ ) for ( @new , @other );
2016-09-26 13:56:24 +02:00
if ( $SLIC3R_DEBUG_SLICE_PROCESSING ) {
$layerm -> export_region_fill_surfaces_to_svg_debug ( "6_clip_fill_surfaces" );
}
2013-02-09 23:36:32 +01:00
}
}
}
2012-04-29 12:51:20 +02:00
sub discover_horizontal_shells {
my $self = shift ;
Slic3r:: debugf "==> DISCOVERING HORIZONTAL SHELLS\n" ;
2014-08-03 18:41:09 +02:00
for my $region_id ( 0 .. ( $self -> print -> region_count - 1 )) {
2014-05-06 11:07:18 +03:00
for ( my $i = 0 ; $i < $self -> layer_count ; $i ++ ) {
2014-06-13 18:45:44 +03:00
my $layerm = $self -> get_layer ( $i ) -> regions -> [ $region_id ];
2012-09-28 15:46:29 +02:00
2015-10-26 23:23:03 +01:00
if ( $layerm -> region -> config -> solid_infill_every_layers && $layerm -> region -> config -> fill_density > 0
&& ( $i % $ layerm -> region -> config -> solid_infill_every_layers ) == 0 ) {
2016-09-26 13:56:24 +02:00
# This is the layer to put the sparse infill in. Mark S_TYPE_INTERNAL surfaces as S_TYPE_INTERNALSOLID or S_TYPE_INTERNALBRIDGE.
# If the sparse infill is not active, the internal surfaces are of type S_TYPE_INTERNAL.
2015-12-19 12:48:48 +01:00
my $type = $layerm -> region -> config -> fill_density == 100 ? S_TYPE_INTERNALSOLID : S_TYPE_INTERNALBRIDGE ;
$_ -> surface_type ( $type ) for @ { $layerm -> fill_surfaces -> filter_by_type ( S_TYPE_INTERNAL )};
2012-09-28 15:46:29 +02:00
}
2014-03-25 01:11:28 +01:00
EXTERNAL: foreach my $type ( S_TYPE_TOP , S_TYPE_BOTTOM , S_TYPE_BOTTOMBRIDGE ) {
2012-12-21 20:25:48 +01:00
# find slices of current type for current layer
2013-08-13 09:45:33 +02:00
# use slices instead of fill_surfaces because they also include the perimeter area
# which needs to be propagated in shells; we need to grow slices like we did for
# fill_surfaces though. Using both ungrown slices and grown fill_surfaces will
# not work in some situations, as there won't be any grown region in the perimeter
# area (this was seen in a model where the top layer had one extra perimeter, thus
2013-09-17 19:24:16 +02:00
# its fill_surfaces were thinner than the lower layer's infill), however it's the best
# solution so far. Growing the external slices by EXTERNAL_INFILL_MARGIN will put
# too much solid infill inside nearly-vertical slopes.
my $solid = [
( map $_ -> p , @ { $layerm -> slices -> filter_by_type ( $type )}),
( map $_ -> p , @ { $layerm -> fill_surfaces -> filter_by_type ( $type )}),
];
2013-03-11 18:37:01 +01:00
next if ! @$solid ;
2013-07-15 12:14:22 +02:00
Slic3r:: debugf "Layer %d has %s surfaces\n" , $i , ( $type == S_TYPE_TOP ) ? 'top' : 'bottom' ;
2012-04-29 12:51:20 +02:00
2012-10-28 12:52:44 +01:00
my $solid_layers = ( $type == S_TYPE_TOP )
2015-10-26 23:23:03 +01:00
? $layerm -> region -> config -> top_solid_layers
: $layerm -> region -> config -> bottom_solid_layers ;
2013-08-08 02:10:34 +02:00
NEIGHBOR: for ( my $n = ( $type == S_TYPE_TOP ) ? $i - 1 : $i + 1 ;
2016-09-26 13:56:24 +02:00
abs ( $n - $i ) < $solid_layers ;
( $type == S_TYPE_TOP ) ? $n -- : $n ++ ) {
2012-09-22 19:04:36 +02:00
2014-05-06 11:07:18 +03:00
next if $n < 0 || $n >= $self -> layer_count ;
2012-09-22 19:04:36 +02:00
Slic3r:: debugf " looking for neighbors on layer %d...\n" , $n ;
2016-09-26 13:56:24 +02:00
# Reference to the lower layer of a TOP surface, or an upper layer of a BOTTOM surface.
2014-06-13 18:45:44 +03:00
my $neighbor_layerm = $self -> get_layer ( $n ) -> regions -> [ $region_id ];
2016-09-26 13:56:24 +02:00
# Reference to the neighbour fill surfaces.
2014-02-07 01:48:47 +01:00
my $neighbor_fill_surfaces = $neighbor_layerm -> fill_surfaces ;
2016-09-26 13:56:24 +02:00
# Clone because we will use these surfaces even after clearing the collection.
my @neighbor_fill_surfaces = map $_ -> clone , @$neighbor_fill_surfaces ;
2012-09-22 19:04:36 +02:00
# find intersection between neighbor and current layer's surfaces
# intersections have contours and holes
2013-07-27 19:41:36 +02:00
# we update $solid so that we limit the next neighbor layer to the areas that were
# found on this one - in other words, solid shells on one layer (for a given external surface)
# are always a subset of the shells found on the previous shell layer
# this approach allows for DWIM in hollow sloping vases, where we want bottom
# shells to be generated in the base but not in the walls (where there are many
# narrow bottom surfaces): reassigning $solid will consider the 'shadow' of the
# upper perimeter as an obstacle and shell will not be propagated to more upper layers
2016-09-26 13:56:24 +02:00
#FIXME How does it work for S_TYPE_INTERNALBRIDGE? This is set for sparse infill. Likely this does not work.
2013-09-06 19:21:38 +02:00
my $new_internal_solid = $solid = intersection (
$solid ,
2013-07-15 12:14:22 +02:00
[ map $_ -> p , grep { ( $_ -> surface_type == S_TYPE_INTERNAL ) || ( $_ -> surface_type == S_TYPE_INTERNALSOLID ) } @neighbor_fill_surfaces ],
2013-07-17 00:29:09 +02:00
1 ,
2012-09-22 19:04:36 +02:00
);
2013-07-28 10:56:41 +02:00
next EXTERNAL if ! @$new_internal_solid ;
2012-09-22 19:04:36 +02:00
2015-10-26 23:23:03 +01:00
if ( $layerm -> region -> config -> fill_density == 0 ) {
2014-02-07 01:48:47 +01:00
# if we're printing a hollow object we discard any solid shell thinner
# than a perimeter width, since it's probably just crossing a sloping wall
# and it's not wanted in a hollow print even if it would make sense when
# obeying the solid shell count option strictly (DWIM!)
2014-06-09 21:14:48 +02:00
my $margin = $neighbor_layerm -> flow ( FLOW_ROLE_EXTERNAL_PERIMETER ) -> scaled_width ;
2014-02-07 01:48:47 +01:00
my $too_narrow = diff (
$new_internal_solid ,
offset2 ( $new_internal_solid , - $margin , + $margin , CLIPPER_OFFSET_SCALE , JT_MITER , 5 ),
1 ,
);
$new_internal_solid = $solid = diff (
$new_internal_solid ,
$too_narrow ,
) if @$too_narrow ;
}
# make sure the new internal solid is wide enough, as it might get collapsed
# when spacing is added in Fill.pm
2016-09-26 13:56:24 +02:00
if ( 0 ) {
2014-02-07 01:48:47 +01:00
my $margin = 3 * $layerm -> flow ( FLOW_ROLE_SOLID_INFILL ) -> scaled_width ; # require at least this size
2013-09-06 17:43:40 +02:00
# we use a higher miterLimit here to handle areas with acute angles
# in those cases, the default miterLimit would cut the corner and we'd
# get a triangle in $too_narrow; if we grow it below then the shell
# would have a different shape from the external surface and we'd still
# have the same angle, so the next shell would be grown even more and so on.
2013-09-06 19:21:38 +02:00
my $too_narrow = diff (
$new_internal_solid ,
offset2 ( $new_internal_solid , - $margin , + $margin , CLIPPER_OFFSET_SCALE , JT_MITER , 5 ),
2013-03-18 17:55:16 +01:00
1 ,
2013-03-11 18:37:01 +01:00
);
if ( @$too_narrow ) {
2014-02-07 01:48:47 +01:00
# grow the collapsing parts and add the extra area to the neighbor layer
# as well as to our original surfaces so that we support this
# additional area in the next shell too
# make sure our grown surfaces don't exceed the fill area
my @grown = @ { intersection (
offset ( $too_narrow , + $margin ),
2015-07-03 23:38:41 +02:00
# Discard bridges as they are grown for anchoring and we can't
# remove such anchors. (This may happen when a bridge is being
# anchored onto a wall where little space remains after the bridge
# is grown, and that little space is an internal solid shell so
# it triggers this too_narrow logic.)
[ map $_ -> p , grep { $_ -> is_internal && ! $_ -> is_bridge } @neighbor_fill_surfaces ],
2014-02-07 01:48:47 +01:00
)};
$new_internal_solid = $solid = [ @grown , @$new_internal_solid ];
2013-03-11 18:37:01 +01:00
}
}
2012-09-22 19:04:36 +02:00
# internal-solid are the union of the existing internal-solid surfaces
# and new ones
my $internal_solid = union_ex ([
( map $_ -> p , grep $_ -> surface_type == S_TYPE_INTERNALSOLID , @neighbor_fill_surfaces ),
2013-09-06 19:21:38 +02:00
@$new_internal_solid ,
2012-09-22 19:04:36 +02:00
]);
2013-03-07 15:47:32 +01:00
# subtract intersections from layer surfaces to get resulting internal surfaces
2012-09-22 19:04:36 +02:00
my $internal = diff_ex (
[ map $_ -> p , grep $_ -> surface_type == S_TYPE_INTERNAL , @neighbor_fill_surfaces ],
[ map @$_ , @$internal_solid ],
2012-05-01 18:51:47 +02:00
1 ,
2012-04-29 12:51:20 +02:00
);
2012-09-22 19:04:36 +02:00
Slic3r:: debugf " %d internal-solid and %d internal surfaces found\n" ,
scalar ( @$internal_solid ), scalar ( @$internal );
2013-03-07 15:47:32 +01:00
# assign resulting internal surfaces to layer
2013-07-15 12:14:22 +02:00
$neighbor_fill_surfaces -> clear ;
2014-11-09 16:23:50 +01:00
$neighbor_fill_surfaces -> append ( $_ )
for map Slic3r::Surface -> new ( expolygon => $_ , surface_type => S_TYPE_INTERNAL ),
@$internal ;
2012-09-22 19:04:36 +02:00
# assign new internal-solid surfaces to layer
2014-11-09 16:23:50 +01:00
$neighbor_fill_surfaces -> append ( $_ )
for map Slic3r::Surface -> new ( expolygon => $_ , surface_type => S_TYPE_INTERNALSOLID ),
@$internal_solid ;
2012-09-22 19:04:36 +02:00
# assign top and bottom surfaces to layer
2014-03-25 01:11:28 +01:00
foreach my $s ( @ { Slic3r::Surface::Collection -> new ( grep { ( $_ -> surface_type == S_TYPE_TOP ) || $_ -> is_bottom } @neighbor_fill_surfaces ) -> group }) {
2012-09-22 19:04:36 +02:00
my $solid_surfaces = diff_ex (
[ map $_ -> p , @$s ],
[ map @$_ , @$internal_solid , @$internal ],
1 ,
);
2014-11-09 16:23:50 +01:00
$neighbor_fill_surfaces -> append ( $_ )
for map $s -> [ 0 ] -> clone ( expolygon => $_ ), @$solid_surfaces ;
2012-09-22 19:04:36 +02:00
}
2012-04-29 12:51:20 +02:00
}
2016-09-26 13:56:24 +02:00
} # foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM, S_TYPE_BOTTOMBRIDGE)
} # for each layer
} # for each region
# Debugging output.
if ( $SLIC3R_DEBUG_SLICE_PROCESSING ) {
for my $region_id ( 0 .. ( $self -> print -> region_count - 1 )) {
for ( my $i = 0 ; $i < $self -> layer_count ; $i ++ ) {
my $layerm = $self -> get_layer ( $i ) -> regions -> [ $region_id ];
$layerm -> export_region_slices_to_svg_debug ( "5_discover_horizontal_shells" );
$layerm -> export_region_fill_surfaces_to_svg_debug ( "5_discover_horizontal_shells" );
} # for each layer
} # for each region
2012-04-29 12:51:20 +02:00
}
}
2016-09-26 13:56:24 +02:00
# combine fill surfaces across layers to honor the "infill every N layers" option
2014-12-08 21:23:42 +01:00
# Idempotence of this method is guaranteed by the fact that we don't remove things from
# fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries.
2012-07-22 20:48:38 +02:00
sub combine_infill {
2012-04-29 12:51:20 +02:00
my $self = shift ;
2014-01-02 10:44:54 +01:00
2014-12-08 21:23:42 +01:00
# define the type used for voids
my %voidtype = (
& S_TYPE_INTERNAL () => S_TYPE_INTERNALVOID ,
);
2014-12-08 20:14:04 +01:00
# work on each region separately
2014-08-03 18:41:09 +02:00
for my $region_id ( 0 .. ( $self -> print -> region_count - 1 )) {
2014-12-08 21:23:42 +01:00
my $region = $self -> print -> get_region ( $region_id );
2014-01-02 10:44:54 +01:00
my $every = $region -> config -> infill_every_layers ;
2014-12-08 20:14:04 +01:00
next unless $every > 1 && $region -> config -> fill_density > 0 ;
2014-01-02 10:44:54 +01:00
2013-02-22 16:24:24 +01:00
# limit the number of combined layers to the maximum height allowed by this regions' nozzle
2014-12-17 00:34:00 +01:00
my $nozzle_diameter = min (
$self -> print -> config -> get_at ( 'nozzle_diameter' , $region -> config -> infill_extruder - 1 ),
$self -> print -> config -> get_at ( 'nozzle_diameter' , $region -> config -> solid_infill_extruder - 1 ),
);
2013-03-10 12:08:18 +01:00
# define the combinations
2014-12-08 20:14:04 +01:00
my %combine = (); # layer_idx => number of additional combined lower layers
2013-03-10 12:08:18 +01:00
{
my $current_height = my $layers = 0 ;
2014-12-08 20:14:04 +01:00
for my $layer_idx ( 0 .. ( $self -> layer_count - 1 )) {
my $layer = $self -> get_layer ( $layer_idx );
next if $layer -> id == 0 ; # skip first print layer (which may not be first layer in array because of raft)
my $height = $layer -> height ;
2013-03-10 12:08:18 +01:00
2014-12-08 20:14:04 +01:00
# check whether the combination of this layer with the lower layers' buffer
# would exceed max layer height or max combined layer count
2016-03-13 12:24:03 +01:00
if ( $current_height + $height >= $nozzle_diameter + epsilon || $layers >= $every ) {
2014-12-08 20:14:04 +01:00
# append combination to lower layer
$combine { $layer_idx - 1 } = $layers ;
2013-03-10 12:08:18 +01:00
$current_height = $layers = 0 ;
}
$current_height += $height ;
$layers ++ ;
}
2014-12-08 20:14:04 +01:00
# append lower layers (if any) to uppermost layer
$combine { $self -> layer_count - 1 } = $layers ;
2013-03-10 12:08:18 +01:00
}
2013-02-22 16:24:24 +01:00
2014-12-08 20:14:04 +01:00
# loop through layers to which we have assigned layers to combine
for my $layer_idx ( sort keys %combine ) {
next unless $combine { $layer_idx } > 1 ;
# get all the LayerRegion objects to be combined
2014-12-08 21:23:42 +01:00
my @layerms = map $self -> get_layer ( $_ ) -> get_region ( $region_id ),
2014-12-08 20:14:04 +01:00
( $layer_idx - ( $combine { $layer_idx } - 1 ) .. $layer_idx );
2012-04-29 12:51:20 +02:00
2013-06-23 18:21:47 +02:00
# only combine internal infill
for my $type ( S_TYPE_INTERNAL ) {
2013-02-10 12:40:43 +01:00
# we need to perform a multi-layer intersection, so let's split it in pairs
2012-04-29 12:51:20 +02:00
2013-02-10 12:40:43 +01:00
# initialize the intersection with the candidates of the lowest layer
2013-09-06 18:36:38 +02:00
my $intersection = [ map $_ -> expolygon , @ { $layerms [ 0 ] -> fill_surfaces -> filter_by_type ( $type )} ];
2012-09-22 19:04:36 +02:00
2013-02-10 12:40:43 +01:00
# start looping from the second layer and intersect the current intersection with it
for my $layerm ( @layerms [ 1 .. $#layerms ]) {
$intersection = intersection_ex (
[ map @$_ , @$intersection ],
2013-09-06 18:36:38 +02:00
[ map @ { $_ -> expolygon }, @ { $layerm -> fill_surfaces -> filter_by_type ( $type )} ],
2013-02-10 12:40:43 +01:00
);
2012-04-29 12:51:20 +02:00
}
2012-09-22 19:04:36 +02:00
2013-02-18 11:52:47 +01:00
my $area_threshold = $layerms [ 0 ] -> infill_area_threshold ;
2013-02-10 12:40:43 +01:00
@$intersection = grep $_ -> area > $area_threshold , @$intersection ;
next if ! @$intersection ;
Slic3r:: debugf " combining %d %s regions from layers %d-%d\n" ,
scalar ( @$intersection ),
( $type == S_TYPE_INTERNAL ? 'internal' : 'internal-solid' ),
2014-12-08 20:14:04 +01:00
$layer_idx - ( $every - 1 ), $layer_idx ;
2013-02-10 12:40:43 +01:00
# $intersection now contains the regions that can be combined across the full amount of layers
# so let's remove those areas from all layers
2013-02-16 07:53:47 -08:00
2013-07-17 00:48:29 +02:00
my @intersection_with_clearance = map @ { $_ -> offset (
2014-01-05 00:36:33 +01:00
$layerms [ - 1 ] -> flow ( FLOW_ROLE_SOLID_INFILL ) -> scaled_width / 2
+ $layerms [ - 1 ] -> flow ( FLOW_ROLE_PERIMETER ) -> scaled_width / 2
2013-03-17 00:57:58 +01:00
# Because fill areas for rectilinear and honeycomb are grown
# later to overlap perimeters, we need to counteract that too.
2015-11-01 19:03:11 +01:00
+ (( $type == S_TYPE_INTERNALSOLID || $region -> config -> fill_pattern =~ /(rectilinear|grid|line|honeycomb)/ )
2015-01-13 20:51:31 +01:00
? $layerms [ - 1 ] -> flow ( FLOW_ROLE_SOLID_INFILL ) -> scaled_width
2013-03-17 00:57:58 +01:00
: 0 )
2013-07-17 00:48:29 +02:00
)}, @$intersection ;
2013-03-17 00:57:58 +01:00
2013-02-16 07:53:47 -08:00
2013-02-10 12:40:43 +01:00
foreach my $layerm ( @layerms ) {
2013-09-06 18:36:38 +02:00
my @this_type = @ { $layerm -> fill_surfaces -> filter_by_type ( $type )};
2013-07-15 15:26:56 +02:00
my @other_types = map $_ -> clone , grep $_ -> surface_type != $type , @ { $layerm -> fill_surfaces };
2013-02-10 12:40:43 +01:00
2013-03-13 01:03:54 +01:00
my @new_this_type = map Slic3r::Surface -> new ( expolygon => $_ , surface_type => $type ),
2013-02-10 12:40:43 +01:00
@ { diff_ex (
2013-09-06 18:36:38 +02:00
[ map $_ -> p , @this_type ],
2013-02-16 07:53:47 -08:00
[ @intersection_with_clearance ],
2013-02-10 12:40:43 +01:00
)};
# apply surfaces back with adjusted depth to the uppermost layer
2015-10-26 23:23:03 +01:00
if ( $layerm -> layer -> id == $self -> get_layer ( $layer_idx ) -> id ) {
2013-03-13 01:03:54 +01:00
push @new_this_type ,
2013-03-17 01:10:40 +01:00
map Slic3r::Surface -> new (
expolygon => $_ ,
surface_type => $type ,
2015-10-26 23:23:03 +01:00
thickness => sum ( map $_ -> layer -> height , @layerms ),
2013-03-17 01:10:40 +01:00
thickness_layers => scalar ( @layerms ),
),
2013-02-10 12:40:43 +01:00
@$intersection ;
2013-03-13 01:03:54 +01:00
} else {
# save void surfaces
2014-12-08 21:23:42 +01:00
push @new_this_type ,
map Slic3r::Surface -> new ( expolygon => $_ , surface_type => $voidtype { $type }),
2013-03-13 01:03:54 +01:00
@ { intersection_ex (
[ map @ { $_ -> expolygon }, @this_type ],
[ @intersection_with_clearance ],
)};
2012-09-22 19:04:36 +02:00
}
2013-02-10 12:40:43 +01:00
2013-07-14 14:56:43 +02:00
$layerm -> fill_surfaces -> clear ;
2014-11-09 16:23:50 +01:00
$layerm -> fill_surfaces -> append ( $_ ) for ( @new_this_type , @other_types );
2012-04-29 12:51:20 +02:00
}
}
}
}
}
2016-09-13 11:24:55 +02:00
# Simplify the sliced model, if "resolution" configuration parameter > 0.
# The simplification is problematic, because it simplifies the slices independent from each other,
# which makes the simplified discretization visible on the object surface.
2013-12-19 15:23:10 +01:00
sub _simplify_slices {
my ( $self , $distance ) = @_ ;
foreach my $layer ( @ { $self -> layers }) {
$layer -> slices -> simplify ( $distance );
$_ -> slices -> simplify ( $distance ) for @ { $layer -> regions };
}
}
2013-12-31 14:33:03 +01:00
sub support_material_flow {
my ( $self , $role ) = @_ ;
$role // = FLOW_ROLE_SUPPORT_MATERIAL ;
my $extruder = ( $role == FLOW_ROLE_SUPPORT_MATERIAL )
? $self -> config -> support_material_extruder
: $self -> config -> support_material_interface_extruder ;
# we use a bogus layer_height because we use the same flow for all
# support material layers
2014-01-03 18:27:46 +01:00
return Slic3r::Flow -> new_from_width (
2014-04-25 19:39:27 +02:00
width => $self -> config -> support_material_extrusion_width || $self -> config -> extrusion_width ,
2013-12-31 14:33:03 +01:00
role => $role ,
2014-01-05 00:36:33 +01:00
nozzle_diameter => $self -> print -> config -> nozzle_diameter -> [ $extruder - 1 ] // $self -> print -> config -> nozzle_diameter -> [ 0 ],
2013-12-31 14:33:03 +01:00
layer_height => $self -> config -> layer_height ,
bridge_flow_ratio => 0 ,
);
}
2012-04-29 12:51:20 +02:00
1 ;