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-05-19 13:40:11 +00:00
use Slic3r::ExtrusionPath ':roles' ;
2014-03-24 16:52:14 +00:00
use Slic3r::Flow ':roles' ;
use Slic3r::Geometry qw( X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points chained_path
convex_hull ) ;
2013-11-02 13:44:30 +00:00
use Slic3r::Geometry::Clipper qw( diff_ex union_ex union_pt intersection_ex intersection offset
2014-03-24 16:52:14 +00:00
offset2 union union_pt_chained JT_ROUND JT_SQUARE ) ;
use Slic3r::Print::State ':steps' ;
2011-09-25 20:11:56 +00:00
2014-03-24 16:52:14 +00:00
has 'config' = > ( is = > 'ro' , default = > sub { Slic3r::Config::Print - > new } ) ;
has 'default_object_config' = > ( is = > 'ro' , default = > sub { Slic3r::Config::PrintObject - > new } ) ;
has 'default_region_config' = > ( is = > 'ro' , default = > sub { Slic3r::Config::PrintRegion - > new } ) ;
has 'placeholder_parser' = > ( is = > 'rw' , default = > sub { Slic3r::GCode::PlaceholderParser - > new } ) ;
2012-04-30 12:56:01 +00:00
has 'objects' = > ( is = > 'rw' , default = > sub { [] } ) ;
2014-03-24 16:52:14 +00:00
has 'status_cb' = > ( is = > 'rw' ) ;
2012-09-23 00:52:31 +00:00
has 'regions' = > ( is = > 'rw' , default = > sub { [] } ) ;
2014-03-24 16:52:14 +00:00
has 'total_used_filament' = > ( is = > 'rw' ) ;
has 'total_extruded_volume' = > ( is = > 'rw' ) ;
has '_state' = > ( is = > 'ro' , default = > sub { Slic3r::Print::State - > new } ) ;
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
2014-03-24 16:52:14 +00:00
sub apply_config {
my ( $ self , $ config ) = @ _ ;
2013-12-18 17:54:11 +00:00
2014-03-26 23:01:33 +00:00
$ config = $ config - > clone ;
$ config - > normalize ;
2014-03-24 16:52:14 +00:00
# apply variables to placeholder parser
$ self - > placeholder_parser - > apply_config ( $ config ) ;
2014-03-16 23:39:07 +00:00
2014-03-24 16:52:14 +00:00
# handle changes to print config
my $ print_diff = $ self - > config - > diff ( $ config ) ;
if ( @$ print_diff ) {
$ self - > config - > apply_dynamic ( $ config ) ;
# TODO: only invalidate changed steps
$ self - > _state - > invalidate_all ;
}
2014-01-02 21:06:58 +00:00
2014-03-24 16:52:14 +00:00
# handle changes to object config defaults
$ self - > default_object_config - > apply_dynamic ( $ config ) ;
foreach my $ object ( @ { $ self - > objects } ) {
# we don't assume that $config contains a full ObjectConfig,
# so we base it on the current print-wise default
my $ new = $ self - > default_object_config - > clone ;
# we override the new config with object-specific options
2014-03-26 23:01:33 +00:00
my $ model_object_config = $ object - > model_object - > config - > clone ;
$ model_object_config - > normalize ;
$ new - > apply_dynamic ( $ model_object_config ) ;
2014-03-24 16:52:14 +00:00
# check whether the new config is different from the current one
my $ diff = $ object - > config - > diff ( $ new ) ;
if ( @$ diff ) {
$ object - > config - > apply ( $ new ) ;
# TODO: only invalidate changed steps
$ object - > _state - > invalidate_all ;
}
}
2014-01-02 21:06:58 +00:00
2014-03-24 16:52:14 +00:00
# handle changes to regions config defaults
$ self - > default_region_config - > apply_dynamic ( $ config ) ;
2014-03-26 23:01:33 +00:00
# All regions now have distinct settings.
# Check whether applying the new region config defaults we'd get different regions.
my $ rearrange_regions = 0 ;
REGION: foreach my $ region_id ( 0 .. $# { $ self - > regions } ) {
2014-03-24 16:52:14 +00:00
foreach my $ object ( @ { $ self - > objects } ) {
foreach my $ volume_id ( @ { $ object - > region_volumes - > [ $ region_id ] } ) {
my $ volume = $ object - > model_object - > volumes - > [ $ volume_id ] ;
2014-03-26 23:01:33 +00:00
my $ new = $ self - > default_region_config - > clone ;
{
my $ model_object_config = $ object - > model_object - > config - > clone ;
$ model_object_config - > normalize ;
$ new - > apply_dynamic ( $ model_object_config ) ;
}
if ( defined $ volume - > material_id ) {
my $ material_config = $ object - > model_object - > model - > materials - > { $ volume - > material_id } - > config - > clone ;
$ material_config - > normalize ;
$ new - > apply_dynamic ( $ material_config ) ;
}
if ( ! $ new - > equals ( $ self - > regions - > [ $ region_id ] - > config ) ) {
$ rearrange_regions = 1 ;
last REGION ;
}
2014-03-24 16:52:14 +00:00
}
}
2014-01-02 21:06:58 +00:00
}
2014-03-26 23:01:33 +00:00
# Some optimization is possible: if the volumes-regions mappings don't change
# but still region configs are changed somehow, we could just apply the diff
# and invalidate the affected steps.
if ( $ rearrange_regions ) {
# the current subdivision of regions does not make sense anymore.
2014-03-24 16:52:14 +00:00
# we need to remove all objects and re-add them
2014-03-26 18:42:01 +00:00
my @ model_objects = map $ _ - > model_object , @ { $ self - > objects } ;
2014-03-24 16:52:14 +00:00
$ self - > delete_all_objects ;
$ self - > add_model_object ( $ _ ) for @ model_objects ;
}
2013-12-18 17:54:11 +00:00
}
2014-03-24 16:52:14 +00:00
sub has_support_material {
2013-02-04 14:48:57 +00:00
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
2014-03-24 16:52:14 +00:00
sub add_model_object {
2012-08-29 14:49:38 +00:00
my $ self = shift ;
2014-03-24 16:52:14 +00:00
my ( $ object , $ obj_idx ) = @ _ ;
2014-03-26 23:01:33 +00:00
my $ object_config = $ object - > config - > clone ;
$ object_config - > normalize ;
2014-03-24 16:52:14 +00:00
my % volumes = ( ) ; # region_id => [ volume_id, ... ]
foreach my $ volume_id ( 0 .. $# { $ object - > volumes } ) {
my $ volume = $ object - > volumes - > [ $ volume_id ] ;
2014-01-01 16:29:15 +00:00
2014-03-24 16:52:14 +00:00
# get the config applied to this volume: start from our global defaults
my $ config = Slic3r::Config::PrintRegion - > new ;
$ config - > apply ( $ self - > default_region_config ) ;
2013-12-30 17:28:41 +00:00
2014-03-24 16:52:14 +00:00
# override the defaults with per-object config and then with per-material config
2014-03-26 23:01:33 +00:00
$ config - > apply_dynamic ( $ object_config ) ;
2014-03-26 18:42:01 +00:00
2014-03-24 16:52:14 +00:00
if ( defined $ volume - > material_id ) {
2014-03-26 23:01:33 +00:00
my $ material_config = $ object - > model - > materials - > { $ volume - > material_id } - > config - > clone ;
$ material_config - > normalize ;
2014-03-24 16:52:14 +00:00
$ config - > apply_dynamic ( $ material_config ) ;
}
# find an existing print region with the same config
my $ region_id ;
foreach my $ i ( 0 .. $# { $ self - > regions } ) {
my $ region = $ self - > regions - > [ $ i ] ;
if ( $ config - > equals ( $ region - > config ) ) {
$ region_id = $ i ;
last ;
2013-12-30 17:28:41 +00:00
}
2012-08-29 14:49:38 +00:00
}
2013-12-30 17:28:41 +00:00
2014-03-24 16:52:14 +00:00
# if no region exists with the same config, create a new one
if ( ! defined $ region_id ) {
push @ { $ self - > regions } , my $ r = Slic3r::Print::Region - > new (
print = > $ self ,
) ;
$ r - > config - > apply ( $ config ) ;
$ region_id = $# { $ self - > regions } ;
}
2013-12-30 17:28:41 +00:00
2014-03-24 16:52:14 +00:00
# assign volume to region
$ volumes { $ region_id } // = [] ;
push @ { $ volumes { $ region_id } } , $ volume_id ;
2013-11-27 11:18:24 +00:00
}
2014-03-24 16:52:14 +00:00
# initialize print object
my $ o = Slic3r::Print::Object - > new (
print = > $ self ,
model_object = > $ object ,
region_volumes = > [ map $ volumes { $ _ } , 0 .. $# { $ self - > regions } ] ,
copies = > [ map Slic3r::Point - > new_scale ( @ { $ _ - > offset } ) , @ { $ object - > instances } ] ,
layer_height_ranges = > $ object - > layer_height_ranges ,
) ;
# apply config to print object
$ o - > config - > apply ( $ self - > default_object_config ) ;
2014-03-26 23:01:33 +00:00
$ o - > config - > apply_dynamic ( $ object_config ) ;
2014-03-24 16:52:14 +00:00
# store print object at the given position
if ( defined $ obj_idx ) {
splice @ { $ self - > objects } , $ obj_idx , 0 , $ o ;
} else {
push @ { $ self - > objects } , $ o ;
2013-12-15 23:36:53 +00:00
}
2014-03-24 16:52:14 +00:00
$ self - > _state - > invalidate ( STEP_SKIRT ) ;
$ self - > _state - > invalidate ( STEP_BRIM ) ;
}
sub delete_object {
my ( $ self , $ obj_idx ) = @ _ ;
splice @ { $ self - > objects } , $ obj_idx , 1 ;
# TODO: purge unused regions
$ self - > _state - > invalidate ( STEP_SKIRT ) ;
$ self - > _state - > invalidate ( STEP_BRIM ) ;
}
sub delete_all_objects {
my ( $ self ) = @ _ ;
@ { $ self - > objects } = ( ) ;
@ { $ self - > regions } = ( ) ;
$ self - > _state - > invalidate ( STEP_SKIRT ) ;
$ self - > _state - > invalidate ( STEP_BRIM ) ;
2011-10-02 07:57:37 +00:00
}
2014-03-26 18:42:01 +00:00
sub reload_object {
my ( $ self , $ obj_idx ) = @ _ ;
# TODO: this method should check whether the per-object config and per-material configs
# have changed in such a way that regions need to be rearranged or we can just apply
# the diff and invalidate something. Same logic as apply_config()
# For now we just re-add all objects since we haven't implemented this incremental logic yet.
# This should also check whether object volumes (parts) have changed.
my @ model_objects = map $ _ - > model_object , @ { $ self - > objects } ;
$ self - > delete_all_objects ;
$ self - > add_model_object ( $ _ ) for @ model_objects ;
}
2012-05-23 09:47:52 +00:00
sub validate {
my $ self = shift ;
2014-03-24 16:52:14 +00:00
if ( $ self - > config - > complete_objects ) {
2012-05-23 09:47:52 +00:00
# check horizontal clearance
{
my @ a = ( ) ;
2014-03-24 16:52:14 +00:00
foreach my $ object ( @ { $ self - > objects } ) {
# get convex hulls of all meshes assigned to this print object
my @ mesh_convex_hulls = map $ object - > model_object - > volumes - > [ $ _ ] - > mesh - > convex_hull ,
map @$ _ ,
grep defined $ _ ,
@ { $ object - > region_volumes } ;
# make a single convex hull for all of them
my $ convex_hull = convex_hull ( [ map @$ _ , @ mesh_convex_hulls ] ) ;
# apply the same transformations we apply to the actual meshes when slicing them
$ object - > model_object - > instances - > [ 0 ] - > transform_polygon ( $ convex_hull , 1 ) ;
# align object to Z = 0 and apply XY shift
$ convex_hull - > translate ( @ { $ object - > _copies_shift } ) ;
# grow convex hull with the clearance margin
( $ convex_hull ) = @ { offset ( [ $ convex_hull ] , scale $ self - > config - > extruder_clearance_radius / 2 , 1 , JT_ROUND , scale ( 0.1 ) ) } ;
# now we need that no instance of $convex_hull does not intersect any of the previously checked object instances
for my $ copy ( @ { $ object - > _shifted_copies } ) {
my $ p = $ convex_hull - > clone ;
$ p - > translate ( @$ copy ) ;
if ( @ { intersection ( \ @ a , [ $ p ] ) } ) {
2012-05-23 09:47:52 +00:00
die "Some objects are too close; your extruder will collide with them.\n" ;
}
2014-03-24 16:52:14 +00:00
@ a = @ { union ( [ @ a , $ p ] ) } ;
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 } ) {
2014-03-24 16:52:14 +00:00
my $ height = $ object - > size - > z ;
2013-06-19 15:34:37 +00:00
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 ;
2014-03-24 16:52:14 +00:00
if ( @ object_height && max ( @ object_height ) > scale $ self - > 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
2014-03-24 16:52:14 +00:00
if ( $ self - > config - > spiral_vase ) {
2013-05-13 18:15:45 +00:00
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
}
2014-03-24 16:52:14 +00:00
# 0-based indices of used extruders
sub extruders {
my ( $ self ) = @ _ ;
2012-09-23 00:40:25 +00:00
# initialize all extruder(s) we need
2014-03-24 16:52:14 +00:00
my @ used_extruders = ( ) ;
foreach my $ region ( @ { $ self - > regions } ) {
push @ used_extruders ,
map $ region - > config - > get ( "${_}_extruder" ) - 1 ,
qw( perimeter infill ) ;
2013-12-31 13:33:03 +00:00
}
2014-03-24 16:52:14 +00:00
foreach my $ object ( @ { $ self - > objects } ) {
push @ used_extruders ,
map $ object - > config - > get ( "${_}_extruder" ) - 1 ,
qw( support_material support_material_interface ) ;
2013-12-31 13:33:03 +00:00
}
2014-03-24 16:52:14 +00:00
my % h = map { $ _ = > 1 } @ used_extruders ;
return [ sort keys % h ] ;
}
sub init_extruders {
my $ self = shift ;
2014-01-03 09:44:36 +00:00
2013-11-15 15:01:15 +00:00
# enforce tall skirt if using ooze_prevention
2014-03-24 16:52:14 +00:00
# FIXME: this is not idempotent (i.e. switching ooze_prevention off will not revert skirt settings)
2013-11-15 15:01:15 +00:00
if ( $ self - > config - > ooze_prevention && @ { $ self - > extruders } > 1 ) {
2014-03-24 16:52:14 +00:00
$ self - > config - > set ( 'skirt_height' , - 1 ) ;
2013-09-18 18:03:59 +00:00
$ self - > config - > set ( 'skirts' , 1 ) if $ self - > config - > skirts == 0 ;
}
2012-09-23 00:40:25 +00:00
}
2014-01-11 16:40:09 +00:00
# this value is not supposed to be compared with $layer->id
# since they have different semantics
2012-04-29 10:51:20 +00:00
sub layer_count {
my $ self = shift ;
2014-01-11 16:40:09 +00:00
return max ( map $ _ - > layer_count , @ { $ 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 } ) {
2014-03-24 16:52:14 +00:00
foreach my $ copy ( @ { $ object - > _shifted_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 } ) {
2014-03-24 16:52:14 +00:00
$ layer - > slices - > simplify ( $ distance ) ;
$ _ - > slices - > simplify ( $ distance ) for @ { $ layer - > regions } ;
2013-03-16 18:58:34 +00:00
}
}
2014-03-24 16:52:14 +00:00
sub process {
my ( $ self ) = @ _ ;
my $ status_cb = $ self - > status_cb // sub { } ;
my $ print_step = sub {
my ( $ step , $ cb ) = @ _ ;
if ( ! $ self - > _state - > done ( $ step ) ) {
$ self - > _state - > set_started ( $ step ) ;
$ cb - > ( ) ;
### Re-enable this for step-based slicing:
### $self->_state->set_done($step);
}
} ;
my $ object_step = sub {
my ( $ step , $ cb ) = @ _ ;
for my $ obj_idx ( 0 .. $# { $ self - > objects } ) {
my $ object = $ self - > objects - > [ $ obj_idx ] ;
if ( ! $ object - > _state - > done ( $ step ) ) {
$ object - > _state - > set_started ( $ step ) ;
$ cb - > ( $ obj_idx ) ;
### Re-enable this for step-based slicing:
### $object->_state->set_done($step);
}
}
} ;
2013-12-19 11:11:02 +00:00
2014-03-24 16:52:14 +00:00
# STEP_INIT_EXTRUDERS
$ print_step - > ( STEP_INIT_EXTRUDERS , sub {
$ self - > init_extruders ;
} ) ;
2013-12-19 11:11:02 +00:00
2014-03-24 16:52:14 +00:00
# STEP_SLICE
2012-04-30 12:56:01 +00:00
# skein the STL into layers
# each layer has surfaces with holes
$ status_cb - > ( 10 , "Processing triangulated mesh" ) ;
2014-03-24 16:52:14 +00:00
$ object_step - > ( STEP_SLICE , sub {
$ self - > objects - > [ $ _ [ 0 ] ] - > slice ;
} ) ;
2012-04-30 12:56:01 +00:00
2013-06-03 09:39:23 +00:00
die "No layers were detected. You might want to repair your STL file(s) or check their size and retry.\n"
2014-03-24 16:52:14 +00:00
if ! grep @ { $ _ - > layers } , @ { $ self - > objects } ;
2013-06-03 09:39:23 +00:00
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" ) ;
2014-03-24 16:52:14 +00:00
$ object_step - > ( STEP_PERIMETERS , sub {
$ self - > objects - > [ $ _ [ 0 ] ] - > make_perimeters ;
} ) ;
$ status_cb - > ( 30 , "Preparing infill" ) ;
$ object_step - > ( STEP_PREPARE_INFILL , sub {
my $ object = $ self - > objects - > [ $ _ [ 0 ] ] ;
# this will assign a type (top/bottom/internal) to $layerm->slices
# and transform $layerm->fill_surfaces from expolygon
# to typed top/bottom/internal surfaces;
$ object - > detect_surfaces_type ;
# decide what surfaces are to be filled
$ _ - > prepare_fill_surfaces for map @ { $ _ - > regions } , @ { $ object - > layers } ;
# this will detect bridges and reverse bridges
# and rearrange top/bottom/internal surfaces
$ object - > process_external_surfaces ;
# detect which fill surfaces are near external layers
# they will be split in internal and internal-solid surfaces
$ object - > discover_horizontal_shells ;
$ object - > clip_fill_surfaces ;
# the following step needs to be done before combination because it may need
# to remove only half of the combined infill
$ object - > bridge_over_infill ;
# combine fill surfaces to honor the "infill every N layers" option
$ object - > combine_infill ;
} ) ;
2012-04-30 12:56:01 +00:00
# this will generate extrusion paths for each layer
2014-03-24 16:52:14 +00:00
$ status_cb - > ( 70 , "Infilling layers" ) ;
$ object_step - > ( STEP_INFILL , sub {
my $ object = $ self - > objects - > [ $ _ [ 0 ] ] ;
2012-04-30 12:56:01 +00:00
Slic3r:: parallelize (
2014-03-24 16:52:14 +00:00
threads = > $ self - > config - > threads ,
2012-09-22 17:04:36 +00:00
items = > sub {
2014-03-24 16:52:14 +00:00
my @ items = ( ) ; # [layer_id, region_id]
for my $ region_id ( 0 .. ( $ self - > regions_count - 1 ) ) {
push @ items , map [ $ _ , $ region_id ] , 0 .. $# { $ object - > layers } ;
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 ) ) {
2014-03-24 16:52:14 +00:00
my ( $ i , $ region_id ) = @$ obj_layer ;
my $ layerm = $ object - > layers - > [ $ i ] - > regions - > [ $ region_id ] ;
2013-07-18 20:29:12 +00:00
$ 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 {
2014-03-24 16:52:14 +00:00
foreach my $ layerm ( map @ { $ _ - > regions } , @ { $ object - > layers } ) {
$ layerm - > fills - > append ( $ object - > fill_maker - > make_fill ( $ layerm ) ) ;
2012-04-30 12:56:01 +00:00
}
} ,
) ;
2014-03-24 16:52:14 +00:00
### we could free memory now, but this would make this step not idempotent
### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers};
} ) ;
2014-03-24 16:02:25 +00:00
2014-03-24 16:52:14 +00:00
# generate support material
$ status_cb - > ( 85 , "Generating support material" ) if $ self - > has_support_material ;
$ object_step - > ( STEP_SUPPORTMATERIAL , sub {
$ self - > objects - > [ $ _ [ 0 ] ] - > generate_support_material ;
} ) ;
2012-04-30 12:56:01 +00:00
# make skirt
2014-03-24 16:52:14 +00:00
$ status_cb - > ( 88 , "Generating skirt/brim" ) ;
$ print_step - > ( STEP_SKIRT , sub {
$ self - > make_skirt ;
} ) ;
$ print_step - > ( STEP_BRIM , sub {
$ 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" ) ;
}
2014-03-24 16:52:14 +00:00
}
sub export_gcode {
my $ self = shift ;
my % params = @ _ ;
my $ status_cb = $ self - > status_cb // sub { } ;
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
2014-03-24 16:52:14 +00:00
if ( @ { $ self - > config - > post_process } ) {
2012-04-30 12:56:01 +00:00
$ status_cb - > ( 95 , "Running post-processing scripts" ) ;
2014-03-24 16:52:14 +00:00
$ self - > config - > setenv ;
for ( @ { $ self - > 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
sub export_svg {
2011-09-18 17:28:12 +00:00
my $ self = shift ;
2012-04-30 12:56:01 +00:00
my % params = @ _ ;
2014-03-24 16:52:14 +00:00
# is this needed?
2012-09-28 14:32:53 +00:00
$ 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
} ;
2014-01-11 16:40:09 +00:00
my @ layers = sort { $ a - > print_z <=> $ b - > print_z }
map { @ { $ _ - > layers } , @ { $ _ - > support_layers } }
@ { $ self - > objects } ;
my $ layer_id = - 1 ;
2012-06-11 12:47:48 +00:00
my @ previous_layer_slices = ( ) ;
2014-01-11 16:40:09 +00:00
for my $ layer ( @ layers ) {
$ layer_id + + ;
# TODO: remove slic3r:z for raft layers
printf $ fh qq{ <g id="layer%d" slic3r:z="%s"> \ n } , $ layer_id , unscale ( $ layer - > slice_z ) ;
2012-04-30 12:56:01 +00:00
2012-06-11 12:47:48 +00:00
my @ current_layer_slices = ( ) ;
2014-01-11 16:40:09 +00:00
# sort slices so that the outermost ones come first
my @ slices = sort { $ a - > contour - > encloses_point ( $ b - > contour - > [ 0 ] ) ? 0 : 1 } @ { $ layer - > slices } ;
foreach my $ copy ( @ { $ layer - > object - > copies } ) {
foreach my $ slice ( @ slices ) {
my $ expolygon = $ slice - > clone ;
$ expolygon - > translate ( @$ copy ) ;
$ print_polygon - > ( $ expolygon - > contour , 'contour' ) ;
$ print_polygon - > ( $ _ , 'hole' ) for @ { $ expolygon - > holes } ;
push @ current_layer_slices , $ expolygon ;
2012-04-30 12:56:01 +00:00
}
}
2012-06-11 12:47:48 +00:00
# generate support material
2014-01-11 16:40:09 +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 ;
2014-03-24 16:52:14 +00:00
return unless $ self - > config - > skirts > 0
|| ( $ self - > config - > ooze_prevention && @ { $ self - > extruders } > 1 ) ;
$ self - > skirt - > clear ; # method must be idempotent
# First off we need to decide how tall the skirt must be.
# The skirt_height option from config is expressed in layers, but our
# object might have different layer heights, so we need to find the print_z
# of the highest layer involved.
# Note that unless skirt_height == -1 (which means it's printed on all layers)
# the actual skirt might not reach this $skirt_height_z value since the print
# order of objects on each layer is not guaranteed and will not generally
# include the thickest object first. It is just guaranteed that a skirt is
# prepended to the first 'n' layers (with 'n' = skirt_height).
# $skirt_height_z in this case is the highest possible skirt height for safety.
my $ skirt_height_z = - 1 ;
foreach my $ object ( @ { $ self - > objects } ) {
my $ skirt_height = ( $ self - > config - > skirt_height == - 1 )
? scalar ( @ { $ object - > layers } )
: min ( $ self - > config - > skirt_height , scalar ( @ { $ object - > layers } ) ) ;
my $ highest_layer = $ object - > layers - > [ $ skirt_height - 1 ] ;
$ skirt_height_z = max ( $ skirt_height_z , $ highest_layer - > print_z ) ;
}
2014-01-12 10:06:21 +00:00
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 = ( ) ;
2014-03-24 16:52:14 +00:00
foreach my $ object ( @ { $ self - > objects } ) {
my @ object_points = ( ) ;
# get object layers up to $skirt_height_z
foreach my $ layer ( @ { $ object - > layers } ) {
last if $ layer - > print_z > $ skirt_height_z ;
push @ object_points , map @$ _ , map @$ _ , @ { $ layer - > slices } ;
}
# get support layers up to $skirt_height_z
foreach my $ layer ( @ { $ object - > support_layers } ) {
last if $ layer - > print_z > $ skirt_height_z ;
push @ object_points , map @ { $ _ - > polyline } , @ { $ layer - > support_fills } if $ layer - > support_fills ;
push @ object_points , map @ { $ _ - > polyline } , @ { $ layer - > support_interface_fills } if $ layer - > support_interface_fills ;
}
# repeat points for each object copy
foreach my $ copy ( @ { $ object - > _shifted_copies } ) {
my @ copy_points = map $ _ - > clone , @ object_points ;
$ _ - > translate ( @$ copy ) for @ copy_points ;
push @ points , @ copy_points ;
2013-07-29 18:49:54 +00:00
}
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
2014-03-24 16:52:14 +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
2014-03-24 16:52:14 +00:00
# skirt may be printed on several layers, having distinct layer heights,
# but loops must be aligned so can't vary width/spacing
2013-02-22 15:08:11 +00:00
# TODO: use each extruder's own flow
2014-03-24 16:52:14 +00:00
my $ first_layer_height = $ self - > objects - > [ 0 ] - > config - > get_value ( 'first_layer_height' ) ;
my $ flow = Slic3r::Flow - > new_from_width (
width = > ( $ self - > config - > first_layer_extrusion_width || $ self - > regions - > [ 0 ] - > config - > perimeter_extrusion_width ) ,
role = > FLOW_ROLE_PERIMETER ,
nozzle_diameter = > $ self - > config - > nozzle_diameter - > [ 0 ] ,
layer_height = > $ first_layer_height ,
bridge_flow_ratio = > 0 ,
) ;
my $ spacing = $ flow - > spacing ;
my $ mm3_per_mm = $ flow - > mm3_per_mm ( $ first_layer_height ) ;
2013-02-22 15:08:11 +00:00
2012-10-29 10:17:57 +00:00
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
2014-03-24 16:52:14 +00:00
my $ distance = scale $ self - > config - > skirt_distance ;
for ( my $ i = $ self - > config - > skirts ; $ i > 0 ; $ i - - ) {
2012-10-29 10:17:57 +00:00
$ distance += scale $ spacing ;
2014-03-24 16:52:14 +00:00
my $ loop = offset ( [ $ convex_hull ] , $ distance , 1 , JT_ROUND , scale ( 0.1 ) ) - > [ 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 ,
2014-03-24 16:52:14 +00:00
mm3_per_mm = > $ mm3_per_mm ,
2013-09-16 08:33:30 +00:00
) ) ;
2012-10-29 10:17:57 +00:00
2014-03-24 16:52:14 +00:00
if ( $ self - > config - > min_skirt_length > 0 ) {
$ extruded_length [ $ extruder_idx ] || = 0 ;
if ( ! $ extruders_e_per_mm [ $ extruder_idx ] ) {
2014-04-26 21:28:32 +00:00
my $ extruder = Slic3r::Extruder - > new ( $ extruder_idx , $ self - > config ) ;
2014-03-24 16:52:14 +00:00
$ extruders_e_per_mm [ $ extruder_idx ] = $ extruder - > e_per_mm ( $ mm3_per_mm ) ;
}
$ extruded_length [ $ extruder_idx ] += unscale $ loop - > length * $ extruders_e_per_mm [ $ extruder_idx ] ;
$ i + + if defined first { ( $ extruded_length [ $ _ ] // 0 ) < $ self - > config - > min_skirt_length } 0 .. $# { $ self - > extruders } ;
if ( $ extruded_length [ $ extruder_idx ] >= $ self - > config - > min_skirt_length ) {
2012-10-29 10:17:57 +00:00
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 ;
2014-03-24 16:52:14 +00:00
return unless $ self - > config - > brim_width > 0 ;
$ self - > brim - > clear ; # method must be idempotent
# brim is only printed on first layer and uses support material extruder
my $ first_layer_height = $ self - > objects - > [ 0 ] - > config - > get_abs_value ( 'first_layer_height' ) ;
my $ flow = Slic3r::Flow - > new_from_width (
width = > ( $ self - > config - > first_layer_extrusion_width || $ self - > regions - > [ 0 ] - > config - > perimeter_extrusion_width ) ,
role = > FLOW_ROLE_PERIMETER ,
nozzle_diameter = > $ self - > config - > get_at ( 'nozzle_diameter' , $ self - > objects - > [ 0 ] - > config - > support_material_extruder - 1 ) ,
layer_height = > $ first_layer_height ,
bridge_flow_ratio = > 0 ,
) ;
my $ mm3_per_mm = $ flow - > mm3_per_mm ( $ first_layer_height ) ;
2013-02-22 15:08:11 +00:00
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 ,
2014-03-24 16:52:14 +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 ,
2014-03-24 16:52:14 +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
}
2014-03-24 16:52:14 +00:00
foreach my $ copy ( @ { $ object - > _shifted_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)
2014-03-24 16:52:14 +00:00
if ( $ self - > config - > skirt_distance + ( ( $ self - > config - > skirts - 1 ) * $ flow - > spacing ) <= $ self - > config - > brim_width ) {
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 = ( ) ;
2014-03-24 16:52:14 +00:00
my $ num_loops = sprintf "%.0f" , $ self - > 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 ,
2014-03-24 16:52:14 +00:00
mm3_per_mm = > $ mm3_per_mm ,
) , reverse @ { union_pt_chained ( \ @ 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" ;
2014-03-24 16:52:50 +00:00
# enable UTF-8 output since user might have entered Unicode characters in fields like notes
binmode $ fh , ':utf8' ;
2012-11-21 19:41:14 +00:00
}
2011-09-03 18:47:38 +00:00
2014-03-24 16:52:14 +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
2014-03-24 16:52:14 +00:00
print $ fh "; $_\n" foreach split /\R/ , $ self - > config - > notes ;
print $ fh "\n" if $ self - > config - > notes ;
2014-04-25 17:39:27 +00:00
my $ first_object = $ self - > objects - > [ 0 ] ;
my $ layer_height = $ first_object - > config - > layer_height ;
2014-03-24 16:52:14 +00:00
for my $ region_id ( 0 .. $# { $ self - > regions } ) {
printf $ fh "; perimeters extrusion width = %.2fmm\n" ,
2014-04-25 17:39:27 +00:00
$ self - > regions - > [ $ region_id ] - > flow ( FLOW_ROLE_PERIMETER , $ layer_height , 0 , 0 , undef , $ first_object ) - > width ;
2014-03-24 16:52:14 +00:00
printf $ fh "; infill extrusion width = %.2fmm\n" ,
2014-04-25 17:39:27 +00:00
$ self - > regions - > [ $ region_id ] - > flow ( FLOW_ROLE_INFILL , $ layer_height , 0 , 0 , undef , $ first_object ) - > width ;
2014-03-24 16:52:14 +00:00
printf $ fh "; solid infill extrusion width = %.2fmm\n" ,
2014-04-25 17:39:27 +00:00
$ self - > regions - > [ $ region_id ] - > flow ( FLOW_ROLE_SOLID_INFILL , $ layer_height , 0 , 0 , undef , $ first_object ) - > width ;
2014-03-24 16:52:14 +00:00
printf $ fh "; top infill extrusion width = %.2fmm\n" ,
2014-04-25 17:39:27 +00:00
$ self - > regions - > [ $ region_id ] - > flow ( FLOW_ROLE_TOP_SOLID_INFILL , $ layer_height , 0 , 0 , undef , $ first_object ) - > width ;
2014-03-24 16:52:14 +00:00
printf $ fh "; support material extrusion width = %.2fmm\n" ,
$ self - > objects - > [ 0 ] - > support_material_flow - > width
if $ self - > has_support_material ;
printf $ fh "; first layer extrusion width = %.2fmm\n" ,
2014-04-25 17:39:27 +00:00
$ self - > regions - > [ $ region_id ] - > flow ( FLOW_ROLE_PERIMETER , $ layer_height , 0 , 1 , undef , $ self - > objects - > [ 0 ] ) - > width
2014-03-24 16:52:14 +00:00
if $ self - > regions - > [ $ region_id ] - > config - > first_layer_extrusion_width ;
print $ fh "\n" ;
}
# prepare the helper object for replacing placeholders in custom G-code and output filename
2014-04-25 17:47:13 +00:00
$ self - > placeholder_parser - > update_timestamp ;
2014-03-24 16:52:14 +00:00
# set up our helper object
2012-09-23 00:40:25 +00:00
my $ gcodegen = Slic3r::GCode - > new (
2014-03-24 16:52:14 +00:00
print_config = > $ self - > config ,
placeholder_parser = > $ self - > placeholder_parser ,
2013-01-17 13:56:31 +00:00
layer_count = > $ self - > layer_count ,
2012-09-23 00:40:25 +00:00
) ;
2014-03-24 16:52:14 +00:00
$ gcodegen - > set_extruders ( $ self - > extruders ) ;
print $ fh "G21 ; set units to millimeters\n" if $ self - > config - > gcode_flavor ne 'makerware' ;
print $ fh $ gcodegen - > set_fan ( 0 , 1 ) if $ self - > config - > cooling && $ self - > config - > disable_fan_first_layers ;
2012-05-20 18:07:39 +00:00
2013-07-31 16:55:23 +00:00
# set bed temperature
2014-03-24 16:52:14 +00:00
if ( ( my $ temp = $ self - > config - > first_layer_bed_temperature ) && $ self - > config - > start_gcode !~ /M(?:190|140)/i ) {
2013-07-31 16:55:23 +00:00
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 ) = @ _ ;
2014-03-24 16:52:14 +00:00
return if $ self - > config - > start_gcode =~ /M(?:109|104)/i ;
for my $ t ( @ { $ self - > extruders } ) {
my $ temp = $ self - > config - > get_at ( 'first_layer_temperature' , $ t ) ;
2013-11-15 15:01:15 +00:00
$ temp += $ self - > config - > standby_temperature_delta if $ self - > config - > ooze_prevention ;
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 ) ;
2014-03-24 16:52:14 +00:00
printf $ fh "%s\n" , $ gcodegen - > placeholder_parser - > process ( $ self - > config - > start_gcode ) ;
2013-07-31 16:55:23 +00:00
$ print_first_layer_temperature - > ( 1 ) ;
# set other general things
2014-03-24 16:52:14 +00:00
print $ fh "G90 ; use absolute coordinates\n" if $ self - > config - > gcode_flavor ne 'makerware' ;
if ( $ self - > config - > gcode_flavor =~ /^(?:reprap|teacup)$/ ) {
2012-07-06 17:57:58 +00:00
printf $ fh $ gcodegen - > reset_e ;
2014-03-24 16:52:14 +00:00
if ( $ self - > config - > use_relative_e_distances ) {
2013-06-03 16:01:14 +00:00
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-08-23 13:42:58 +00:00
# initialize a motion planner for object-to-object travel moves
2014-03-24 16:52:14 +00:00
if ( $ self - > config - > avoid_crossing_perimeters ) {
2012-08-23 13:42:58 +00:00
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 ( [
2014-03-24 16:52:14 +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 ) {
2013-07-16 18:09:53 +00:00
my $ expolygon = Slic3r::ExPolygon - > new ( $ convex_hull ) ;
my @ island = @ { $ expolygon - > offset_ex ( scale $ distance_from_objects , 1 , JT_SQUARE ) } ;
2014-03-24 16:52:14 +00:00
foreach my $ copy ( @ { $ self - > objects - > [ $ obj_idx ] - > _shifted_copies } ) {
2013-11-18 16:20:48 +00:00
push @ islands , map { my $ c = $ _ - > clone ; $ c - > translate ( @$ copy ) ; $ c } @ island ;
2013-02-04 18:33:30 +00:00
}
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 ] ) ,
2013-12-22 18:07:07 +00:00
internal = > 0 ,
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
2013-11-15 15:01:15 +00:00
if ( $ self - > config - > ooze_prevention ) {
2014-03-24 16:52:14 +00:00
my @ skirt_points = map @$ _ , @ { $ self - > skirt } ;
if ( @ skirt_points ) {
my $ outer_skirt = convex_hull ( \ @ skirt_points ) ;
my @ skirts = ( ) ;
foreach my $ extruder_id ( @ { $ self - > extruders } ) {
push @ skirts , my $ s = $ outer_skirt - > clone ;
$ s - > translate ( map scale ( $ _ ) , @ { $ self - > config - > get_at ( 'extruder_offset' , $ extruder_id ) } ) ;
}
my $ convex_hull = convex_hull ( [ map @$ _ , @ skirts ] ) ;
$ gcodegen - > standby_points ( [ map $ _ - > clone , map @$ _ , map $ _ - > subdivide ( scale 10 ) , @ { offset ( [ $ convex_hull ] , scale 3 ) } ] ) ;
2013-11-15 14:52:11 +00:00
}
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 ,
) ;
2012-05-20 18:07:39 +00:00
2014-03-24 16:52:14 +00:00
# set initial extruder only after custom start G-code
print $ fh $ gcodegen - > set_extruder ( $ self - > extruders - > [ 0 ] ) ;
2012-05-20 18:07:39 +00:00
# do all objects for each layer
2014-03-24 16:52:14 +00:00
if ( $ self - > config - > complete_objects ) {
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 ) {
2014-03-24 16:52:14 +00:00
for my $ copy ( @ { $ self - > objects - > [ $ obj_idx ] - > _shifted_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 ) {
2014-03-24 16:52:14 +00:00
$ gcodegen - > set_shift ( map 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 (
2014-03-24 16:52:14 +00:00
config = > $ self - > config ,
2013-04-03 23:17:44 +00:00
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 ) {
2014-03-24 16:52:14 +00:00
printf $ fh $ gcodegen - > set_bed_temperature ( $ self - > config - > first_layer_bed_temperature ) ,
if $ self - > 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
2014-03-24 16:52:14 +00:00
my @ obj_idx = @ { chained_path ( [ map Slic3r::Point - > new ( @ { $ _ - > _shifted_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 (
2014-03-24 16:52:14 +00:00
config = > $ self - > config ,
2013-04-03 23:17:44 +00:00
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 (
2014-03-24 16:52:14 +00:00
$ layer_gcode - > process_layer ( $ layer , $ layer - > object - > _shifted_copies ) ,
2013-07-29 18:49:54 +00:00
$ 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 ) ;
2014-03-24 16:52:14 +00:00
printf $ fh "%s\n" , $ gcodegen - > placeholder_parser - > process ( $ self - > config - > end_gcode ) ;
$ self - > total_used_filament ( 0 ) ;
$ self - > total_extruded_volume ( 0 ) ;
foreach my $ extruder_id ( @ { $ self - > extruders } ) {
my $ extruder = $ gcodegen - > extruders - > { $ extruder_id } ;
# the final retraction doesn't really count as "used filament"
my $ used_filament = $ extruder - > absolute_E + $ extruder - > retract_length ;
my $ extruded_volume = $ extruder - > extruded_volume ( $ used_filament ) ;
2013-08-28 18:13:18 +00:00
printf $ fh "; filament used = %.1fmm (%.1fcm3)\n" ,
2014-03-24 16:52:14 +00:00
$ used_filament , $ extruded_volume / 1000 ;
$ self - > total_used_filament ( $ self - > total_used_filament + $ used_filament ) ;
$ self - > total_extruded_volume ( $ self - > total_extruded_volume + $ extruded_volume ) ;
2013-08-28 18:13:18 +00:00
}
2011-12-20 14:29:15 +00:00
2014-03-24 16:52:14 +00:00
# append full config
print $ fh "\n" ;
foreach my $ opt_key ( sort @ { $ self - > config - > get_keys } ) {
next if $ Slic3r:: Config:: Options - > { $ opt_key } { shortcut } ;
printf $ fh "; %s = %s\n" , $ opt_key , $ self - > config - > serialize ( $ opt_key ) ;
2012-11-18 18:53:52 +00:00
}
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 ;
2014-03-24 16:52:14 +00:00
my ( $ path ) = @ _ ;
2012-09-12 14:30:44 +00:00
2014-03-24 16:52:14 +00:00
return undef if ! @ { $ self - > objects } ;
my $ input_file = first { defined $ _ } map $ _ - > model_object - > input_file , @ { $ self - > objects } ;
return undef if ! defined $ input_file ;
my $ filename = my $ filename_base = basename ( $ input_file ) ;
$ filename_base =~ s/\.[^.]+$// ; # without suffix
my $ extra = {
input_filename = > $ filename ,
input_filename_base = > $ filename_base ,
} ;
2013-11-10 23:08:50 +00:00
if ( $ path && - d $ path ) {
# if output path is an existing directory, we take that and append
# the specified filename format
$ path = File::Spec - > join ( $ path , $ self - > config - > output_filename_format ) ;
} elsif ( ! $ path ) {
# if no explicit output file was defined, we take the input
# file directory and append the specified filename format
$ path = ( fileparse ( $ input_file ) ) [ 1 ] . $ self - > config - > output_filename_format ;
} else {
# path is a full path to a file so we use it as it is
}
2014-03-15 19:45:10 +00:00
2014-03-24 16:52:14 +00:00
return $ self - > placeholder_parser - > process ( $ path , $ extra ) ;
2013-12-18 17:54:11 +00:00
}
2014-03-24 16:52:14 +00:00
sub invalidate_step {
my ( $ self , $ step , $ obj_idx ) = @ _ ;
2013-12-19 17:54:24 +00:00
2014-03-24 16:52:14 +00:00
# invalidate $step in the correct state object
if ( $ Slic3r:: Print:: State:: print_step - > { $ step } ) {
$ self - > _state - > invalidate ( $ step ) ;
} else {
# object step
if ( defined $ obj_idx ) {
$ self - > objects - > [ $ obj_idx ] - > _state - > invalidate ( $ step ) ;
} else {
$ _ - > _state - > invalidate ( $ step ) for @ { $ self - > objects } ;
}
}
# recursively invalidate steps depending on $step
$ self - > invalidate_step ( $ _ )
for grep { grep { $ _ == $ step } @ { $ Slic3r:: Print:: State:: prereqs { $ _ } } }
keys % Slic3r:: Print:: State:: prereqs ;
}
# This method assigns extruders to the volumes having a material
# but not having extruders set in the material config.
sub auto_assign_extruders {
my ( $ self , $ model_object ) = @ _ ;
2014-03-26 23:01:33 +00:00
# only assign extruders if object has more than one volume
return if @ { $ model_object - > volumes } == 1 ;
2014-03-24 16:52:14 +00:00
my $ extruders = scalar @ { $ self - > config - > nozzle_diameter } ;
foreach my $ i ( 0 .. $# { $ model_object - > volumes } ) {
my $ volume = $ model_object - > volumes - > [ $ i ] ;
if ( defined $ volume - > material_id ) {
my $ material = $ model_object - > model - > materials - > { $ volume - > material_id } ;
my $ config = $ material - > config ;
my $ extruder_id = $ i + 1 ;
$ config - > set_ifndef ( 'extruder' , $ extruder_id ) ;
}
}
2013-12-30 17:28:41 +00:00
}
2011-09-01 19:06:28 +00:00
1 ;