2016-10-04 11:54:10 +00:00
# Instantiated by Slic3r::Print::Object->_support_material()
# only generate() and contact_distance() are called from the outside of this module.
2013-09-17 21:27:57 +00:00
package Slic3r::Print::SupportMaterial ;
use Moo ;
use List::Util qw( sum min max ) ;
use Slic3r::ExtrusionPath ':roles' ;
2014-01-02 16:24:23 +00:00
use Slic3r::Flow ':roles' ;
2015-06-02 17:44:29 +00:00
use Slic3r::Geometry qw( epsilon scale scaled_epsilon PI rad2deg deg2rad convex_hull ) ;
2013-11-21 17:47:25 +00:00
use Slic3r::Geometry::Clipper qw( offset diff union union_ex intersection offset_ex offset2
2016-11-28 16:33:17 +00:00
intersection_pl offset2_ex diff_pl JT_MITER JT_ROUND ) ;
2013-09-17 21:27:57 +00:00
use Slic3r::Surface ':types' ;
2014-01-02 23:34:30 +00: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 09:19:26 +00:00
use constant DEBUG_CONTACT_ONLY = > 0 ;
2013-09-17 21:27:57 +00:00
2013-10-27 14:22:44 +00:00
# increment used to reach MARGIN in steps to avoid trespassing thin objects
2016-12-20 11:19:13 +00:00
use constant MARGIN = > 1.5 ;
2013-10-27 14:22:44 +00:00
use constant MARGIN_STEP = > MARGIN / 3 ;
2014-01-05 15:51:16 +00:00
# generate a tree-like structure to save material
use constant PILLAR_SIZE = > 2.5 ;
use constant PILLAR_SPACING = > 10 ;
2013-09-17 21:27:57 +00:00
sub generate {
2016-10-04 11:54:10 +00:00
# $object is Slic3r::Print::Object
2013-10-26 15:56:59 +00:00
my ( $ self , $ object ) = @ _ ;
2013-10-26 15:20:54 +00:00
# Determine the top surfaces of the support, defined as:
2013-10-27 09:19:26 +00:00
# contact = overhangs - clearance + margin
2013-10-26 15:20:54 +00: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 15:56:59 +00:00
my ( $ contact , $ overhang ) = $ self - > contact_area ( $ object ) ;
2013-10-26 15:20:54 +00: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 15:56:59 +00:00
my ( $ top ) = $ self - > object_top ( $ object , $ contact ) ;
2016-10-04 11:54:10 +00:00
2013-10-26 15:20:54 +00: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 15:51:16 +00:00
my $ support_z = $ self - > support_layers_z (
2016-11-30 15:06:12 +00:00
$ object ,
2013-10-26 15:56:59 +00:00
[ sort keys %$ contact ] ,
[ sort keys %$ top ] ,
max ( map $ _ - > height , @ { $ object - > layers } )
) ;
2013-10-26 15:20:54 +00: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 15:51:16 +00:00
my $ shape = [] ;
if ( $ self - > object_config - > support_material_pattern eq 'pillars' ) {
$ self - > generate_pillars_shape ( $ contact , $ support_z , $ shape ) ;
}
2013-10-26 15:20:54 +00:00
# Propagate contact layers downwards to generate interface layers
2016-10-16 14:30:56 +00:00
my ( $ interface ) = $ self - > generate_top_interface_layers ( $ support_z , $ contact , $ top ) ;
2013-10-27 22:20:06 +00:00
$ self - > clip_with_object ( $ interface , $ support_z , $ object ) ;
2014-01-05 15:51:16 +00:00
$ self - > clip_with_shape ( $ interface , $ shape ) if @$ shape ;
2013-10-26 15:20:54 +00: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 22:20:06 +00:00
$ self - > clip_with_object ( $ base , $ support_z , $ object ) ;
2014-01-05 15:51:16 +00:00
$ self - > clip_with_shape ( $ base , $ shape ) if @$ shape ;
2013-10-26 15:20:54 +00:00
2014-12-23 00:04:25 +00: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 15:20:54 +00:00
# Install support layers into object.
2014-05-08 09:07:37 +00:00
for my $ i ( 0 .. $#$ support_z ) {
2014-05-06 08:07:18 +00: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 17:45:30 +00:00
) ;
2014-05-08 09:07:37 +00:00
if ( $ i >= 1 ) {
2014-05-06 08:07:18 +00: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 09:07:37 +00:00
}
}
2013-10-26 15:20:54 +00:00
# Generate the actual toolpaths and save them into each layer.
2013-10-26 15:56:59 +00:00
$ self - > generate_toolpaths ( $ object , $ overhang , $ contact , $ interface , $ base ) ;
2013-10-26 15:20:54 +00:00
}
sub contact_area {
2016-10-04 11:54:10 +00:00
# $object is Slic3r::Print::Object
2013-10-26 15:56:59 +00:00
my ( $ self , $ object ) = @ _ ;
2013-09-17 21:27:57 +00:00
# if user specified a custom angle threshold, convert it to radians
my $ threshold_rad ;
2014-01-02 16:24:23 +00: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 21:27:57 +00:00
Slic3r:: debugf "Threshold angle = %d°\n" , rad2deg ( $ threshold_rad ) ;
}
2016-10-04 11:54:10 +00: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 21:27:57 +00:00
# determine contact areas
my % contact = ( ) ; # contact_z => [ polygons ]
2013-10-27 14:22:44 +00:00
my % overhang = ( ) ; # contact_z => [ polygons ] - this stores the actual overhang supported by each contact layer
2013-11-02 10:16:44 +00:00
for my $ layer_id ( 0 .. $# { $ object - > layers } ) {
2013-12-23 19:12:39 +00: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 16:24:23 +00:00
if ( $ self - > object_config - > raft_layers == 0 ) {
2013-11-02 10:16:44 +00:00
next if $ layer_id == 0 ;
2014-01-02 16:24:23 +00:00
} elsif ( ! $ self - > object_config - > support_material ) {
2013-11-02 10:16:44 +00: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 15:45:44 +00:00
my $ layer = $ object - > get_layer ( $ layer_id ) ;
2016-10-04 11:54:10 +00: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 21:27:57 +00:00
# detect overhangs and contact areas needed to support them
my ( @ overhang , @ contact ) = ( ) ;
2013-12-23 19:12:39 +00: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 12:54:47 +00: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 14:30:56 +00:00
# Extend by SUPPORT_MATERIAL_MARGIN, which is 1.5mm
# MARGIN is the C++ Slic3r::SUPPORT_MATERIAL_MARGIN constant.
2013-12-23 19:12:39 +00:00
push @ contact , @ { offset ( \ @ overhang , scale + MARGIN ) } ;
} else {
2014-06-13 15:45:44 +00:00
my $ lower_layer = $ object - > get_layer ( $ layer_id - 1 ) ;
2013-12-23 19:12:39 +00:00
foreach my $ layerm ( @ { $ layer - > regions } ) {
2016-10-16 14:30:56 +00:00
# Extrusion width accounts for the roundings of the extrudates.
# It is the maximum widh of the extrudate.
2014-06-09 19:14:48 +00:00
my $ fw = $ layerm - > flow ( FLOW_ROLE_EXTERNAL_PERIMETER ) - > scaled_width ;
2013-12-23 19:12:39 +00:00
my $ diff ;
2013-09-17 21:27:57 +00:00
2013-12-23 19:12:39 +00:00
# If a threshold angle was specified, use a different logic for detecting overhangs.
if ( defined $ threshold_rad
2014-01-02 16:24:23 +00:00
|| $ layer_id < $ self - > object_config - > support_material_enforce_layers
2014-05-26 12:09:42 +00:00
|| ( $ self - > object_config - > raft_layers > 0 && $ layer_id == 0 ) ) {
2013-12-23 19:12:39 +00:00
my $ d = defined $ threshold_rad
? scale $ lower_layer - > height * ( ( cos $ threshold_rad ) / ( sin $ threshold_rad ) )
: 0 ;
2013-09-17 21:27:57 +00:00
2016-10-16 14:30:56 +00:00
# Shrinking the supported layer by layer_height/atan(threshold_rad).
2013-12-23 19:12:39 +00:00
$ diff = diff (
offset ( [ map $ _ - > p , @ { $ layerm - > slices } ] , - $ d ) ,
[ map @$ _ , @ { $ lower_layer - > slices } ] ,
) ;
2013-09-17 21:27:57 +00:00
2013-12-23 19:12:39 +00: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 14:30:56 +00:00
# Automatic overhang detection.
2013-12-23 19:12:39 +00:00
$ diff = diff (
2014-05-01 17:11:53 +00:00
[ map $ _ - > p , @ { $ layerm - > slices } ] ,
2016-10-16 14:30:56 +00: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 19:12:39 +00:00
) ;
2013-10-27 14:22:44 +00:00
2013-12-23 19:12:39 +00:00
# collapse very tiny spots
$ diff = offset2 ( $ diff , - $ fw /10, +$fw/ 10 ) ;
2013-10-27 14:22:44 +00:00
2013-12-23 19:12:39 +00:00
# $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
}
2014-04-26 15:19:50 +00:00
if ( $ self - > object_config - > dont_support_bridges ) {
2014-04-29 23:20:18 +00: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 13:16:15 +00:00
my $ bridge_flow = $ layerm - > flow ( FLOW_ROLE_PERIMETER , 1 ) ;
2014-04-29 23:20:18 +00: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 08:46:51 +00:00
my @ overhang_perimeters =
map { $ _ - > isa ( 'Slic3r::ExtrusionLoop' ) ? $ _ - > polygon - > split_at_first_point : $ _ - > polyline - > clone }
2015-01-08 14:19:56 +00:00
map @$ _ , @ { $ layerm - > perimeters } ;
2014-04-29 23:20:18 +00: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 13:16:15 +00: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-29 23:20:18 +00:00
}
2014-04-26 15:19:50 +00: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-29 23:20:18 +00:00
2014-04-26 15:19:50 +00:00
$ diff = diff (
$ diff ,
2014-04-29 23:20:18 +00:00
[
( map @$ _ , @ bridges ) ,
@$ bridged_perimeters ,
] ,
2014-04-26 15:19:50 +00:00
1 ,
) ;
2014-04-29 21:40:52 +00:00
2014-04-26 15:19:50 +00:00
push @$ diff , @ { intersection (
[ map @ { $ _ - > grow ( + scale MARGIN ) } , @ { $ layerm - > unsupported_bridge_edges } ] ,
[ map @$ _ , @ bridges ] ,
2014-04-29 21:40:52 +00:00
) } ;
2014-04-26 15:19:50 +00:00
} else {
# just remove bridged areas
$ diff = diff (
$ diff ,
2015-10-26 22:23:03 +00:00
$ layerm - > bridged ,
2014-04-26 15:19:50 +00:00
1 ,
) ;
}
2016-10-04 11:54:10 +00: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 15:19:50 +00:00
}
2016-10-04 11:54:10 +00:00
2013-12-23 19:12:39 +00:00
next if ! @$ diff ;
push @ overhang , @$ diff ; # NOTE: this is not the full overhang as it misses the outermost half of the perimeter width!
2013-09-17 21:27:57 +00:00
2013-12-23 19:12:39 +00:00
# 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 11:54:10 +00: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 19:12:39 +00:00
for ( $ fw /2, map {scale MARGIN_STEP} 1..(MARGIN / MARGIN_STEP ) ) {
$ diff = diff (
2016-10-16 14:30:56 +00:00
offset (
$ diff ,
$ _ ,
JT_ROUND ,
2016-11-28 16:33:17 +00:00
scale ( 0.05 ) ) ,
2016-10-16 14:30:56 +00:00
$ slices_margin
2013-12-23 19:12:39 +00:00
) ;
}
2013-09-17 21:27:57 +00:00
}
2013-12-23 19:12:39 +00:00
push @ contact , @$ diff ;
2013-09-17 21:27:57 +00: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-04 23:36:33 +00:00
my @ nozzle_diameters = map $ self - > print_config - > get_at ( 'nozzle_diameter' , $ _ ) ,
2014-12-16 23:34:00 +00:00
map { $ _ - > config - > perimeter_extruder - 1 , $ _ - > config - > infill_extruder - 1 , $ _ - > config - > solid_infill_extruder - 1 }
2015-10-26 22:23:03 +00:00
map $ _ - > region , @ { $ layer - > regions } ;
2013-09-17 21:27:57 +00:00
my $ nozzle_diameter = sum ( @ nozzle_diameters ) / @ nozzle_diameters ;
2015-01-19 08:52:24 +00:00
my $ contact_z = $ layer - > print_z - $ self - > contact_distance ( $ layer - > height , $ nozzle_diameter ) ;
2013-09-17 21:27:57 +00:00
2016-10-16 14:30:56 +00: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 17:44:29 +00:00
next if $ contact_z < $ self - > object_config - > get_value ( 'first_layer_height' ) - epsilon ;
2013-09-17 21:27:57 +00:00
$ contact { $ contact_z } = [ @ contact ] ;
$ overhang { $ contact_z } = [ @ overhang ] ;
if ( 0 ) {
require "Slic3r/SVG.pm" ;
2016-10-21 08:18:01 +00:00
Slic3r::SVG:: output ( Slic3r:: DEBUG_OUT_PATH_PREFIX . "contact_" . $ contact_z . ".svg" ,
2016-10-04 11:54:10 +00:00
green_expolygons = > union_ex ( $ buildplate_only_top_surfaces ) ,
blue_expolygons = > union_ex ( \ @ contact ) ,
red_expolygons = > union_ex ( \ @ overhang ) ,
2013-09-17 21:27:57 +00:00
) ;
}
}
}
2016-10-04 11:54:10 +00:00
2013-10-26 15:20:54 +00:00
return ( \ % contact , \ % overhang ) ;
}
sub object_top {
2013-10-26 15:56:59 +00:00
my ( $ self , $ object , $ contact ) = @ _ ;
2013-09-17 21:27:57 +00: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 11:54:10 +00:00
return \ % top if ( $ self - > object_config - > support_material_buildplate_only ) ;
2016-10-16 14:30:56 +00:00
# Sum of unsupported contact areas above the current $layer->print_z.
2013-10-27 09:19:26 +00: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 14:30:56 +00: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 09:19:26 +00: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-20 03:30:02 +00:00
$ top { $ layer - > print_z } = offset ( $ touching , $ self - > flow - > scaled_width + $ self - > object_config - > support_material_xy_spacing ) ;
2013-09-17 21:27:57 +00:00
}
2013-10-27 09:19:26 +00:00
# remove the areas that touched from the projection that will continue on
# next, lower, top surfaces
$ projection = diff ( $ projection , $ touching ) ;
2013-09-17 21:27:57 +00:00
}
}
2013-10-26 15:20:54 +00:00
return \ % top ;
}
sub support_layers_z {
2016-11-30 15:06:12 +00:00
my ( $ self , $ object , $ contact_z , $ top_z , $ max_object_layer_height ) = @ _ ;
2013-10-26 15:20:54 +00: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-04 23:36:33 +00:00
my $ nozzle_diameter = $ self - > print_config - > get_at ( 'nozzle_diameter' , $ self - > object_config - > support_material_extruder - 1 ) ;
2013-10-26 15:56:59 +00:00
my $ support_material_height = max ( $ max_object_layer_height , $ nozzle_diameter * 0.75 ) ;
2015-01-19 08:52:24 +00:00
my $ contact_distance = $ self - > contact_distance ( $ support_material_height , $ nozzle_diameter ) ;
2014-12-23 21:18:43 +00:00
# initialize known, fixed, support layers
2016-11-30 15:06:12 +00: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 15:20:54 +00:00
# enforce first layer height
2014-01-02 16:24:23 +00:00
my $ first_layer_height = $ self - > object_config - > get_value ( 'first_layer_height' ) ;
2013-10-26 15:20:54 +00:00
shift @ z while @ z && $ z [ 0 ] <= $ first_layer_height ;
unshift @ z , $ first_layer_height ;
2015-06-02 18:03:57 +00:00
2013-11-02 10:16:44 +00:00
# add raft layers by dividing the space between first layer and
# first contact layer evenly
2014-01-02 16:24:23 +00:00
if ( $ self - > object_config - > raft_layers > 1 && @ z >= 2 ) {
2013-11-02 10:16:44 +00:00
# $z[1] is last raft layer (contact layer for the first layer object)
2014-01-02 16:24:23 +00:00
my $ height = ( $ z [ 1 ] - $ z [ 0 ] ) / ( $ self - > object_config - > raft_layers - 1 ) ;
2015-06-02 17:58:36 +00:00
# since we already have two raft layers ($z[0] and $z[1]) we need to insert
# raft_layers-2 more
2013-11-02 10:16:44 +00:00
splice @ z , 1 , 0 ,
2014-06-11 17:58:11 +00:00
map { sprintf "%.2f" , $ _ }
2013-11-02 10:16:44 +00:00
map { $ z [ 0 ] + $ height * $ _ }
2015-06-02 17:58:36 +00:00
1 .. ( $ self - > object_config - > raft_layers - 2 ) ;
2013-11-02 10:16:44 +00:00
}
2016-11-30 15:06:12 +00: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 15:20:54 +00: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 14:30:56 +00:00
sub generate_top_interface_layers {
2013-10-26 15:20:54 +00:00
my ( $ self , $ support_z , $ contact , $ top ) = @ _ ;
2016-10-16 14:30:56 +00: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 21:27:57 +00:00
# let's now generate interface layers below contact areas
my % interface = ( ) ; # layer_id => [ polygons ]
2014-04-29 21:29:52 +00:00
my $ interface_layers_num = $ self - > object_config - > support_material_interface_layers ;
2013-10-26 15:20:54 +00:00
for my $ layer_id ( 0 .. $#$ support_z ) {
my $ z = $ support_z - > [ $ layer_id ] ;
my $ this = $ contact - > { $ z } // next ;
2013-09-17 21:27:57 +00:00
# count contact layer as interface layer
2014-04-29 21:29:52 +00:00
for ( my $ i = $ layer_id - 1 ; $ i >= 0 && $ i > $ layer_id - $ interface_layers_num ; $ i - - ) {
2013-10-26 15:20:54 +00:00
$ z = $ support_z - > [ $ i ] ;
2013-10-27 09:19:26 +00:00
my @ overlapping_layers = $ self - > overlapping_layers ( $ i , $ support_z ) ;
my @ overlapping_z = map $ support_z - > [ $ _ ] , @ overlapping_layers ;
2013-09-17 21:27:57 +00: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 09:19:26 +00:00
# surfaces vertically before performing the diff, but this needs
# investigation.
2013-09-17 21:27:57 +00: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 09:19:26 +00: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 21:27:57 +00:00
] ,
1 ,
) ;
}
}
2013-10-26 15:20:54 +00:00
return \ % interface ;
}
2014-12-23 00:04:25 +00:00
sub generate_bottom_interface_layers {
my ( $ self , $ support_z , $ base , $ top , $ interface ) = @ _ ;
2016-10-04 11:54:10 +00: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 00:04:25 +00: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 13:29:21 +00: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 00:04:25 +00:00
2014-12-29 13:29:21 +00:00
# discard too small areas
$ interface_area = [ grep abs ( $ _ - > area ) >= $ area_threshold , @$ interface_area ] ;
2014-12-23 00:04:25 +00:00
2014-12-29 13:29:21 +00:00
# subtract new interface area from base
$ base - > { $ layer_id } = diff (
$ base - > { $ layer_id } ,
$ interface_area ,
) ;
2014-12-23 00:04:25 +00:00
2014-12-29 13:29:21 +00:00
# add new interface area to interface
push @ { $ interface - > { $ layer_id } } , @$ interface_area ;
}
2014-12-23 00:04:25 +00:00
$ interface_layers + + ;
last if $ interface_layers == $ self - > object_config - > support_material_interface_layers ;
}
}
}
2013-10-26 15:20:54 +00:00
sub generate_base_layers {
my ( $ self , $ support_z , $ contact , $ interface , $ top ) = @ _ ;
2013-09-17 21:27:57 +00:00
# let's now generate support layers under interface layers
2013-10-26 15:20:54 +00:00
my $ base = { } ; # layer_id => [ polygons ]
2013-09-17 21:27:57 +00:00
{
2016-10-16 14:30:56 +00:00
my $ fillet_radius_scaled = scale ( $ self - > object_config - > support_material_spacing ) ;
2013-10-26 15:20:54 +00:00
for my $ i ( reverse 0 .. $#$ support_z - 1 ) {
my $ z = $ support_z - > [ $ i ] ;
2013-10-27 09:19:26 +00:00
my @ overlapping_layers = $ self - > overlapping_layers ( $ i , $ support_z ) ;
my @ overlapping_z = map $ support_z - > [ $ _ ] , @ overlapping_layers ;
2013-10-27 17:54:52 +00:00
# in case we have no interface layers, look at upper contact
2013-11-14 17:13:57 +00:00
# (1 interface layer means we only have contact layer, so $interface->{$i+1} is empty)
2013-10-27 17:54:52 +00:00
my @ upper_contact = ( ) ;
2014-01-02 16:24:23 +00:00
if ( $ self - > object_config - > support_material_interface_layers <= 1 ) {
2013-10-27 17:54:52 +00:00
@ upper_contact = @ { $ contact - > { $ support_z - > [ $ i + 1 ] } || [] } ;
}
2016-10-16 14:30:56 +00: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 15:20:54 +00:00
$ base - > { $ i } = diff (
2013-09-17 21:27:57 +00:00
[
2013-10-26 15:20:54 +00:00
@ { $ base - > { $ i + 1 } || [] } , # support regions on upper layer
@ { $ interface - > { $ i + 1 } || [] } , # interface regions on upper layer
2013-10-27 17:54:52 +00:00
@ upper_contact , # contact regions on upper layer
2013-09-17 21:27:57 +00:00
] ,
2016-10-16 14:30:56 +00:00
$ trim_polygons ,
1 , # safety offset to merge the touching source polygons
2013-09-17 21:27:57 +00:00
) ;
2016-10-16 14:30:56 +00: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 16:33:17 +00:00
0.2 * $ fillet_radius_scaled ) ,
2016-10-16 14:30:56 +00:00
$ trim_polygons ,
0 ) ; # don't apply the safety offset.
}
2013-09-17 21:27:57 +00:00
}
}
2013-10-26 15:20:54 +00:00
return $ base ;
}
2013-09-17 21:27:57 +00:00
2014-04-29 21:29:52 +00: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 22:20:06 +00: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 21:29:52 +00: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-20 03:30:02 +00: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 22:20:06 +00:00
$ support - > { $ i } = diff (
$ support - > { $ i } ,
2016-03-20 03:30:02 +00:00
offset ( [ map @$ _ , map @ { $ _ - > slices } , @ layers ] , + $ self - > object_config - > support_material_xy_spacing ) ,
2013-10-27 22:20:06 +00:00
) ;
}
}
2013-10-26 15:20:54 +00:00
sub generate_toolpaths {
2013-10-26 15:56:59 +00:00
my ( $ self , $ object , $ overhang , $ contact , $ interface , $ base ) = @ _ ;
2013-10-26 15:20:54 +00:00
2014-01-02 23:34:30 +00:00
my $ flow = $ self - > flow ;
my $ interface_flow = $ self - > interface_flow ;
2013-10-26 15:20:54 +00:00
# shape of contact area
2016-11-30 16:33:55 +00:00
my $ contact_loops = $ self - > object_config - > support_material_interface_contact_loops ? 1 : 0 ;
2016-04-09 17:47:08 +00:00
my $ circle_radius = 1.5 * $ interface_flow - > scaled_width - ( $ self - > object_config - > support_material_xy_spacing / 0.000001 ) ;
2013-10-26 15:20:54 +00:00
my $ circle_distance = 3 * $ circle_radius ;
2013-10-27 14:22:44 +00: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 15:20:54 +00:00
2013-09-17 21:27:57 +00:00
Slic3r:: debugf "Generating patterns\n" ;
# prepare fillers
2014-01-02 16:24:23 +00:00
my $ pattern = $ self - > object_config - > support_material_pattern ;
2016-10-04 12:38:13 +00:00
my $ with_sheath = $ self - > object_config - > support_material_with_sheath ;
2014-01-02 16:24:23 +00:00
my @ angles = ( $ self - > object_config - > support_material_angle ) ;
2013-09-17 21:27:57 +00:00
if ( $ pattern eq 'rectilinear-grid' ) {
$ pattern = 'rectilinear' ;
push @ angles , $ angles [ 0 ] + 90 ;
2014-01-05 15:51:16 +00:00
} elsif ( $ pattern eq 'pillars' ) {
$ pattern = 'honeycomb' ;
2013-09-17 21:27:57 +00:00
}
2014-01-02 16:24:23 +00:00
my $ interface_angle = $ self - > object_config - > support_material_angle + 90 ;
2014-01-02 23:34:30 +00: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 16:24:23 +00:00
my $ support_spacing = $ self - > object_config - > support_material_spacing + $ flow - > spacing ;
2013-09-17 21:27:57 +00:00
my $ support_density = $ support_spacing == 0 ? 1 : $ flow - > spacing / $ support_spacing ;
my $ process_layer = sub {
my ( $ layer_id ) = @ _ ;
2013-10-26 15:56:59 +00:00
my $ layer = $ object - > support_layers - > [ $ layer_id ] ;
2013-10-26 15:20:54 +00:00
my $ z = $ layer - > print_z ;
2013-09-17 21:27:57 +00:00
2014-11-07 22:18:35 +00: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 15:20:54 +00:00
my $ overhang = $ overhang - > { $ z } || [] ;
my $ contact = $ contact - > { $ z } || [] ;
my $ interface = $ interface - > { $ layer_id } || [] ;
my $ base = $ base - > { $ layer_id } || [] ;
2013-09-17 21:27:57 +00:00
2013-10-27 09:19:26 +00:00
if ( DEBUG_CONTACT_ONLY ) {
$ interface = [] ;
$ base = [] ;
}
2013-09-17 21:27:57 +00:00
if ( 0 ) {
require "Slic3r/SVG.pm" ;
2016-10-21 08:18:01 +00:00
Slic3r::SVG:: output ( Slic3r:: DEBUG_OUT_PATH_PREFIX . "layer_" . $ z . ".svg" ,
2016-10-16 14:30:56 +00:00
blue_expolygons = > union_ex ( $ base ) ,
2013-09-17 21:27:57 +00:00
red_expolygons = > union_ex ( $ contact ) ,
green_expolygons = > union_ex ( $ interface ) ,
) ;
}
# islands
2013-10-26 15:20:54 +00:00
$ layer - > support_islands - > append ( @ { union_ex ( [ @$ interface , @$ base , @$ contact ] ) } ) ;
2013-09-17 21:27:57 +00:00
# contact
my $ contact_infill = [] ;
2014-01-02 16:24:23 +00:00
if ( $ self - > object_config - > support_material_interface_layers == 0 ) {
2013-10-27 17:54:52 +00:00
# if no interface layers were requested we treat the contact layer
# exactly as a generic base layer
push @$ base , @$ contact ;
2016-11-30 16:33:55 +00: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 21:27:57 +00:00
# generate the outermost loop
2014-05-02 08:49:14 +00:00
# find centerline of the external loop (or any other kind of extrusions should the loop be skipped)
2016-04-09 17:47:08 +00:00
$ contact = offset ( $ contact , - ( $ _interface_flow - > scaled_width + ( $ self - > object_config - > support_material_xy_spacing / 0.000001)) / 2 ) ;
2014-05-02 08:49:14 +00:00
2013-10-27 19:48:50 +00:00
my @ loops0 = ( ) ;
2013-09-17 21:27:57 +00:00
{
# find centerline of the external loop of the contours
2014-05-02 08:49:14 +00:00
my @ external_loops = @$ contact ;
2013-09-17 21:27:57 +00:00
2013-10-27 14:22:44 +00:00
# only consider the loops facing the overhang
{
2014-11-07 22:18:35 +00:00
my $ overhang_with_margin = offset ( $ overhang , + $ _interface_flow - > scaled_width / 2 ) ;
2013-10-27 15:37:04 +00:00
@ external_loops = grep {
2013-11-21 17:47:25 +00:00
@ { intersection_pl (
[ $ _ - > split_at_first_point ] ,
$ overhang_with_margin ,
) }
2013-10-27 15:37:04 +00:00
} @ external_loops ;
2013-10-27 14:22:44 +00:00
}
2013-09-17 21:27:57 +00:00
# apply a pattern to the loop
2013-11-11 19:59:58 +00:00
my @ positions = map @ { Slic3r::Polygon - > new ( @$ _ ) - > equally_spaced_points ( $ circle_distance ) } , @ external_loops ;
2013-09-17 21:27:57 +00:00
@ loops0 = @ { diff (
[ @ external_loops ] ,
2013-10-27 14:22:44 +00:00
[ map { my $ c = $ circle - > clone ; $ c - > translate ( @$ _ ) ; $ c } @ positions ] ,
2013-09-17 21:27:57 +00:00
) } ;
}
# make more loops
my @ loops = @ loops0 ;
for my $ i ( 2 .. $ contact_loops ) {
2014-11-07 22:18:35 +00: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 21:27:57 +00:00
}
# clip such loops to the side oriented towards the object
2013-11-21 17:47:25 +00:00
@ loops = @ { intersection_pl (
[ map $ _ - > split_at_first_point , @ loops ] ,
offset ( $ overhang , + scale MARGIN ) ,
) } ;
2013-09-17 21:27:57 +00:00
# add the contact infill area to the interface area
2013-10-27 14:22:44 +00: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 10:35:58 +00:00
[ map @ { $ _ - > grow ( $ circle_radius * 1.1 ) } , @ loops ] ,
2013-10-27 14:22:44 +00:00
) ;
2013-09-17 21:27:57 +00:00
# transform loops into ExtrusionPath objects
2014-11-07 22:18:35 +00:00
my $ mm3_per_mm = $ _interface_flow - > mm3_per_mm ;
2013-09-17 21:27:57 +00:00
@ loops = map Slic3r::ExtrusionPath - > new (
2014-01-03 17:27:46 +00:00
polyline = > $ _ ,
2014-05-12 20:59:49 +00:00
role = > EXTR_ROLE_SUPPORTMATERIAL_INTERFACE ,
2014-01-03 17:27:46 +00:00
mm3_per_mm = > $ mm3_per_mm ,
2014-11-07 22:18:35 +00:00
width = > $ _interface_flow - > width ,
2014-04-29 21:15:36 +00:00
height = > $ layer - > height ,
2013-09-17 21:27:57 +00:00
) , @ loops ;
$ layer - > support_interface_fills - > append ( @ loops ) ;
}
2016-10-20 15:44:46 +00: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 09:47:00 +00:00
interface = > Slic3r::Filler - > new_from_type ( 'rectilinear' ) ,
support = > Slic3r::Filler - > new_from_type ( $ pattern ) ,
2016-10-20 15:44:46 +00:00
) ;
2016-11-02 09:47:00 +00: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 21:27:57 +00:00
# interface and contact infill
if ( @$ interface || @$ contact_infill ) {
2016-10-16 14:30:56 +00:00
$ fillers { interface } - > set_angle ( $ interface_angle ) ;
$ fillers { interface } - > set_spacing ( $ _interface_flow - > spacing ) ;
2013-09-17 21:27:57 +00:00
2014-04-29 21:29:52 +00:00
# find centerline of the external loop
2014-11-07 22:18:35 +00:00
$ interface = offset2 ( $ interface , + scaled_epsilon , - ( scaled_epsilon + $ _interface_flow - > scaled_width / 2 ) ) ;
2014-04-29 21:29:52 +00:00
2013-10-27 15:37:04 +00:00
# join regions by offsetting them to ensure they're merged
2013-10-27 14:22:44 +00:00
$ interface = offset ( [ @$ interface , @$ contact_infill ] , scaled_epsilon ) ;
2013-09-17 21:27:57 +00:00
2013-10-27 15:37:04 +00: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 21:27:57 +00:00
my @ paths = ( ) ;
foreach my $ expolygon ( @ { union_ex ( $ interface ) } ) {
2016-10-16 14:30:56 +00:00
my $ polylines = $ fillers { interface } - > fill_surface (
2013-09-17 21:27:57 +00:00
Slic3r::Surface - > new ( expolygon = > $ expolygon , surface_type = > S_TYPE_INTERNAL ) ,
2014-12-23 00:04:25 +00:00
density = > $ interface_density ,
2014-04-05 08:54:24 +00:00
layer_height = > $ layer - > height ,
2014-12-23 00:04:25 +00:00
complete = > 1 ,
2013-09-17 21:27:57 +00:00
) ;
2014-12-23 00:04:25 +00:00
my $ mm3_per_mm = $ _interface_flow - > mm3_per_mm ;
2013-09-17 21:27:57 +00:00
push @ paths , map Slic3r::ExtrusionPath - > new (
2016-09-12 11:26:17 +00:00
polyline = > Slic3r::Polyline - > new ( @$ _ ) ,
2014-05-12 20:59:49 +00:00
role = > EXTR_ROLE_SUPPORTMATERIAL_INTERFACE ,
2014-01-03 17:27:46 +00:00
mm3_per_mm = > $ mm3_per_mm ,
2014-12-23 00:04:25 +00:00
width = > $ _interface_flow - > width ,
2014-04-29 21:15:36 +00:00
height = > $ layer - > height ,
2016-10-16 14:30:56 +00:00
) , @$ polylines ,
}
2013-10-27 14:22:44 +00:00
2013-09-17 21:27:57 +00:00
$ layer - > support_interface_fills - > append ( @ paths ) ;
}
# support or flange
2013-10-26 15:20:54 +00:00
if ( @$ base ) {
2013-09-17 21:27:57 +00:00
my $ filler = $ fillers { support } ;
2016-10-16 14:30:56 +00:00
$ filler - > set_angle ( $ angles [ ( $ layer_id ) % @ angles ] ) ;
2015-02-27 17:43:15 +00: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 14:30:56 +00:00
$ filler - > set_spacing ( $ flow - > spacing ) ;
2015-02-27 17:43:15 +00:00
2014-01-03 17:27:46 +00:00
my $ density = $ support_density ;
2014-11-07 22:18:35 +00:00
my $ base_flow = $ _flow ;
2013-09-17 21:27:57 +00:00
2014-04-29 21:29:52 +00:00
# find centerline of the external loop/extrusions
2014-11-07 22:18:35 +00:00
my $ to_infill = offset2_ex ( $ base , + scaled_epsilon , - ( scaled_epsilon + $ _flow - > scaled_width / 2 ) ) ;
2016-10-16 14:30:56 +00:00
if ( 0 ) {
require "Slic3r/SVG.pm" ;
2016-10-21 08:18:01 +00:00
Slic3r::SVG:: output ( Slic3r:: DEBUG_OUT_PATH_PREFIX . "to_infill_base" . $ z . ".svg" ,
2016-10-16 14:30:56 +00:00
red_expolygons = > union_ex ( $ contact ) ,
green_expolygons = > union_ex ( $ interface ) ,
blue_expolygons = > $ to_infill ,
) ;
}
2014-04-29 21:29:52 +00:00
2013-09-17 21:27:57 +00:00
my @ paths = ( ) ;
# base flange
if ( $ layer_id == 0 ) {
$ filler = $ fillers { interface } ;
2016-10-16 14:30:56 +00:00
$ filler - > set_angle ( $ self - > object_config - > support_material_angle + 90 ) ;
2013-09-17 21:27:57 +00:00
$ density = 0.5 ;
2014-01-03 17:27:46 +00:00
$ base_flow = $ self - > first_layer_flow ;
2015-02-27 17:43:15 +00:00
# use the proper spacing for first layer as we don't need to align
# its pattern to the other layers
2016-10-16 14:30:56 +00:00
$ filler - > set_spacing ( $ base_flow - > spacing ) ;
2016-10-04 12:38:13 +00:00
} elsif ( $ with_sheath ) {
2013-09-17 21:27:57 +00:00
# draw a perimeter all around support infill
# TODO: use brim ordering algorithm
2014-11-07 22:18:35 +00:00
my $ mm3_per_mm = $ _flow - > mm3_per_mm ;
2013-09-17 21:27:57 +00:00
push @ paths , map Slic3r::ExtrusionPath - > new (
2014-01-03 17:27:46 +00:00
polyline = > $ _ - > split_at_first_point ,
role = > EXTR_ROLE_SUPPORTMATERIAL ,
mm3_per_mm = > $ mm3_per_mm ,
2014-11-07 22:18:35 +00:00
width = > $ _flow - > width ,
2014-04-29 21:15:36 +00:00
height = > $ layer - > height ,
2013-09-17 21:27:57 +00:00
) , map @$ _ , @$ to_infill ;
# TODO: use offset2_ex()
2014-11-07 22:18:35 +00:00
$ to_infill = offset_ex ( [ map @$ _ , @$ to_infill ] , - $ _flow - > scaled_spacing ) ;
2013-09-17 21:27:57 +00:00
}
foreach my $ expolygon ( @$ to_infill ) {
2016-10-16 14:30:56 +00:00
my $ polylines = $ filler - > fill_surface (
2013-09-17 21:27:57 +00:00
Slic3r::Surface - > new ( expolygon = > $ expolygon , surface_type = > S_TYPE_INTERNAL ) ,
2014-01-03 17:27:46 +00:00
density = > $ density ,
2014-04-05 08:54:24 +00:00
layer_height = > $ layer - > height ,
2014-01-03 17:27:46 +00:00
complete = > 1 ,
2013-09-17 21:27:57 +00:00
) ;
push @ paths , map Slic3r::ExtrusionPath - > new (
2016-09-12 11:26:17 +00:00
polyline = > Slic3r::Polyline - > new ( @$ _ ) ,
2014-01-03 17:27:46 +00:00
role = > EXTR_ROLE_SUPPORTMATERIAL ,
2016-10-16 14:30:56 +00:00
mm3_per_mm = > $ base_flow - > mm3_per_mm ,
2014-12-23 00:04:25 +00:00
width = > $ base_flow - > width ,
2014-04-29 21:15:36 +00:00
height = > $ layer - > height ,
2016-10-16 14:30:56 +00:00
) , @$ polylines ;
2013-09-17 21:27:57 +00:00
}
$ layer - > support_fills - > append ( @ paths ) ;
}
if ( 0 ) {
require "Slic3r/SVG.pm" ;
2013-10-26 15:20:54 +00:00
Slic3r::SVG:: output ( "islands_" . $ z . ".svg" ,
2013-09-17 21:27:57 +00: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 16:24:23 +00:00
threads = > $ self - > print_config - > threads ,
2013-10-26 15:56:59 +00:00
items = > [ 0 .. $# { $ object - > support_layers } ] ,
2013-09-17 21:27:57 +00: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 15:56:59 +00:00
$ process_layer - > ( $ _ ) for 0 .. $# { $ object - > support_layers } ;
2013-09-17 21:27:57 +00:00
} ,
) ;
}
2014-01-05 15:51:16 +00:00
sub generate_pillars_shape {
my ( $ self , $ contact , $ support_z , $ shape ) = @ _ ;
2014-04-07 13:24:17 +00:00
# this prevents supplying an empty point set to BoundingBox constructor
return if ! %$ contact ;
2014-01-05 15:51:16 +00:00
my $ pillar_size = scale PILLAR_SIZE ;
my $ pillar_spacing = scale PILLAR_SPACING ;
2016-10-16 14:30:56 +00:00
# A regular grid of pillars, filling the 2D bounding box.
# arrayref of polygons
2014-01-05 15:51:16 +00:00
my $ grid ; # arrayref of polygons
{
2016-10-16 14:30:56 +00:00
# Rectangle with a side of 2.5x2.5mm.
2014-01-05 15:51:16 +00:00
my $ pillar = Slic3r::Polygon - > new (
[ 0 , 0 ] ,
[ $ pillar_size , 0 ] ,
[ $ pillar_size , $ pillar_size ] ,
[ 0 , $ pillar_size ] ,
) ;
2014-04-07 13:24:17 +00:00
2016-10-16 14:30:56 +00:00
# A regular grid of pillars, filling the 2D bounding box.
2014-01-05 15:51:16 +00:00
my @ pillars = ( ) ;
2016-10-16 14:30:56 +00:00
# 2D bounding box of the projection of all contact polygons.
2014-01-05 15:51:16 +00: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-29 23:51:19 +00:00
# don't clip bottom layer with shape so that we
# can generate a continuous base flange
2014-06-11 18:00:21 +00:00
# also don't clip raft layers
2014-04-29 23:51:19 +00:00
next if $ i == 0 ;
2014-06-11 18:00:21 +00:00
next if $ i < $ self - > object_config - > raft_layers ;
2014-01-05 15:51:16 +00:00
$ support - > { $ i } = intersection (
$ support - > { $ i } ,
$ shape - > [ $ i ] ,
) ;
}
}
2013-10-27 09:19:26 +00: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 22:26:48 +00:00
sub contact_distance {
2015-01-19 08:52:24 +00:00
my ( $ self , $ layer_height , $ nozzle_diameter ) = @ _ ;
2016-10-16 14:30:56 +00:00
2015-01-19 08:52:24 +00:00
my $ extra = $ self - > object_config - > support_material_contact_distance ;
if ( $ extra == 0 ) {
return $ layer_height ;
} else {
return $ nozzle_diameter + $ extra ;
}
2014-01-11 22:26:48 +00:00
}
2013-09-17 21:27:57 +00:00
1 ;