2016-10-04 13:54:10 +02:00
# Instantiated by Slic3r::Print::Object->_support_material()
# only generate() and contact_distance() are called from the outside of this module.
2013-09-17 23:27:57 +02:00
package Slic3r::Print::SupportMaterial ;
use Moo ;
use List::Util qw(sum min max) ;
use Slic3r::ExtrusionPath ':roles' ;
2014-01-02 17:24:23 +01:00
use Slic3r::Flow ':roles' ;
2015-06-02 19:44:29 +02:00
use Slic3r::Geometry qw(epsilon scale scaled_epsilon PI rad2deg deg2rad convex_hull) ;
2013-11-21 18:47:25 +01:00
use Slic3r::Geometry::Clipper qw(offset diff union union_ex intersection offset_ex offset2
2016-11-28 17:33:17 +01:00
intersection_pl offset2_ex diff_pl JT_MITER JT_ROUND) ;
2013-09-17 23:27:57 +02:00
use Slic3r::Surface ':types' ;
2014-01-03 00:34:30 +01:00
has 'print_config' => ( is => 'rw' , required => 1 );
has 'object_config' => ( is => 'rw' , required => 1 );
has 'flow' => ( is => 'rw' , required => 1 );
has 'first_layer_flow' => ( is => 'rw' , required => 1 );
has 'interface_flow' => ( is => 'rw' , required => 1 );
2013-10-27 10:19:26 +01:00
use constant DEBUG_CONTACT_ONLY => 0 ;
2013-09-17 23:27:57 +02:00
2013-10-27 15:22:44 +01:00
# increment used to reach MARGIN in steps to avoid trespassing thin objects
2016-12-20 12:19:13 +01:00
use constant MARGIN => 1.5 ;
2013-10-27 15:22:44 +01:00
use constant MARGIN_STEP => MARGIN / 3 ;
2014-01-05 16:51:16 +01:00
# generate a tree-like structure to save material
use constant PILLAR_SIZE => 2.5 ;
use constant PILLAR_SPACING => 10 ;
2013-09-17 23:27:57 +02:00
sub generate {
2016-10-04 13:54:10 +02:00
# $object is Slic3r::Print::Object
2013-10-26 17:56:59 +02:00
my ( $self , $object ) = @_ ;
2013-10-26 17:20:54 +02:00
# Determine the top surfaces of the support, defined as:
2013-10-27 10:19:26 +01:00
# contact = overhangs - clearance + margin
2013-10-26 17:20:54 +02:00
# This method is responsible for identifying what contact surfaces
# should the support material expose to the object in order to guarantee
# that it will be effective, regardless of how it's built below.
2013-10-26 17:56:59 +02:00
my ( $contact , $overhang ) = $self -> contact_area ( $object );
2013-10-26 17:20:54 +02:00
# Determine the top surfaces of the object. We need these to determine
# the layer heights of support material and to clip support to the object
# silhouette.
2013-10-26 17:56:59 +02:00
my ( $top ) = $self -> object_top ( $object , $contact );
2016-10-04 13:54:10 +02:00
2013-10-26 17:20:54 +02:00
# We now know the upper and lower boundaries for our support material object
# (@$contact_z and @$top_z), so we can generate intermediate layers.
2014-01-05 16:51:16 +01:00
my $support_z = $self -> support_layers_z (
2016-11-30 16:06:12 +01:00
$object ,
2013-10-26 17:56:59 +02:00
[ sort keys %$contact ],
[ sort keys %$top ],
max ( map $_ -> height , @ { $object -> layers })
);
2013-10-26 17:20:54 +02:00
# If we wanted to apply some special logic to the first support layers lying on
# object's top surfaces this is the place to detect them
2014-01-05 16:51:16 +01:00
my $shape = [] ;
if ( $self -> object_config -> support_material_pattern eq 'pillars' ) {
$self -> generate_pillars_shape ( $contact , $support_z , $shape );
}
2013-10-26 17:20:54 +02:00
# Propagate contact layers downwards to generate interface layers
2016-10-16 16:30:56 +02:00
my ( $interface ) = $self -> generate_top_interface_layers ( $support_z , $contact , $top );
2013-10-27 23:20:06 +01:00
$self -> clip_with_object ( $interface , $support_z , $object );
2014-01-05 16:51:16 +01:00
$self -> clip_with_shape ( $interface , $shape ) if @$shape ;
2013-10-26 17:20:54 +02:00
# Propagate contact layers and interface layers downwards to generate
# the main support layers.
my ( $base ) = $self -> generate_base_layers ( $support_z , $contact , $interface , $top );
2013-10-27 23:20:06 +01:00
$self -> clip_with_object ( $base , $support_z , $object );
2014-01-05 16:51:16 +01:00
$self -> clip_with_shape ( $base , $shape ) if @$shape ;
2013-10-26 17:20:54 +02:00
2014-12-23 01:04:25 +01:00
# Detect what part of base support layers are "reverse interfaces" because they
# lie above object's top surfaces.
$self -> generate_bottom_interface_layers ( $support_z , $base , $top , $interface );
2013-10-26 17:20:54 +02:00
# Install support layers into object.
2014-05-08 11:07:37 +02:00
for my $i ( 0 .. $#$support_z ) {
2014-05-06 11:07:18 +03:00
$object -> add_support_layer (
$i , # id
( $i == 0 ) ? $support_z -> [ $i ] : ( $support_z -> [ $i ] - $support_z -> [ $i - 1 ]), # height
$support_z -> [ $i ], # print_z
2015-01-30 18:45:30 +01:00
);
2014-05-08 11:07:37 +02:00
if ( $i >= 1 ) {
2014-05-06 11:07:18 +03:00
$object -> support_layers -> [ - 2 ] -> set_upper_layer ( $object -> support_layers -> [ - 1 ]);
$object -> support_layers -> [ - 1 ] -> set_lower_layer ( $object -> support_layers -> [ - 2 ]);
2014-05-08 11:07:37 +02:00
}
}
2013-10-26 17:20:54 +02:00
# Generate the actual toolpaths and save them into each layer.
2013-10-26 17:56:59 +02:00
$self -> generate_toolpaths ( $object , $overhang , $contact , $interface , $base );
2013-10-26 17:20:54 +02:00
}
sub contact_area {
2016-10-04 13:54:10 +02:00
# $object is Slic3r::Print::Object
2013-10-26 17:56:59 +02:00
my ( $self , $object ) = @_ ;
2013-09-17 23:27:57 +02:00
# if user specified a custom angle threshold, convert it to radians
my $threshold_rad ;
2014-01-02 17:24:23 +01:00
if ( $self -> object_config -> support_material_threshold ) {
$threshold_rad = deg2rad ( $self -> object_config -> support_material_threshold + 1 ); # +1 makes the threshold inclusive
2013-09-17 23:27:57 +02:00
Slic3r:: debugf "Threshold angle = %d°\n" , rad2deg ( $threshold_rad );
}
2016-10-04 13:54:10 +02:00
# Build support on a build plate only? If so, then collect top surfaces into $buildplate_only_top_surfaces
# and subtract $buildplate_only_top_surfaces from the contact surfaces, so
# there is no contact surface supported by a top surface.
my $buildplate_only = $self -> object_config -> support_material && $self -> object_config -> support_material_buildplate_only ;
my $buildplate_only_top_surfaces = [] ;
2013-09-17 23:27:57 +02:00
# determine contact areas
my %contact = (); # contact_z => [ polygons ]
2013-10-27 15:22:44 +01:00
my %overhang = (); # contact_z => [ polygons ] - this stores the actual overhang supported by each contact layer
2013-11-02 11:16:44 +01:00
for my $layer_id ( 0 .. $# { $object -> layers }) {
2013-12-23 20:12:39 +01:00
# note $layer_id might != $layer->id when raft_layers > 0
# so $layer_id == 0 means first object layer
# and $layer->id == 0 means first print layer (including raft)
2014-01-02 17:24:23 +01:00
if ( $self -> object_config -> raft_layers == 0 ) {
2013-11-02 11:16:44 +01:00
next if $layer_id == 0 ;
2014-01-02 17:24:23 +01:00
} elsif ( ! $self -> object_config -> support_material ) {
2013-11-02 11:16:44 +01:00
# if we are only going to generate raft just check
# the 'overhangs' of the first object layer
last if $layer_id > 0 ;
}
2014-06-13 18:45:44 +03:00
my $layer = $object -> get_layer ( $layer_id );
2016-10-04 13:54:10 +02:00
if ( $buildplate_only ) {
# Collect the top surfaces up to this layer and merge them.
my $projection_new = [] ;
push @$projection_new , ( map $_ -> p , map @ { $_ -> slices -> filter_by_type ( S_TYPE_TOP )}, @ { $layer -> regions } );
if ( @$projection_new ) {
# Merge the new top surfaces with the preceding top surfaces.
# Apply the safety offset to the newly added polygons, so they will connect
# with the polygons collected before,
# but don't apply the safety offset during the union operation as it would
# inflate the polygons over and over.
push @$buildplate_only_top_surfaces , @ { offset ( $projection_new , scale ( 0.01 )) };
$buildplate_only_top_surfaces = union ( $buildplate_only_top_surfaces , 0 );
}
}
2013-09-17 23:27:57 +02:00
# detect overhangs and contact areas needed to support them
my ( @overhang , @contact ) = ();
2013-12-23 20:12:39 +01:00
if ( $layer_id == 0 ) {
# this is the first object layer, so we're here just to get the object
# footprint for the raft
2014-05-29 14:54:47 +02:00
# we only consider contours and discard holes to get a more continuous raft
push @overhang , map $_ -> clone , map $_ -> contour , @ { $layer -> slices };
2016-10-16 16:30:56 +02:00
# Extend by SUPPORT_MATERIAL_MARGIN, which is 1.5mm
# MARGIN is the C++ Slic3r::SUPPORT_MATERIAL_MARGIN constant.
2013-12-23 20:12:39 +01:00
push @contact , @ { offset ( \ @overhang , scale + MARGIN )};
} else {
2014-06-13 18:45:44 +03:00
my $lower_layer = $object -> get_layer ( $layer_id - 1 );
2013-12-23 20:12:39 +01:00
foreach my $layerm ( @ { $layer -> regions }) {
2016-10-16 16:30:56 +02:00
# Extrusion width accounts for the roundings of the extrudates.
# It is the maximum widh of the extrudate.
2014-06-09 21:14:48 +02:00
my $fw = $layerm -> flow ( FLOW_ROLE_EXTERNAL_PERIMETER ) -> scaled_width ;
2013-12-23 20:12:39 +01:00
my $diff ;
2013-09-17 23:27:57 +02:00
2013-12-23 20:12:39 +01:00
# If a threshold angle was specified, use a different logic for detecting overhangs.
if ( defined $threshold_rad
2014-01-02 17:24:23 +01:00
|| $layer_id < $self -> object_config -> support_material_enforce_layers
2014-05-26 14:09:42 +02:00
|| ( $self -> object_config -> raft_layers > 0 && $layer_id == 0 )) {
2013-12-23 20:12:39 +01:00
my $d = defined $threshold_rad
? scale $lower_layer -> height * (( cos $threshold_rad ) / ( sin $threshold_rad ))
: 0 ;
2013-09-17 23:27:57 +02:00
2016-10-16 16:30:56 +02:00
# Shrinking the supported layer by layer_height/atan(threshold_rad).
2013-09-17 23:27:57 +02:00
$diff = diff (
2013-12-23 20:12:39 +01:00
offset ([ map $_ -> p , @ { $layerm -> slices } ], - $d ),
[ map @$_ , @ { $lower_layer -> slices } ],
2013-09-17 23:27:57 +02:00
);
2013-12-23 20:12:39 +01:00
# only enforce spacing from the object ($fw/2) if the threshold angle
# is not too high: in that case, $d will be very small (as we need to catch
# very short overhangs), and such contact area would be eaten by the
# enforced spacing, resulting in high threshold angles to be almost ignored
$diff = diff (
offset ( $diff , $d - $fw / 2 ),
[ map @$_ , @ { $lower_layer -> slices } ],
) if $d > $fw / 2 ;
} else {
2016-10-16 16:30:56 +02:00
# Automatic overhang detection.
2013-12-23 20:12:39 +01:00
$diff = diff (
2014-05-01 19:11:53 +02:00
[ map $_ -> p , @ { $layerm -> slices } ],
2016-10-16 16:30:56 +02:00
offset ([ map @$_ , @ { $lower_layer -> slices } ],
#FIXME Vojtech: Why 2x extrusion width? Isn't this too much? Should it not be /2?
+ $fw / 2 ),
2013-12-23 20:12:39 +01:00
);
# collapse very tiny spots
$diff = offset2 ( $diff , - $fw /10, +$fw/ 10 );
# $diff now contains the ring or stripe comprised between the boundary of
# lower slices and the centerline of the last perimeter in this overhanging layer.
# Void $diff means that there's no upper perimeter whose centerline is
# outside the lower slice boundary, thus no overhang
2013-09-17 23:27:57 +02:00
}
2014-04-26 17:19:50 +02:00
if ( $self -> object_config -> dont_support_bridges ) {
2014-04-30 01:20:18 +02:00
# compute the area of bridging perimeters
# Note: this is duplicate code from GCode.pm, we need to refactor
my $bridged_perimeters ; # Polygons
{
2014-04-30 15:16:15 +02:00
my $bridge_flow = $layerm -> flow ( FLOW_ROLE_PERIMETER , 1 );
2014-04-30 01:20:18 +02:00
my $nozzle_diameter = $self -> print_config -> get_at ( 'nozzle_diameter' , $layerm -> region -> config -> perimeter_extruder - 1 );
my $lower_grown_slices = offset ([ map @$_ , @ { $lower_layer -> slices } ], + scale ( $nozzle_diameter / 2 ));
# TODO: split_at_first_point() could split a bridge mid-way
2014-04-30 10:46:51 +02:00
my @overhang_perimeters =
map { $_ -> isa ( 'Slic3r::ExtrusionLoop' ) ? $_ -> polygon -> split_at_first_point : $_ -> polyline -> clone }
2015-01-08 15:19:56 +01:00
map @$_ , @ { $layerm -> perimeters };
2014-04-30 01:20:18 +02:00
# workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline()
$_ -> [ 0 ] -> translate ( 1 , 0 ) for @overhang_perimeters ;
@overhang_perimeters = @ { diff_pl (
\ @overhang_perimeters ,
$lower_grown_slices ,
)};
# only consider straight overhangs
@overhang_perimeters = grep $_ -> is_straight , @overhang_perimeters ;
# only consider overhangs having endpoints inside layer's slices
foreach my $polyline ( @overhang_perimeters ) {
$polyline -> extend_start ( $fw );
$polyline -> extend_end ( $fw );
}
@overhang_perimeters = grep {
$layer -> slices -> contains_point ( $_ -> first_point ) && $layer -> slices -> contains_point ( $_ -> last_point )
} @overhang_perimeters ;
2014-04-30 15:16:15 +02:00
# convert bridging polylines into polygons by inflating them with their thickness
{
# since we're dealing with bridges, we can't assume width is larger than spacing,
# so we take the largest value and also apply safety offset to be ensure no gaps
# are left in between
my $w = max ( $bridge_flow -> scaled_width , $bridge_flow -> scaled_spacing );
$bridged_perimeters = union ([
map @ { $_ -> grow ( $w / 2 + 10 )}, @overhang_perimeters
]);
}
2014-04-30 01:20:18 +02:00
}
2014-04-26 17:19:50 +02:00
if ( 1 ) {
# remove the entire bridges and only support the unsupported edges
my @bridges = map $_ -> expolygon ,
grep $_ -> bridge_angle != - 1 ,
@ { $layerm -> fill_surfaces -> filter_by_type ( S_TYPE_BOTTOMBRIDGE )};
2014-04-30 01:20:18 +02:00
2014-04-26 17:19:50 +02:00
$diff = diff (
$diff ,
2014-04-30 01:20:18 +02:00
[
( map @$_ , @bridges ),
@$bridged_perimeters ,
],
2014-04-26 17:19:50 +02:00
1 ,
);
2014-04-29 23:40:52 +02:00
2014-04-26 17:19:50 +02:00
push @$diff , @ { intersection (
[ map @ { $_ -> grow ( + scale MARGIN )}, @ { $layerm -> unsupported_bridge_edges } ],
[ map @$_ , @bridges ],
2014-04-29 23:40:52 +02:00
)};
2014-04-26 17:19:50 +02:00
} else {
# just remove bridged areas
$diff = diff (
$diff ,
2015-10-26 23:23:03 +01:00
$layerm -> bridged ,
2014-04-26 17:19:50 +02:00
1 ,
);
}
2016-10-04 13:54:10 +02:00
} # if ($self->object_config->dont_support_bridges)
if ( $buildplate_only ) {
# Don't support overhangs above the top surfaces.
# This step is done before the contact surface is calcuated by growing the overhang region.
$diff = diff ( $diff , $buildplate_only_top_surfaces );
2014-04-26 17:19:50 +02:00
}
2016-10-04 13:54:10 +02:00
2013-12-23 20:12:39 +01:00
next if ! @$diff ;
push @overhang , @$diff ; # NOTE: this is not the full overhang as it misses the outermost half of the perimeter width!
# Let's define the required contact area by using a max gap of half the upper
# extrusion width and extending the area according to the configured margin.
# We increment the area in steps because we don't want our support to overflow
# on the other side of the object (if it's very thin).
{
2016-10-04 13:54:10 +02:00
my $slices_margin = offset ([ map @$_ , @ { $lower_layer -> slices } ], + $fw / 2 );
if ( $buildplate_only ) {
# Trim the inflated contact surfaces by the top surfaces as well.
push @$slices_margin , map $_ -> clone , @ { $buildplate_only_top_surfaces };
$slices_margin = union ( $slices_margin );
}
2013-12-23 20:12:39 +01:00
for ( $fw /2, map {scale MARGIN_STEP} 1..(MARGIN / MARGIN_STEP )) {
$diff = diff (
2016-10-16 16:30:56 +02:00
offset (
$diff ,
$_ ,
JT_ROUND ,
2016-11-28 17:33:17 +01:00
scale ( 0.05 )),
2016-10-16 16:30:56 +02:00
$slices_margin
2013-12-23 20:12:39 +01:00
);
}
}
push @contact , @$diff ;
2013-09-17 23:27:57 +02:00
}
}
next if ! @contact ;
# now apply the contact areas to the layer were they need to be made
{
# get the average nozzle diameter used on this layer
2014-01-05 00:36:33 +01:00
my @nozzle_diameters = map $self -> print_config -> get_at ( 'nozzle_diameter' , $_ ),
2014-12-17 00:34:00 +01:00
map { $_ -> config -> perimeter_extruder - 1 , $_ -> config -> infill_extruder - 1 , $_ -> config -> solid_infill_extruder - 1 }
2015-10-26 23:23:03 +01:00
map $_ -> region , @ { $layer -> regions };
2013-09-17 23:27:57 +02:00
my $nozzle_diameter = sum ( @nozzle_diameters ) / @nozzle_diameters ;
2015-01-19 09:52:24 +01:00
my $contact_z = $layer -> print_z - $self -> contact_distance ( $layer -> height , $nozzle_diameter );
2013-09-17 23:27:57 +02:00
2016-10-16 16:30:56 +02:00
# Ignore this contact area if it's too low.
#FIXME Better to control the thickness of the interface layer printed, but that would
# require having attributes (extrusion width / height, bridge flow etc) per island.
2015-06-02 19:44:29 +02:00
next if $contact_z < $self -> object_config -> get_value ( 'first_layer_height' ) - epsilon ;
2013-09-17 23:27:57 +02:00
$contact { $contact_z } = [ @contact ];
$overhang { $contact_z } = [ @overhang ];
if ( 0 ) {
require "Slic3r/SVG.pm" ;
2016-10-21 10:18:01 +02:00
Slic3r::SVG:: output ( Slic3r:: DEBUG_OUT_PATH_PREFIX . "contact_" . $contact_z . ".svg" ,
2016-10-04 13:54:10 +02:00
green_expolygons => union_ex ( $buildplate_only_top_surfaces ),
blue_expolygons => union_ex ( \ @contact ),
red_expolygons => union_ex ( \ @overhang ),
2013-09-17 23:27:57 +02:00
);
}
}
}
2016-10-04 13:54:10 +02:00
2013-10-26 17:20:54 +02:00
return ( \ %contact , \ %overhang );
}
sub object_top {
2013-10-26 17:56:59 +02:00
my ( $self , $object , $contact ) = @_ ;
2013-09-17 23:27:57 +02:00
# find object top surfaces
# we'll use them to clip our support and detect where does it stick
my %top = (); # print_z => [ expolygons ]
2016-10-04 13:54:10 +02:00
return \ %top if ( $self -> object_config -> support_material_buildplate_only );
2016-10-16 16:30:56 +02:00
# Sum of unsupported contact areas above the current $layer->print_z.
2013-10-27 10:19:26 +01:00
my $projection = [] ;
foreach my $layer ( reverse @ { $object -> layers }) {
if ( my @top = map @ { $_ -> slices -> filter_by_type ( S_TYPE_TOP )}, @ { $layer -> regions }) {
# compute projection of the contact areas above this top layer
# first add all the 'new' contact areas to the current projection
# ('new' means all the areas that are lower than the last top layer
# we considered)
my $min_top = min ( keys %top ) // max ( keys %$contact );
# use <= instead of just < because otherwise we'd ignore any contact regions
# having the same Z of top layers
push @$projection , map @ { $contact -> { $_ }}, grep { $_ > $layer -> print_z && $_ <= $min_top } keys %$contact ;
2016-10-16 16:30:56 +02:00
# Now find whether any projection of the contact surfaces above $layer->print_z not yet supported by any top surfaces above $layer->z falls onto this top surface.
# $touching are the contact surfaces supported exclusively by this @top surfaaces.
2013-10-27 10:19:26 +01:00
my $touching = intersection ( $projection , [ map $_ -> p , @top ]);
if ( @$touching ) {
# grow top surfaces so that interface and support generation are generated
# with some spacing from object - it looks we don't need the actual
# top shapes so this can be done here
2016-03-19 22:30:02 -05:00
$top { $layer -> print_z } = offset ( $touching , $self -> flow -> scaled_width + $self -> object_config -> support_material_xy_spacing );
2013-09-17 23:27:57 +02:00
}
2013-10-27 10:19:26 +01:00
# remove the areas that touched from the projection that will continue on
# next, lower, top surfaces
$projection = diff ( $projection , $touching );
2013-09-17 23:27:57 +02:00
}
}
2013-10-26 17:20:54 +02:00
return \ %top ;
}
sub support_layers_z {
2016-11-30 16:06:12 +01:00
my ( $self , $object , $contact_z , $top_z , $max_object_layer_height ) = @_ ;
2013-10-26 17:20:54 +02:00
# quick table to check whether a given Z is a top surface
my %top = map { $_ => 1 } @$top_z ;
# determine layer height for any non-contact layer
# we use max() to prevent many ultra-thin layers to be inserted in case
# layer_height > nozzle_diameter * 0.75
2014-01-05 00:36:33 +01:00
my $nozzle_diameter = $self -> print_config -> get_at ( 'nozzle_diameter' , $self -> object_config -> support_material_extruder - 1 );
2013-10-26 17:56:59 +02:00
my $support_material_height = max ( $max_object_layer_height , $nozzle_diameter * 0.75 );
2015-01-19 09:52:24 +01:00
my $contact_distance = $self -> contact_distance ( $support_material_height , $nozzle_diameter );
2014-12-23 22:18:43 +01:00
# initialize known, fixed, support layers
2016-11-30 16:06:12 +01:00
my @z = @$contact_z ;
my $synchronize = $self -> object_config -> support_material_synchronize_layers ;
if ( ! $synchronize ) {
push @z ,
# TODO: why we have this?
# Vojtech: To detect the bottom interface layers by finding a Z value in the $top_z.
@$top_z ;
push @z ,
# Top surfaces of the bottom interface layers.
( map $_ + $contact_distance , @$top_z );
}
@z = sort { $a <=> $b } @z ;
2013-10-26 17:20:54 +02:00
# enforce first layer height
2014-01-02 17:24:23 +01:00
my $first_layer_height = $self -> object_config -> get_value ( 'first_layer_height' );
2013-10-26 17:20:54 +02:00
shift @z while @z && $z [ 0 ] <= $first_layer_height ;
unshift @z , $first_layer_height ;
2015-06-02 20:03:57 +02:00
2013-11-02 11:16:44 +01:00
# add raft layers by dividing the space between first layer and
# first contact layer evenly
2014-01-02 17:24:23 +01:00
if ( $self -> object_config -> raft_layers > 1 && @z >= 2 ) {
2013-11-02 11:16:44 +01:00
# $z[1] is last raft layer (contact layer for the first layer object)
2014-01-02 17:24:23 +01:00
my $height = ( $z [ 1 ] - $z [ 0 ]) / ( $self -> object_config -> raft_layers - 1 );
2015-06-02 19:58:36 +02:00
# since we already have two raft layers ($z[0] and $z[1]) we need to insert
# raft_layers-2 more
2013-11-02 11:16:44 +01:00
splice @z , 1 , 0 ,
2014-06-11 19:58:11 +02:00
map { sprintf "%.2f" , $_ }
2013-11-02 11:16:44 +01:00
map { $z [ 0 ] + $height * $_ }
2015-06-02 19:58:36 +02:00
1 .. ( $self -> object_config -> raft_layers - 2 );
2013-11-02 11:16:44 +01:00
}
2016-11-30 16:06:12 +01:00
if ( $synchronize ) {
@z = splice @z , $self -> object_config -> raft_layers ;
# if ($self->object_config->raft_layers > scalar(@z));
push @z , map $_ -> print_z , @ { $object -> layers };
} else {
# create other layers (skip raft layers as they're already done and use thicker layers)
for ( my $i = $#z ; $i >= $self -> object_config -> raft_layers ; $i -- ) {
my $target_height = $support_material_height ;
if ( $i > 0 && $top { $z [ $i - 1 ] }) {
# Bridge flow?
#FIXME We want to enforce not only the bridge flow height, but also the interface gap!
# This will introduce an additional layer if the gap is set to an extreme value!
$target_height = $nozzle_diameter ;
}
# enforce first layer height
#FIXME better to split the layers regularly, than to bite a constant height one at a time,
# and then be left with a very thin layer at the end.
if (( $i == 0 && $z [ $i ] > $target_height + $first_layer_height )
|| ( $z [ $i ] - $z [ $i - 1 ] > $target_height + Slic3r::Geometry:: epsilon )) {
splice @z , $i , 0 , ( $z [ $i ] - $target_height );
$i ++ ;
}
2013-10-26 17:20:54 +02:00
}
}
# remove duplicates and make sure all 0.x values have the leading 0
{
my %sl = map { 1 * $_ => 1 } @z ;
@z = sort { $a <=> $b } keys %sl ;
}
return \ @z ;
}
2016-10-16 16:30:56 +02:00
sub generate_top_interface_layers {
2013-10-26 17:20:54 +02:00
my ( $self , $support_z , $contact , $top ) = @_ ;
2016-10-16 16:30:56 +02:00
# If no interface layers are allowed, don't generate top interface layers.
return if $self -> object_config -> support_material_interface_layers == 0 ;
2013-09-17 23:27:57 +02:00
# let's now generate interface layers below contact areas
my %interface = (); # layer_id => [ polygons ]
2014-04-29 23:29:52 +02:00
my $interface_layers_num = $self -> object_config -> support_material_interface_layers ;
2013-10-26 17:20:54 +02:00
for my $layer_id ( 0 .. $#$support_z ) {
my $z = $support_z -> [ $layer_id ];
my $this = $contact -> { $z } // next ;
2013-09-17 23:27:57 +02:00
# count contact layer as interface layer
2014-04-29 23:29:52 +02:00
for ( my $i = $layer_id - 1 ; $i >= 0 && $i > $layer_id - $interface_layers_num ; $i -- ) {
2013-10-26 17:20:54 +02:00
$z = $support_z -> [ $i ];
2013-10-27 10:19:26 +01:00
my @overlapping_layers = $self -> overlapping_layers ( $i , $support_z );
my @overlapping_z = map $support_z -> [ $_ ], @overlapping_layers ;
2013-09-17 23:27:57 +02:00
# Compute interface area on this layer as diff of upper contact area
# (or upper interface area) and layer slices.
# This diff is responsible of the contact between support material and
# the top surfaces of the object. We should probably offset the top
2013-10-27 10:19:26 +01:00
# surfaces vertically before performing the diff, but this needs
# investigation.
2013-09-17 23:27:57 +02:00
$this = $interface { $i } = diff (
[
@$this , # clipped projection of the current contact regions
@ { $interface { $i } || [] }, # interface regions already applied to this layer
],
[
2013-10-27 10:19:26 +01:00
( map @$_ , map $top -> { $_ }, grep exists $top -> { $_ }, @overlapping_z ), # top slices on this layer
( map @$_ , map $contact -> { $_ }, grep exists $contact -> { $_ }, @overlapping_z ), # contact regions on this layer
2013-09-17 23:27:57 +02:00
],
1 ,
);
}
}
2013-10-26 17:20:54 +02:00
return \ %interface ;
}
2014-12-23 01:04:25 +01:00
sub generate_bottom_interface_layers {
my ( $self , $support_z , $base , $top , $interface ) = @_ ;
2016-10-04 13:54:10 +02:00
# If no interface layers are allowed, don't generate bottom interface layers.
return if $self -> object_config -> support_material_interface_layers == 0 ;
2014-12-23 01:04:25 +01:00
my $area_threshold = $self -> interface_flow -> scaled_spacing ** 2 ;
# loop through object's top surfaces
foreach my $top_z ( sort keys %$top ) {
my $this = $top -> { $top_z };
# keep a count of the interface layers we generated for this top surface
my $interface_layers = 0 ;
# loop through support layers until we find the one(s) right above the top
# surface
foreach my $layer_id ( 0 .. $#$support_z ) {
my $z = $support_z -> [ $layer_id ];
next unless $z > $top_z ;
2014-12-29 14:29:21 +01:00
if ( $base -> { $layer_id }) {
# get the support material area that should be considered interface
my $interface_area = intersection (
$base -> { $layer_id },
$this ,
);
2014-12-23 01:04:25 +01:00
2014-12-29 14:29:21 +01:00
# discard too small areas
$interface_area = [ grep abs ( $_ -> area ) >= $area_threshold , @$interface_area ];
2014-12-23 01:04:25 +01:00
2014-12-29 14:29:21 +01:00
# subtract new interface area from base
$base -> { $layer_id } = diff (
$base -> { $layer_id },
$interface_area ,
);
2014-12-23 01:04:25 +01:00
2014-12-29 14:29:21 +01:00
# add new interface area to interface
push @ { $interface -> { $layer_id }}, @$interface_area ;
}
2014-12-23 01:04:25 +01:00
$interface_layers ++ ;
last if $interface_layers == $self -> object_config -> support_material_interface_layers ;
}
}
}
2013-10-26 17:20:54 +02:00
sub generate_base_layers {
my ( $self , $support_z , $contact , $interface , $top ) = @_ ;
2013-09-17 23:27:57 +02:00
# let's now generate support layers under interface layers
2013-10-26 17:20:54 +02:00
my $base = {}; # layer_id => [ polygons ]
2013-09-17 23:27:57 +02:00
{
2016-10-16 16:30:56 +02:00
my $fillet_radius_scaled = scale ( $self -> object_config -> support_material_spacing );
2013-10-26 17:20:54 +02:00
for my $i ( reverse 0 .. $#$support_z - 1 ) {
my $z = $support_z -> [ $i ];
2013-10-27 10:19:26 +01:00
my @overlapping_layers = $self -> overlapping_layers ( $i , $support_z );
my @overlapping_z = map $support_z -> [ $_ ], @overlapping_layers ;
2013-10-27 18:54:52 +01:00
# in case we have no interface layers, look at upper contact
2013-11-14 18:13:57 +01:00
# (1 interface layer means we only have contact layer, so $interface->{$i+1} is empty)
2013-10-27 18:54:52 +01:00
my @upper_contact = ();
2014-01-02 17:24:23 +01:00
if ( $self -> object_config -> support_material_interface_layers <= 1 ) {
2013-10-27 18:54:52 +01:00
@upper_contact = @ { $contact -> { $support_z -> [ $i + 1 ]} || [] };
}
2016-10-16 16:30:56 +02:00
my $trim_polygons = [
( map @$_ , map $top -> { $_ }, grep exists $top -> { $_ }, @overlapping_z ), # top slices on this layer
( map @$_ , map $interface -> { $_ }, grep exists $interface -> { $_ }, @overlapping_layers ), # interface regions on this layer
( map @$_ , map $contact -> { $_ }, grep exists $contact -> { $_ }, @overlapping_z ), # contact regions on this layer
];
2013-10-26 17:20:54 +02:00
$base -> { $i } = diff (
2013-09-17 23:27:57 +02:00
[
2013-10-26 17:20:54 +02:00
@ { $base -> { $i + 1 } || [] }, # support regions on upper layer
@ { $interface -> { $i + 1 } || [] }, # interface regions on upper layer
2013-10-27 18:54:52 +01:00
@upper_contact , # contact regions on upper layer
2013-09-17 23:27:57 +02:00
],
2016-10-16 16:30:56 +02:00
$trim_polygons ,
1 , # safety offset to merge the touching source polygons
2013-09-17 23:27:57 +02:00
);
2016-10-16 16:30:56 +02:00
if ( 0 ) {
# Fillet the base polygons and trim them again with the top, interface and contact layers.
$base -> { $i } = diff (
offset2 (
$base -> { $i },
$fillet_radius_scaled ,
- $fillet_radius_scaled ,
# Use a geometric offsetting for filleting.
JT_ROUND ,
2016-11-28 17:33:17 +01:00
0.2 * $fillet_radius_scaled ),
2016-10-16 16:30:56 +02:00
$trim_polygons ,
0 ); # don't apply the safety offset.
}
2013-09-17 23:27:57 +02:00
}
}
2013-10-26 17:20:54 +02:00
return $base ;
}
2013-09-17 23:27:57 +02:00
2014-04-29 23:29:52 +02:00
# This method removes object silhouette from support material
# (it's used with interface and base only). It removes a bit more,
# leaving a thin gap between object and support in the XY plane.
2013-10-27 23:20:06 +01:00
sub clip_with_object {
my ( $self , $support , $support_z , $object ) = @_ ;
foreach my $i ( keys %$support ) {
next if ! @ { $support -> { $i }};
my $zmax = $support_z -> [ $i ];
my $zmin = ( $i == 0 ) ? 0 : $support_z -> [ $i - 1 ];
my @layers = grep { $_ -> print_z > $zmin && ( $_ -> print_z - $_ -> height ) < $zmax }
@ { $object -> layers };
2014-04-29 23:29:52 +02:00
# $layer->slices contains the full shape of layer, thus including
# perimeter's width. $support contains the full shape of support
# material, thus including the width of its foremost extrusion.
2016-03-19 22:30:02 -05:00
# We leave a gap equal to a full extrusion width + an offset
# if the user wants to play around with this setting.
2013-10-27 23:20:06 +01:00
$support -> { $i } = diff (
$support -> { $i },
2016-03-19 22:30:02 -05:00
offset ([ map @$_ , map @ { $_ -> slices }, @layers ], + $self -> object_config -> support_material_xy_spacing ),
2013-10-27 23:20:06 +01:00
);
}
}
2013-10-26 17:20:54 +02:00
sub generate_toolpaths {
2013-10-26 17:56:59 +02:00
my ( $self , $object , $overhang , $contact , $interface , $base ) = @_ ;
2013-10-26 17:20:54 +02:00
2014-01-03 00:34:30 +01:00
my $flow = $self -> flow ;
my $interface_flow = $self -> interface_flow ;
2013-10-26 17:20:54 +02:00
# shape of contact area
2016-11-30 17:33:55 +01:00
my $contact_loops = $self -> object_config -> support_material_interface_contact_loops ? 1 : 0 ;
2016-04-09 12:47:08 -05:00
my $circle_radius = 1.5 * $interface_flow -> scaled_width - ( $self -> object_config -> support_material_xy_spacing / 0.000001 ) ;
2013-10-26 17:20:54 +02:00
my $circle_distance = 3 * $circle_radius ;
2013-10-27 15:22:44 +01:00
my $circle = Slic3r::Polygon -> new ( map [ $circle_radius * cos $_ , $circle_radius * sin $_ ],
( 5 * PI /3, 4*PI/ 3 , PI , 2 * PI /3, PI/ 3 , 0 ));
2013-10-26 17:20:54 +02:00
2013-09-17 23:27:57 +02:00
Slic3r:: debugf "Generating patterns\n" ;
# prepare fillers
2014-01-02 17:24:23 +01:00
my $pattern = $self -> object_config -> support_material_pattern ;
2016-10-04 14:38:13 +02:00
my $with_sheath = $self -> object_config -> support_material_with_sheath ;
2014-01-02 17:24:23 +01:00
my @angles = ( $self -> object_config -> support_material_angle );
2013-09-17 23:27:57 +02:00
if ( $pattern eq 'rectilinear-grid' ) {
$pattern = 'rectilinear' ;
push @angles , $angles [ 0 ] + 90 ;
2014-01-05 16:51:16 +01:00
} elsif ( $pattern eq 'pillars' ) {
$pattern = 'honeycomb' ;
2013-09-17 23:27:57 +02:00
}
2014-01-02 17:24:23 +01:00
my $interface_angle = $self -> object_config -> support_material_angle + 90 ;
2014-01-03 00:34:30 +01:00
my $interface_spacing = $self -> object_config -> support_material_interface_spacing + $interface_flow -> spacing ;
my $interface_density = $interface_spacing == 0 ? 1 : $interface_flow -> spacing / $interface_spacing ;
2014-01-02 17:24:23 +01:00
my $support_spacing = $self -> object_config -> support_material_spacing + $flow -> spacing ;
2013-09-17 23:27:57 +02:00
my $support_density = $support_spacing == 0 ? 1 : $flow -> spacing / $support_spacing ;
my $process_layer = sub {
my ( $layer_id ) = @_ ;
2013-10-26 17:56:59 +02:00
my $layer = $object -> support_layers -> [ $layer_id ];
2013-10-26 17:20:54 +02:00
my $z = $layer -> print_z ;
2013-09-17 23:27:57 +02:00
2014-11-07 23:18:35 +01:00
# we redefine flows locally by applying this layer's height
my $_flow = $flow -> clone ;
my $_interface_flow = $interface_flow -> clone ;
$_flow -> set_height ( $layer -> height );
$_interface_flow -> set_height ( $layer -> height );
2013-10-26 17:20:54 +02:00
my $overhang = $overhang -> { $z } || [] ;
my $contact = $contact -> { $z } || [] ;
my $interface = $interface -> { $layer_id } || [] ;
my $base = $base -> { $layer_id } || [] ;
2013-09-17 23:27:57 +02:00
2013-10-27 10:19:26 +01:00
if ( DEBUG_CONTACT_ONLY ) {
$interface = [] ;
$base = [] ;
}
2013-09-17 23:27:57 +02:00
if ( 0 ) {
require "Slic3r/SVG.pm" ;
2016-10-21 10:18:01 +02:00
Slic3r::SVG:: output ( Slic3r:: DEBUG_OUT_PATH_PREFIX . "layer_" . $z . ".svg" ,
2016-10-16 16:30:56 +02:00
blue_expolygons => union_ex ( $base ),
2013-09-17 23:27:57 +02:00
red_expolygons => union_ex ( $contact ),
green_expolygons => union_ex ( $interface ),
);
}
# islands
2013-10-26 17:20:54 +02:00
$layer -> support_islands -> append ( @ { union_ex ([ @$interface , @$base , @$contact ])});
2013-09-17 23:27:57 +02:00
# contact
my $contact_infill = [] ;
2014-01-02 17:24:23 +01:00
if ( $self -> object_config -> support_material_interface_layers == 0 ) {
2013-10-27 18:54:52 +01:00
# if no interface layers were requested we treat the contact layer
# exactly as a generic base layer
push @$base , @$contact ;
2016-11-30 17:33:55 +01:00
} elsif ( $contact_loops == 0 ) {
# No contact loops, but some interface layers. Print the contact layer as a normal interface layer.
push @$interface , @$contact ;
} elsif ( @$contact ) {
2013-09-17 23:27:57 +02:00
# generate the outermost loop
2014-05-02 10:49:14 +02:00
# find centerline of the external loop (or any other kind of extrusions should the loop be skipped)
2016-04-09 12:47:08 -05:00
$contact = offset ( $contact , - ( $_interface_flow -> scaled_width + ( $self -> object_config -> support_material_xy_spacing / 0.000001)) / 2 );
2014-05-02 10:49:14 +02:00
2013-10-27 20:48:50 +01:00
my @loops0 = ();
2013-09-17 23:27:57 +02:00
{
# find centerline of the external loop of the contours
2014-05-02 10:49:14 +02:00
my @external_loops = @$contact ;
2013-09-17 23:27:57 +02:00
2013-10-27 15:22:44 +01:00
# only consider the loops facing the overhang
{
2014-11-07 23:18:35 +01:00
my $overhang_with_margin = offset ( $overhang , + $_interface_flow -> scaled_width / 2 );
2013-10-27 16:37:04 +01:00
@external_loops = grep {
2013-11-21 18:47:25 +01:00
@ { intersection_pl (
[ $_ -> split_at_first_point ],
$overhang_with_margin ,
)}
2013-10-27 16:37:04 +01:00
} @external_loops ;
2013-10-27 15:22:44 +01:00
}
2013-09-17 23:27:57 +02:00
# apply a pattern to the loop
2013-11-11 20:59:58 +01:00
my @positions = map @ { Slic3r::Polygon -> new ( @$_ ) -> equally_spaced_points ( $circle_distance )}, @external_loops ;
2013-09-17 23:27:57 +02:00
@loops0 = @ { diff (
[ @external_loops ],
2013-10-27 15:22:44 +01:00
[ map { my $c = $circle -> clone ; $c -> translate ( @$_ ); $c } @positions ],
2013-09-17 23:27:57 +02:00
)};
}
# make more loops
my @loops = @loops0 ;
for my $i ( 2 .. $contact_loops ) {
2014-11-07 23:18:35 +01:00
my $d = ( $i - 1 ) * $_interface_flow -> scaled_spacing ;
push @loops , @ { offset2 ( \ @loops0 , - $d - 0.5 * $_interface_flow -> scaled_spacing , + 0.5 * $_interface_flow -> scaled_spacing )};
2013-09-17 23:27:57 +02:00
}
# clip such loops to the side oriented towards the object
2013-11-21 18:47:25 +01:00
@loops = @ { intersection_pl (
[ map $_ -> split_at_first_point , @loops ],
offset ( $overhang , + scale MARGIN ),
)};
2013-09-17 23:27:57 +02:00
# add the contact infill area to the interface area
2013-10-27 15:22:44 +01:00
# note that growing loops by $circle_radius ensures no tiny
# extrusions are left inside the circles; however it creates
# a very large gap between loops and contact_infill, so maybe another
# solution should be found to achieve both goals
$contact_infill = diff (
$contact ,
2013-11-20 11:35:58 +01:00
[ map @ { $_ -> grow ( $circle_radius * 1.1 )}, @loops ],
2013-10-27 15:22:44 +01:00
);
2013-09-17 23:27:57 +02:00
# transform loops into ExtrusionPath objects
2014-11-07 23:18:35 +01:00
my $mm3_per_mm = $_interface_flow -> mm3_per_mm ;
2013-09-17 23:27:57 +02:00
@loops = map Slic3r::ExtrusionPath -> new (
2014-01-03 18:27:46 +01:00
polyline => $_ ,
2014-05-12 22:59:49 +02:00
role => EXTR_ROLE_SUPPORTMATERIAL_INTERFACE ,
2014-01-03 18:27:46 +01:00
mm3_per_mm => $mm3_per_mm ,
2014-11-07 23:18:35 +01:00
width => $_interface_flow -> width ,
2014-04-29 23:15:36 +02:00
height => $layer -> height ,
2013-09-17 23:27:57 +02:00
), @loops ;
$layer -> support_interface_fills -> append ( @loops );
}
2016-10-20 17:44:46 +02:00
# Allocate the fillers exclusively in the worker threads! Don't allocate them at the main thread,
# as Perl copies the C++ pointers by default, so then the C++ objects are shared between threads!
my %fillers = (
2016-11-02 10:47:00 +01:00
interface => Slic3r::Filler -> new_from_type ( 'rectilinear' ),
support => Slic3r::Filler -> new_from_type ( $pattern ),
2016-10-20 17:44:46 +02:00
);
2016-11-02 10:47:00 +01:00
my $bounding_box = $object -> bounding_box ;
$fillers { interface } -> set_bounding_box ( $object -> bounding_box );
$fillers { support } -> set_bounding_box ( $object -> bounding_box );
2013-09-17 23:27:57 +02:00
# interface and contact infill
if ( @$interface || @$contact_infill ) {
2016-10-16 16:30:56 +02:00
$fillers { interface } -> set_angle ( $interface_angle );
$fillers { interface } -> set_spacing ( $_interface_flow -> spacing );
2013-09-17 23:27:57 +02:00
2014-04-29 23:29:52 +02:00
# find centerline of the external loop
2014-11-07 23:18:35 +01:00
$interface = offset2 ( $interface , + scaled_epsilon , - ( scaled_epsilon + $_interface_flow -> scaled_width / 2 ));
2014-04-29 23:29:52 +02:00
2013-10-27 16:37:04 +01:00
# join regions by offsetting them to ensure they're merged
2013-10-27 15:22:44 +01:00
$interface = offset ([ @$interface , @$contact_infill ], scaled_epsilon );
2013-09-17 23:27:57 +02:00
2013-10-27 16:37:04 +01:00
# turn base support into interface when it's contained in our holes
# (this way we get wider interface anchoring)
{
my @p = @$interface ;
@$interface = ();
foreach my $p ( @p ) {
if ( $p -> is_clockwise ) {
my $p2 = $p -> clone ;
$p2 -> make_counter_clockwise ;
next if ! @ { diff ([ $p2 ], $base , 1 )};
}
push @$interface , $p ;
}
}
$base = diff ( $base , $interface );
2013-09-17 23:27:57 +02:00
my @paths = ();
foreach my $expolygon ( @ { union_ex ( $interface )}) {
2016-10-16 16:30:56 +02:00
my $polylines = $fillers { interface } -> fill_surface (
2013-09-17 23:27:57 +02:00
Slic3r::Surface -> new ( expolygon => $expolygon , surface_type => S_TYPE_INTERNAL ),
2014-12-23 01:04:25 +01:00
density => $interface_density ,
2014-04-05 10:54:24 +02:00
layer_height => $layer -> height ,
2014-12-23 01:04:25 +01:00
complete => 1 ,
2013-09-17 23:27:57 +02:00
);
2014-12-23 01:04:25 +01:00
my $mm3_per_mm = $_interface_flow -> mm3_per_mm ;
2013-09-17 23:27:57 +02:00
push @paths , map Slic3r::ExtrusionPath -> new (
2016-09-12 13:26:17 +02:00
polyline => Slic3r::Polyline -> new ( @$_ ),
2014-05-12 22:59:49 +02:00
role => EXTR_ROLE_SUPPORTMATERIAL_INTERFACE ,
2014-01-03 18:27:46 +01:00
mm3_per_mm => $mm3_per_mm ,
2014-12-23 01:04:25 +01:00
width => $_interface_flow -> width ,
2014-04-29 23:15:36 +02:00
height => $layer -> height ,
2016-10-16 16:30:56 +02:00
), @$polylines ,
}
2013-10-27 15:22:44 +01:00
2013-09-17 23:27:57 +02:00
$layer -> support_interface_fills -> append ( @paths );
}
# support or flange
2013-10-26 17:20:54 +02:00
if ( @$base ) {
2013-09-17 23:27:57 +02:00
my $filler = $fillers { support };
2016-10-16 16:30:56 +02:00
$filler -> set_angle ( $angles [ ( $layer_id ) % @ angles ]);
2015-02-27 18:43:15 +01:00
# We don't use $base_flow->spacing because we need a constant spacing
# value that guarantees that all layers are correctly aligned.
2016-10-16 16:30:56 +02:00
$filler -> set_spacing ( $flow -> spacing );
2015-02-27 18:43:15 +01:00
2014-01-03 18:27:46 +01:00
my $density = $support_density ;
2014-11-07 23:18:35 +01:00
my $base_flow = $_flow ;
2013-09-17 23:27:57 +02:00
2014-04-29 23:29:52 +02:00
# find centerline of the external loop/extrusions
2014-11-07 23:18:35 +01:00
my $to_infill = offset2_ex ( $base , + scaled_epsilon , - ( scaled_epsilon + $_flow -> scaled_width / 2 ));
2016-10-16 16:30:56 +02:00
if ( 0 ) {
require "Slic3r/SVG.pm" ;
2016-10-21 10:18:01 +02:00
Slic3r::SVG:: output ( Slic3r:: DEBUG_OUT_PATH_PREFIX . "to_infill_base" . $z . ".svg" ,
2016-10-16 16:30:56 +02:00
red_expolygons => union_ex ( $contact ),
green_expolygons => union_ex ( $interface ),
blue_expolygons => $to_infill ,
);
}
2014-04-29 23:29:52 +02:00
2013-09-17 23:27:57 +02:00
my @paths = ();
# base flange
if ( $layer_id == 0 ) {
$filler = $fillers { interface };
2016-10-16 16:30:56 +02:00
$filler -> set_angle ( $self -> object_config -> support_material_angle + 90 );
2013-09-17 23:27:57 +02:00
$density = 0.5 ;
2014-01-03 18:27:46 +01:00
$base_flow = $self -> first_layer_flow ;
2015-02-27 18:43:15 +01:00
# use the proper spacing for first layer as we don't need to align
# its pattern to the other layers
2016-10-16 16:30:56 +02:00
$filler -> set_spacing ( $base_flow -> spacing );
2016-10-04 14:38:13 +02:00
} elsif ( $with_sheath ) {
2013-09-17 23:27:57 +02:00
# draw a perimeter all around support infill
# TODO: use brim ordering algorithm
2014-11-07 23:18:35 +01:00
my $mm3_per_mm = $_flow -> mm3_per_mm ;
2013-09-17 23:27:57 +02:00
push @paths , map Slic3r::ExtrusionPath -> new (
2014-01-03 18:27:46 +01:00
polyline => $_ -> split_at_first_point ,
role => EXTR_ROLE_SUPPORTMATERIAL ,
mm3_per_mm => $mm3_per_mm ,
2014-11-07 23:18:35 +01:00
width => $_flow -> width ,
2014-04-29 23:15:36 +02:00
height => $layer -> height ,
2013-09-17 23:27:57 +02:00
), map @$_ , @$to_infill ;
# TODO: use offset2_ex()
2014-11-07 23:18:35 +01:00
$to_infill = offset_ex ([ map @$_ , @$to_infill ], - $_flow -> scaled_spacing );
2013-09-17 23:27:57 +02:00
}
foreach my $expolygon ( @$to_infill ) {
2016-10-16 16:30:56 +02:00
my $polylines = $filler -> fill_surface (
2013-09-17 23:27:57 +02:00
Slic3r::Surface -> new ( expolygon => $expolygon , surface_type => S_TYPE_INTERNAL ),
2014-01-03 18:27:46 +01:00
density => $density ,
2014-04-05 10:54:24 +02:00
layer_height => $layer -> height ,
2014-01-03 18:27:46 +01:00
complete => 1 ,
2013-09-17 23:27:57 +02:00
);
push @paths , map Slic3r::ExtrusionPath -> new (
2016-09-12 13:26:17 +02:00
polyline => Slic3r::Polyline -> new ( @$_ ),
2014-01-03 18:27:46 +01:00
role => EXTR_ROLE_SUPPORTMATERIAL ,
2016-10-16 16:30:56 +02:00
mm3_per_mm => $base_flow -> mm3_per_mm ,
2014-12-23 01:04:25 +01:00
width => $base_flow -> width ,
2014-04-29 23:15:36 +02:00
height => $layer -> height ,
2016-10-16 16:30:56 +02:00
), @$polylines ;
2013-09-17 23:27:57 +02:00
}
$layer -> support_fills -> append ( @paths );
}
if ( 0 ) {
require "Slic3r/SVG.pm" ;
2013-10-26 17:20:54 +02:00
Slic3r::SVG:: output ( "islands_" . $z . ".svg" ,
2013-09-17 23:27:57 +02:00
red_expolygons => union_ex ( $contact ),
green_expolygons => union_ex ( $interface ),
green_polylines => [ map $_ -> unpack -> polyline , @ { $layer -> support_contact_fills } ],
polylines => [ map $_ -> unpack -> polyline , @ { $layer -> support_fills } ],
);
}
};
Slic3r:: parallelize (
2014-01-02 17:24:23 +01:00
threads => $self -> print_config -> threads ,
2013-10-26 17:56:59 +02:00
items => [ 0 .. $# { $object -> support_layers } ],
2013-09-17 23:27:57 +02:00
thread_cb => sub {
my $q = shift ;
while ( defined ( my $layer_id = $q -> dequeue )) {
$process_layer -> ( $layer_id );
}
},
no_threads_cb => sub {
2013-10-26 17:56:59 +02:00
$process_layer -> ( $_ ) for 0 .. $# { $object -> support_layers };
2013-09-17 23:27:57 +02:00
},
);
}
2014-01-05 16:51:16 +01:00
sub generate_pillars_shape {
my ( $self , $contact , $support_z , $shape ) = @_ ;
2014-04-07 15:24:17 +02:00
# this prevents supplying an empty point set to BoundingBox constructor
return if ! %$contact ;
2014-01-05 16:51:16 +01:00
my $pillar_size = scale PILLAR_SIZE ;
my $pillar_spacing = scale PILLAR_SPACING ;
2016-10-16 16:30:56 +02:00
# A regular grid of pillars, filling the 2D bounding box.
# arrayref of polygons
2014-01-05 16:51:16 +01:00
my $grid ; # arrayref of polygons
{
2016-10-16 16:30:56 +02:00
# Rectangle with a side of 2.5x2.5mm.
2014-01-05 16:51:16 +01:00
my $pillar = Slic3r::Polygon -> new (
[ 0 , 0 ],
[ $pillar_size , 0 ],
[ $pillar_size , $pillar_size ],
[ 0 , $pillar_size ],
);
2014-04-07 15:24:17 +02:00
2016-10-16 16:30:56 +02:00
# A regular grid of pillars, filling the 2D bounding box.
2014-01-05 16:51:16 +01:00
my @pillars = ();
2016-10-16 16:30:56 +02:00
# 2D bounding box of the projection of all contact polygons.
2014-01-05 16:51:16 +01:00
my $bb = Slic3r::Geometry::BoundingBox -> new_from_points ([ map @$_ , map @$_ , values %$contact ]);
for ( my $x = $bb -> x_min ; $x <= $bb -> x_max - $pillar_size ; $x += $pillar_spacing ) {
for ( my $y = $bb -> y_min ; $y <= $bb -> y_max - $pillar_size ; $y += $pillar_spacing ) {
push @pillars , my $p = $pillar -> clone ;
$p -> translate ( $x , $y );
}
}
$grid = union ( \ @pillars );
}
# add pillars to every layer
for my $i ( 0 .. $#$support_z ) {
$shape -> [ $i ] = [ @$grid ];
}
# build capitals
for my $i ( 0 .. $#$support_z ) {
my $z = $support_z -> [ $i ];
my $capitals = intersection (
$grid ,
$contact -> { $z } // [] ,
);
# work on one pillar at time (if any) to prevent the capitals from being merged
# but store the contact area supported by the capital because we need to make
# sure nothing is left
my $contact_supported_by_capitals = [] ;
foreach my $capital ( @$capitals ) {
# enlarge capital tops
$capital = offset ([ $capital ], + ( $pillar_spacing - $pillar_size ) / 2 );
push @$contact_supported_by_capitals , @$capital ;
for ( my $j = $i - 1 ; $j >= 0 ; $j -- ) {
my $jz = $support_z -> [ $j ];
$capital = offset ( $capital , - $self -> interface_flow -> scaled_width / 2 );
last if ! @$capitals ;
push @ { $shape -> [ $j ] }, @$capital ;
}
}
# Capitals will not generally cover the whole contact area because there will be
# remainders. For now we handle this situation by projecting such unsupported
# areas to the ground, just like we would do with a normal support.
my $contact_not_supported_by_capitals = diff (
$contact -> { $z } // [] ,
$contact_supported_by_capitals ,
);
if ( @$contact_not_supported_by_capitals ) {
for ( my $j = $i - 1 ; $j >= 0 ; $j -- ) {
push @ { $shape -> [ $j ] }, @$contact_not_supported_by_capitals ;
}
}
}
}
sub clip_with_shape {
my ( $self , $support , $shape ) = @_ ;
foreach my $i ( keys %$support ) {
2014-04-30 01:51:19 +02:00
# don't clip bottom layer with shape so that we
# can generate a continuous base flange
2014-06-11 20:00:21 +02:00
# also don't clip raft layers
2014-04-30 01:51:19 +02:00
next if $i == 0 ;
2014-06-11 20:00:21 +02:00
next if $i < $self -> object_config -> raft_layers ;
2014-01-05 16:51:16 +01:00
$support -> { $i } = intersection (
$support -> { $i },
$shape -> [ $i ],
);
}
}
2013-10-27 10:19:26 +01:00
# this method returns the indices of the layers overlapping with the given one
sub overlapping_layers {
my ( $self , $i , $support_z ) = @_ ;
my $zmax = $support_z -> [ $i ];
my $zmin = ( $i == 0 ) ? 0 : $support_z -> [ $i - 1 ];
return grep {
my $zmax2 = $support_z -> [ $_ ];
my $zmin2 = ( $_ == 0 ) ? 0 : $support_z -> [ $_ - 1 ];
$zmax > $zmin2 && $zmin < $zmax2 ;
} 0 .. $#$support_z ;
}
2014-01-11 23:26:48 +01:00
sub contact_distance {
2015-01-19 09:52:24 +01:00
my ( $self , $layer_height , $nozzle_diameter ) = @_ ;
2016-10-16 16:30:56 +02:00
2015-01-19 09:52:24 +01:00
my $extra = $self -> object_config -> support_material_contact_distance ;
if ( $extra == 0 ) {
return $layer_height ;
} else {
return $nozzle_diameter + $extra ;
}
2014-01-11 23:26:48 +01:00
}
2013-09-17 23:27:57 +02:00
1 ;