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 ;
2012-10-29 10:17:57 +00:00
use List::Util qw( 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-06-03 19:54:55 +00:00
use Slic3r::Geometry qw( X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points
nearest_point chained_path ) ;
2013-05-10 11:47:40 +00:00
use Slic3r::Geometry::Clipper qw( diff_ex union_ex union_pt intersection_ex offset
2013-05-11 07:24:48 +00:00
offset2 traverse_pt JT_ROUND JT_SQUARE PFT_EVENODD ) ;
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-04-29 10:51:20 +00:00
has 'total_extrusion_length' = > ( is = > 'rw' ) ;
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
has 'skirt' = > (
2011-09-01 19:06:28 +00:00
is = > 'rw' ,
2012-04-29 10:51:20 +00:00
#isa => 'ArrayRef[Slic3r::ExtrusionLoop]',
2011-09-01 19:06:28 +00:00
default = > sub { [] } ,
) ;
2012-06-23 19:31:29 +00:00
# ordered collection of extrusion paths to build a brim
has 'brim' = > (
is = > 'rw' ,
#isa => 'ArrayRef[Slic3r::ExtrusionLoop]',
default = > sub { [] } ,
) ;
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
}
2012-07-27 19:13:03 +00:00
}
2013-02-04 14:48:57 +00:00
sub _build_has_support_material {
my $ self = shift ;
return $ self - > config - > support_material
|| $ self - > config - > raft_layers > 0
|| $ self - > config - > support_material_enforce_layers > 0 ;
}
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 ) = @ _ ;
2012-09-23 00:40:25 +00:00
# append/merge materials and preserve a mapping between the original material ID
# and our numeric material index
my % materials = ( ) ;
{
my @ material_ids = sort keys % { $ model - > materials } ;
@ material_ids = ( 0 ) if ! @ material_ids ;
2012-09-23 00:52:31 +00:00
for ( my $ i = $ self - > regions_count ; $ i < @ material_ids ; $ i + + ) {
push @ { $ self - > regions } , Slic3r::Print::Region - > new ;
$ materials { $ material_ids [ $ i ] } = $# { $ self - > regions } ;
2012-09-23 00:40:25 +00:00
}
}
2012-09-22 17:04:36 +00:00
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
###$model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects;
2013-05-18 14:48:26 +00:00
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 } ) {
2012-09-23 00:52:31 +00:00
my $ region_id = defined $ volume - > material_id ? $ materials { $ volume - > material_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-06-02 14:56:08 +00:00
foreach my $ mesh ( grep $ _ , @ meshes ) {
2012-09-22 17:04:36 +00:00
$ mesh - > check_manifoldness ;
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 } ) {
$ mesh - > rotate ( $ object - > instances - > [ 0 ] - > rotation , $ object - > center ) ;
$ 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 ) ;
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
my $ bb = Slic3r::Geometry::BoundingBox - > new_from_points_3D ( [ map @ { $ _ - > used_vertices } , grep $ _ , @ meshes ] ) ;
my @ align2 = map - $ bb - > extents - > [ $ _ ] [ MIN ] , ( X , Y , Z ) ;
$ _ - > move ( @ 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-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 ,
layer_height_ranges = > $ object - > layer_height_ranges ,
2012-09-22 17:04:36 +00:00
) ;
2012-04-30 12:56:01 +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 } ;
2012-05-23 09:47:52 +00:00
my $ convex_hull = Slic3r::Polygon - > new ( convex_hull ( \ @ points ) ) ;
2013-04-02 03:12:51 +00:00
( $ clearance ) = map Slic3r::Polygon - > new ( $ _ ) ,
Slic3r::Geometry::Clipper:: 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 ) ;
if ( @ { intersection_ex ( \ @ a , [ $ copy_clearance ] ) } ) {
die "Some objects are too close; your extruder will collide with them.\n" ;
}
@ a = map @$ _ , @ { union_ex ( [ @ a , $ copy_clearance ] ) } ;
}
}
}
# 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 ,
( map $ self - > config - > get ( "${_}_extruder" ) - 1 , qw( perimeter infill support_material ) ) ,
( values % extruder_mapping ) ,
) ;
for my $ extruder_id ( keys % { { map { $ _ = > 1 } @ used_extruders } } ) {
$ self - > extruders - > [ $ extruder_id ] = Slic3r::Extruder - > new (
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-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
) ) ;
}
}
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-06-16 10:21:25 +00:00
return Slic3r::Geometry::BoundingBox - > new_from_points ( \ @ 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 } ) {
@$ _ = map $ _ - > simplify ( $ distance ) , @$ _
for $ layer - > slices , ( map $ _ - > slices , @ { $ layer - > regions } ) ;
}
}
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-03-07 14:47:32 +00:00
$ _ - > process_external_surfaces for map @ { $ _ - > regions } , map @ { $ _ - > layers } , @ { $ 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 ;
$ Slic3r:: Geometry:: Clipper:: clipper = Math::Clipper - > new ;
my $ fills = { } ;
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 ] ;
2012-04-30 12:56:01 +00:00
$ fills - > { $ obj_idx } || = { } ;
2012-09-22 17:04:36 +00:00
$ fills - > { $ obj_idx } { $ layer_id } || = { } ;
2012-09-23 00:52:31 +00:00
$ fills - > { $ obj_idx } { $ layer_id } { $ region_id } = [
2013-05-19 09:35:41 +00:00
$ object - > fill_maker - > make_fill ( $ object - > layers - > [ $ layer_id ] - > regions - > [ $ region_id ] ) ,
2012-09-22 17:04:36 +00:00
] ;
2012-04-30 12:56:01 +00:00
}
return $ fills ;
} ,
collect_cb = > sub {
my $ fills = shift ;
foreach my $ obj_idx ( keys %$ fills ) {
2012-09-22 17:04:36 +00:00
my $ object = $ self - > objects - > [ $ obj_idx ] ;
2012-04-30 12:56:01 +00:00
foreach my $ layer_id ( keys % { $ fills - > { $ obj_idx } } ) {
2012-09-22 17:04:36 +00:00
my $ layer = $ object - > layers - > [ $ layer_id ] ;
2012-09-23 00:52:31 +00:00
foreach my $ region_id ( keys % { $ fills - > { $ obj_idx } { $ layer_id } } ) {
$ layer - > regions - > [ $ region_id ] - > fills ( $ fills - > { $ obj_idx } { $ layer_id } { $ region_id } ) ;
2012-09-22 17:04:36 +00:00
}
2012-03-06 03:55:21 +00:00
}
}
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-05-19 09:35:41 +00:00
$ layerm - > fills ( [ $ 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)
2012-09-23 00:52:31 +00:00
$ _ - > fill_surfaces ( undef ) 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!
printf "Filament required: %.1fmm (%.1fcm3)\n" ,
$ self - > total_extrusion_length , $ self - > total_extrusion_volume ;
}
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 } ;
if ( $ params { output_file } ) {
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' ) ;
$ 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
2012-09-21 14:52:05 +00:00
my $ support_point = nearest_point ( $ expolygon - > contour - > [ 0 ] , \ @ supported_points )
or next ;
2012-09-28 14:32:53 +00:00
my $ anchor_point = nearest_point ( $ support_point , $ expolygon - > 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 ;
2012-07-27 19:13:03 +00:00
return unless $ Slic3r:: Config - > skirts > 0 ;
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-01-18 16:36:01 +00:00
my $ skirt_height = $ Slic3r:: Config - > skirt_height ;
$ skirt_height = $ self - > objects - > [ $ obj_idx ] - > layer_count if $ skirt_height > $ self - > objects - > [ $ obj_idx ] - > layer_count ;
2013-01-01 22:28:48 +00:00
my @ layers = map $ self - > objects - > [ $ obj_idx ] - > layers - > [ $ _ ] , 0 .. ( $ skirt_height - 1 ) ;
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-09-23 00:52:31 +00:00
( map @$ _ , map @ { $ _ - > thin_walls } , map @ { $ _ - > regions } , @ layers ) ,
2012-07-20 12:39:07 +00:00
( map @ { $ _ - > unpack - > polyline } , map @ { $ _ - > support_fills - > paths } , grep $ _ - > support_fills , @ layers ) ,
2012-04-29 10:51:20 +00:00
) ;
2012-09-22 14:19:24 +00:00
push @ points , map move_points ( $ _ , @ layer_points ) , @ { $ self - > objects - > [ $ obj_idx ] - > 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
2012-04-29 10:51:20 +00:00
my $ convex_hull = convex_hull ( \ @ 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 ;
2012-11-04 23:17:46 +00:00
my ( $ loop ) = Slic3r::Geometry::Clipper:: offset ( [ $ convex_hull ] , $ distance , 0.0001 , JT_ROUND ) ;
2012-10-14 20:10:49 +00:00
push @ { $ self - > skirt } , Slic3r::ExtrusionLoop - > pack (
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 ,
2011-12-30 18:59:51 +00:00
) ;
2012-10-29 10:17:57 +00:00
if ( $ Slic3r:: Config - > min_skirt_length > 0 ) {
bless $ loop , 'Slic3r::Polygon' ;
$ 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
@ { $ self - > skirt } = reverse @ { $ self - > skirt } ;
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 } ) {
2012-08-06 18:54:49 +00:00
my $ layer0 = $ self - > objects - > [ $ obj_idx ] - > layers - > [ 0 ] ;
my @ object_islands = (
( map $ _ - > contour , @ { $ layer0 - > slices } ) ,
2012-09-23 00:52:31 +00:00
( map { $ _ - > isa ( 'Slic3r::Polygon' ) ? $ _ : $ _ - > grow ( $ grow_distance ) } map @ { $ _ - > thin_walls } , @ { $ layer0 - > regions } ) ,
2012-08-07 20:04:58 +00:00
( map $ _ - > unpack - > polyline - > grow ( $ grow_distance ) , map @ { $ _ - > support_fills - > paths } , grep $ _ - > support_fills , $ layer0 ) ,
2012-08-06 18:54:49 +00:00
) ;
2012-09-22 14:19:24 +00:00
foreach my $ copy ( @ { $ self - > objects - > [ $ obj_idx ] - > copies } ) {
2012-06-23 19:31:29 +00:00
push @ islands , map $ _ - > clone - > translate ( @$ copy ) , @ object_islands ;
}
}
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 ) {
2012-10-14 20:10:49 +00:00
push @ islands , map $ _ - > unpack - > split_at_first_point - > polyline - > grow ( $ grow_distance ) , @ { $ self - > skirt } ;
}
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
2012-09-28 13:27:33 +00:00
# TODO: we need the offset inwards/offset outwards logic to avoid overlapping extrusions
2013-05-16 10:29:46 +00:00
push @ loops , offset2 ( \ @ islands , ( $ i - 1.5 ) * $ flow - > scaled_spacing , + 1.0 * $ flow - > scaled_spacing , undef , JT_SQUARE ) ;
2012-06-23 19:31:29 +00:00
}
2013-05-09 12:52:56 +00:00
2013-05-11 07:24:48 +00:00
@ { $ self - > brim } = map Slic3r::ExtrusionLoop - > pack (
polygon = > Slic3r::Polygon - > new ( $ _ ) ,
role = > EXTR_ROLE_SKIRT ,
flow_spacing = > $ flow - > spacing ,
) , reverse traverse_pt ( union_pt ( \ @ loops , PFT_EVENODD ) ) ;
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-01-17 13:56:31 +00:00
multiple_extruders = > ( @ { $ self - > extruders } > 1 ) ,
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
2011-09-03 18:47:38 +00:00
# write start commands to file
2012-07-27 19:13:03 +00:00
printf $ fh $ gcodegen - > set_bed_temperature ( $ Slic3r:: Config - > first_layer_bed_temperature , 1 ) ,
2013-03-09 18:51:09 +00:00
if $ Slic3r:: Config - > first_layer_bed_temperature && $ Slic3r:: Config - > start_gcode !~ /M(?:190|140)/i ;
2012-08-22 15:58:38 +00:00
my $ print_first_layer_temperature = sub {
2012-09-23 00:40:25 +00:00
for my $ t ( grep $ self - > extruders - > [ $ _ ] , 0 .. $# { $ Slic3r:: Config - > first_layer_temperature } ) {
printf $ fh $ gcodegen - > set_temperature ( $ self - > extruders - > [ $ t ] - > first_layer_temperature , 0 , $ t )
if $ self - > extruders - > [ $ t ] - > first_layer_temperature ;
2012-08-22 15:58:38 +00:00
}
} ;
2013-03-09 18:51:09 +00:00
$ print_first_layer_temperature - > ( ) if $ Slic3r:: Config - > start_gcode !~ /M(?:109|104)/i ;
2012-07-27 19:13:03 +00:00
printf $ fh "%s\n" , $ Slic3r:: Config - > replace_options ( $ Slic3r:: Config - > start_gcode ) ;
2012-09-23 00:40:25 +00:00
for my $ t ( grep $ self - > extruders - > [ $ _ ] , 0 .. $# { $ Slic3r:: Config - > first_layer_temperature } ) {
printf $ fh $ gcodegen - > set_temperature ( $ self - > extruders - > [ $ t ] - > first_layer_temperature , 1 , $ t )
2013-03-09 18:51:09 +00:00
if $ self - > extruders - > [ $ t ] - > first_layer_temperature && $ Slic3r:: Config - > start_gcode !~ /M(?:109|104)/i ;
2012-06-28 14:22:11 +00:00
}
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
}
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 ( [
2012-10-24 09:49:31 +00:00
map @ { $ _ - > contour } , 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 ) {
my @ island = Slic3r::ExPolygon - > new ( $ convex_hull )
- > translate ( scale $ shift [ X ] , scale $ shift [ Y ] )
- > offset_ex ( scale $ distance_from_objects , 1 , JT_SQUARE ) ;
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-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-03-10 14:36:52 +00:00
for my $ layer ( @ { $ self - > objects - > [ $ obj_idx ] - > 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
my @ obj_idx = chained_path ( [ map $ _ - > copies - > [ 0 ] , @ { $ self - > objects } ] ) ;
# sort layers by Z
my % layers = ( ) ; # print_z => [ layer, layer, layer ] by obj_idx
foreach my $ obj_idx ( 0 .. $# { $ self - > objects } ) {
foreach my $ layer ( @ { $ self - > objects - > [ $ obj_idx ] - > layers } ) {
$ layers { $ layer - > print_z } || = [] ;
$ layers { $ layer - > print_z } [ $ obj_idx ] = $ layer ; # turn this into [$layer] when merging support layers
}
}
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 ) {
next unless my $ layer = $ layers { $ print_z } [ $ obj_idx ] ;
print $ fh $ buffer - > append (
$ layer_gcode - > process_layer ( $ layer , $ layer - > object - > copies ) ,
$ layer - > object . "" ,
$ layer - > id ,
$ layer - > print_z ,
) ;
}
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
}
2011-12-20 14:29:15 +00:00
# save statistic data
2012-06-28 12:44:54 +00:00
$ self - > total_extrusion_length ( $ gcodegen - > total_extrusion_length ) ;
2011-12-20 14:29:15 +00:00
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 ) ;
2012-07-27 19:13:03 +00:00
printf $ fh "%s\n" , $ Slic3r:: Config - > replace_options ( $ Slic3r:: Config - > end_gcode ) ;
2011-09-03 18:47:38 +00:00
2011-12-20 14:29:15 +00:00
printf $ fh "; filament used = %.1fmm (%.1fcm3)\n" ,
$ self - > total_extrusion_length , $ self - > total_extrusion_volume ;
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 ;
}
2011-12-20 14:29:15 +00:00
sub total_extrusion_volume {
my $ self = shift ;
2012-09-23 00:40:25 +00:00
return $ self - > total_extrusion_length * ( $ self - > extruders - > [ 0 ] - > filament_diameter ** 2 ) * PI /4 / 1000 ;
2011-12-20 14:29:15 +00:00
}
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 ) = @ _ ;
# if no input file was supplied, take the first one from our objects
$ input_file || = $ self - > objects - > [ 0 ] - > input_file ;
2012-11-23 10:24:04 +00:00
return undef if ! defined $ input_file ;
2012-04-30 12:56:01 +00:00
2012-08-01 15:12:16 +00:00
# if output path is an existing directory, we take that and append
# the specified filename format
$ path = File::Spec - > join ( $ path , $ Slic3r:: Config - > output_filename_format ) if ( $ path && - d $ path ) ;
2012-04-30 12:56:01 +00:00
# if no explicit output file was defined, we take the input
# file directory and append the specified filename format
2012-07-27 19:13:03 +00:00
$ path || = ( fileparse ( $ input_file ) ) [ 1 ] . $ Slic3r:: Config - > output_filename_format ;
2012-04-30 12:56:01 +00:00
my $ input_filename = my $ input_filename_base = basename ( $ input_file ) ;
$ input_filename_base =~ s/\.(?:stl|amf(?:\.xml)?)$//i ;
2012-07-27 19:13:03 +00:00
return $ Slic3r:: Config - > replace_options ( $ path , {
2012-04-30 12:56:01 +00:00
input_filename = > $ input_filename ,
input_filename_base = > $ input_filename_base ,
2012-09-12 13:29:44 +00:00
% { $ self - > extra_variables } ,
2012-04-30 12:56:01 +00:00
} ) ;
}
2011-09-01 19:06:28 +00:00
1 ;