2011-09-01 21:06:28 +02:00
package Slic3r::Print ;
2011-09-06 11:50:43 +02:00
use Moo ;
2011-09-01 21:06:28 +02:00
2012-04-30 14:56:01 +02:00
use File::Basename qw(basename fileparse) ;
2012-08-07 23:37:16 +02:00
use File::Spec ;
2013-07-29 20:49:54 +02:00
use List::Util qw(min max first) ;
2012-10-22 15:29:54 +02:00
use Math::ConvexHull::MonotoneChain qw(convex_hull) ;
2012-05-19 15:40:11 +02:00
use Slic3r::ExtrusionPath ':roles' ;
2013-08-27 00:52:20 +02:00
use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points chained_path) ;
2013-11-02 14:44:30 +01:00
use Slic3r::Geometry::Clipper qw(diff_ex union_ex union_pt intersection_ex intersection offset
2013-08-27 01:26:44 +02:00
offset2 traverse_pt JT_ROUND JT_SQUARE) ;
2012-04-30 14:56:01 +02:00
use Time::HiRes qw(gettimeofday tv_interval) ;
2011-09-25 22:11:56 +02:00
2012-07-29 17:02:46 +02:00
has 'config' => ( is => 'rw' , default => sub { Slic3r::Config -> new_from_defaults }, trigger => 1 );
2012-09-12 15:29:44 +02:00
has 'extra_variables' => ( is => 'rw' , default => sub {{}});
2012-04-30 14:56:01 +02:00
has 'objects' => ( is => 'rw' , default => sub { [] });
2012-09-23 02:40:25 +02:00
has 'processing_time' => ( is => 'rw' );
has 'extruders' => ( is => 'rw' , default => sub { [] });
2012-09-23 02:52:31 +02:00
has 'regions' => ( is => 'rw' , default => sub { [] });
2012-09-23 02:40:25 +02:00
has 'support_material_flow' => ( is => 'rw' );
has 'first_layer_support_material_flow' => ( is => 'rw' );
2013-02-04 15:48:57 +01:00
has 'has_support_material' => ( is => 'lazy' );
2011-09-03 20:47:38 +02:00
2012-04-29 12:51:20 +02:00
# ordered collection of extrusion paths to build skirt loops
2013-09-16 10:33:30 +02:00
has 'skirt' => ( is => 'rw' , default => sub { Slic3r::ExtrusionPath::Collection -> new });
2011-09-01 21:06:28 +02:00
2012-06-23 21:31:29 +02:00
# ordered collection of extrusion paths to build a brim
2013-09-16 10:33:30 +02:00
has 'brim' => ( is => 'rw' , default => sub { Slic3r::ExtrusionPath::Collection -> new });
2012-06-23 21:31:29 +02:00
2012-07-27 21:13:03 +02:00
sub BUILD {
my $self = shift ;
2012-07-29 17:02:46 +02:00
# call this manually because the 'default' coderef doesn't trigger the trigger
$self -> _trigger_config ;
}
sub _trigger_config {
my $self = shift ;
2012-07-27 21:13:03 +02:00
# store config in a handy place
$ Slic3r:: Config = $self -> config ;
2012-08-30 23:04:56 +02:00
# legacy with existing config files
$self -> config -> set ( 'first_layer_height' , $self -> config -> layer_height )
if ! $self -> config -> first_layer_height ;
$self -> config -> set_ifndef ( 'small_perimeter_speed' , $self -> config -> perimeter_speed );
$self -> config -> set_ifndef ( 'bridge_speed' , $self -> config -> infill_speed );
$self -> config -> set_ifndef ( 'solid_infill_speed' , $self -> config -> infill_speed );
$self -> config -> set_ifndef ( 'top_solid_infill_speed' , $self -> config -> solid_infill_speed );
2012-10-25 12:21:04 +02:00
$self -> config -> set_ifndef ( 'top_solid_layers' , $self -> config -> solid_layers );
$self -> config -> set_ifndef ( 'bottom_solid_layers' , $self -> config -> solid_layers );
2012-08-30 23:04:56 +02:00
2012-07-27 21:13:03 +02:00
# G-code flavors
$self -> config -> set ( 'extrusion_axis' , 'A' ) if $self -> config -> gcode_flavor eq 'mach3' ;
$self -> config -> set ( 'extrusion_axis' , '' ) if $self -> config -> gcode_flavor eq 'no-extrusion' ;
2013-05-13 20:15:45 +02:00
# enforce some settings when spiral_vase is set
if ( $self -> config -> spiral_vase ) {
$self -> config -> set ( 'perimeters' , 1 );
$self -> config -> set ( 'fill_density' , 0 );
$self -> config -> set ( 'top_solid_layers' , 0 );
$self -> config -> set ( 'support_material' , 0 );
2013-05-14 12:42:48 +02:00
$self -> config -> set ( 'support_material_enforce_layers' , 0 );
2013-05-13 21:55:34 +02:00
$self -> config -> set ( 'retract_layer_change' , [ 0 ]); # TODO: only apply this to the spiral layers
2013-05-13 20:15:45 +02:00
}
2013-07-29 19:43:57 +02:00
# force all retraction lift values to be the same
$self -> config -> set ( 'retract_lift' , [ map $self -> config -> retract_lift -> [ 0 ], @ { $self -> config -> retract_lift } ]);
2012-07-27 21:13:03 +02:00
}
2013-02-04 15:48:57 +01:00
sub _build_has_support_material {
my $self = shift ;
2013-08-25 14:37:50 +02:00
return ( first { $_ -> config -> support_material } @ { $self -> objects })
|| ( first { $_ -> config -> raft_layers > 0 } @ { $self -> objects })
|| ( first { $_ -> config -> support_material_enforce_layers > 0 } @ { $self -> objects });
2013-02-04 15:48:57 +01:00
}
2013-05-18 16:48:26 +02:00
# caller is responsible for supplying models whose objects don't collide
# and have explicit instance positions
2012-08-29 16:49:38 +02:00
sub add_model {
my $self = shift ;
my ( $model ) = @_ ;
2013-05-18 16:48:26 +02:00
# optimization: if avoid_crossing_perimeters is enabled, split
# this mesh into distinct objects so that we reduce the complexity
# of the graphs
2013-06-16 16:04:19 +02:00
# -- Disabling this one because there are too many legit objects having nested shells
2013-07-29 19:50:47 +02:00
# -- It also caused a bug where plater rotation was applied to each single object by the
# -- code below (thus around its own center), instead of being applied to the whole
# -- thing before the split.
2013-06-16 16:04:19 +02:00
###$model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects;
2013-05-18 16:48:26 +02:00
2013-08-25 19:52:32 +02:00
my %unmapped_materials = ();
2012-08-29 16:49:38 +02:00
foreach my $object ( @ { $model -> objects }) {
2013-06-15 12:10:57 +02:00
# we align object to origin before applying transformations
2013-06-13 14:33:10 +02:00
my @align = $object -> align_to_origin ;
2013-05-18 16:48:26 +02:00
# extract meshes by material
2012-09-23 02:52:31 +02:00
my @meshes = (); # by region_id
2012-09-22 19:04:36 +02:00
foreach my $volume ( @ { $object -> volumes }) {
2013-08-25 19:52:32 +02:00
my $region_id ;
if ( defined $volume -> material_id ) {
if ( $object -> material_mapping ) {
$region_id = $object -> material_mapping -> { $volume -> material_id } - 1
if defined $object -> material_mapping -> { $volume -> material_id };
}
$region_id // = $unmapped_materials { $volume -> material_id };
if ( ! defined $region_id ) {
$region_id = $unmapped_materials { $volume -> material_id } = scalar ( keys %unmapped_materials );
}
}
$region_id // = 0 ;
2012-09-22 19:38:25 +02:00
my $mesh = $volume -> mesh -> clone ;
2013-05-18 16:48:26 +02:00
# should the object contain multiple volumes of the same material, merge them
2012-09-23 02:52:31 +02:00
$meshes [ $region_id ] = $meshes [ $region_id ]
? Slic3r::TriangleMesh -> merge ( $meshes [ $region_id ], $mesh )
2012-09-22 19:38:25 +02:00
: $mesh ;
2012-08-29 16:49:38 +02:00
}
2013-08-25 19:52:32 +02:00
$self -> regions -> [ $_ ] // = Slic3r::Print::Region -> new for 0 .. $#meshes ;
2012-08-29 16:49:38 +02:00
2013-06-02 16:56:08 +02:00
foreach my $mesh ( grep $_ , @meshes ) {
2013-06-12 16:53:19 +02:00
# the order of these transformations must be the same as the one used in plater
# to make the object positioning consistent with the visual preview
2013-06-13 14:33:10 +02:00
# we ignore the per-instance transformations currently and only
2013-05-18 16:48:26 +02:00
# consider the first one
2013-06-13 14:33:10 +02:00
if ( $object -> instances && @ { $object -> instances }) {
2013-09-10 00:40:46 +02:00
$mesh -> rotate ( $object -> instances -> [ 0 ] -> rotation , $object -> center_2D );
2013-06-13 14:33:10 +02:00
$mesh -> scale ( $object -> instances -> [ 0 ] -> scaling_factor );
}
2012-09-22 19:04:36 +02:00
2013-05-18 16:48:26 +02:00
$mesh -> scale ( 1 / & Slic3r:: SCALING_FACTOR );
2013-09-10 00:40:46 +02:00
$mesh -> repair ;
2012-09-22 19:04:36 +02:00
}
2012-09-22 21:03:57 +02:00
2013-06-15 12:10:57 +02:00
# we also align object after transformations so that we only work with positive coordinates
# and the assumption that bounding_box === size works
2013-09-11 11:55:08 +02:00
my $bb = Slic3r::Geometry::BoundingBox -> merge ( map $_ -> bounding_box , grep $_ , @meshes );
2013-06-15 12:10:57 +02:00
my @align2 = map - $bb -> extents -> [ $_ ][ MIN ], ( X , Y , Z );
2013-09-10 00:40:46 +02:00
$_ -> translate ( @align2 ) for grep $_ , @meshes ;
2013-06-02 16:56:08 +02:00
2012-09-22 19:04:36 +02:00
# initialize print object
2013-05-18 16:48:26 +02:00
push @ { $self -> objects }, Slic3r::Print::Object -> new (
2012-09-22 19:04:36 +02:00
print => $self ,
meshes => [ @meshes ],
2013-06-02 16:56:08 +02:00
copies => [
2013-09-05 14:23:31 +02:00
map Slic3r::Point -> new ( @$_ ),
2013-06-02 19:32:53 +02:00
$object -> instances
2013-06-15 12:10:57 +02:00
? ( map [ scale ( $_ -> offset -> [ X ] - $align [ X ]) - $align2 [ X ], scale ( $_ -> offset -> [ Y ] - $align [ Y ]) - $align2 [ Y ] ], @ { $object -> instances })
2013-06-02 19:32:53 +02:00
: [ 0 , 0 ],
2013-06-02 16:56:08 +02:00
],
2013-06-15 12:10:57 +02:00
size => $bb -> size , # transformed size
2013-03-10 14:58:49 +01:00
input_file => $object -> input_file ,
2013-08-25 14:37:50 +02:00
config_overrides => $object -> config ,
2013-03-10 14:58:49 +01:00
layer_height_ranges => $object -> layer_height_ranges ,
2012-09-22 19:04:36 +02:00
);
2012-04-30 14:56:01 +02:00
}
2013-11-02 15:49:20 +01:00
if ( ! defined $self -> extra_variables -> { input_filename }) {
if ( defined ( my $input_file = $self -> objects -> [ 0 ] -> input_file )) {
2013-11-11 00:08:50 +01:00
@ { $self -> extra_variables }{ qw(input_filename input_filename_base) } = parse_filename ( $input_file );
2013-11-02 15:49:20 +01:00
}
}
2011-10-02 09:57:37 +02:00
}
2012-05-23 11:47:52 +02:00
sub validate {
my $self = shift ;
2012-07-27 21:13:03 +02:00
if ( $ Slic3r:: Config -> complete_objects ) {
2012-05-23 11:47:52 +02:00
# check horizontal clearance
{
my @a = ();
for my $obj_idx ( 0 .. $# { $self -> objects }) {
my $clearance ;
{
2012-09-22 19:04:36 +02:00
my @points = map [ @$_ [ X , Y ] ], map @ { $_ -> vertices }, @ { $self -> objects -> [ $obj_idx ] -> meshes };
2013-07-05 14:29:57 +02:00
my $convex_hull = Slic3r::Polygon -> new ( @ { convex_hull ( \ @points )});
2013-11-02 14:44:30 +01:00
( $clearance ) = @ { offset ([ $convex_hull ], scale $ Slic3r:: Config -> extruder_clearance_radius / 2 , 1 , JT_ROUND )};
2012-05-23 11:47:52 +02:00
}
2012-09-22 16:19:24 +02:00
for my $copy ( @ { $self -> objects -> [ $obj_idx ] -> copies }) {
2012-05-23 11:47:52 +02:00
my $copy_clearance = $clearance -> clone ;
$copy_clearance -> translate ( @$copy );
2013-09-17 23:38:23 +02:00
if ( @ { intersection ( \ @a , [ $copy_clearance ]) }) {
2012-05-23 11:47:52 +02:00
die "Some objects are too close; your extruder will collide with them.\n" ;
}
2013-11-02 14:44:30 +01:00
@a = map $_ -> clone , map @$_ , @ { union_ex ([ @a , $copy_clearance ])};
2012-05-23 11:47:52 +02:00
}
}
}
# check vertical clearance
{
2013-06-19 17:34:37 +02:00
my @object_height = ();
foreach my $object ( @ { $self -> objects }) {
my $height = $object -> size -> [ Z ];
push @object_height , $height for @ { $object -> copies };
}
@object_height = sort { $a <=> $b } @object_height ;
# ignore the tallest *copy* (this is why we repeat height for all of them):
# it will be printed as last one so its height doesn't matter
pop @object_height ;
2013-06-19 18:01:24 +02:00
if ( @object_height && max ( @object_height ) > scale $ Slic3r:: Config -> extruder_clearance_height ) {
2012-05-23 11:47:52 +02:00
die "Some objects are too tall and cannot be printed without extruder collisions.\n" ;
}
}
}
2013-05-13 20:15:45 +02:00
if ( $ Slic3r:: Config -> spiral_vase ) {
if (( map @ { $_ -> copies }, @ { $self -> objects }) > 1 ) {
die "The Spiral Vase option can only be used when printing a single object.\n" ;
}
2013-05-13 21:55:34 +02:00
if ( @ { $self -> regions } > 1 ) {
die "The Spiral Vase option can only be used when printing single material objects.\n" ;
}
2013-05-13 20:15:45 +02:00
}
2012-05-23 11:47:52 +02:00
}
2012-09-23 02:40:25 +02:00
sub init_extruders {
my $self = shift ;
2012-09-23 02:52:31 +02:00
# map regions to extruders (ghetto mapping for now)
my %extruder_mapping = map { $_ => $_ } 0 .. $# { $self -> regions };
2012-09-23 02:40:25 +02:00
# initialize all extruder(s) we need
my @used_extruders = (
0 ,
2013-07-31 19:52:25 +02:00
( map $self -> config -> get ( "${_}_extruder" ) - 1 , qw(perimeter infill support_material support_material_interface) ),
2012-09-23 02:40:25 +02:00
( values %extruder_mapping ),
);
for my $extruder_id ( keys % {{ map { $_ => 1 } @used_extruders }}) {
$self -> extruders -> [ $extruder_id ] = Slic3r::Extruder -> new (
2013-08-28 20:13:18 +02:00
config => $self -> config ,
2012-09-23 02:40:25 +02:00
id => $extruder_id ,
map { $_ => $self -> config -> get ( $_ ) -> [ $extruder_id ] // $self -> config -> get ( $_ ) -> [ 0 ] } #/
@ { & Slic3r::Extruder:: OPTIONS }
);
}
2012-09-23 02:52:31 +02:00
# calculate regions' flows
for my $region_id ( 0 .. $# { $self -> regions }) {
my $region = $self -> regions -> [ $region_id ];
2012-09-23 02:40:25 +02:00
# per-role extruders and flows
2013-03-17 00:02:31 +01:00
for ( qw(perimeter infill solid_infill top_infill) ) {
my $extruder_name = $_ ;
$extruder_name =~ s/^(?:solid|top)_// ;
2012-09-23 02:52:31 +02:00
$region -> extruders -> { $_ } = ( $self -> regions_count > 1 )
? $self -> extruders -> [ $extruder_mapping { $region_id }]
2013-03-07 16:00:58 +01:00
: $self -> extruders -> [ $self -> config -> get ( "${extruder_name}_extruder" ) - 1 ];
2012-09-23 02:52:31 +02:00
$region -> flows -> { $_ } = $region -> extruders -> { $_ } -> make_flow (
2012-09-23 02:40:25 +02:00
width => $self -> config -> get ( "${_}_extrusion_width" ) || $self -> config -> extrusion_width ,
2013-03-17 00:21:17 +01:00
role => $_ ,
2012-09-23 02:40:25 +02:00
);
2012-09-23 02:52:31 +02:00
$region -> first_layer_flows -> { $_ } = $region -> extruders -> { $_ } -> make_flow (
2012-09-23 02:40:25 +02:00
layer_height => $self -> config -> get_value ( 'first_layer_height' ),
width => $self -> config -> first_layer_extrusion_width ,
2013-03-17 00:21:17 +01:00
role => $_ ,
2013-02-22 16:08:11 +01:00
) if $self -> config -> first_layer_extrusion_width ;
2012-09-23 02:40:25 +02:00
}
}
# calculate support material flow
2013-07-29 20:49:54 +02:00
# Note: we should calculate a different flow for support material interface
2013-02-04 15:48:57 +01:00
if ( $self -> has_support_material ) {
2012-09-23 02:40:25 +02:00
my $extruder = $self -> extruders -> [ $self -> config -> support_material_extruder - 1 ];
$self -> support_material_flow ( $extruder -> make_flow (
width => $self -> config -> support_material_extrusion_width || $self -> config -> extrusion_width ,
2013-03-17 00:21:17 +01:00
role => 'support_material' ,
2012-09-23 02:40:25 +02:00
));
$self -> first_layer_support_material_flow ( $extruder -> make_flow (
layer_height => $self -> config -> get_value ( 'first_layer_height' ),
width => $self -> config -> first_layer_extrusion_width ,
2013-03-17 00:21:17 +01:00
role => 'support_material' ,
2012-09-23 02:40:25 +02:00
));
}
2013-09-18 20:03:59 +02:00
2013-11-15 16:01:15 +01:00
# enforce tall skirt if using ooze_prevention
# NOTE: this is not idempotent (i.e. switching ooze_prevention off will not revert skirt settings)
if ( $self -> config -> ooze_prevention && @ { $self -> extruders } > 1 ) {
2013-09-18 20:03:59 +02:00
$self -> config -> set ( 'skirt_height' , 9999999999 );
$self -> config -> set ( 'skirts' , 1 ) if $self -> config -> skirts == 0 ;
}
2012-09-23 02:40:25 +02:00
}
2012-04-29 12:51:20 +02:00
sub layer_count {
my $self = shift ;
2013-01-17 14:56:31 +01:00
return max ( map { scalar @ { $_ -> layers } } @ { $self -> objects });
2012-04-29 12:51:20 +02:00
}
2012-09-23 02:52:31 +02:00
sub regions_count {
2012-09-23 02:40:25 +02:00
my $self = shift ;
2012-09-23 02:52:31 +02:00
return scalar @ { $self -> regions };
2012-09-23 02:40:25 +02:00
}
2012-04-30 14:56:01 +02:00
sub bounding_box {
my $self = shift ;
my @points = ();
2013-05-18 16:48:26 +02:00
foreach my $object ( @ { $self -> objects }) {
foreach my $copy ( @ { $object -> copies }) {
2012-04-30 14:56:01 +02:00
push @points ,
[ $copy -> [ X ], $copy -> [ Y ] ],
2013-06-16 12:21:25 +02:00
[ $copy -> [ X ] + $object -> size -> [ X ], $copy -> [ Y ] + $object -> size -> [ Y ] ];
2012-04-30 14:56:01 +02:00
}
}
2013-08-26 23:27:51 +02:00
return Slic3r::Geometry::BoundingBox -> new_from_points ([ map Slic3r::Point -> new ( @$_ ), @points ]);
2012-04-30 14:56:01 +02:00
}
sub size {
my $self = shift ;
2013-06-16 12:21:25 +02:00
return $self -> bounding_box -> size ;
2012-04-30 14:56:01 +02:00
}
2013-03-16 19:58:34 +01:00
sub _simplify_slices {
my $self = shift ;
my ( $distance ) = @_ ;
foreach my $layer ( map @ { $_ -> layers }, @ { $self -> objects }) {
2013-07-15 15:26:56 +02:00
my @new = map $_ -> simplify ( $distance ), map $_ -> clone , @ { $layer -> slices };
2013-07-14 15:06:49 +02:00
$layer -> slices -> clear ;
$layer -> slices -> append ( @new );
2013-07-14 15:03:45 +02:00
foreach my $layerm ( @ { $layer -> regions }) {
2013-07-15 15:26:56 +02:00
my @new = map $_ -> simplify ( $distance ), map $_ -> clone , @ { $layerm -> slices };
2013-07-14 15:03:45 +02:00
$layerm -> slices -> clear ;
$layerm -> slices -> append ( @new );
}
2013-03-16 19:58:34 +01:00
}
}
2012-04-30 14:56:01 +02:00
sub export_gcode {
my $self = shift ;
my %params = @_ ;
2012-09-23 02:40:25 +02:00
$self -> init_extruders ;
2012-04-30 14:56:01 +02:00
my $status_cb = $params { status_cb } || sub {};
my $t0 = [ gettimeofday ];
# skein the STL into layers
# each layer has surfaces with holes
$status_cb -> ( 10 , "Processing triangulated mesh" );
2013-03-16 19:39:00 +01:00
$_ -> slice for @ { $self -> objects };
2012-04-30 14:56:01 +02:00
2013-06-03 11:39:23 +02:00
# remove empty layers and abort if there are no more
# as some algorithms assume all objects have at least one layer
# note: this will change object indexes
@ { $self -> objects } = grep @ { $_ -> layers }, @ { $self -> objects };
die "No layers were detected. You might want to repair your STL file(s) or check their size and retry.\n"
if ! @ { $self -> objects };
2013-03-16 19:58:34 +01:00
if ( $ Slic3r:: Config -> resolution ) {
$status_cb -> ( 15 , "Simplifying input" );
$self -> _simplify_slices ( scale $ Slic3r:: Config -> resolution );
}
2012-04-30 14:56:01 +02:00
# make perimeters
# this will add a set of extrusion loops to each layer
# as well as generate infill boundaries
$status_cb -> ( 20 , "Generating perimeters" );
2012-05-05 16:36:10 +02:00
$_ -> make_perimeters for @ { $self -> objects };
2012-04-30 14:56:01 +02:00
2012-09-23 02:52:31 +02:00
# simplify slices (both layer and region slices),
2012-09-22 19:04:36 +02:00
# we only need the max resolution for perimeters
2013-03-16 19:58:34 +01:00
$self -> _simplify_slices ( & Slic3r:: SCALED_RESOLUTION );
2012-06-23 20:08:08 +02:00
2013-03-07 15:47:32 +01:00
# this will assign a type (top/bottom/internal) to $layerm->slices
# and transform $layerm->fill_surfaces from expolygon
2012-12-22 23:57:39 +01:00
# to typed top/bottom/internal surfaces;
2012-04-30 14:56:01 +02:00
$status_cb -> ( 30 , "Detecting solid surfaces" );
$_ -> detect_surfaces_type for @ { $self -> objects };
# decide what surfaces are to be filled
$status_cb -> ( 35 , "Preparing infill surfaces" );
2012-09-23 02:52:31 +02:00
$_ -> prepare_fill_surfaces for map @ { $_ -> regions }, map @ { $_ -> layers }, @ { $self -> objects };
2012-04-30 14:56:01 +02:00
# this will detect bridges and reverse bridges
# and rearrange top/bottom/internal surfaces
$status_cb -> ( 45 , "Detect bridges" );
2013-11-11 20:37:06 +01:00
$_ -> process_external_surfaces for @ { $self -> objects };
2012-04-30 14:56:01 +02:00
# detect which fill surfaces are near external layers
# they will be split in internal and internal-solid surfaces
$status_cb -> ( 60 , "Generating horizontal shells" );
$_ -> discover_horizontal_shells for @ { $self -> objects };
2013-02-09 23:36:32 +01:00
$_ -> clip_fill_surfaces for @ { $self -> objects };
2013-02-23 21:39:13 +01:00
# the following step needs to be done before combination because it may need
# to remove only half of the combined infill
$_ -> bridge_over_infill for @ { $self -> objects };
2012-04-30 14:56:01 +02:00
# combine fill surfaces to honor the "infill every N layers" option
$status_cb -> ( 70 , "Combining infill" );
2012-07-22 20:48:38 +02:00
$_ -> combine_infill for @ { $self -> objects };
2012-04-30 14:56:01 +02:00
# this will generate extrusion paths for each layer
$status_cb -> ( 80 , "Infilling layers" );
{
Slic3r:: parallelize (
2012-09-22 19:04:36 +02:00
items => sub {
my @items = (); # [obj_idx, layer_id]
for my $obj_idx ( 0 .. $# { $self -> objects }) {
2012-09-23 02:52:31 +02:00
for my $region_id ( 0 .. ( $self -> regions_count - 1 )) {
push @items , map [ $obj_idx , $_ , $region_id ], 0 .. ( $self -> objects -> [ $obj_idx ] -> layer_count - 1 );
2012-09-22 19:04:36 +02:00
}
}
@items ;
},
2012-04-30 14:56:01 +02:00
thread_cb => sub {
my $q = shift ;
while ( defined ( my $obj_layer = $q -> dequeue )) {
2012-09-23 02:52:31 +02:00
my ( $obj_idx , $layer_id , $region_id ) = @$obj_layer ;
2013-05-19 11:35:41 +02:00
my $object = $self -> objects -> [ $obj_idx ];
2013-07-18 22:29:12 +02:00
my $layerm = $object -> layers -> [ $layer_id ] -> regions -> [ $region_id ];
$layerm -> fills -> append ( $object -> fill_maker -> make_fill ( $layerm ) );
2012-04-30 14:56:01 +02:00
}
},
2013-07-18 22:29:12 +02:00
collect_cb => sub {},
2012-04-30 14:56:01 +02:00
no_threads_cb => sub {
2012-09-23 02:52:31 +02:00
foreach my $layerm ( map @ { $_ -> regions }, map @ { $_ -> layers }, @ { $self -> objects }) {
2013-07-18 22:29:12 +02:00
$layerm -> fills -> append ( $layerm -> layer -> object -> fill_maker -> make_fill ( $layerm ));
2012-04-30 14:56:01 +02:00
}
},
);
}
# generate support material
2013-02-04 15:48:57 +01:00
if ( $self -> has_support_material ) {
2012-04-30 14:56:01 +02:00
$status_cb -> ( 85 , "Generating support material" );
2012-09-22 19:04:36 +02:00
$_ -> generate_support_material for @ { $self -> objects };
2012-04-30 14:56:01 +02:00
}
# free memory (note that support material needs fill_surfaces)
2013-07-14 14:56:43 +02:00
$_ -> fill_surfaces -> clear for map @ { $_ -> regions }, map @ { $_ -> layers }, @ { $self -> objects };
2012-04-30 14:56:01 +02:00
# make skirt
$status_cb -> ( 88 , "Generating skirt" );
$self -> make_skirt ;
2012-10-14 22:10:49 +02:00
$self -> make_brim ; # must come after make_skirt
2012-04-30 14:56:01 +02:00
2013-02-27 11:26:52 +01:00
# time to make some statistics
if ( 0 ) {
eval "use Devel::Size" ;
print "MEMORY USAGE:\n" ;
printf " meshes = %.1fMb\n" , List::Util:: sum ( map Devel::Size:: total_size ( $_ -> meshes ), @ { $self -> objects }) /1024/ 1024 ;
printf " layer slices = %.1fMb\n" , List::Util:: sum ( map Devel::Size:: total_size ( $_ -> slices ), map @ { $_ -> layers }, @ { $self -> objects }) /1024/ 1024 ;
printf " region slices = %.1fMb\n" , List::Util:: sum ( map Devel::Size:: total_size ( $_ -> slices ), map @ { $_ -> regions }, map @ { $_ -> layers }, @ { $self -> objects }) /1024/ 1024 ;
printf " perimeters = %.1fMb\n" , List::Util:: sum ( map Devel::Size:: total_size ( $_ -> perimeters ), map @ { $_ -> regions }, map @ { $_ -> layers }, @ { $self -> objects }) /1024/ 1024 ;
printf " fills = %.1fMb\n" , List::Util:: sum ( map Devel::Size:: total_size ( $_ -> fills ), map @ { $_ -> regions }, map @ { $_ -> layers }, @ { $self -> objects }) /1024/ 1024 ;
printf " print object = %.1fMb\n" , Devel::Size:: total_size ( $self ) /1024/ 1024 ;
}
2013-04-27 15:02:13 +02:00
if ( 0 ) {
eval "use Slic3r::Test::SectionCut" ;
Slic3r::Test::SectionCut -> new ( print => $self ) -> export_svg ( "section_cut.svg" );
}
2013-02-27 11:26:52 +01:00
2012-04-30 14:56:01 +02:00
# output everything to a G-code file
my $output_file = $self -> expanded_output_filepath ( $params { output_file });
2012-11-23 11:24:04 +01:00
$status_cb -> ( 90 , "Exporting G-code" . ( $output_file ? " to $output_file" : "" ));
2012-11-21 20:41:14 +01:00
$self -> write_gcode ( $params { output_fh } || $output_file );
2012-04-30 14:56:01 +02:00
# run post-processing scripts
2012-07-27 21:13:03 +02:00
if ( @ { $ Slic3r:: Config -> post_process }) {
2012-04-30 14:56:01 +02:00
$status_cb -> ( 95 , "Running post-processing scripts" );
2012-07-27 21:13:03 +02:00
$ Slic3r:: Config -> setenv ;
for ( @ { $ Slic3r:: Config -> post_process }) {
2012-04-30 14:56:01 +02:00
Slic3r:: debugf " '%s' '%s'\n" , $_ , $output_file ;
system ( $_ , $output_file );
}
}
# output some statistics
2012-11-21 20:41:14 +01:00
unless ( $params { quiet }) {
$self -> processing_time ( tv_interval ( $t0 ));
printf "Done. Process took %d minutes and %.3f seconds\n" ,
int ( $self -> processing_time / 60 ),
$self -> processing_time - int ( $self -> processing_time / 60 ) * 60 ;
# TODO: more statistics!
2013-08-28 20:13:18 +02:00
print map sprintf ( "Filament required: %.1fmm (%.1fcm3)\n" ,
$_ -> absolute_E , $_ -> extruded_volume / 1000 ),
@ { $self -> extruders };
2012-11-21 20:41:14 +01:00
}
2012-04-30 14:56:01 +02:00
}
sub export_svg {
my $self = shift ;
my %params = @_ ;
2012-09-28 16:32:53 +02:00
# this shouldn't be needed, but we're currently relying on ->make_surfaces() which
# calls ->perimeter_flow
$self -> init_extruders ;
2013-03-16 19:39:00 +01:00
$_ -> slice for @ { $self -> objects };
2012-04-30 14:56:01 +02:00
2013-06-07 12:00:03 +02:00
my $fh = $params { output_fh };
2013-10-13 11:45:22 +02:00
if ( ! $fh ) {
2013-06-07 12:00:03 +02:00
my $output_file = $self -> expanded_output_filepath ( $params { output_file });
$output_file =~ s/\.gcode$/.svg/i ;
Slic3r:: open ( \ $fh , ">" , $output_file ) or die "Failed to open $output_file for writing\n" ;
print "Exporting to $output_file..." unless $params { quiet };
}
2012-04-30 14:56:01 +02:00
my $print_size = $self -> size ;
print $fh sprintf << "EOF" , unscale ( $print_size -> [ X ]), unscale ( $print_size -> [ Y ]);
< ? xml version = "1.0" encoding = "UTF-8" standalone = "yes" ? >
<! DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" >
< svg width = "%s" height = "%s" xmlns = "http://www.w3.org/2000/svg" xmlns:svg = "http://www.w3.org/2000/svg" xmlns:xlink = "http://www.w3.org/1999/xlink" xmlns:slic3r = "http://slic3r.org/namespaces/slic3r" >
<!--
Generated using Slic3r $ Slic3r:: VERSION
http: //s lic3r . org /
-->
EOF
my $print_polygon = sub {
my ( $polygon , $type ) = @_ ;
printf $fh qq{ <polygon slic3r:type="%s" points="%s" style="fill: %s" />\n} ,
$type , ( join ' ' , map { join ',' , map unscale $_ , @$_ } @$polygon ),
2012-05-21 18:29:19 +02:00
( $type eq 'contour' ? 'white' : 'black' );
2012-04-30 14:56:01 +02:00
};
2012-06-11 14:47:48 +02:00
my @previous_layer_slices = ();
2012-04-30 14:56:01 +02:00
for my $layer_id ( 0 .. $self -> layer_count - 1 ) {
my @layers = map $_ -> layers -> [ $layer_id ], @ { $self -> objects };
printf $fh qq{ <g id="layer%d" slic3r:z="%s">\n} , $layer_id , unscale + ( grep defined $_ , @layers )[ 0 ] -> slice_z ;
2012-06-11 14:47:48 +02:00
my @current_layer_slices = ();
2012-06-11 13:50:38 +02:00
for my $obj_idx ( 0 .. $# { $self -> objects }) {
my $layer = $self -> objects -> [ $obj_idx ] -> layers -> [ $layer_id ] or next ;
2012-04-30 14:56:01 +02:00
# sort slices so that the outermost ones come first
2012-09-28 16:32:53 +02:00
my @slices = sort { $a -> contour -> encloses_point ( $b -> contour -> [ 0 ]) ? 0 : 1 } @ { $layer -> slices };
2012-09-22 16:19:24 +02:00
foreach my $copy ( @ { $self -> objects -> [ $obj_idx ] -> copies }) {
2012-04-30 14:56:01 +02:00
foreach my $slice ( @slices ) {
2012-09-28 16:32:53 +02:00
my $expolygon = $slice -> clone ;
2012-04-30 14:56:01 +02:00
$expolygon -> translate ( @$copy );
$print_polygon -> ( $expolygon -> contour , 'contour' );
2013-08-26 23:09:18 +02:00
$print_polygon -> ( $_ , 'hole' ) for @ { $expolygon -> holes };
2012-06-11 14:47:48 +02:00
push @current_layer_slices , $expolygon ;
2012-04-30 14:56:01 +02:00
}
}
}
2012-06-11 14:47:48 +02:00
# generate support material
2013-02-04 15:48:57 +01:00
if ( $self -> has_support_material && $layer_id > 0 ) {
2012-06-11 14:47:48 +02:00
my ( @supported_slices , @unsupported_slices ) = ();
foreach my $expolygon ( @current_layer_slices ) {
my $intersection = intersection_ex (
[ map @$_ , @previous_layer_slices ],
$expolygon ,
);
@$intersection
? push @supported_slices , $expolygon
: push @unsupported_slices , $expolygon ;
}
my @supported_points = map @$_ , @$_ , @supported_slices ;
foreach my $expolygon ( @unsupported_slices ) {
# look for the nearest point to this island among all
# supported points
2013-08-27 00:52:20 +02:00
my $contour = $expolygon -> contour ;
my $support_point = $contour -> first_point -> nearest_point ( \ @supported_points )
2012-09-21 16:52:05 +02:00
or next ;
2013-08-27 00:52:20 +02:00
my $anchor_point = $support_point -> nearest_point ([ @$contour ]);
2012-06-11 20:42:39 +02:00
printf $fh qq{ <line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width: 2; stroke: white" />\n} ,
2012-06-11 14:47:48 +02:00
map @$_ , $support_point , $anchor_point ;
}
}
2012-04-30 14:56:01 +02:00
print $fh qq{ </g>\n} ;
2012-06-11 14:47:48 +02:00
@previous_layer_slices = @current_layer_slices ;
2012-04-30 14:56:01 +02:00
}
print $fh "</svg>\n" ;
close $fh ;
2013-06-07 12:00:03 +02:00
print "Done.\n" unless $params { quiet };
2011-09-18 19:28:12 +02:00
}
2012-04-29 12:51:20 +02:00
sub make_skirt {
2011-11-13 18:41:12 +01:00
my $self = shift ;
2013-09-18 20:03:59 +02:00
return unless $ Slic3r:: Config -> skirts > 0
2013-11-15 16:01:15 +01:00
|| ( $ Slic3r:: Config -> ooze_prevention && @ { $self -> extruders } > 1 );
2011-11-13 18:41:12 +01:00
# collect points from all layers contained in skirt height
2012-04-29 12:51:20 +02:00
my @points = ();
foreach my $obj_idx ( 0 .. $# { $self -> objects }) {
2013-07-29 20:49:54 +02:00
my $object = $self -> objects -> [ $obj_idx ];
my @layers = map $object -> layers -> [ $_ ], 0 .. min ( $ Slic3r:: Config -> skirt_height - 1 , $# { $object -> layers });
2012-04-29 12:51:20 +02:00
my @layer_points = (
2012-09-22 19:38:25 +02:00
( map @$_ , map @$_ , map @ { $_ -> slices }, @layers ),
2012-04-29 12:51:20 +02:00
);
2013-07-29 20:49:54 +02:00
if ( @ { $object -> support_layers }) {
my @support_layers = map $object -> support_layers -> [ $_ ], 0 .. min ( $ Slic3r:: Config -> skirt_height - 1 , $# { $object -> support_layers });
push @layer_points ,
2013-09-04 00:10:53 +02:00
( map @ { $_ -> polyline }, map @ { $_ -> support_fills }, grep $_ -> support_fills , @support_layers ),
( map @ { $_ -> polyline }, map @ { $_ -> support_interface_fills }, grep $_ -> support_interface_fills , @support_layers );
2013-07-29 20:49:54 +02:00
}
push @points , map move_points ( $_ , @layer_points ), @ { $object -> copies };
2012-04-29 12:51:20 +02:00
}
2012-03-31 18:32:53 +02:00
return if @points < 3 ; # at least three points required for a convex hull
2011-11-13 18:41:12 +01:00
# find out convex hull
2013-07-15 20:31:43 +02:00
my $convex_hull = convex_hull ([ map $_ -> arrayref , @points ]);
2011-11-13 18:41:12 +01:00
2012-10-29 11:17:57 +01:00
my @extruded_length = (); # for each extruder
2013-02-22 16:08:11 +01:00
# TODO: use each extruder's own flow
my $spacing = $self -> objects -> [ 0 ] -> layers -> [ 0 ] -> regions -> [ 0 ] -> perimeter_flow -> spacing ;
2012-10-29 11:17:57 +01:00
my $first_layer_height = $ Slic3r:: Config -> get_value ( 'first_layer_height' );
my @extruders_e_per_mm = ();
my $extruder_idx = 0 ;
2011-11-13 18:41:12 +01:00
# draw outlines from outside to inside
2012-10-29 11:17:57 +01:00
# loop while we have less skirts than required or any extruder hasn't reached the min length if any
my $distance = scale $ Slic3r:: Config -> skirt_distance ;
2012-07-27 21:13:03 +02:00
for ( my $i = $ Slic3r:: Config -> skirts ; $i > 0 ; $i -- ) {
2012-10-29 11:17:57 +01:00
$distance += scale $spacing ;
2013-07-17 00:48:29 +02:00
my $loop = Slic3r::Geometry::Clipper:: offset ([ $convex_hull ], $distance , 0.0001 , JT_ROUND ) -> [ 0 ];
2013-09-16 10:33:30 +02:00
$self -> skirt -> append ( Slic3r::ExtrusionLoop -> new (
2012-10-29 11:17:57 +01:00
polygon => Slic3r::Polygon -> new ( @$loop ),
2012-10-15 10:57:15 +02:00
role => EXTR_ROLE_SKIRT ,
2012-10-29 11:17:57 +01:00
flow_spacing => $spacing ,
2013-09-16 10:33:30 +02:00
));
2012-10-29 11:17:57 +01:00
if ( $ Slic3r:: Config -> min_skirt_length > 0 ) {
$extruded_length [ $extruder_idx ] ||= 0 ;
$extruders_e_per_mm [ $extruder_idx ] ||= $self -> extruders -> [ $extruder_idx ] -> e_per_mm ( $spacing , $first_layer_height );
$extruded_length [ $extruder_idx ] += unscale $loop -> length * $extruders_e_per_mm [ $extruder_idx ];
$i ++ if defined first { ( $extruded_length [ $_ ] // 0 ) < $ Slic3r:: Config -> min_skirt_length } 0 .. $# { $self -> extruders };
if ( $extruded_length [ $extruder_idx ] >= $ Slic3r:: Config -> min_skirt_length ) {
if ( $extruder_idx < $# { $self -> extruders }) {
$extruder_idx ++ ;
next ;
}
}
}
2011-11-13 18:41:12 +01:00
}
2012-10-29 11:17:57 +01:00
2013-09-16 10:33:30 +02:00
$self -> skirt -> reverse ;
2012-02-19 12:03:36 +01:00
}
2012-06-23 21:31:29 +02:00
sub make_brim {
my $self = shift ;
2012-07-27 21:13:03 +02:00
return unless $ Slic3r:: Config -> brim_width > 0 ;
2012-06-23 21:31:29 +02:00
2013-02-22 16:08:11 +01:00
my $flow = $self -> objects -> [ 0 ] -> layers -> [ 0 ] -> regions -> [ 0 ] -> perimeter_flow ;
my $grow_distance = $flow -> scaled_width / 2 ;
2012-06-23 21:31:29 +02:00
my @islands = (); # array of polygons
foreach my $obj_idx ( 0 .. $# { $self -> objects }) {
2013-07-29 20:49:54 +02:00
my $object = $self -> objects -> [ $obj_idx ];
my $layer0 = $object -> layers -> [ 0 ];
2012-08-06 20:54:49 +02:00
my @object_islands = (
( map $_ -> contour , @ { $layer0 -> slices }),
);
2013-07-29 20:49:54 +02:00
if ( @ { $object -> support_layers }) {
my $support_layer0 = $object -> support_layers -> [ 0 ];
push @object_islands ,
2013-09-04 00:10:53 +02:00
( map $_ -> polyline -> grow ( $grow_distance ), @ { $support_layer0 -> support_fills })
2013-07-29 20:49:54 +02:00
if $support_layer0 -> support_fills ;
2013-07-31 16:29:44 +02:00
push @object_islands ,
2013-09-04 00:10:53 +02:00
( map $_ -> polyline -> grow ( $grow_distance ), @ { $support_layer0 -> support_interface_fills })
2013-07-31 16:29:44 +02:00
if $support_layer0 -> support_interface_fills ;
2013-07-29 20:49:54 +02:00
}
foreach my $copy ( @ { $object -> copies }) {
2013-09-16 10:33:30 +02:00
push @islands , map { $_ -> translate ( @$copy ); $_ } map $_ -> clone , @object_islands ;
2012-06-23 21:31:29 +02:00
}
}
2012-10-14 22:10:49 +02:00
# if brim touches skirt, make it around skirt too
2013-02-22 16:08:11 +01:00
# TODO: calculate actual skirt width (using each extruder's flow in multi-extruder setups)
if ( $ Slic3r:: Config -> skirt_distance + (( $ Slic3r:: Config -> skirts - 1 ) * $flow -> spacing ) <= $ Slic3r:: Config -> brim_width ) {
2013-07-16 09:49:34 +02:00
push @islands , map $_ -> split_at_first_point -> polyline -> grow ( $grow_distance ), @ { $self -> skirt };
2012-10-14 22:10:49 +02:00
}
2013-05-09 14:52:56 +02:00
my @loops = ();
2013-02-22 16:08:11 +01:00
my $num_loops = sprintf "%.0f" , $ Slic3r:: Config -> brim_width / $flow -> width ;
2012-06-23 21:31:29 +02:00
for my $i ( reverse 1 .. $num_loops ) {
2012-08-06 20:26:08 +02:00
# JT_SQUARE ensures no vertex is outside the given offset distance
2013-05-09 14:52:56 +02:00
# -0.5 because islands are not represented by their centerlines
2013-08-09 14:22:41 +02:00
# (first offset more, then step back - reverse order than the one used for
# perimeters because here we're offsetting outwards)
2013-08-26 16:25:42 +02:00
push @loops , @ { offset2 ( \ @islands , ( $i + 0.5 ) * $flow -> scaled_spacing , - 1.0 * $flow -> scaled_spacing , 100000 , JT_SQUARE )};
2012-06-23 21:31:29 +02:00
}
2013-05-09 14:52:56 +02:00
2013-09-16 10:33:30 +02:00
$self -> brim -> append ( map Slic3r::ExtrusionLoop -> new (
2013-07-05 14:29:57 +02:00
polygon => Slic3r::Polygon -> new ( @$_ ),
2013-05-11 09:24:48 +02:00
role => EXTR_ROLE_SKIRT ,
flow_spacing => $flow -> spacing ,
2013-09-16 10:33:30 +02:00
), reverse traverse_pt ( union_pt ( \ @loops ) ));
2012-06-23 21:31:29 +02:00
}
2012-04-30 14:56:01 +02:00
sub write_gcode {
2011-09-03 20:47:38 +02:00
my $self = shift ;
my ( $file ) = @_ ;
2012-11-21 20:41:14 +01:00
# open output gcode file if we weren't supplied a file-handle
my $fh ;
if ( ref $file eq 'IO::Scalar' ) {
$fh = $file ;
} else {
2013-01-13 10:18:34 +01:00
Slic3r:: open ( \ $fh , ">" , $file )
2012-11-21 20:41:14 +01:00
or die "Failed to open $file for writing\n" ;
}
2011-09-03 20:47:38 +02:00
2011-12-01 22:20:48 +01:00
# write some information
my @lt = localtime ;
2012-05-01 15:01:56 +02:00
printf $fh "; generated by Slic3r $Slic3r::VERSION on %04d-%02d-%02d at %02d:%02d:%02d\n\n" ,
2011-12-30 18:57:58 +01:00
$lt [ 5 ] + 1900 , $lt [ 4 ] + 1 , $lt [ 3 ], $lt [ 2 ], $lt [ 1 ], $lt [ 0 ];
2012-02-05 20:55:17 +01:00
2012-07-27 21:13:03 +02:00
print $fh "; $_\n" foreach split /\R/ , $ Slic3r:: Config -> notes ;
print $fh "\n" if $ Slic3r:: Config -> notes ;
2011-12-01 22:20:48 +01:00
2013-03-19 19:15:53 +01:00
for ( qw(layer_height perimeters top_solid_layers bottom_solid_layers fill_density perimeter_speed infill_speed travel_speed) ) {
2012-07-27 21:13:03 +02:00
printf $fh "; %s = %s\n" , $_ , $ Slic3r:: Config -> $_ ;
2011-12-01 22:20:48 +01:00
}
2012-06-28 16:22:11 +02:00
for ( qw(nozzle_diameter filament_diameter extrusion_multiplier) ) {
2012-07-27 21:13:03 +02:00
printf $fh "; %s = %s\n" , $_ , $ Slic3r:: Config -> $_ -> [ 0 ];
2012-06-28 16:22:11 +02:00
}
2012-10-29 00:31:25 +01:00
printf $fh "; perimeters extrusion width = %.2fmm\n" , $self -> regions -> [ 0 ] -> flows -> { perimeter } -> width ;
printf $fh "; infill extrusion width = %.2fmm\n" , $self -> regions -> [ 0 ] -> flows -> { infill } -> width ;
2013-03-17 00:21:17 +01:00
printf $fh "; solid infill extrusion width = %.2fmm\n" , $self -> regions -> [ 0 ] -> flows -> { solid_infill } -> width ;
printf $fh "; top infill extrusion width = %.2fmm\n" , $self -> regions -> [ 0 ] -> flows -> { top_infill } -> width ;
2012-10-29 00:31:25 +01:00
printf $fh "; support material extrusion width = %.2fmm\n" , $self -> support_material_flow -> width
if $self -> support_material_flow ;
2013-02-22 16:08:11 +01:00
printf $fh "; first layer extrusion width = %.2fmm\n" , $self -> regions -> [ 0 ] -> first_layer_flows -> { perimeter } -> width
if $self -> regions -> [ 0 ] -> first_layer_flows -> { perimeter };
2011-12-01 22:20:48 +01:00
print $fh "\n" ;
2012-05-20 20:07:39 +02:00
# set up our extruder object
2012-09-23 02:40:25 +02:00
my $gcodegen = Slic3r::GCode -> new (
2013-05-31 11:19:36 +02:00
config => $self -> config ,
2013-11-24 13:04:16 +01:00
extra_variables => $self -> extra_variables ,
2013-08-09 19:46:20 +02:00
extruders => $self -> extruders , # we should only pass the *used* extruders (but maintain the Tx indices right!)
2013-01-17 14:56:31 +01:00
layer_count => $self -> layer_count ,
2012-09-23 02:40:25 +02:00
);
2013-06-07 23:24:53 +02:00
print $fh "G21 ; set units to millimeters\n" if $ Slic3r:: Config -> gcode_flavor ne 'makerware' ;
2012-07-27 21:13:03 +02:00
print $fh $gcodegen -> set_fan ( 0 , 1 ) if $ Slic3r:: Config -> cooling && $ Slic3r:: Config -> disable_fan_first_layers ;
2012-05-20 20:07:39 +02:00
2013-07-31 18:55:23 +02:00
# set bed temperature
if (( my $temp = $ Slic3r:: Config -> first_layer_bed_temperature ) && $ Slic3r:: Config -> start_gcode !~ /M(?:190|140)/i ) {
printf $fh $gcodegen -> set_bed_temperature ( $temp , 1 );
}
# set extruder(s) temperature before and after start G-code
2012-08-22 17:58:38 +02:00
my $print_first_layer_temperature = sub {
2013-07-31 18:55:23 +02:00
my ( $wait ) = @_ ;
return if $ Slic3r:: Config -> start_gcode =~ /M(?:109|104)/i ;
for my $t ( 0 .. $# { $self -> extruders }) {
my $temp = $self -> extruders -> [ $t ] -> first_layer_temperature ;
2013-11-15 16:01:15 +01:00
$temp += $self -> config -> standby_temperature_delta if $self -> config -> ooze_prevention ;
2013-07-31 18:55:23 +02:00
printf $fh $gcodegen -> set_temperature ( $temp , $wait , $t ) if $temp > 0 ;
2012-08-22 17:58:38 +02:00
}
};
2013-07-31 18:55:23 +02:00
$print_first_layer_temperature -> ( 0 );
2013-11-24 13:04:16 +01:00
printf $fh "%s\n" , $gcodegen -> replace_variables ( $ Slic3r:: Config -> start_gcode );
2013-07-31 18:55:23 +02:00
$print_first_layer_temperature -> ( 1 );
# set other general things
2013-06-07 23:24:53 +02:00
print $fh "G90 ; use absolute coordinates\n" if $ Slic3r:: Config -> gcode_flavor ne 'makerware' ;
2012-07-27 21:13:03 +02:00
if ( $ Slic3r:: Config -> gcode_flavor =~ /^(?:reprap|teacup)$/ ) {
2012-07-06 19:57:58 +02:00
printf $fh $gcodegen -> reset_e ;
2013-06-03 18:01:14 +02:00
if ( $ Slic3r:: Config -> use_relative_e_distances ) {
print $fh "M83 ; use relative distances for extrusion\n" ;
} else {
print $fh "M82 ; use absolute distances for extrusion\n" ;
2012-02-20 11:44:30 +01:00
}
2011-09-03 20:47:38 +02:00
}
2013-08-09 19:46:20 +02:00
# always start with first extruder
# TODO: make sure we select the first *used* extruder
print $fh $gcodegen -> set_extruder ( $self -> extruders -> [ 0 ]);
2012-02-19 10:48:58 +01:00
# calculate X,Y shift to center print around specified origin
2013-06-16 12:21:25 +02:00
my $print_bb = $self -> bounding_box ;
my $print_size = $print_bb -> size ;
2012-02-19 10:48:58 +01:00
my @shift = (
2013-06-17 13:32:53 +02:00
$ Slic3r:: Config -> print_center -> [ X ] - unscale ( $print_size -> [ X ] / 2 + $print_bb -> x_min ),
$ Slic3r:: Config -> print_center -> [ Y ] - unscale ( $print_size -> [ Y ] / 2 + $print_bb -> y_min ),
2011-09-26 11:42:08 +02:00
);
2011-09-05 12:21:27 +02:00
2012-08-23 15:42:58 +02:00
# initialize a motion planner for object-to-object travel moves
if ( $ Slic3r:: Config -> avoid_crossing_perimeters ) {
my $distance_from_objects = 1 ;
# compute the offsetted convex hull for each object and repeat it for each copy.
my @islands = ();
foreach my $obj_idx ( 0 .. $# { $self -> objects }) {
2013-02-04 19:33:30 +01:00
my $convex_hull = convex_hull ([
2013-07-15 22:57:22 +02:00
map @ { $_ -> contour -> pp }, map @ { $_ -> slices }, @ { $self -> objects -> [ $obj_idx ] -> layers },
2013-02-04 19:33:30 +01:00
]);
# discard layers only containing thin walls (offset would fail on an empty polygon)
if ( @$convex_hull ) {
2013-07-16 20:09:53 +02:00
my $expolygon = Slic3r::ExPolygon -> new ( $convex_hull );
$expolygon -> translate ( scale $shift [ X ], scale $shift [ Y ]);
my @island = @ { $expolygon -> offset_ex ( scale $distance_from_objects , 1 , JT_SQUARE )};
2013-02-04 19:33:30 +01:00
foreach my $copy ( @ { $self -> objects -> [ $obj_idx ] -> copies }) {
2013-11-18 17:20:48 +01:00
push @islands , map { my $c = $_ -> clone ; $c -> translate ( @$copy ); $c } @island ;
2013-02-04 19:33:30 +01:00
}
2012-08-23 15:42:58 +02:00
}
}
2012-08-23 21:10:04 +02:00
$gcodegen -> external_mp ( Slic3r::GCode::MotionPlanner -> new (
2012-08-23 15:42:58 +02:00
islands => union_ex ([ map @$_ , @islands ]),
2013-12-22 18:47:39 +01:00
internal => 0 ,
2012-08-23 21:10:04 +02:00
));
2012-08-23 15:42:58 +02:00
}
2013-09-18 20:03:59 +02:00
# calculate wiping points if needed
2013-11-15 16:01:15 +01:00
if ( $self -> config -> ooze_prevention ) {
2013-09-18 20:03:59 +02:00
my $outer_skirt = Slic3r::Polygon -> new ( @ { convex_hull ([ map $_ -> pp , map @$_ , @ { $self -> skirt } ])});
2013-11-15 15:52:11 +01:00
my @skirts = ();
foreach my $extruder ( @ { $self -> extruders }) {
push @skirts , my $s = $outer_skirt -> clone ;
$s -> translate ( map scale ( $_ ), @ { $extruder -> extruder_offset });
}
my $convex_hull = Slic3r::Polygon -> new ( @ { convex_hull ([ map @$_ , map $_ -> pp , @skirts ])});
$gcodegen -> standby_points ([ map $_ -> clone , map @$_ , map $_ -> subdivide ( scale 10 ), @ { offset ([ $convex_hull ], scale 3 )} ]);
2013-09-18 20:03:59 +02:00
}
2013-05-18 16:57:44 +02:00
# prepare the layer processor
my $layer_gcode = Slic3r::GCode::Layer -> new (
print => $self ,
gcodegen => $gcodegen ,
shift => \ @shift ,
);
2012-05-20 20:07:39 +02:00
# do all objects for each layer
2012-07-27 21:13:03 +02:00
if ( $ Slic3r:: Config -> complete_objects ) {
2012-05-20 20:07:39 +02:00
2012-05-21 20:19:30 +02:00
# print objects from the smallest to the tallest to avoid collisions
# when moving onto next object starting point
2013-06-03 21:40:13 +02:00
my @obj_idx = sort { $self -> objects -> [ $a ] -> size -> [ Z ] <=> $self -> objects -> [ $b ] -> size -> [ Z ] } 0 .. $# { $self -> objects };
2012-05-20 20:07:39 +02:00
my $finished_objects = 0 ;
2012-05-21 20:19:30 +02:00
for my $obj_idx ( @obj_idx ) {
2012-09-22 16:19:24 +02:00
for my $copy ( @ { $self -> objects -> [ $obj_idx ] -> copies }) {
2012-05-20 20:07:39 +02:00
# move to the origin position for the copy we're going to print.
# this happens before Z goes down to layer 0 again, so that
# no collision happens hopefully.
if ( $finished_objects > 0 ) {
2012-11-06 19:54:21 +01:00
$gcodegen -> set_shift ( map $shift [ $_ ] + unscale $copy -> [ $_ ], X , Y );
2012-06-28 14:44:54 +02:00
print $fh $gcodegen -> retract ;
print $fh $gcodegen -> G0 ( Slic3r::Point -> new ( 0 , 0 ), undef , 0 , 'move to origin position for next object' );
2012-05-20 20:07:39 +02:00
}
2013-04-04 01:17:44 +02:00
my $buffer = Slic3r::GCode::CoolingBuffer -> new (
config => $ Slic3r:: Config ,
gcodegen => $gcodegen ,
);
2013-07-29 20:49:54 +02:00
my $object = $self -> objects -> [ $obj_idx ];
my @layers = sort { $a -> print_z <=> $b -> print_z } @ { $object -> layers }, @ { $object -> support_layers };
for my $layer ( @layers ) {
2012-05-20 20:07:39 +02:00
# if we are printing the bottom layer of an object, and we have already finished
# another one, set first layer temperatures. this happens before the Z move
# is triggered, so machine has more time to reach such temperatures
2013-03-10 15:36:52 +01:00
if ( $layer -> id == 0 && $finished_objects > 0 ) {
2012-07-27 21:13:03 +02:00
printf $fh $gcodegen -> set_bed_temperature ( $ Slic3r:: Config -> first_layer_bed_temperature ),
if $ Slic3r:: Config -> first_layer_bed_temperature ;
2012-08-22 17:58:38 +02:00
$print_first_layer_temperature -> ();
2012-05-20 20:07:39 +02:00
}
2013-05-31 12:18:33 +02:00
print $fh $buffer -> append (
$layer_gcode -> process_layer ( $layer , [ $copy ]),
$layer -> object . "" ,
$layer -> id ,
$layer -> print_z ,
);
2012-05-20 20:07:39 +02:00
}
2013-04-04 01:17:44 +02:00
print $fh $buffer -> flush ;
2012-05-20 20:07:39 +02:00
$finished_objects ++ ;
}
}
} else {
2013-06-03 21:54:55 +02:00
# order objects using a nearest neighbor search
2013-07-16 17:13:01 +02:00
my @obj_idx = chained_path ([ map Slic3r::Point -> new ( @ { $_ -> copies -> [ 0 ]}), @ { $self -> objects } ]);
2013-06-03 21:54:55 +02:00
# sort layers by Z
2013-07-29 20:49:54 +02:00
my %layers = (); # print_z => [ [layers], [layers], [layers] ] by obj_idx
2013-06-03 21:54:55 +02:00
foreach my $obj_idx ( 0 .. $# { $self -> objects }) {
2013-07-29 20:49:54 +02:00
my $object = $self -> objects -> [ $obj_idx ];
foreach my $layer ( @ { $object -> layers }, @ { $object -> support_layers }) {
2013-06-03 21:54:55 +02:00
$layers { $layer -> print_z } ||= [] ;
2013-07-29 20:49:54 +02:00
$layers { $layer -> print_z }[ $obj_idx ] ||= [] ;
push @ { $layers { $layer -> print_z }[ $obj_idx ]}, $layer ;
2013-06-03 21:54:55 +02:00
}
}
2013-04-04 01:17:44 +02:00
my $buffer = Slic3r::GCode::CoolingBuffer -> new (
config => $ Slic3r:: Config ,
gcodegen => $gcodegen ,
);
2013-06-03 21:54:55 +02:00
foreach my $print_z ( sort { $a <=> $b } keys %layers ) {
foreach my $obj_idx ( @obj_idx ) {
2013-07-29 20:49:54 +02:00
foreach my $layer ( @ { $layers { $print_z }[ $obj_idx ] // [] }) {
print $fh $buffer -> append (
$layer_gcode -> process_layer ( $layer , $layer -> object -> copies ),
$layer -> object . ref ( $layer ), # differentiate $obj_id between normal layers and support layers
$layer -> id ,
$layer -> print_z ,
);
}
2013-06-03 21:54:55 +02:00
}
2013-05-18 16:57:44 +02:00
}
2013-04-04 01:17:44 +02:00
print $fh $buffer -> flush ;
2011-09-03 20:47:38 +02:00
}
# write end commands to file
2013-03-30 00:36:14 +01:00
print $fh $gcodegen -> retract if $gcodegen -> extruder ; # empty prints don't even set an extruder
2012-06-28 14:44:54 +02:00
print $fh $gcodegen -> set_fan ( 0 );
2013-11-24 13:04:16 +01:00
printf $fh "%s\n" , $gcodegen -> replace_variables ( $ Slic3r:: Config -> end_gcode );
2011-09-03 20:47:38 +02:00
2013-08-28 20:13:18 +02:00
foreach my $extruder ( @ { $self -> extruders }) {
printf $fh "; filament used = %.1fmm (%.1fcm3)\n" ,
$extruder -> absolute_E , $extruder -> extruded_volume / 1000 ;
}
2011-12-20 15:29:15 +01:00
2012-11-18 19:53:52 +01:00
if ( $ Slic3r:: Config -> gcode_comments ) {
# append full config
print $fh "\n" ;
foreach my $opt_key ( sort keys % { $ Slic3r:: Config }) {
next if $ Slic3r::Config:: Options -> { $opt_key }{ shortcut };
next if $ Slic3r::Config:: Options -> { $opt_key }{ gui_only };
printf $fh "; %s = %s\n" , $opt_key , $ Slic3r:: Config -> serialize ( $opt_key );
}
}
2011-09-03 20:47:38 +02:00
# close our gcode file
close $fh ;
}
2012-09-12 16:30:44 +02:00
# this method will return the supplied input file path after expanding its
2012-04-30 14:56:01 +02:00
# format variables with their values
sub expanded_output_filepath {
my $self = shift ;
2012-09-12 16:30:44 +02:00
my ( $path , $input_file ) = @_ ;
2013-11-11 00:08:50 +01:00
my $extra_variables = {};
if ( $input_file ) {
@$extra_variables { qw(input_filename input_filename_base) } = parse_filename ( $input_file );
} else {
# if no input file was supplied, take the first one from our objects
$input_file = $self -> objects -> [ 0 ] -> input_file // return undef ;
}
2012-04-30 14:56:01 +02:00
2013-11-11 00:08:50 +01:00
if ( $path && - d $path ) {
# if output path is an existing directory, we take that and append
# the specified filename format
$path = File::Spec -> join ( $path , $self -> config -> output_filename_format );
} elsif ( ! $path ) {
# if no explicit output file was defined, we take the input
# file directory and append the specified filename format
$path = ( fileparse ( $input_file ))[ 1 ] . $self -> config -> output_filename_format ;
} else {
# path is a full path to a file so we use it as it is
}
2012-04-30 14:56:01 +02:00
2013-11-24 13:04:16 +01:00
return $self -> config -> replace_options ( $path , { % { $self -> extra_variables }, %$extra_variables });
2012-04-30 14:56:01 +02:00
}
2013-11-11 00:08:50 +01:00
# given the path to a file, this function returns its filename with and without extension
sub parse_filename {
my ( $path ) = @_ ;
my $filename = my $filename_base = basename ( $path );
$filename_base =~ s/\.[^.]+$// ;
return ( $filename , $filename_base );
}
2011-09-01 21:06:28 +02:00
1 ;