2011-09-01 19:06:28 +00:00
package Slic3r::Print ;
2011-09-06 09:50:43 +00:00
use Moo ;
2011-09-01 19:06:28 +00:00
2012-04-30 12:56:01 +00:00
use File::Basename qw( basename fileparse ) ;
2012-08-07 21:37:16 +00:00
use File::Spec ;
2013-07-29 18:49:54 +00:00
use List::Util qw( min max first ) ;
2012-10-22 13:29:54 +00:00
use Math::ConvexHull::MonotoneChain qw( convex_hull ) ;
2012-05-19 13:40:11 +00:00
use Slic3r::ExtrusionPath ':roles' ;
2013-08-26 22:52:20 +00:00
use Slic3r::Geometry qw( X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points chained_path ) ;
2013-11-02 13:44:30 +00:00
use Slic3r::Geometry::Clipper qw( diff_ex union_ex union_pt intersection_ex intersection offset
2013-08-26 23:26:44 +00:00
offset2 traverse_pt JT_ROUND JT_SQUARE ) ;
2012-04-30 12:56:01 +00:00
use Time::HiRes qw( gettimeofday tv_interval ) ;
2011-09-25 20:11:56 +00:00
2012-07-29 15:02:46 +00:00
has 'config' = > ( is = > 'rw' , default = > sub { Slic3r::Config - > new_from_defaults } , trigger = > 1 ) ;
2012-09-12 13:29:44 +00:00
has 'extra_variables' = > ( is = > 'rw' , default = > sub { { } } ) ;
2012-04-30 12:56:01 +00:00
has 'objects' = > ( is = > 'rw' , default = > sub { [] } ) ;
2012-09-23 00:40:25 +00:00
has 'processing_time' = > ( is = > 'rw' ) ;
has 'extruders' = > ( is = > 'rw' , default = > sub { [] } ) ;
2012-09-23 00:52:31 +00:00
has 'regions' = > ( is = > 'rw' , default = > sub { [] } ) ;
2012-09-23 00:40:25 +00:00
has 'support_material_flow' = > ( is = > 'rw' ) ;
has 'first_layer_support_material_flow' = > ( is = > 'rw' ) ;
2013-02-04 14:48:57 +00:00
has 'has_support_material' = > ( is = > 'lazy' ) ;
2011-09-03 18:47:38 +00:00
2012-04-29 10:51:20 +00:00
# ordered collection of extrusion paths to build skirt loops
2013-09-16 08:33:30 +00:00
has 'skirt' = > ( is = > 'rw' , default = > sub { Slic3r::ExtrusionPath::Collection - > new } ) ;
2011-09-01 19:06:28 +00:00
2012-06-23 19:31:29 +00:00
# ordered collection of extrusion paths to build a brim
2013-09-16 08:33:30 +00:00
has 'brim' = > ( is = > 'rw' , default = > sub { Slic3r::ExtrusionPath::Collection - > new } ) ;
2012-06-23 19:31:29 +00:00
2012-07-27 19:13:03 +00:00
sub BUILD {
my $ self = shift ;
2012-07-29 15:02:46 +00: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 19:13:03 +00:00
# store config in a handy place
$ Slic3r:: Config = $ self - > config ;
2012-08-30 21:04:56 +00: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 10:21:04 +00: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 21:04:56 +00:00
2012-07-27 19:13:03 +00: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 18:15:45 +00: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 10:42:48 +00:00
$ self - > config - > set ( 'support_material_enforce_layers' , 0 ) ;
2013-05-13 19:55:34 +00:00
$ self - > config - > set ( 'retract_layer_change' , [ 0 ] ) ; # TODO: only apply this to the spiral layers
2013-05-13 18:15:45 +00:00
}
2013-07-29 17:43:57 +00: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 19:13:03 +00:00
}
2013-02-04 14:48:57 +00:00
sub _build_has_support_material {
my $ self = shift ;
2013-08-25 12:37:50 +00: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 14:48:57 +00:00
}
2013-05-18 14:48:26 +00:00
# caller is responsible for supplying models whose objects don't collide
# and have explicit instance positions
2012-08-29 14:49:38 +00:00
sub add_model {
my $ self = shift ;
my ( $ model ) = @ _ ;
2013-05-18 14:48:26 +00: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 14:04:19 +00:00
# -- Disabling this one because there are too many legit objects having nested shells
2013-07-29 17:50:47 +00: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 14:04:19 +00:00
###$model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects;
2013-05-18 14:48:26 +00:00
2013-08-25 17:52:32 +00:00
my % unmapped_materials = ( ) ;
2012-08-29 14:49:38 +00:00
foreach my $ object ( @ { $ model - > objects } ) {
2013-06-15 10:10:57 +00:00
# we align object to origin before applying transformations
2013-06-13 12:33:10 +00:00
my @ align = $ object - > align_to_origin ;
2013-05-18 14:48:26 +00:00
# extract meshes by material
2012-09-23 00:52:31 +00:00
my @ meshes = ( ) ; # by region_id
2012-09-22 17:04:36 +00:00
foreach my $ volume ( @ { $ object - > volumes } ) {
2013-08-25 17:52:32 +00: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 17:38:25 +00:00
my $ mesh = $ volume - > mesh - > clone ;
2013-05-18 14:48:26 +00:00
# should the object contain multiple volumes of the same material, merge them
2012-09-23 00:52:31 +00:00
$ meshes [ $ region_id ] = $ meshes [ $ region_id ]
? Slic3r::TriangleMesh - > merge ( $ meshes [ $ region_id ] , $ mesh )
2012-09-22 17:38:25 +00:00
: $ mesh ;
2012-08-29 14:49:38 +00:00
}
2013-08-25 17:52:32 +00:00
$ self - > regions - > [ $ _ ] // = Slic3r::Print::Region - > new for 0 .. $# meshes ;
2012-08-29 14:49:38 +00:00
2013-06-02 14:56:08 +00:00
foreach my $ mesh ( grep $ _ , @ meshes ) {
2013-06-12 14:53:19 +00: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 12:33:10 +00:00
# we ignore the per-instance transformations currently and only
2013-05-18 14:48:26 +00:00
# consider the first one
2013-06-13 12:33:10 +00:00
if ( $ object - > instances && @ { $ object - > instances } ) {
2013-09-09 22:40:46 +00:00
$ mesh - > rotate ( $ object - > instances - > [ 0 ] - > rotation , $ object - > center_2D ) ;
2013-06-13 12:33:10 +00:00
$ mesh - > scale ( $ object - > instances - > [ 0 ] - > scaling_factor ) ;
}
2012-09-22 17:04:36 +00:00
2013-05-18 14:48:26 +00:00
$ mesh - > scale ( 1 / & Slic3r:: SCALING_FACTOR ) ;
2013-09-09 22:40:46 +00:00
$ mesh - > repair ;
2012-09-22 17:04:36 +00:00
}
2012-09-22 19:03:57 +00:00
2013-06-15 10:10:57 +00: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 09:55:08 +00:00
my $ bb = Slic3r::Geometry::BoundingBox - > merge ( map $ _ - > bounding_box , grep $ _ , @ meshes ) ;
2013-06-15 10:10:57 +00:00
my @ align2 = map - $ bb - > extents - > [ $ _ ] [ MIN ] , ( X , Y , Z ) ;
2013-09-09 22:40:46 +00:00
$ _ - > translate ( @ align2 ) for grep $ _ , @ meshes ;
2013-06-02 14:56:08 +00:00
2012-09-22 17:04:36 +00:00
# initialize print object
2013-05-18 14:48:26 +00:00
push @ { $ self - > objects } , Slic3r::Print::Object - > new (
2012-09-22 17:04:36 +00:00
print = > $ self ,
meshes = > [ @ meshes ] ,
2013-06-02 14:56:08 +00:00
copies = > [
2013-09-05 12:23:31 +00:00
map Slic3r::Point - > new ( @$ _ ) ,
2013-06-02 17:32:53 +00:00
$ object - > instances
2013-06-15 10:10:57 +00:00
? ( map [ scale ( $ _ - > offset - > [ X ] - $ align [ X ] ) - $ align2 [ X ] , scale ( $ _ - > offset - > [ Y ] - $ align [ Y ] ) - $ align2 [ Y ] ] , @ { $ object - > instances } )
2013-06-02 17:32:53 +00:00
: [ 0 , 0 ] ,
2013-06-02 14:56:08 +00:00
] ,
2013-06-15 10:10:57 +00:00
size = > $ bb - > size , # transformed size
2013-03-10 13:58:49 +00:00
input_file = > $ object - > input_file ,
2013-08-25 12:37:50 +00:00
config_overrides = > $ object - > config ,
2013-03-10 13:58:49 +00:00
layer_height_ranges = > $ object - > layer_height_ranges ,
2012-09-22 17:04:36 +00:00
) ;
2012-04-30 12:56:01 +00:00
}
2013-11-02 14:49:20 +00:00
if ( ! defined $ self - > extra_variables - > { input_filename } ) {
if ( defined ( my $ input_file = $ self - > objects - > [ 0 ] - > input_file ) ) {
2013-11-10 23:08:50 +00:00
@ { $ self - > extra_variables } { qw( input_filename input_filename_base ) } = parse_filename ( $ input_file ) ;
2013-11-02 14:49:20 +00:00
}
}
2011-10-02 07:57:37 +00:00
}
2012-05-23 09:47:52 +00:00
sub validate {
my $ self = shift ;
2012-07-27 19:13:03 +00:00
if ( $ Slic3r:: Config - > complete_objects ) {
2012-05-23 09:47:52 +00:00
# check horizontal clearance
{
my @ a = ( ) ;
for my $ obj_idx ( 0 .. $# { $ self - > objects } ) {
my $ clearance ;
{
2012-09-22 17:04:36 +00:00
my @ points = map [ @$ _ [ X , Y ] ] , map @ { $ _ - > vertices } , @ { $ self - > objects - > [ $ obj_idx ] - > meshes } ;
2013-07-05 12:29:57 +00:00
my $ convex_hull = Slic3r::Polygon - > new ( @ { convex_hull ( \ @ points ) } ) ;
2013-11-02 13:44:30 +00:00
( $ clearance ) = @ { offset ( [ $ convex_hull ] , scale $ Slic3r:: Config - > extruder_clearance_radius / 2 , 1 , JT_ROUND ) } ;
2012-05-23 09:47:52 +00:00
}
2012-09-22 14:19:24 +00:00
for my $ copy ( @ { $ self - > objects - > [ $ obj_idx ] - > copies } ) {
2012-05-23 09:47:52 +00:00
my $ copy_clearance = $ clearance - > clone ;
$ copy_clearance - > translate ( @$ copy ) ;
2013-09-17 21:38:23 +00:00
if ( @ { intersection ( \ @ a , [ $ copy_clearance ] ) } ) {
2012-05-23 09:47:52 +00:00
die "Some objects are too close; your extruder will collide with them.\n" ;
}
2013-11-02 13:44:30 +00:00
@ a = map $ _ - > clone , map @$ _ , @ { union_ex ( [ @ a , $ copy_clearance ] ) } ;
2012-05-23 09:47:52 +00:00
}
}
}
# check vertical clearance
{
2013-06-19 15:34:37 +00: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 16:01:24 +00:00
if ( @ object_height && max ( @ object_height ) > scale $ Slic3r:: Config - > extruder_clearance_height ) {
2012-05-23 09:47:52 +00:00
die "Some objects are too tall and cannot be printed without extruder collisions.\n" ;
}
}
}
2013-05-13 18:15:45 +00: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 19:55:34 +00:00
if ( @ { $ self - > regions } > 1 ) {
die "The Spiral Vase option can only be used when printing single material objects.\n" ;
}
2013-05-13 18:15:45 +00:00
}
2012-05-23 09:47:52 +00:00
}
2012-09-23 00:40:25 +00:00
sub init_extruders {
my $ self = shift ;
2012-09-23 00:52:31 +00:00
# map regions to extruders (ghetto mapping for now)
my % extruder_mapping = map { $ _ = > $ _ } 0 .. $# { $ self - > regions } ;
2012-09-23 00:40:25 +00:00
# initialize all extruder(s) we need
my @ used_extruders = (
0 ,
2013-07-31 17:52:25 +00:00
( map $ self - > config - > get ( "${_}_extruder" ) - 1 , qw( perimeter infill support_material support_material_interface ) ) ,
2012-09-23 00:40:25 +00:00
( values % extruder_mapping ) ,
) ;
for my $ extruder_id ( keys % { { map { $ _ = > 1 } @ used_extruders } } ) {
$ self - > extruders - > [ $ extruder_id ] = Slic3r::Extruder - > new (
2013-08-28 18:13:18 +00:00
config = > $ self - > config ,
2012-09-23 00:40:25 +00:00
id = > $ extruder_id ,
map { $ _ = > $ self - > config - > get ( $ _ ) - > [ $ extruder_id ] // $ self - > config - > get ( $ _ ) - > [ 0 ] } #/
@ { & Slic3r::Extruder:: OPTIONS }
) ;
}
2012-09-23 00:52:31 +00:00
# calculate regions' flows
for my $ region_id ( 0 .. $# { $ self - > regions } ) {
my $ region = $ self - > regions - > [ $ region_id ] ;
2012-09-23 00:40:25 +00:00
# per-role extruders and flows
2013-03-16 23:02:31 +00:00
for ( qw( perimeter infill solid_infill top_infill ) ) {
my $ extruder_name = $ _ ;
$ extruder_name =~ s/^(?:solid|top)_// ;
2012-09-23 00:52:31 +00:00
$ region - > extruders - > { $ _ } = ( $ self - > regions_count > 1 )
? $ self - > extruders - > [ $ extruder_mapping { $ region_id } ]
2013-03-07 15:00:58 +00:00
: $ self - > extruders - > [ $ self - > config - > get ( "${extruder_name}_extruder" ) - 1 ] ;
2012-09-23 00:52:31 +00:00
$ region - > flows - > { $ _ } = $ region - > extruders - > { $ _ } - > make_flow (
2012-09-23 00:40:25 +00:00
width = > $ self - > config - > get ( "${_}_extrusion_width" ) || $ self - > config - > extrusion_width ,
2013-03-16 23:21:17 +00:00
role = > $ _ ,
2012-09-23 00:40:25 +00:00
) ;
2012-09-23 00:52:31 +00:00
$ region - > first_layer_flows - > { $ _ } = $ region - > extruders - > { $ _ } - > make_flow (
2012-09-23 00:40:25 +00:00
layer_height = > $ self - > config - > get_value ( 'first_layer_height' ) ,
width = > $ self - > config - > first_layer_extrusion_width ,
2013-03-16 23:21:17 +00:00
role = > $ _ ,
2013-02-22 15:08:11 +00:00
) if $ self - > config - > first_layer_extrusion_width ;
2012-09-23 00:40:25 +00:00
}
}
# calculate support material flow
2013-07-29 18:49:54 +00:00
# Note: we should calculate a different flow for support material interface
2013-02-04 14:48:57 +00:00
if ( $ self - > has_support_material ) {
2012-09-23 00:40:25 +00: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-16 23:21:17 +00:00
role = > 'support_material' ,
2012-09-23 00:40:25 +00: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-16 23:21:17 +00:00
role = > 'support_material' ,
2012-09-23 00:40:25 +00:00
) ) ;
}
2013-09-18 18:03:59 +00:00
# enforce tall skirt if using standby_temperature
# NOTE: this is not idempotent (i.e. switching standby_temperature off will not revert skirt settings)
if ( $ self - > config - > standby_temperature ) {
$ self - > config - > set ( 'skirt_height' , 9999999999 ) ;
$ self - > config - > set ( 'skirts' , 1 ) if $ self - > config - > skirts == 0 ;
}
2012-09-23 00:40:25 +00:00
}
2012-04-29 10:51:20 +00:00
sub layer_count {
my $ self = shift ;
2013-01-17 13:56:31 +00:00
return max ( map { scalar @ { $ _ - > layers } } @ { $ self - > objects } ) ;
2012-04-29 10:51:20 +00:00
}
2012-09-23 00:52:31 +00:00
sub regions_count {
2012-09-23 00:40:25 +00:00
my $ self = shift ;
2012-09-23 00:52:31 +00:00
return scalar @ { $ self - > regions } ;
2012-09-23 00:40:25 +00:00
}
2012-04-30 12:56:01 +00:00
sub bounding_box {
my $ self = shift ;
my @ points = ( ) ;
2013-05-18 14:48:26 +00:00
foreach my $ object ( @ { $ self - > objects } ) {
foreach my $ copy ( @ { $ object - > copies } ) {
2012-04-30 12:56:01 +00:00
push @ points ,
[ $ copy - > [ X ] , $ copy - > [ Y ] ] ,
2013-06-16 10:21:25 +00:00
[ $ copy - > [ X ] + $ object - > size - > [ X ] , $ copy - > [ Y ] + $ object - > size - > [ Y ] ] ;
2012-04-30 12:56:01 +00:00
}
}
2013-08-26 21:27:51 +00:00
return Slic3r::Geometry::BoundingBox - > new_from_points ( [ map Slic3r::Point - > new ( @$ _ ) , @ points ] ) ;
2012-04-30 12:56:01 +00:00
}
2012-03-06 03:55:21 +00:00
2012-04-30 12:56:01 +00:00
sub size {
my $ self = shift ;
2013-06-16 10:21:25 +00:00
return $ self - > bounding_box - > size ;
2012-04-30 12:56:01 +00:00
}
2012-03-06 03:55:21 +00:00
2013-03-16 18:58:34 +00:00
sub _simplify_slices {
my $ self = shift ;
my ( $ distance ) = @ _ ;
foreach my $ layer ( map @ { $ _ - > layers } , @ { $ self - > objects } ) {
2013-07-15 13:26:56 +00:00
my @ new = map $ _ - > simplify ( $ distance ) , map $ _ - > clone , @ { $ layer - > slices } ;
2013-07-14 13:06:49 +00:00
$ layer - > slices - > clear ;
$ layer - > slices - > append ( @ new ) ;
2013-07-14 13:03:45 +00:00
foreach my $ layerm ( @ { $ layer - > regions } ) {
2013-07-15 13:26:56 +00:00
my @ new = map $ _ - > simplify ( $ distance ) , map $ _ - > clone , @ { $ layerm - > slices } ;
2013-07-14 13:03:45 +00:00
$ layerm - > slices - > clear ;
$ layerm - > slices - > append ( @ new ) ;
}
2013-03-16 18:58:34 +00:00
}
}
2012-04-30 12:56:01 +00:00
sub export_gcode {
my $ self = shift ;
my % params = @ _ ;
2012-09-23 00:40:25 +00:00
$ self - > init_extruders ;
2012-04-30 12:56:01 +00: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 18:39:00 +00:00
$ _ - > slice for @ { $ self - > objects } ;
2012-04-30 12:56:01 +00:00
2013-06-03 09:39:23 +00: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 18:58:34 +00:00
if ( $ Slic3r:: Config - > resolution ) {
$ status_cb - > ( 15 , "Simplifying input" ) ;
$ self - > _simplify_slices ( scale $ Slic3r:: Config - > resolution ) ;
}
2012-04-30 12:56:01 +00: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 14:36:10 +00:00
$ _ - > make_perimeters for @ { $ self - > objects } ;
2012-04-30 12:56:01 +00:00
2012-09-23 00:52:31 +00:00
# simplify slices (both layer and region slices),
2012-09-22 17:04:36 +00:00
# we only need the max resolution for perimeters
2013-03-16 18:58:34 +00:00
$ self - > _simplify_slices ( & Slic3r:: SCALED_RESOLUTION ) ;
2012-06-23 18:08:08 +00:00
2013-03-07 14:47:32 +00:00
# this will assign a type (top/bottom/internal) to $layerm->slices
# and transform $layerm->fill_surfaces from expolygon
2012-12-22 22:57:39 +00:00
# to typed top/bottom/internal surfaces;
2012-04-30 12:56:01 +00: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 00:52:31 +00:00
$ _ - > prepare_fill_surfaces for map @ { $ _ - > regions } , map @ { $ _ - > layers } , @ { $ self - > objects } ;
2012-04-30 12:56:01 +00:00
# this will detect bridges and reverse bridges
# and rearrange top/bottom/internal surfaces
$ status_cb - > ( 45 , "Detect bridges" ) ;
2013-11-11 19:37:06 +00:00
$ _ - > process_external_surfaces for @ { $ self - > objects } ;
2012-04-30 12:56:01 +00: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 22:36:32 +00:00
$ _ - > clip_fill_surfaces for @ { $ self - > objects } ;
2013-02-23 20:39:13 +00: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 12:56:01 +00:00
# combine fill surfaces to honor the "infill every N layers" option
$ status_cb - > ( 70 , "Combining infill" ) ;
2012-07-22 18:48:38 +00:00
$ _ - > combine_infill for @ { $ self - > objects } ;
2012-04-30 12:56:01 +00:00
# this will generate extrusion paths for each layer
$ status_cb - > ( 80 , "Infilling layers" ) ;
{
Slic3r:: parallelize (
2012-09-22 17:04:36 +00:00
items = > sub {
my @ items = ( ) ; # [obj_idx, layer_id]
for my $ obj_idx ( 0 .. $# { $ self - > objects } ) {
2012-09-23 00:52:31 +00: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 17:04:36 +00:00
}
}
@ items ;
} ,
2012-04-30 12:56:01 +00:00
thread_cb = > sub {
my $ q = shift ;
while ( defined ( my $ obj_layer = $ q - > dequeue ) ) {
2012-09-23 00:52:31 +00:00
my ( $ obj_idx , $ layer_id , $ region_id ) = @$ obj_layer ;
2013-05-19 09:35:41 +00:00
my $ object = $ self - > objects - > [ $ obj_idx ] ;
2013-07-18 20:29:12 +00:00
my $ layerm = $ object - > layers - > [ $ layer_id ] - > regions - > [ $ region_id ] ;
$ layerm - > fills - > append ( $ object - > fill_maker - > make_fill ( $ layerm ) ) ;
2012-03-06 03:55:21 +00:00
}
2012-04-30 12:56:01 +00:00
} ,
2013-07-18 20:29:12 +00:00
collect_cb = > sub { } ,
2012-04-30 12:56:01 +00:00
no_threads_cb = > sub {
2012-09-23 00:52:31 +00:00
foreach my $ layerm ( map @ { $ _ - > regions } , map @ { $ _ - > layers } , @ { $ self - > objects } ) {
2013-07-18 20:29:12 +00:00
$ layerm - > fills - > append ( $ layerm - > layer - > object - > fill_maker - > make_fill ( $ layerm ) ) ;
2012-04-30 12:56:01 +00:00
}
} ,
) ;
}
# generate support material
2013-02-04 14:48:57 +00:00
if ( $ self - > has_support_material ) {
2012-04-30 12:56:01 +00:00
$ status_cb - > ( 85 , "Generating support material" ) ;
2012-09-22 17:04:36 +00:00
$ _ - > generate_support_material for @ { $ self - > objects } ;
2012-04-30 12:56:01 +00:00
}
# free memory (note that support material needs fill_surfaces)
2013-07-14 12:56:43 +00:00
$ _ - > fill_surfaces - > clear for map @ { $ _ - > regions } , map @ { $ _ - > layers } , @ { $ self - > objects } ;
2012-04-30 12:56:01 +00:00
# make skirt
$ status_cb - > ( 88 , "Generating skirt" ) ;
$ self - > make_skirt ;
2012-10-14 20:10:49 +00:00
$ self - > make_brim ; # must come after make_skirt
2012-04-30 12:56:01 +00:00
2013-02-27 10:26:52 +00: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 13:02:13 +00:00
if ( 0 ) {
eval "use Slic3r::Test::SectionCut" ;
Slic3r::Test::SectionCut - > new ( print = > $ self ) - > export_svg ( "section_cut.svg" ) ;
}
2013-02-27 10:26:52 +00:00
2012-04-30 12:56:01 +00:00
# output everything to a G-code file
my $ output_file = $ self - > expanded_output_filepath ( $ params { output_file } ) ;
2012-11-23 10:24:04 +00:00
$ status_cb - > ( 90 , "Exporting G-code" . ( $ output_file ? " to $output_file" : "" ) ) ;
2012-11-21 19:41:14 +00:00
$ self - > write_gcode ( $ params { output_fh } || $ output_file ) ;
2012-04-30 12:56:01 +00:00
# run post-processing scripts
2012-07-27 19:13:03 +00:00
if ( @ { $ Slic3r:: Config - > post_process } ) {
2012-04-30 12:56:01 +00:00
$ status_cb - > ( 95 , "Running post-processing scripts" ) ;
2012-07-27 19:13:03 +00:00
$ Slic3r:: Config - > setenv ;
for ( @ { $ Slic3r:: Config - > post_process } ) {
2012-04-30 12:56:01 +00:00
Slic3r:: debugf " '%s' '%s'\n" , $ _ , $ output_file ;
system ( $ _ , $ output_file ) ;
2012-02-19 09:48:58 +00:00
}
}
2012-04-30 12:56:01 +00:00
# output some statistics
2012-11-21 19:41:14 +00: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 18:13:18 +00:00
print map sprintf ( "Filament required: %.1fmm (%.1fcm3)\n" ,
$ _ - > absolute_E , $ _ - > extruded_volume / 1000 ) ,
@ { $ self - > extruders } ;
2012-11-21 19:41:14 +00:00
}
2012-02-19 09:48:58 +00:00
}
2012-04-30 12:56:01 +00:00
sub export_svg {
2011-09-18 17:28:12 +00:00
my $ self = shift ;
2012-04-30 12:56:01 +00:00
my % params = @ _ ;
2012-09-28 14:32:53 +00:00
# this shouldn't be needed, but we're currently relying on ->make_surfaces() which
# calls ->perimeter_flow
$ self - > init_extruders ;
2013-03-16 18:39:00 +00:00
$ _ - > slice for @ { $ self - > objects } ;
2012-04-30 12:56:01 +00:00
2013-06-07 10:00:03 +00:00
my $ fh = $ params { output_fh } ;
2013-10-13 09:45:22 +00:00
if ( ! $ fh ) {
2013-06-07 10:00:03 +00: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 12:56:01 +00: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 16:29:19 +00:00
( $ type eq 'contour' ? 'white' : 'black' ) ;
2012-04-30 12:56:01 +00:00
} ;
2012-06-11 12:47:48 +00:00
my @ previous_layer_slices = ( ) ;
2012-04-30 12:56:01 +00: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 12:47:48 +00:00
my @ current_layer_slices = ( ) ;
2012-06-11 11:50:38 +00:00
for my $ obj_idx ( 0 .. $# { $ self - > objects } ) {
my $ layer = $ self - > objects - > [ $ obj_idx ] - > layers - > [ $ layer_id ] or next ;
2012-04-30 12:56:01 +00:00
# sort slices so that the outermost ones come first
2012-09-28 14:32:53 +00:00
my @ slices = sort { $ a - > contour - > encloses_point ( $ b - > contour - > [ 0 ] ) ? 0 : 1 } @ { $ layer - > slices } ;
2012-09-22 14:19:24 +00:00
foreach my $ copy ( @ { $ self - > objects - > [ $ obj_idx ] - > copies } ) {
2012-04-30 12:56:01 +00:00
foreach my $ slice ( @ slices ) {
2012-09-28 14:32:53 +00:00
my $ expolygon = $ slice - > clone ;
2012-04-30 12:56:01 +00:00
$ expolygon - > translate ( @$ copy ) ;
$ print_polygon - > ( $ expolygon - > contour , 'contour' ) ;
2013-08-26 21:09:18 +00:00
$ print_polygon - > ( $ _ , 'hole' ) for @ { $ expolygon - > holes } ;
2012-06-11 12:47:48 +00:00
push @ current_layer_slices , $ expolygon ;
2012-04-30 12:56:01 +00:00
}
}
}
2012-06-11 12:47:48 +00:00
# generate support material
2013-02-04 14:48:57 +00:00
if ( $ self - > has_support_material && $ layer_id > 0 ) {
2012-06-11 12:47:48 +00: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-26 22:52:20 +00:00
my $ contour = $ expolygon - > contour ;
my $ support_point = $ contour - > first_point - > nearest_point ( \ @ supported_points )
2012-09-21 14:52:05 +00:00
or next ;
2013-08-26 22:52:20 +00:00
my $ anchor_point = $ support_point - > nearest_point ( [ @$ contour ] ) ;
2012-06-11 18:42:39 +00:00
printf $ fh qq{ <line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width: 2; stroke: white" /> \ n } ,
2012-06-11 12:47:48 +00:00
map @$ _ , $ support_point , $ anchor_point ;
}
}
2012-04-30 12:56:01 +00:00
print $ fh qq{ </g> \ n } ;
2012-06-11 12:47:48 +00:00
@ previous_layer_slices = @ current_layer_slices ;
2012-04-30 12:56:01 +00:00
}
print $ fh "</svg>\n" ;
close $ fh ;
2013-06-07 10:00:03 +00:00
print "Done.\n" unless $ params { quiet } ;
2011-09-25 20:11:56 +00:00
}
2012-04-29 10:51:20 +00:00
sub make_skirt {
2011-11-13 17:41:12 +00:00
my $ self = shift ;
2013-09-18 18:03:59 +00:00
return unless $ Slic3r:: Config - > skirts > 0
|| ( $ Slic3r:: Config - > standby_temperature && @ { $ self - > extruders } > 1 ) ;
2011-11-13 17:41:12 +00:00
# collect points from all layers contained in skirt height
2012-04-29 10:51:20 +00:00
my @ points = ( ) ;
foreach my $ obj_idx ( 0 .. $# { $ self - > objects } ) {
2013-07-29 18:49:54 +00:00
my $ object = $ self - > objects - > [ $ obj_idx ] ;
my @ layers = map $ object - > layers - > [ $ _ ] , 0 .. min ( $ Slic3r:: Config - > skirt_height - 1 , $# { $ object - > layers } ) ;
2012-04-29 10:51:20 +00:00
my @ layer_points = (
2012-09-22 17:38:25 +00:00
( map @$ _ , map @$ _ , map @ { $ _ - > slices } , @ layers ) ,
2012-04-29 10:51:20 +00:00
) ;
2013-07-29 18:49:54 +00: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-03 22:10:53 +00: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 18:49:54 +00:00
}
push @ points , map move_points ( $ _ , @ layer_points ) , @ { $ object - > copies } ;
2012-04-29 10:51:20 +00:00
}
2012-03-31 16:32:53 +00:00
return if @ points < 3 ; # at least three points required for a convex hull
2011-11-13 17:41:12 +00:00
# find out convex hull
2013-07-15 18:31:43 +00:00
my $ convex_hull = convex_hull ( [ map $ _ - > arrayref , @ points ] ) ;
2011-11-13 17:41:12 +00:00
2012-10-29 10:17:57 +00:00
my @ extruded_length = ( ) ; # for each extruder
2013-02-22 15:08:11 +00:00
# TODO: use each extruder's own flow
my $ spacing = $ self - > objects - > [ 0 ] - > layers - > [ 0 ] - > regions - > [ 0 ] - > perimeter_flow - > spacing ;
2012-10-29 10:17:57 +00:00
my $ first_layer_height = $ Slic3r:: Config - > get_value ( 'first_layer_height' ) ;
my @ extruders_e_per_mm = ( ) ;
my $ extruder_idx = 0 ;
2011-11-13 17:41:12 +00:00
# draw outlines from outside to inside
2012-10-29 10:17:57 +00: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 19:13:03 +00:00
for ( my $ i = $ Slic3r:: Config - > skirts ; $ i > 0 ; $ i - - ) {
2012-10-29 10:17:57 +00:00
$ distance += scale $ spacing ;
2013-07-16 22:48:29 +00:00
my $ loop = Slic3r::Geometry::Clipper:: offset ( [ $ convex_hull ] , $ distance , 0.0001 , JT_ROUND ) - > [ 0 ] ;
2013-09-16 08:33:30 +00:00
$ self - > skirt - > append ( Slic3r::ExtrusionLoop - > new (
2012-10-29 10:17:57 +00:00
polygon = > Slic3r::Polygon - > new ( @$ loop ) ,
2012-10-15 08:57:15 +00:00
role = > EXTR_ROLE_SKIRT ,
2012-10-29 10:17:57 +00:00
flow_spacing = > $ spacing ,
2013-09-16 08:33:30 +00:00
) ) ;
2012-10-29 10:17:57 +00: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 17:41:12 +00:00
}
2012-10-29 10:17:57 +00:00
2013-09-16 08:33:30 +00:00
$ self - > skirt - > reverse ;
2012-02-19 11:03:36 +00:00
}
2012-06-23 19:31:29 +00:00
sub make_brim {
my $ self = shift ;
2012-07-27 19:13:03 +00:00
return unless $ Slic3r:: Config - > brim_width > 0 ;
2012-06-23 19:31:29 +00:00
2013-02-22 15:08:11 +00:00
my $ flow = $ self - > objects - > [ 0 ] - > layers - > [ 0 ] - > regions - > [ 0 ] - > perimeter_flow ;
my $ grow_distance = $ flow - > scaled_width / 2 ;
2012-06-23 19:31:29 +00:00
my @ islands = ( ) ; # array of polygons
foreach my $ obj_idx ( 0 .. $# { $ self - > objects } ) {
2013-07-29 18:49:54 +00:00
my $ object = $ self - > objects - > [ $ obj_idx ] ;
my $ layer0 = $ object - > layers - > [ 0 ] ;
2012-08-06 18:54:49 +00:00
my @ object_islands = (
( map $ _ - > contour , @ { $ layer0 - > slices } ) ,
) ;
2013-07-29 18:49:54 +00:00
if ( @ { $ object - > support_layers } ) {
my $ support_layer0 = $ object - > support_layers - > [ 0 ] ;
push @ object_islands ,
2013-09-03 22:10:53 +00:00
( map $ _ - > polyline - > grow ( $ grow_distance ) , @ { $ support_layer0 - > support_fills } )
2013-07-29 18:49:54 +00:00
if $ support_layer0 - > support_fills ;
2013-07-31 14:29:44 +00:00
push @ object_islands ,
2013-09-03 22:10:53 +00:00
( map $ _ - > polyline - > grow ( $ grow_distance ) , @ { $ support_layer0 - > support_interface_fills } )
2013-07-31 14:29:44 +00:00
if $ support_layer0 - > support_interface_fills ;
2013-07-29 18:49:54 +00:00
}
foreach my $ copy ( @ { $ object - > copies } ) {
2013-09-16 08:33:30 +00:00
push @ islands , map { $ _ - > translate ( @$ copy ) ; $ _ } map $ _ - > clone , @ object_islands ;
2012-06-23 19:31:29 +00:00
}
}
2012-10-14 20:10:49 +00:00
# if brim touches skirt, make it around skirt too
2013-02-22 15:08:11 +00: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 07:49:34 +00:00
push @ islands , map $ _ - > split_at_first_point - > polyline - > grow ( $ grow_distance ) , @ { $ self - > skirt } ;
2012-10-14 20:10:49 +00:00
}
2013-05-09 12:52:56 +00:00
my @ loops = ( ) ;
2013-02-22 15:08:11 +00:00
my $ num_loops = sprintf "%.0f" , $ Slic3r:: Config - > brim_width / $ flow - > width ;
2012-06-23 19:31:29 +00:00
for my $ i ( reverse 1 .. $ num_loops ) {
2012-08-06 18:26:08 +00:00
# JT_SQUARE ensures no vertex is outside the given offset distance
2013-05-09 12:52:56 +00:00
# -0.5 because islands are not represented by their centerlines
2013-08-09 12:22:41 +00:00
# (first offset more, then step back - reverse order than the one used for
# perimeters because here we're offsetting outwards)
2013-08-26 14:25:42 +00:00
push @ loops , @ { offset2 ( \ @ islands , ( $ i + 0.5 ) * $ flow - > scaled_spacing , - 1.0 * $ flow - > scaled_spacing , 100000 , JT_SQUARE ) } ;
2012-06-23 19:31:29 +00:00
}
2013-05-09 12:52:56 +00:00
2013-09-16 08:33:30 +00:00
$ self - > brim - > append ( map Slic3r::ExtrusionLoop - > new (
2013-07-05 12:29:57 +00:00
polygon = > Slic3r::Polygon - > new ( @$ _ ) ,
2013-05-11 07:24:48 +00:00
role = > EXTR_ROLE_SKIRT ,
flow_spacing = > $ flow - > spacing ,
2013-09-16 08:33:30 +00:00
) , reverse traverse_pt ( union_pt ( \ @ loops ) ) ) ;
2012-06-23 19:31:29 +00:00
}
2012-04-30 12:56:01 +00:00
sub write_gcode {
2011-09-03 18:47:38 +00:00
my $ self = shift ;
my ( $ file ) = @ _ ;
2012-11-21 19:41:14 +00: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 09:18:34 +00:00
Slic3r:: open ( \ $ fh , ">" , $ file )
2012-11-21 19:41:14 +00:00
or die "Failed to open $file for writing\n" ;
}
2011-09-03 18:47:38 +00:00
2011-12-01 21:20:48 +00:00
# write some information
my @ lt = localtime ;
2012-05-01 13:01:56 +00:00
printf $ fh "; generated by Slic3r $Slic3r::VERSION on %04d-%02d-%02d at %02d:%02d:%02d\n\n" ,
2011-12-30 17:57:58 +00:00
$ lt [ 5 ] + 1900 , $ lt [ 4 ] + 1 , $ lt [ 3 ] , $ lt [ 2 ] , $ lt [ 1 ] , $ lt [ 0 ] ;
2012-02-05 19:55:17 +00:00
2012-07-27 19:13:03 +00:00
print $ fh "; $_\n" foreach split /\R/ , $ Slic3r:: Config - > notes ;
print $ fh "\n" if $ Slic3r:: Config - > notes ;
2011-12-01 21:20:48 +00:00
2013-03-19 18:15:53 +00:00
for ( qw( layer_height perimeters top_solid_layers bottom_solid_layers fill_density perimeter_speed infill_speed travel_speed ) ) {
2012-07-27 19:13:03 +00:00
printf $ fh "; %s = %s\n" , $ _ , $ Slic3r:: Config - > $ _ ;
2011-12-01 21:20:48 +00:00
}
2012-06-28 14:22:11 +00:00
for ( qw( nozzle_diameter filament_diameter extrusion_multiplier ) ) {
2012-07-27 19:13:03 +00:00
printf $ fh "; %s = %s\n" , $ _ , $ Slic3r:: Config - > $ _ - > [ 0 ] ;
2012-06-28 14:22:11 +00:00
}
2012-10-28 23:31:25 +00: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-16 23:21:17 +00: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-28 23:31:25 +00:00
printf $ fh "; support material extrusion width = %.2fmm\n" , $ self - > support_material_flow - > width
if $ self - > support_material_flow ;
2013-02-22 15:08:11 +00: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 21:20:48 +00:00
print $ fh "\n" ;
2012-05-20 18:07:39 +00:00
# set up our extruder object
2012-09-23 00:40:25 +00:00
my $ gcodegen = Slic3r::GCode - > new (
2013-05-31 09:19:36 +00:00
config = > $ self - > config ,
2013-08-09 17:46:20 +00:00
extruders = > $ self - > extruders , # we should only pass the *used* extruders (but maintain the Tx indices right!)
2013-01-17 13:56:31 +00:00
layer_count = > $ self - > layer_count ,
2012-09-23 00:40:25 +00:00
) ;
2013-06-07 21:24:53 +00:00
print $ fh "G21 ; set units to millimeters\n" if $ Slic3r:: Config - > gcode_flavor ne 'makerware' ;
2012-07-27 19:13:03 +00:00
print $ fh $ gcodegen - > set_fan ( 0 , 1 ) if $ Slic3r:: Config - > cooling && $ Slic3r:: Config - > disable_fan_first_layers ;
2012-05-20 18:07:39 +00:00
2013-07-31 16:55:23 +00: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 15:58:38 +00:00
my $ print_first_layer_temperature = sub {
2013-07-31 16:55:23 +00: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-09-18 23:29:34 +00:00
$ temp += $ self - > config - > standby_temperature_delta if $ self - > config - > standby_temperature ;
2013-07-31 16:55:23 +00:00
printf $ fh $ gcodegen - > set_temperature ( $ temp , $ wait , $ t ) if $ temp > 0 ;
2012-08-22 15:58:38 +00:00
}
} ;
2013-07-31 16:55:23 +00:00
$ print_first_layer_temperature - > ( 0 ) ;
2013-11-02 14:49:20 +00:00
printf $ fh "%s\n" , $ self - > replace_variables ( $ Slic3r:: Config - > start_gcode ) ;
2013-07-31 16:55:23 +00:00
$ print_first_layer_temperature - > ( 1 ) ;
# set other general things
2013-06-07 21:24:53 +00:00
print $ fh "G90 ; use absolute coordinates\n" if $ Slic3r:: Config - > gcode_flavor ne 'makerware' ;
2012-07-27 19:13:03 +00:00
if ( $ Slic3r:: Config - > gcode_flavor =~ /^(?:reprap|teacup)$/ ) {
2012-07-06 17:57:58 +00:00
printf $ fh $ gcodegen - > reset_e ;
2013-06-03 16:01:14 +00: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 10:44:30 +00:00
}
2011-09-03 18:47:38 +00:00
}
2013-08-09 17:46:20 +00: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 09:48:58 +00:00
# calculate X,Y shift to center print around specified origin
2013-06-16 10:21:25 +00:00
my $ print_bb = $ self - > bounding_box ;
my $ print_size = $ print_bb - > size ;
2012-02-19 09:48:58 +00:00
my @ shift = (
2013-06-17 11:32:53 +00: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 09:42:08 +00:00
) ;
2011-09-05 10:21:27 +00:00
2012-08-23 13:42:58 +00: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 18:33:30 +00:00
my $ convex_hull = convex_hull ( [
2013-07-15 20:57:22 +00:00
map @ { $ _ - > contour - > pp } , map @ { $ _ - > slices } , @ { $ self - > objects - > [ $ obj_idx ] - > layers } ,
2013-02-04 18:33:30 +00:00
] ) ;
# discard layers only containing thin walls (offset would fail on an empty polygon)
if ( @$ convex_hull ) {
2013-07-16 18:09:53 +00: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 18:33:30 +00:00
foreach my $ copy ( @ { $ self - > objects - > [ $ obj_idx ] - > copies } ) {
push @ islands , map $ _ - > clone - > translate ( @$ copy ) , @ island ;
}
2012-08-23 13:42:58 +00:00
}
}
2012-08-23 19:10:04 +00:00
$ gcodegen - > external_mp ( Slic3r::GCode::MotionPlanner - > new (
2012-08-23 13:42:58 +00:00
islands = > union_ex ( [ map @$ _ , @ islands ] ) ,
no_internal = > 1 ,
2012-08-23 19:10:04 +00:00
) ) ;
2012-08-23 13:42:58 +00:00
}
2013-09-18 18:03:59 +00:00
# calculate wiping points if needed
if ( $ self - > config - > standby_temperature ) {
my $ outer_skirt = Slic3r::Polygon - > new ( @ { convex_hull ( [ map $ _ - > pp , map @$ _ , @ { $ self - > skirt } ] ) } ) ;
2013-09-19 08:44:29 +00:00
$ gcodegen - > standby_points ( [ map $ _ - > clone , map @$ _ , map $ _ - > subdivide ( scale 10 ) , @ { offset ( [ $ outer_skirt ] , scale 3 ) } ] ) ;
2013-09-18 18:03:59 +00:00
}
2013-05-18 14:57:44 +00:00
# prepare the layer processor
my $ layer_gcode = Slic3r::GCode::Layer - > new (
print = > $ self ,
gcodegen = > $ gcodegen ,
shift = > \ @ shift ,
) ;
2012-05-20 18:07:39 +00:00
# do all objects for each layer
2012-07-27 19:13:03 +00:00
if ( $ Slic3r:: Config - > complete_objects ) {
2012-05-20 18:07:39 +00:00
2012-05-21 18:19:30 +00:00
# print objects from the smallest to the tallest to avoid collisions
# when moving onto next object starting point
2013-06-03 19:40:13 +00:00
my @ obj_idx = sort { $ self - > objects - > [ $ a ] - > size - > [ Z ] <=> $ self - > objects - > [ $ b ] - > size - > [ Z ] } 0 .. $# { $ self - > objects } ;
2012-02-25 20:01:00 +00:00
2012-05-20 18:07:39 +00:00
my $ finished_objects = 0 ;
2012-05-21 18:19:30 +00:00
for my $ obj_idx ( @ obj_idx ) {
2012-09-22 14:19:24 +00:00
for my $ copy ( @ { $ self - > objects - > [ $ obj_idx ] - > copies } ) {
2012-05-20 18:07:39 +00: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 18:54:21 +00:00
$ gcodegen - > set_shift ( map $ shift [ $ _ ] + unscale $ copy - > [ $ _ ] , X , Y ) ;
2012-06-28 12:44:54 +00: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 18:07:39 +00:00
}
2013-04-03 23:17:44 +00:00
my $ buffer = Slic3r::GCode::CoolingBuffer - > new (
config = > $ Slic3r:: Config ,
gcodegen = > $ gcodegen ,
) ;
2013-07-29 18:49:54 +00: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 18:07:39 +00: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 14:36:52 +00:00
if ( $ layer - > id == 0 && $ finished_objects > 0 ) {
2012-07-27 19:13:03 +00:00
printf $ fh $ gcodegen - > set_bed_temperature ( $ Slic3r:: Config - > first_layer_bed_temperature ) ,
if $ Slic3r:: Config - > first_layer_bed_temperature ;
2012-08-22 15:58:38 +00:00
$ print_first_layer_temperature - > ( ) ;
2012-05-20 18:07:39 +00:00
}
2013-05-31 10:18:33 +00:00
print $ fh $ buffer - > append (
$ layer_gcode - > process_layer ( $ layer , [ $ copy ] ) ,
$ layer - > object . "" ,
$ layer - > id ,
$ layer - > print_z ,
) ;
2012-05-20 18:07:39 +00:00
}
2013-04-03 23:17:44 +00:00
print $ fh $ buffer - > flush ;
2012-05-20 18:07:39 +00:00
$ finished_objects + + ;
}
}
} else {
2013-06-03 19:54:55 +00:00
# order objects using a nearest neighbor search
2013-07-16 15:13:01 +00:00
my @ obj_idx = chained_path ( [ map Slic3r::Point - > new ( @ { $ _ - > copies - > [ 0 ] } ) , @ { $ self - > objects } ] ) ;
2013-06-03 19:54:55 +00:00
# sort layers by Z
2013-07-29 18:49:54 +00:00
my % layers = ( ) ; # print_z => [ [layers], [layers], [layers] ] by obj_idx
2013-06-03 19:54:55 +00:00
foreach my $ obj_idx ( 0 .. $# { $ self - > objects } ) {
2013-07-29 18:49:54 +00:00
my $ object = $ self - > objects - > [ $ obj_idx ] ;
foreach my $ layer ( @ { $ object - > layers } , @ { $ object - > support_layers } ) {
2013-06-03 19:54:55 +00:00
$ layers { $ layer - > print_z } || = [] ;
2013-07-29 18:49:54 +00:00
$ layers { $ layer - > print_z } [ $ obj_idx ] || = [] ;
push @ { $ layers { $ layer - > print_z } [ $ obj_idx ] } , $ layer ;
2013-06-03 19:54:55 +00:00
}
}
2013-04-03 23:17:44 +00:00
my $ buffer = Slic3r::GCode::CoolingBuffer - > new (
config = > $ Slic3r:: Config ,
gcodegen = > $ gcodegen ,
) ;
2013-06-03 19:54:55 +00:00
foreach my $ print_z ( sort { $ a <=> $ b } keys % layers ) {
foreach my $ obj_idx ( @ obj_idx ) {
2013-07-29 18:49:54 +00: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 19:54:55 +00:00
}
2013-05-18 14:57:44 +00:00
}
2013-04-03 23:17:44 +00:00
print $ fh $ buffer - > flush ;
2011-09-03 18:47:38 +00:00
}
# write end commands to file
2013-03-29 23:36:14 +00:00
print $ fh $ gcodegen - > retract if $ gcodegen - > extruder ; # empty prints don't even set an extruder
2012-06-28 12:44:54 +00:00
print $ fh $ gcodegen - > set_fan ( 0 ) ;
2013-11-02 14:49:20 +00:00
printf $ fh "%s\n" , $ self - > replace_variables ( $ Slic3r:: Config - > end_gcode ) ;
2011-09-03 18:47:38 +00:00
2013-08-28 18:13:18 +00:00
foreach my $ extruder ( @ { $ self - > extruders } ) {
printf $ fh "; filament used = %.1fmm (%.1fcm3)\n" ,
$ extruder - > absolute_E , $ extruder - > extruded_volume / 1000 ;
}
2011-12-20 14:29:15 +00:00
2012-11-18 18:53:52 +00: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 18:47:38 +00:00
# close our gcode file
close $ fh ;
}
2012-09-12 14:30:44 +00:00
# this method will return the supplied input file path after expanding its
2012-04-30 12:56:01 +00:00
# format variables with their values
sub expanded_output_filepath {
my $ self = shift ;
2012-09-12 14:30:44 +00:00
my ( $ path , $ input_file ) = @ _ ;
2013-11-10 23:08:50 +00: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 ;
}
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 12:56:01 +00:00
2013-11-10 23:08:50 +00:00
return $ self - > replace_variables ( $ path , $ extra_variables ) ;
2013-11-02 14:49:20 +00:00
}
sub replace_variables {
my ( $ self , $ string , $ extra ) = @ _ ;
return $ self - > config - > replace_options ( $ string , { % { $ self - > extra_variables } , % { $ extra || { } } } ) ;
2012-04-30 12:56:01 +00:00
}
2013-11-10 23:08:50 +00: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 19:06:28 +00:00
1 ;