2011-09-01 21:06:28 +02:00
package Slic3r::Print ;
2011-09-06 11:50:43 +02:00
use Moo ;
2011-09-01 21:06:28 +02:00
2012-04-30 14:56:01 +02:00
use File::Basename qw(basename fileparse) ;
2012-08-07 23:37:16 +02:00
use File::Spec ;
2012-10-29 11:17:57 +01:00
use List::Util qw(max first) ;
2012-10-22 15:29:54 +02:00
use Math::ConvexHull::MonotoneChain qw(convex_hull) ;
2012-05-19 15:40:11 +02:00
use Slic3r::ExtrusionPath ':roles' ;
2012-10-22 15:29:54 +02:00
use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point) ;
2012-08-06 20:26:08 +02:00
use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex offset JT_ROUND JT_SQUARE) ;
2012-04-30 14:56:01 +02:00
use Time::HiRes qw(gettimeofday tv_interval) ;
2011-09-25 22:11:56 +02:00
2012-07-29 17:02:46 +02:00
has 'config' => ( is => 'rw' , default => sub { Slic3r::Config -> new_from_defaults }, trigger => 1 );
2012-09-12 15:29:44 +02:00
has 'extra_variables' => ( is => 'rw' , default => sub {{}});
2012-04-30 14:56:01 +02:00
has 'objects' => ( is => 'rw' , default => sub { [] });
2012-04-29 12:51:20 +02:00
has 'total_extrusion_length' => ( is => 'rw' );
2012-09-23 02:40:25 +02:00
has 'processing_time' => ( is => 'rw' );
has 'extruders' => ( is => 'rw' , default => sub { [] });
2012-09-23 02:52:31 +02:00
has 'regions' => ( is => 'rw' , default => sub { [] });
2012-09-23 02:40:25 +02:00
has 'support_material_flow' => ( is => 'rw' );
has 'first_layer_support_material_flow' => ( is => 'rw' );
2013-02-04 15:48:57 +01:00
has 'has_support_material' => ( is => 'lazy' );
2011-09-03 20:47:38 +02:00
2012-04-29 12:51:20 +02:00
# ordered collection of extrusion paths to build skirt loops
has 'skirt' => (
2011-09-01 21:06:28 +02:00
is => 'rw' ,
2012-04-29 12:51:20 +02:00
#isa => 'ArrayRef[Slic3r::ExtrusionLoop]',
2011-09-01 21:06:28 +02:00
default => sub { [] },
);
2012-06-23 21:31:29 +02:00
# ordered collection of extrusion paths to build a brim
has 'brim' => (
is => 'rw' ,
#isa => 'ArrayRef[Slic3r::ExtrusionLoop]',
default => sub { [] },
);
2012-07-27 21:13:03 +02:00
sub BUILD {
my $self = shift ;
2012-07-29 17:02:46 +02:00
# call this manually because the 'default' coderef doesn't trigger the trigger
$self -> _trigger_config ;
}
sub _trigger_config {
my $self = shift ;
2012-07-27 21:13:03 +02:00
# store config in a handy place
$ Slic3r:: Config = $self -> config ;
2012-08-30 23:04:56 +02:00
# legacy with existing config files
$self -> config -> set ( 'first_layer_height' , $self -> config -> layer_height )
if ! $self -> config -> first_layer_height ;
$self -> config -> set_ifndef ( 'small_perimeter_speed' , $self -> config -> perimeter_speed );
$self -> config -> set_ifndef ( 'bridge_speed' , $self -> config -> infill_speed );
$self -> config -> set_ifndef ( 'solid_infill_speed' , $self -> config -> infill_speed );
$self -> config -> set_ifndef ( 'top_solid_infill_speed' , $self -> config -> solid_infill_speed );
2012-10-25 12:21:04 +02:00
$self -> config -> set_ifndef ( 'top_solid_layers' , $self -> config -> solid_layers );
$self -> config -> set_ifndef ( 'bottom_solid_layers' , $self -> config -> solid_layers );
2012-08-30 23:04:56 +02:00
2012-07-27 21:13:03 +02:00
# G-code flavors
$self -> config -> set ( 'extrusion_axis' , 'A' ) if $self -> config -> gcode_flavor eq 'mach3' ;
$self -> config -> set ( 'extrusion_axis' , '' ) if $self -> config -> gcode_flavor eq 'no-extrusion' ;
}
2013-02-04 15:48:57 +01: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 ;
}
2012-08-29 16:49:38 +02:00
sub add_model {
my $self = shift ;
my ( $model ) = @_ ;
2012-09-23 02:40:25 +02: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 02:52:31 +02: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-08-29 16:49:38 +02:00
}
2012-04-30 14:56:01 +02:00
}
2012-08-29 16:49:38 +02:00
foreach my $object ( @ { $model -> objects }) {
2012-09-23 02:52:31 +02:00
my @meshes = (); # by region_id
2012-08-29 16:49:38 +02:00
2012-09-22 19:04:36 +02:00
foreach my $volume ( @ { $object -> volumes }) {
# should the object contain multiple volumes of the same material, merge them
2012-09-23 02:52:31 +02:00
my $region_id = defined $volume -> material_id ? $materials { $volume -> material_id } : 0 ;
2012-09-22 19:38:25 +02:00
my $mesh = $volume -> mesh -> clone ;
2012-09-23 02:52:31 +02:00
$meshes [ $region_id ] = $meshes [ $region_id ]
? Slic3r::TriangleMesh -> merge ( $meshes [ $region_id ], $mesh )
2012-09-22 19:38:25 +02:00
: $mesh ;
2012-08-29 16:49:38 +02:00
}
2012-09-22 19:04:36 +02:00
foreach my $mesh ( @meshes ) {
next unless $mesh ;
$mesh -> check_manifoldness ;
if ( $object -> instances ) {
# we ignore the per-instance rotation currently and only
# consider the first one
$mesh -> rotate ( $object -> instances -> [ 0 ] -> rotation );
}
$mesh -> rotate ( $ Slic3r:: Config -> rotate );
$mesh -> scale ( $ Slic3r:: Config -> scale / & Slic3r:: SCALING_FACTOR );
}
2012-09-22 21:03:57 +02:00
my $complete_mesh = Slic3r::TriangleMesh -> merge ( grep defined $_ , @meshes );
2012-09-22 19:04:36 +02:00
# initialize print object
2012-09-22 21:03:57 +02:00
my $print_object = Slic3r::Print::Object -> new (
2012-09-22 19:04:36 +02:00
print => $self ,
meshes => [ @meshes ],
2012-09-22 21:03:57 +02:00
size => [ $complete_mesh -> size ],
2013-03-10 14:58:49 +01:00
input_file => $object -> input_file ,
layer_height_ranges => $object -> layer_height_ranges ,
2012-09-22 19:04:36 +02:00
);
2012-09-22 21:03:57 +02:00
push @ { $self -> objects }, $print_object ;
# align object to origin
{
my @extents = $complete_mesh -> extents ;
foreach my $mesh ( grep defined $_ , @meshes ) {
$mesh -> move ( map - $extents [ $_ ][ MIN ], X , Y , Z );
}
}
2012-08-29 16:49:38 +02:00
if ( $object -> instances ) {
# replace the default [0,0] instance with the custom ones
2013-03-16 20:56:14 +01:00
$print_object -> copies ([ map [ scale $_ -> offset -> [ X ], scale $_ -> offset -> [ Y ] ], @ { $object -> instances } ]);
2012-08-29 16:49:38 +02:00
}
2012-04-30 14:56:01 +02:00
}
2011-10-02 09:57:37 +02:00
}
2012-05-23 11:47:52 +02:00
sub validate {
my $self = shift ;
2012-07-27 21:13:03 +02:00
if ( $ Slic3r:: Config -> complete_objects ) {
2012-05-23 11:47:52 +02:00
# check horizontal clearance
{
my @a = ();
for my $obj_idx ( 0 .. $# { $self -> objects }) {
my $clearance ;
{
2012-09-22 19:04:36 +02:00
my @points = map [ @$_ [ X , Y ] ], map @ { $_ -> vertices }, @ { $self -> objects -> [ $obj_idx ] -> meshes };
2012-05-23 11:47:52 +02:00
my $convex_hull = Slic3r::Polygon -> new ( convex_hull ( \ @points ));
2013-03-26 14:03:24 +01:00
( $clearance ) = offset ([ $convex_hull ], scale $ Slic3r:: Config -> extruder_clearance_radius / 2 , 1 , JT_ROUND );
2012-05-23 11:47:52 +02:00
}
2012-09-22 16:19:24 +02:00
for my $copy ( @ { $self -> objects -> [ $obj_idx ] -> copies }) {
2012-05-23 11:47:52 +02:00
my $copy_clearance = $clearance -> clone ;
$copy_clearance -> translate ( @$copy );
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
{
my @obj_copies = $self -> object_copies ;
pop @obj_copies ; # ignore the last copy: its height doesn't matter
2012-09-22 19:04:36 +02:00
my $scaled_clearance = scale $ Slic3r:: Config -> extruder_clearance_height ;
if ( grep { + ( $_ -> size )[ Z ] > $scaled_clearance } map @ { $self -> objects -> [ $_ -> [ 0 ]] -> meshes }, @obj_copies ) {
2012-05-23 11:47:52 +02:00
die "Some objects are too tall and cannot be printed without extruder collisions.\n" ;
}
}
}
}
2012-09-23 02:40:25 +02:00
sub init_extruders {
my $self = shift ;
2012-09-23 02:52:31 +02:00
# map regions to extruders (ghetto mapping for now)
my %extruder_mapping = map { $_ => $_ } 0 .. $# { $self -> regions };
2012-09-23 02:40:25 +02:00
# initialize all extruder(s) we need
my @used_extruders = (
0 ,
( 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 02:52:31 +02:00
# calculate regions' flows
for my $region_id ( 0 .. $# { $self -> regions }) {
my $region = $self -> regions -> [ $region_id ];
2012-09-23 02:40:25 +02:00
# per-role extruders and flows
2013-03-17 00:02:31 +01:00
for ( qw(perimeter infill solid_infill top_infill) ) {
my $extruder_name = $_ ;
$extruder_name =~ s/^(?:solid|top)_// ;
2012-09-23 02:52:31 +02:00
$region -> extruders -> { $_ } = ( $self -> regions_count > 1 )
? $self -> extruders -> [ $extruder_mapping { $region_id }]
2013-03-07 16:00:58 +01:00
: $self -> extruders -> [ $self -> config -> get ( "${extruder_name}_extruder" ) - 1 ];
2012-09-23 02:52:31 +02:00
$region -> flows -> { $_ } = $region -> extruders -> { $_ } -> make_flow (
2012-09-23 02:40:25 +02:00
width => $self -> config -> get ( "${_}_extrusion_width" ) || $self -> config -> extrusion_width ,
2013-03-17 00:21:17 +01:00
role => $_ ,
2012-09-23 02:40:25 +02:00
);
2012-09-23 02:52:31 +02:00
$region -> first_layer_flows -> { $_ } = $region -> extruders -> { $_ } -> make_flow (
2012-09-23 02:40:25 +02:00
layer_height => $self -> config -> get_value ( 'first_layer_height' ),
width => $self -> config -> first_layer_extrusion_width ,
2013-03-17 00:21:17 +01:00
role => $_ ,
2013-02-22 16:08:11 +01:00
) if $self -> config -> first_layer_extrusion_width ;
2012-09-23 02:40:25 +02:00
}
}
# calculate support material flow
2013-02-04 15:48:57 +01:00
if ( $self -> has_support_material ) {
2012-09-23 02:40:25 +02:00
my $extruder = $self -> extruders -> [ $self -> config -> support_material_extruder - 1 ];
$self -> support_material_flow ( $extruder -> make_flow (
width => $self -> config -> support_material_extrusion_width || $self -> config -> extrusion_width ,
2013-03-17 00:21:17 +01:00
role => 'support_material' ,
2012-09-23 02:40:25 +02:00
));
$self -> first_layer_support_material_flow ( $extruder -> make_flow (
layer_height => $self -> config -> get_value ( 'first_layer_height' ),
width => $self -> config -> first_layer_extrusion_width ,
2013-03-17 00:21:17 +01:00
role => 'support_material' ,
2012-09-23 02:40:25 +02:00
));
}
}
2012-05-23 11:47:52 +02:00
sub object_copies {
my $self = shift ;
my @oc = ();
for my $obj_idx ( 0 .. $# { $self -> objects }) {
2012-09-22 16:19:24 +02:00
push @oc , map [ $obj_idx , $_ ], @ { $self -> objects -> [ $obj_idx ] -> copies };
2012-05-23 11:47:52 +02:00
}
return @oc ;
}
2012-04-29 12:51:20 +02:00
sub layer_count {
my $self = shift ;
2013-01-17 14:56:31 +01:00
return max ( map { scalar @ { $_ -> layers } } @ { $self -> objects });
2012-04-29 12:51:20 +02:00
}
2012-09-23 02:52:31 +02:00
sub regions_count {
2012-09-23 02:40:25 +02:00
my $self = shift ;
2012-09-23 02:52:31 +02:00
return scalar @ { $self -> regions };
2012-09-23 02:40:25 +02:00
}
2012-04-30 14:56:01 +02:00
sub duplicate {
2012-02-19 10:48:58 +01:00
my $self = shift ;
2012-07-27 21:13:03 +02:00
if ( $ Slic3r:: Config -> duplicate_grid -> [ X ] > 1 || $ Slic3r:: Config -> duplicate_grid -> [ Y ] > 1 ) {
2012-04-29 12:51:20 +02:00
if ( @ { $self -> objects } > 1 ) {
die "Grid duplication is not supported with multiple objects\n" ;
}
my $object = $self -> objects -> [ 0 ];
2012-03-06 14:55:21 +11:00
# generate offsets for copies
2012-07-27 21:13:03 +02:00
my $dist = scale $ Slic3r:: Config -> duplicate_distance ;
2012-09-22 16:19:24 +02:00
@ { $self -> objects -> [ 0 ] -> copies } = ();
2012-07-27 21:13:03 +02:00
for my $x_copy ( 1 .. $ Slic3r:: Config -> duplicate_grid -> [ X ]) {
for my $y_copy ( 1 .. $ Slic3r:: Config -> duplicate_grid -> [ Y ]) {
2012-09-22 16:19:24 +02:00
push @ { $self -> objects -> [ 0 ] -> copies }, [
2012-08-25 20:14:01 +02:00
( $object -> size -> [ X ] + $dist ) * ( $x_copy - 1 ),
( $object -> size -> [ Y ] + $dist ) * ( $y_copy - 1 ),
2012-03-06 14:55:21 +11:00
];
}
2012-02-19 10:48:58 +01:00
}
2012-07-27 21:13:03 +02:00
} elsif ( $ Slic3r:: Config -> duplicate > 1 ) {
2012-09-22 16:19:24 +02:00
foreach my $object ( @ { $self -> objects }) {
@ { $object -> copies } = map [ 0 , 0 ], 1 .. $ Slic3r:: Config -> duplicate ;
2012-04-29 12:51:20 +02:00
}
2012-04-30 14:56:01 +02:00
$self -> arrange_objects ;
2012-02-19 10:48:58 +01:00
}
}
2012-04-30 14:56:01 +02:00
sub arrange_objects {
2011-09-18 19:28:12 +02:00
my $self = shift ;
2012-04-30 14:56:01 +02:00
2012-09-22 16:19:24 +02:00
my $total_parts = scalar map @ { $_ -> copies }, @ { $self -> objects };
2012-08-29 19:37:27 +02:00
my $partx = max ( map $_ -> size -> [ X ], @ { $self -> objects });
my $party = max ( map $_ -> size -> [ Y ], @ { $self -> objects });
2012-05-29 09:38:53 +02:00
2012-04-30 14:56:01 +02:00
my @positions = Slic3r::Geometry:: arrange
2012-09-12 16:30:44 +02:00
( $total_parts , $partx , $party , ( map scale $_ , @ { $ Slic3r:: Config -> bed_size }), scale $ Slic3r:: Config -> min_object_distance , $self -> config );
2012-04-30 14:56:01 +02:00
2012-09-22 16:19:24 +02:00
@ { $_ -> copies } = splice @positions , 0 , scalar @ { $_ -> copies } for @ { $self -> objects };
2012-04-30 14:56:01 +02:00
}
sub bounding_box {
my $self = shift ;
my @points = ();
foreach my $obj_idx ( 0 .. $# { $self -> objects }) {
my $object = $self -> objects -> [ $obj_idx ];
2012-09-22 16:19:24 +02:00
foreach my $copy ( @ { $self -> objects -> [ $obj_idx ] -> copies }) {
2012-04-30 14:56:01 +02:00
push @points ,
[ $copy -> [ X ], $copy -> [ Y ] ],
2012-08-25 20:14:01 +02:00
[ $copy -> [ X ] + $object -> size -> [ X ], $copy -> [ Y ] ],
[ $copy -> [ X ] + $object -> size -> [ X ], $copy -> [ Y ] + $object -> size -> [ Y ] ],
[ $copy -> [ X ], $copy -> [ Y ] + $object -> size -> [ Y ] ];
2012-04-30 14:56:01 +02:00
}
}
return Slic3r::Geometry:: bounding_box ( \ @points );
}
sub size {
my $self = shift ;
my @bb = $self -> bounding_box ;
return [ $bb [ X2 ] - $bb [ X1 ], $bb [ Y2 ] - $bb [ Y1 ] ];
}
2013-03-16 19:58:34 +01: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 14:56:01 +02:00
sub export_gcode {
my $self = shift ;
my %params = @_ ;
2012-09-23 02:40:25 +02:00
$self -> init_extruders ;
2012-04-30 14:56:01 +02:00
my $status_cb = $params { status_cb } || sub {};
my $t0 = [ gettimeofday ];
# skein the STL into layers
# each layer has surfaces with holes
$status_cb -> ( 10 , "Processing triangulated mesh" );
2013-03-16 19:39:00 +01:00
$_ -> slice for @ { $self -> objects };
2012-04-30 14:56:01 +02:00
2013-03-16 19:58:34 +01:00
if ( $ Slic3r:: Config -> resolution ) {
$status_cb -> ( 15 , "Simplifying input" );
$self -> _simplify_slices ( scale $ Slic3r:: Config -> resolution );
}
2012-04-30 14:56:01 +02:00
# make perimeters
# this will add a set of extrusion loops to each layer
# as well as generate infill boundaries
$status_cb -> ( 20 , "Generating perimeters" );
2012-05-05 16:36:10 +02:00
$_ -> make_perimeters for @ { $self -> objects };
2012-04-30 14:56:01 +02:00
2012-09-23 02:52:31 +02:00
# simplify slices (both layer and region slices),
2012-09-22 19:04:36 +02:00
# we only need the max resolution for perimeters
2013-03-16 19:58:34 +01:00
$self -> _simplify_slices ( & Slic3r:: SCALED_RESOLUTION );
2012-06-23 20:08:08 +02:00
2013-03-07 15:47:32 +01:00
# this will assign a type (top/bottom/internal) to $layerm->slices
# and transform $layerm->fill_surfaces from expolygon
2012-12-22 23:57:39 +01:00
# to typed top/bottom/internal surfaces;
2012-04-30 14:56:01 +02:00
$status_cb -> ( 30 , "Detecting solid surfaces" );
$_ -> detect_surfaces_type for @ { $self -> objects };
# decide what surfaces are to be filled
$status_cb -> ( 35 , "Preparing infill surfaces" );
2012-09-23 02:52:31 +02:00
$_ -> prepare_fill_surfaces for map @ { $_ -> regions }, map @ { $_ -> layers }, @ { $self -> objects };
2012-04-30 14:56:01 +02:00
# this will detect bridges and reverse bridges
# and rearrange top/bottom/internal surfaces
$status_cb -> ( 45 , "Detect bridges" );
2013-03-07 15:47:32 +01:00
$_ -> process_external_surfaces for map @ { $_ -> regions }, map @ { $_ -> layers }, @ { $self -> objects };
2012-04-30 14:56:01 +02:00
# detect which fill surfaces are near external layers
# they will be split in internal and internal-solid surfaces
$status_cb -> ( 60 , "Generating horizontal shells" );
$_ -> discover_horizontal_shells for @ { $self -> objects };
2013-02-09 23:36:32 +01:00
$_ -> clip_fill_surfaces for @ { $self -> objects };
2013-02-23 21:39:13 +01:00
# the following step needs to be done before combination because it may need
# to remove only half of the combined infill
$_ -> bridge_over_infill for @ { $self -> objects };
2012-04-30 14:56:01 +02:00
# combine fill surfaces to honor the "infill every N layers" option
$status_cb -> ( 70 , "Combining infill" );
2012-07-22 20:48:38 +02:00
$_ -> combine_infill for @ { $self -> objects };
2012-04-30 14:56:01 +02:00
# this will generate extrusion paths for each layer
$status_cb -> ( 80 , "Infilling layers" );
{
my $fill_maker = Slic3r::Fill -> new ( 'print' => $self );
Slic3r:: parallelize (
2012-09-22 19:04:36 +02:00
items => sub {
my @items = (); # [obj_idx, layer_id]
for my $obj_idx ( 0 .. $# { $self -> objects }) {
2012-09-23 02:52:31 +02:00
for my $region_id ( 0 .. ( $self -> regions_count - 1 )) {
push @items , map [ $obj_idx , $_ , $region_id ], 0 .. ( $self -> objects -> [ $obj_idx ] -> layer_count - 1 );
2012-09-22 19:04:36 +02:00
}
}
@items ;
},
2012-04-30 14:56:01 +02:00
thread_cb => sub {
my $q = shift ;
$ Slic3r::Geometry::Clipper:: clipper = Math::Clipper -> new ;
my $fills = {};
while ( defined ( my $obj_layer = $q -> dequeue )) {
2012-09-23 02:52:31 +02:00
my ( $obj_idx , $layer_id , $region_id ) = @$obj_layer ;
2012-04-30 14:56:01 +02:00
$fills -> { $obj_idx } ||= {};
2012-09-22 19:04:36 +02:00
$fills -> { $obj_idx }{ $layer_id } ||= {};
2012-09-23 02:52:31 +02:00
$fills -> { $obj_idx }{ $layer_id }{ $region_id } = [
$fill_maker -> make_fill ( $self -> objects -> [ $obj_idx ] -> layers -> [ $layer_id ] -> regions -> [ $region_id ]),
2012-09-22 19:04:36 +02:00
];
2012-04-30 14:56:01 +02:00
}
return $fills ;
},
collect_cb => sub {
my $fills = shift ;
foreach my $obj_idx ( keys %$fills ) {
2012-09-22 19:04:36 +02:00
my $object = $self -> objects -> [ $obj_idx ];
2012-04-30 14:56:01 +02:00
foreach my $layer_id ( keys % { $fills -> { $obj_idx }}) {
2012-09-22 19:04:36 +02:00
my $layer = $object -> layers -> [ $layer_id ];
2012-09-23 02:52:31 +02: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 19:04:36 +02:00
}
2012-04-30 14:56:01 +02:00
}
}
},
no_threads_cb => sub {
2012-09-23 02:52:31 +02:00
foreach my $layerm ( map @ { $_ -> regions }, map @ { $_ -> layers }, @ { $self -> objects }) {
2012-09-22 19:04:36 +02:00
$layerm -> fills ([ $fill_maker -> make_fill ( $layerm ) ]);
2012-04-30 14:56:01 +02:00
}
},
);
}
# generate support material
2013-02-04 15:48:57 +01:00
if ( $self -> has_support_material ) {
2012-04-30 14:56:01 +02:00
$status_cb -> ( 85 , "Generating support material" );
2012-09-22 19:04:36 +02:00
$_ -> generate_support_material for @ { $self -> objects };
2012-04-30 14:56:01 +02:00
}
# free memory (note that support material needs fill_surfaces)
2012-09-23 02:52:31 +02:00
$_ -> fill_surfaces ( undef ) for map @ { $_ -> regions }, map @ { $_ -> layers }, @ { $self -> objects };
2012-04-30 14:56:01 +02:00
# make skirt
$status_cb -> ( 88 , "Generating skirt" );
$self -> make_skirt ;
2012-10-14 22:10:49 +02:00
$self -> make_brim ; # must come after make_skirt
2012-04-30 14:56:01 +02:00
2013-02-27 11:26:52 +01:00
# time to make some statistics
if ( 0 ) {
eval "use Devel::Size" ;
print "MEMORY USAGE:\n" ;
printf " meshes = %.1fMb\n" , List::Util:: sum ( map Devel::Size:: total_size ( $_ -> meshes ), @ { $self -> objects }) /1024/ 1024 ;
printf " layer slices = %.1fMb\n" , List::Util:: sum ( map Devel::Size:: total_size ( $_ -> slices ), map @ { $_ -> layers }, @ { $self -> objects }) /1024/ 1024 ;
printf " region slices = %.1fMb\n" , List::Util:: sum ( map Devel::Size:: total_size ( $_ -> slices ), map @ { $_ -> regions }, map @ { $_ -> layers }, @ { $self -> objects }) /1024/ 1024 ;
printf " perimeters = %.1fMb\n" , List::Util:: sum ( map Devel::Size:: total_size ( $_ -> perimeters ), map @ { $_ -> regions }, map @ { $_ -> layers }, @ { $self -> objects }) /1024/ 1024 ;
printf " fills = %.1fMb\n" , List::Util:: sum ( map Devel::Size:: total_size ( $_ -> fills ), map @ { $_ -> regions }, map @ { $_ -> layers }, @ { $self -> objects }) /1024/ 1024 ;
printf " print object = %.1fMb\n" , Devel::Size:: total_size ( $self ) /1024/ 1024 ;
}
2012-04-30 14:56:01 +02:00
# output everything to a G-code file
my $output_file = $self -> expanded_output_filepath ( $params { output_file });
2012-11-23 11:24:04 +01:00
$status_cb -> ( 90 , "Exporting G-code" . ( $output_file ? " to $output_file" : "" ));
2012-11-21 20:41:14 +01:00
$self -> write_gcode ( $params { output_fh } || $output_file );
2012-04-30 14:56:01 +02:00
# run post-processing scripts
2012-07-27 21:13:03 +02:00
if ( @ { $ Slic3r:: Config -> post_process }) {
2012-04-30 14:56:01 +02:00
$status_cb -> ( 95 , "Running post-processing scripts" );
2012-07-27 21:13:03 +02:00
$ Slic3r:: Config -> setenv ;
for ( @ { $ Slic3r:: Config -> post_process }) {
2012-04-30 14:56:01 +02:00
Slic3r:: debugf " '%s' '%s'\n" , $_ , $output_file ;
system ( $_ , $output_file );
}
}
# output some statistics
2012-11-21 20:41:14 +01:00
unless ( $params { quiet }) {
$self -> processing_time ( tv_interval ( $t0 ));
printf "Done. Process took %d minutes and %.3f seconds\n" ,
int ( $self -> processing_time / 60 ),
$self -> processing_time - int ( $self -> processing_time / 60 ) * 60 ;
# TODO: more statistics!
printf "Filament required: %.1fmm (%.1fcm3)\n" ,
$self -> total_extrusion_length , $self -> total_extrusion_volume ;
}
2012-04-30 14:56:01 +02:00
}
sub export_svg {
my $self = shift ;
my %params = @_ ;
2012-09-28 16:32:53 +02:00
# this shouldn't be needed, but we're currently relying on ->make_surfaces() which
# calls ->perimeter_flow
$self -> init_extruders ;
2013-03-16 19:39:00 +01:00
$_ -> slice for @ { $self -> objects };
2012-04-30 14:56:01 +02:00
$self -> arrange_objects ;
my $output_file = $self -> expanded_output_filepath ( $params { output_file });
$output_file =~ s/\.gcode$/.svg/i ;
2013-01-13 10:18:34 +01:00
Slic3r:: open ( \ my $fh , ">" , $output_file ) or die "Failed to open $output_file for writing\n" ;
2012-04-30 14:56:01 +02:00
print "Exporting to $output_file..." ;
my $print_size = $self -> size ;
print $fh sprintf << "EOF" , unscale ( $print_size -> [ X ]), unscale ( $print_size -> [ Y ]);
< ? xml version = "1.0" encoding = "UTF-8" standalone = "yes" ? >
<! DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" >
< svg width = "%s" height = "%s" xmlns = "http://www.w3.org/2000/svg" xmlns:svg = "http://www.w3.org/2000/svg" xmlns:xlink = "http://www.w3.org/1999/xlink" xmlns:slic3r = "http://slic3r.org/namespaces/slic3r" >
<!--
Generated using Slic3r $ Slic3r:: VERSION
http: //s lic3r . org /
-->
EOF
my $print_polygon = sub {
my ( $polygon , $type ) = @_ ;
printf $fh qq{ <polygon slic3r:type="%s" points="%s" style="fill: %s" />\n} ,
$type , ( join ' ' , map { join ',' , map unscale $_ , @$_ } @$polygon ),
2012-05-21 18:29:19 +02:00
( $type eq 'contour' ? 'white' : 'black' );
2012-04-30 14:56:01 +02:00
};
2012-06-11 14:47:48 +02:00
my @previous_layer_slices = ();
2012-04-30 14:56:01 +02:00
for my $layer_id ( 0 .. $self -> layer_count - 1 ) {
my @layers = map $_ -> layers -> [ $layer_id ], @ { $self -> objects };
printf $fh qq{ <g id="layer%d" slic3r:z="%s">\n} , $layer_id , unscale + ( grep defined $_ , @layers )[ 0 ] -> slice_z ;
2012-06-11 14:47:48 +02:00
my @current_layer_slices = ();
2012-06-11 13:50:38 +02:00
for my $obj_idx ( 0 .. $# { $self -> objects }) {
my $layer = $self -> objects -> [ $obj_idx ] -> layers -> [ $layer_id ] or next ;
2012-04-30 14:56:01 +02:00
# sort slices so that the outermost ones come first
2012-09-28 16:32:53 +02:00
my @slices = sort { $a -> contour -> encloses_point ( $b -> contour -> [ 0 ]) ? 0 : 1 } @ { $layer -> slices };
2012-09-22 16:19:24 +02:00
foreach my $copy ( @ { $self -> objects -> [ $obj_idx ] -> copies }) {
2012-04-30 14:56:01 +02:00
foreach my $slice ( @slices ) {
2012-09-28 16:32:53 +02:00
my $expolygon = $slice -> clone ;
2012-04-30 14:56:01 +02:00
$expolygon -> translate ( @$copy );
$print_polygon -> ( $expolygon -> contour , 'contour' );
$print_polygon -> ( $_ , 'hole' ) for $expolygon -> holes ;
2012-06-11 14:47:48 +02:00
push @current_layer_slices , $expolygon ;
2012-04-30 14:56:01 +02:00
}
}
}
2012-06-11 14:47:48 +02:00
# generate support material
2013-02-04 15:48:57 +01:00
if ( $self -> has_support_material && $layer_id > 0 ) {
2012-06-11 14:47:48 +02:00
my ( @supported_slices , @unsupported_slices ) = ();
foreach my $expolygon ( @current_layer_slices ) {
my $intersection = intersection_ex (
[ map @$_ , @previous_layer_slices ],
$expolygon ,
);
@$intersection
? push @supported_slices , $expolygon
: push @unsupported_slices , $expolygon ;
}
my @supported_points = map @$_ , @$_ , @supported_slices ;
foreach my $expolygon ( @unsupported_slices ) {
# look for the nearest point to this island among all
# supported points
2012-09-21 16:52:05 +02:00
my $support_point = nearest_point ( $expolygon -> contour -> [ 0 ], \ @supported_points )
or next ;
2012-09-28 16:32:53 +02:00
my $anchor_point = nearest_point ( $support_point , $expolygon -> contour );
2012-06-11 20:42:39 +02:00
printf $fh qq{ <line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width: 2; stroke: white" />\n} ,
2012-06-11 14:47:48 +02:00
map @$_ , $support_point , $anchor_point ;
}
}
2012-04-30 14:56:01 +02:00
print $fh qq{ </g>\n} ;
2012-06-11 14:47:48 +02:00
@previous_layer_slices = @current_layer_slices ;
2012-04-30 14:56:01 +02:00
}
print $fh "</svg>\n" ;
close $fh ;
print "Done.\n" ;
2011-09-18 19:28:12 +02:00
}
2012-04-29 12:51:20 +02:00
sub make_skirt {
2011-11-13 18:41:12 +01:00
my $self = shift ;
2012-07-27 21:13:03 +02:00
return unless $ Slic3r:: Config -> skirts > 0 ;
2011-11-13 18:41:12 +01:00
# collect points from all layers contained in skirt height
2012-04-29 12:51:20 +02:00
my @points = ();
foreach my $obj_idx ( 0 .. $# { $self -> objects }) {
2013-01-18 08:36:01 -08: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 23:28:48 +01:00
my @layers = map $self -> objects -> [ $obj_idx ] -> layers -> [ $_ ], 0 .. ( $skirt_height - 1 );
2012-04-29 12:51:20 +02:00
my @layer_points = (
2012-09-22 19:38:25 +02:00
( map @$_ , map @$_ , map @ { $_ -> slices }, @layers ),
2012-09-23 02:52:31 +02:00
( map @$_ , map @ { $_ -> thin_walls }, map @ { $_ -> regions }, @layers ),
2012-07-20 14:39:07 +02:00
( map @ { $_ -> unpack -> polyline }, map @ { $_ -> support_fills -> paths }, grep $_ -> support_fills , @layers ),
2012-04-29 12:51:20 +02:00
);
2012-09-22 16:19:24 +02:00
push @points , map move_points ( $_ , @layer_points ), @ { $self -> objects -> [ $obj_idx ] -> copies };
2012-04-29 12:51:20 +02:00
}
2012-03-31 18:32:53 +02:00
return if @points < 3 ; # at least three points required for a convex hull
2011-11-13 18:41:12 +01:00
# find out convex hull
2012-04-29 12:51:20 +02:00
my $convex_hull = convex_hull ( \ @points );
2011-11-13 18:41:12 +01:00
2012-10-29 11:17:57 +01:00
my @extruded_length = (); # for each extruder
2013-02-22 16:08:11 +01:00
# TODO: use each extruder's own flow
my $spacing = $self -> objects -> [ 0 ] -> layers -> [ 0 ] -> regions -> [ 0 ] -> perimeter_flow -> spacing ;
2012-10-29 11:17:57 +01:00
my $first_layer_height = $ Slic3r:: Config -> get_value ( 'first_layer_height' );
my @extruders_e_per_mm = ();
my $extruder_idx = 0 ;
2011-11-13 18:41:12 +01:00
# draw outlines from outside to inside
2012-10-29 11:17:57 +01:00
# loop while we have less skirts than required or any extruder hasn't reached the min length if any
my $distance = scale $ Slic3r:: Config -> skirt_distance ;
2012-07-27 21:13:03 +02:00
for ( my $i = $ Slic3r:: Config -> skirts ; $i > 0 ; $i -- ) {
2012-10-29 11:17:57 +01:00
$distance += scale $spacing ;
2012-11-05 00:17:46 +01:00
my ( $loop ) = Slic3r::Geometry::Clipper:: offset ([ $convex_hull ], $distance , 0.0001 , JT_ROUND );
2012-10-14 22:10:49 +02:00
push @ { $self -> skirt }, Slic3r::ExtrusionLoop -> pack (
2012-10-29 11:17:57 +01:00
polygon => Slic3r::Polygon -> new ( @$loop ),
2012-10-15 10:57:15 +02:00
role => EXTR_ROLE_SKIRT ,
2012-10-29 11:17:57 +01:00
flow_spacing => $spacing ,
2011-12-30 19:59:51 +01:00
);
2012-10-29 11:17:57 +01: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 18:41:12 +01:00
}
2012-10-29 11:17:57 +01:00
@ { $self -> skirt } = reverse @ { $self -> skirt };
2012-02-19 12:03:36 +01:00
}
2012-06-23 21:31:29 +02:00
sub make_brim {
my $self = shift ;
2012-07-27 21:13:03 +02:00
return unless $ Slic3r:: Config -> brim_width > 0 ;
2012-06-23 21:31:29 +02:00
2013-02-22 16:08:11 +01:00
my $flow = $self -> objects -> [ 0 ] -> layers -> [ 0 ] -> regions -> [ 0 ] -> perimeter_flow ;
my $grow_distance = $flow -> scaled_width / 2 ;
2012-06-23 21:31:29 +02:00
my @islands = (); # array of polygons
foreach my $obj_idx ( 0 .. $# { $self -> objects }) {
2012-08-06 20:54:49 +02:00
my $layer0 = $self -> objects -> [ $obj_idx ] -> layers -> [ 0 ];
my @object_islands = (
( map $_ -> contour , @ { $layer0 -> slices }),
2012-09-23 02:52:31 +02:00
( map { $_ -> isa ( 'Slic3r::Polygon' ) ? $_ : $_ -> grow ( $grow_distance ) } map @ { $_ -> thin_walls }, @ { $layer0 -> regions }),
2012-08-07 22:04:58 +02:00
( map $_ -> unpack -> polyline -> grow ( $grow_distance ), map @ { $_ -> support_fills -> paths }, grep $_ -> support_fills , $layer0 ),
2012-08-06 20:54:49 +02:00
);
2012-09-22 16:19:24 +02:00
foreach my $copy ( @ { $self -> objects -> [ $obj_idx ] -> copies }) {
2012-06-23 21:31:29 +02:00
push @islands , map $_ -> clone -> translate ( @$copy ), @object_islands ;
}
}
2012-10-14 22:10:49 +02:00
# if brim touches skirt, make it around skirt too
2013-02-22 16:08:11 +01:00
# TODO: calculate actual skirt width (using each extruder's flow in multi-extruder setups)
if ( $ Slic3r:: Config -> skirt_distance + (( $ Slic3r:: Config -> skirts - 1 ) * $flow -> spacing ) <= $ Slic3r:: Config -> brim_width ) {
2012-10-14 22:10:49 +02:00
push @islands , map $_ -> unpack -> split_at_first_point -> polyline -> grow ( $grow_distance ), @ { $self -> skirt };
}
2013-02-22 16:08:11 +01:00
my $num_loops = sprintf "%.0f" , $ Slic3r:: Config -> brim_width / $flow -> width ;
2012-06-23 21:31:29 +02:00
for my $i ( reverse 1 .. $num_loops ) {
2012-08-06 20:26:08 +02:00
# JT_SQUARE ensures no vertex is outside the given offset distance
2012-07-20 14:39:07 +02:00
push @ { $self -> brim }, Slic3r::ExtrusionLoop -> pack (
2012-10-15 10:57:15 +02:00
polygon => Slic3r::Polygon -> new ( $_ ),
role => EXTR_ROLE_SKIRT ,
2013-02-22 16:08:11 +01:00
flow_spacing => $flow -> spacing ,
2013-02-24 16:40:14 +01:00
) for Slic3r::Geometry::Clipper:: offset ( \ @islands , ( $i - 0.5 ) * $flow -> scaled_spacing , undef , JT_SQUARE ); # -0.5 because islands are not represented by their centerlines
2012-09-28 15:27:33 +02:00
# TODO: we need the offset inwards/offset outwards logic to avoid overlapping extrusions
2012-06-23 21:31:29 +02:00
}
}
2012-04-30 14:56:01 +02:00
sub write_gcode {
2011-09-03 20:47:38 +02:00
my $self = shift ;
my ( $file ) = @_ ;
2012-11-21 20:41:14 +01:00
# open output gcode file if we weren't supplied a file-handle
my $fh ;
if ( ref $file eq 'IO::Scalar' ) {
$fh = $file ;
} else {
2013-01-13 10:18:34 +01:00
Slic3r:: open ( \ $fh , ">" , $file )
2012-11-21 20:41:14 +01:00
or die "Failed to open $file for writing\n" ;
}
2011-09-03 20:47:38 +02:00
2011-12-01 22:20:48 +01:00
# write some information
my @lt = localtime ;
2012-05-01 15:01:56 +02:00
printf $fh "; generated by Slic3r $Slic3r::VERSION on %04d-%02d-%02d at %02d:%02d:%02d\n\n" ,
2011-12-30 18:57:58 +01:00
$lt [ 5 ] + 1900 , $lt [ 4 ] + 1 , $lt [ 3 ], $lt [ 2 ], $lt [ 1 ], $lt [ 0 ];
2012-02-05 20:55:17 +01:00
2012-07-27 21:13:03 +02:00
print $fh "; $_\n" foreach split /\R/ , $ Slic3r:: Config -> notes ;
print $fh "\n" if $ Slic3r:: Config -> notes ;
2011-12-01 22:20:48 +01:00
2013-03-19 19:15:53 +01:00
for ( qw(layer_height perimeters top_solid_layers bottom_solid_layers fill_density perimeter_speed infill_speed travel_speed) ) {
2012-07-27 21:13:03 +02:00
printf $fh "; %s = %s\n" , $_ , $ Slic3r:: Config -> $_ ;
2011-12-01 22:20:48 +01:00
}
2012-06-28 16:22:11 +02:00
for ( qw(nozzle_diameter filament_diameter extrusion_multiplier) ) {
2012-07-27 21:13:03 +02:00
printf $fh "; %s = %s\n" , $_ , $ Slic3r:: Config -> $_ -> [ 0 ];
2012-06-28 16:22:11 +02:00
}
2012-10-29 00:31:25 +01:00
printf $fh "; perimeters extrusion width = %.2fmm\n" , $self -> regions -> [ 0 ] -> flows -> { perimeter } -> width ;
printf $fh "; infill extrusion width = %.2fmm\n" , $self -> regions -> [ 0 ] -> flows -> { infill } -> width ;
2013-03-17 00:21:17 +01:00
printf $fh "; solid infill extrusion width = %.2fmm\n" , $self -> regions -> [ 0 ] -> flows -> { solid_infill } -> width ;
printf $fh "; top infill extrusion width = %.2fmm\n" , $self -> regions -> [ 0 ] -> flows -> { top_infill } -> width ;
2012-10-29 00:31:25 +01:00
printf $fh "; support material extrusion width = %.2fmm\n" , $self -> support_material_flow -> width
if $self -> support_material_flow ;
2013-02-22 16:08:11 +01:00
printf $fh "; first layer extrusion width = %.2fmm\n" , $self -> regions -> [ 0 ] -> first_layer_flows -> { perimeter } -> width
if $self -> regions -> [ 0 ] -> first_layer_flows -> { perimeter };
2011-12-01 22:20:48 +01:00
print $fh "\n" ;
2012-05-20 20:07:39 +02:00
# set up our extruder object
2012-09-23 02:40:25 +02:00
my $gcodegen = Slic3r::GCode -> new (
2013-01-17 14:56:31 +01:00
multiple_extruders => ( @ { $self -> extruders } > 1 ),
layer_count => $self -> layer_count ,
2012-09-23 02:40:25 +02:00
);
2012-07-27 21:13:03 +02:00
my $min_print_speed = 60 * $ Slic3r:: Config -> min_print_speed ;
2012-06-28 14:44:54 +02:00
my $dec = $gcodegen -> dec ;
2013-03-09 19:51:09 +01:00
print $fh "G21 ; set units to millimeters\n" ;
2012-07-27 21:13:03 +02:00
print $fh $gcodegen -> set_fan ( 0 , 1 ) if $ Slic3r:: Config -> cooling && $ Slic3r:: Config -> disable_fan_first_layers ;
2012-05-20 20:07:39 +02:00
2011-09-03 20:47:38 +02:00
# write start commands to file
2012-07-27 21:13:03 +02:00
printf $fh $gcodegen -> set_bed_temperature ( $ Slic3r:: Config -> first_layer_bed_temperature , 1 ),
2013-03-09 19:51:09 +01:00
if $ Slic3r:: Config -> first_layer_bed_temperature && $ Slic3r:: Config -> start_gcode !~ /M(?:190|140)/i ;
2012-08-22 17:58:38 +02:00
my $print_first_layer_temperature = sub {
2012-09-23 02:40:25 +02: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 17:58:38 +02:00
}
};
2013-03-09 19:51:09 +01:00
$print_first_layer_temperature -> () if $ Slic3r:: Config -> start_gcode !~ /M(?:109|104)/i ;
2012-07-27 21:13:03 +02:00
printf $fh "%s\n" , $ Slic3r:: Config -> replace_options ( $ Slic3r:: Config -> start_gcode );
2012-09-23 02:40:25 +02: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 19:51:09 +01:00
if $self -> extruders -> [ $t ] -> first_layer_temperature && $ Slic3r:: Config -> start_gcode !~ /M(?:109|104)/i ;
2012-06-28 16:22:11 +02:00
}
2011-09-03 20:47:38 +02:00
print $fh "G90 ; use absolute coordinates\n" ;
2012-07-27 21:13:03 +02:00
if ( $ Slic3r:: Config -> gcode_flavor =~ /^(?:reprap|teacup)$/ ) {
2012-07-06 19:57:58 +02:00
printf $fh $gcodegen -> reset_e ;
2013-01-17 14:56:31 +01:00
if ( $ Slic3r:: Config -> gcode_flavor =~ /^(?:reprap|makerbot|sailfish)$/ ) {
2012-07-27 21:13:03 +02:00
if ( $ Slic3r:: Config -> use_relative_e_distances ) {
2012-02-24 23:12:16 +01:00
print $fh "M83 ; use relative distances for extrusion\n" ;
} else {
print $fh "M82 ; use absolute distances for extrusion\n" ;
}
2012-02-20 11:44:30 +01:00
}
2011-09-03 20:47:38 +02:00
}
2012-02-19 10:48:58 +01:00
# calculate X,Y shift to center print around specified origin
2012-04-30 14:56:01 +02:00
my @print_bb = $self -> bounding_box ;
2012-02-19 10:48:58 +01:00
my @shift = (
2012-07-27 21:13:03 +02:00
$ Slic3r:: Config -> print_center -> [ X ] - ( unscale ( $print_bb [ X2 ] - $print_bb [ X1 ]) / 2 ) - unscale $print_bb [ X1 ],
$ Slic3r:: Config -> print_center -> [ Y ] - ( unscale ( $print_bb [ Y2 ] - $print_bb [ Y1 ]) / 2 ) - unscale $print_bb [ Y1 ],
2011-09-26 11:42:08 +02:00
);
2011-09-05 12:21:27 +02:00
2012-08-23 15:42:58 +02:00
# initialize a motion planner for object-to-object travel moves
if ( $ Slic3r:: Config -> avoid_crossing_perimeters ) {
my $distance_from_objects = 1 ;
# compute the offsetted convex hull for each object and repeat it for each copy.
my @islands = ();
foreach my $obj_idx ( 0 .. $# { $self -> objects }) {
2013-02-04 19:33:30 +01:00
my $convex_hull = convex_hull ([
2012-10-24 11:49:31 +02:00
map @ { $_ -> contour }, map @ { $_ -> slices }, @ { $self -> objects -> [ $obj_idx ] -> layers },
2013-02-04 19:33:30 +01:00
]);
# discard layers only containing thin walls (offset would fail on an empty polygon)
if ( @$convex_hull ) {
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 15:42:58 +02:00
}
}
2012-08-23 21:10:04 +02:00
$gcodegen -> external_mp ( Slic3r::GCode::MotionPlanner -> new (
2012-08-23 15:42:58 +02:00
islands => union_ex ([ map @$_ , @islands ]),
no_internal => 1 ,
2012-08-23 21:10:04 +02:00
));
2012-08-23 15:42:58 +02:00
}
2012-05-20 20:07:39 +02:00
# prepare the logic to print one layer
2012-05-31 09:37:39 +02:00
my $skirt_done = 0 ; # count of skirt layers done
2012-06-30 16:14:45 +02:00
my $brim_done = 0 ;
2013-03-10 15:36:52 +01:00
my $second_layer_things_done = 0 ;
2012-08-23 15:42:58 +02:00
my $last_obj_copy = "" ;
2012-05-20 20:07:39 +02:00
my $extrude_layer = sub {
2013-03-10 15:36:52 +01:00
my ( $layer , $object_copies ) = @_ ;
2012-05-20 20:07:39 +02:00
my $gcode = "" ;
2013-03-10 15:36:52 +01:00
if ( ! $second_layer_things_done && $layer -> id == 1 ) {
2012-09-23 02:40:25 +02:00
for my $t ( grep $self -> extruders -> [ $_ ], 0 .. $# { $ Slic3r:: Config -> temperature }) {
$gcode .= $gcodegen -> set_temperature ( $self -> extruders -> [ $t ] -> temperature , 0 , $t )
if $self -> extruders -> [ $t ] -> temperature && $self -> extruders -> [ $t ] -> temperature != $self -> extruders -> [ $t ] -> first_layer_temperature ;
2012-06-28 16:22:11 +02:00
}
2012-07-27 21:13:03 +02:00
$gcode .= $gcodegen -> set_bed_temperature ( $ Slic3r:: Config -> bed_temperature )
2012-10-24 22:57:19 +02:00
if $ Slic3r:: Config -> bed_temperature && $ Slic3r:: Config -> bed_temperature != $ Slic3r:: Config -> first_layer_bed_temperature ;
2012-02-26 14:54:38 +01:00
}
2013-02-03 17:23:50 +01:00
# set new layer, but don't move Z as support material contact areas may need an intermediate one
2013-03-10 15:36:52 +01:00
$gcode .= $gcodegen -> change_layer ( $layer );
2012-06-28 14:44:54 +02:00
$gcodegen -> elapsed_time ( 0 );
2012-02-25 21:01:00 +01:00
2012-12-20 17:01:01 +01:00
# prepare callback to call as soon as a Z command is generated
$gcodegen -> move_z_callback ( sub {
$gcodegen -> move_z_callback ( undef ); # circular ref or not?
2012-12-20 17:03:50 +01:00
return "" if ! $ Slic3r:: Config -> layer_gcode ;
return $ Slic3r:: Config -> replace_options ( $ Slic3r:: Config -> layer_gcode ) . "\n" ;
2012-12-20 17:01:01 +01:00
});
2012-02-25 21:01:00 +01:00
2012-04-29 12:51:20 +02:00
# extrude skirt
2012-07-27 21:13:03 +02:00
if ( $skirt_done < $ Slic3r:: Config -> skirt_height ) {
2012-08-23 15:42:58 +02:00
$gcodegen -> set_shift ( @shift );
2012-10-28 19:24:24 +01:00
$gcode .= $gcodegen -> set_extruder ( $self -> extruders -> [ 0 ]); # move_z requires extruder
$gcode .= $gcodegen -> move_z ( $gcodegen -> layer -> print_z );
2012-06-23 21:31:29 +02:00
# skip skirt if we have a large brim
2013-03-10 15:36:52 +01:00
if ( $layer -> id < $ Slic3r:: Config -> skirt_height ) {
2012-10-24 19:13:40 +02:00
# distribute skirt loops across all extruders
for my $i ( 0 .. $# { $self -> skirt }) {
2012-11-05 14:26:55 +01:00
# when printing layers > 0 ignore 'min_skirt_length' and
# just use the 'skirts' setting; also just use the current extruder
2013-03-10 15:36:52 +01:00
last if ( $layer -> id > 0 ) && ( $i >= $ Slic3r:: Config -> skirts );
2012-11-05 14:26:55 +01:00
$gcode .= $gcodegen -> set_extruder ( $self -> extruders -> [ ( $i / @ { $self -> extruders }) % @ { $self -> extruders } ])
2013-03-10 15:36:52 +01:00
if $layer -> id == 0 ;
2012-10-24 19:13:40 +02:00
$gcode .= $gcodegen -> extrude_loop ( $self -> skirt -> [ $i ], 'skirt' );
}
2012-05-20 20:07:39 +02:00
}
2012-05-31 09:37:39 +02:00
$skirt_done ++ ;
2012-08-23 21:10:04 +02:00
$gcodegen -> straight_once ( 1 );
2012-04-29 12:51:20 +02:00
}
2011-09-05 20:00:59 +02:00
2012-06-23 21:31:29 +02:00
# extrude brim
2013-03-10 15:36:52 +01:00
if ( ! $brim_done ) {
2012-11-07 23:06:32 +01:00
$gcode .= $gcodegen -> set_extruder ( $self -> extruders -> [ $ Slic3r:: Config -> support_material_extruder - 1 ]); # move_z requires extruder
2012-10-28 19:24:24 +01:00
$gcode .= $gcodegen -> move_z ( $gcodegen -> layer -> print_z );
2012-11-06 19:54:21 +01:00
$gcodegen -> set_shift ( @shift );
2012-06-28 14:44:54 +02:00
$gcode .= $gcodegen -> extrude_loop ( $_ , 'brim' ) for @ { $self -> brim };
2012-06-30 16:14:45 +02:00
$brim_done = 1 ;
2012-08-23 21:10:04 +02:00
$gcodegen -> straight_once ( 1 );
2012-06-23 21:31:29 +02:00
}
2013-03-10 15:36:52 +01:00
for my $copy ( @$object_copies ) {
$gcodegen -> new_object ( 1 ) if $last_obj_copy && $last_obj_copy ne "$copy" ;
$last_obj_copy = "$copy" ;
2012-05-20 20:07:39 +02:00
2012-11-06 19:54:21 +01:00
$gcodegen -> set_shift ( map $shift [ $_ ] + unscale $copy -> [ $_ ], X , Y );
2012-05-20 20:07:39 +02:00
2012-10-28 16:59:20 +01:00
# extrude support material before other things because it might use a lower Z
# and also because we avoid travelling on other things when printing it
2013-02-04 15:48:57 +01:00
if ( $self -> has_support_material ) {
2013-02-03 17:23:50 +01:00
$gcode .= $gcodegen -> move_z ( $layer -> support_material_contact_z )
if ( $layer -> support_contact_fills && @ { $layer -> support_contact_fills -> paths });
2012-10-28 16:59:20 +01:00
$gcode .= $gcodegen -> set_extruder ( $self -> extruders -> [ $ Slic3r:: Config -> support_material_extruder - 1 ]);
2013-02-03 17:23:50 +01:00
if ( $layer -> support_contact_fills ) {
$gcode .= $gcodegen -> extrude_path ( $_ , 'support material contact area' )
2013-02-05 17:27:45 +01:00
for $layer -> support_contact_fills -> chained_path ( $gcodegen -> last_pos );
2012-11-02 22:39:08 +00:00
}
2012-10-28 16:59:20 +01:00
$gcode .= $gcodegen -> move_z ( $layer -> print_z );
2012-11-02 22:39:08 +00:00
if ( $layer -> support_fills ) {
$gcode .= $gcodegen -> extrude_path ( $_ , 'support material' )
2013-02-05 17:27:45 +01:00
for $layer -> support_fills -> chained_path ( $gcodegen -> last_pos );
2012-11-02 22:39:08 +00:00
}
2012-10-28 16:59:20 +01:00
}
2012-10-30 10:45:55 +01:00
# set actual Z - this will force a retraction
2012-10-28 16:59:20 +01:00
$gcode .= $gcodegen -> move_z ( $layer -> print_z );
2012-05-20 20:07:39 +02:00
2013-03-10 17:28:03 +01:00
# tweak region ordering to save toolchanges
my @region_ids = 0 .. ( $self -> regions_count - 1 );
if ( $gcodegen -> multiple_extruders ) {
my $last_extruder = $gcodegen -> extruder ;
my $best_region_id = first { $self -> regions -> [ $_ ] -> extruders -> { perimeter } eq $last_extruder } @region_ids ;
2013-03-10 17:40:19 +01:00
@region_ids = ( $best_region_id , grep $_ != $best_region_id , @region_ids ) if $best_region_id ;
2013-03-10 17:28:03 +01:00
}
foreach my $region_id ( @region_ids ) {
2012-09-23 02:52:31 +02:00
my $layerm = $layer -> regions -> [ $region_id ];
my $region = $self -> regions -> [ $region_id ];
2012-09-22 19:04:36 +02:00
2013-03-10 16:09:03 +01:00
my @islands = ();
if ( $ Slic3r:: Config -> avoid_crossing_perimeters ) {
push @islands , map + { perimeters => [] , fills => [] }, @ { $layer -> slices };
PERIMETER: foreach my $perimeter ( @ { $layerm -> perimeters }) {
2013-03-19 16:02:03 +01:00
my $p = $perimeter -> unpack ;
2013-03-10 16:09:03 +01:00
for my $i ( 0 .. $# { $layer -> slices } - 1 ) {
2013-03-19 16:02:03 +01:00
if ( $layer -> slices -> [ $i ] -> contour -> encloses_point ( $p -> first_point )) {
push @ { $islands [ $i ]{ perimeters } }, $p ;
2013-03-10 16:09:03 +01:00
next PERIMETER ;
}
}
2013-03-19 16:02:03 +01:00
push @ { $islands [ - 1 ]{ perimeters } }, $p ; # optimization
2013-03-10 16:09:03 +01:00
}
FILL: foreach my $fill ( @ { $layerm -> fills }) {
2013-03-19 16:02:03 +01:00
my $f = $fill -> unpack ;
2013-03-10 16:09:03 +01:00
for my $i ( 0 .. $# { $layer -> slices } - 1 ) {
2013-03-19 16:02:03 +01:00
if ( $layer -> slices -> [ $i ] -> contour -> encloses_point ( $f -> first_point )) {
push @ { $islands [ $i ]{ fills } }, $f ;
2013-03-10 16:09:03 +01:00
next FILL ;
}
}
2013-03-19 16:02:03 +01:00
push @ { $islands [ - 1 ]{ fills } }, $f ; # optimization
2013-03-10 16:09:03 +01:00
}
} else {
push @islands , {
perimeters => $layerm -> perimeters ,
fills => $layerm -> fills ,
};
2012-10-24 19:38:18 +02:00
}
2012-09-22 19:04:36 +02:00
2013-03-10 16:09:03 +01:00
foreach my $island ( @islands ) {
2013-03-10 17:40:19 +01:00
my $extrude_perimeters = sub {
2013-03-10 19:07:41 +01:00
return if ! @ { $island -> { perimeters } };
2013-03-10 16:09:03 +01:00
$gcode .= $gcodegen -> set_extruder ( $region -> extruders -> { perimeter });
$gcode .= $gcodegen -> extrude ( $_ , 'perimeter' ) for @ { $island -> { perimeters } };
2013-03-10 17:40:19 +01:00
};
2013-03-10 16:09:03 +01:00
2013-03-10 17:40:19 +01:00
my $extrude_fills = sub {
2013-03-10 19:07:41 +01:00
return if ! @ { $island -> { fills } };
2013-03-10 16:09:03 +01:00
$gcode .= $gcodegen -> set_extruder ( $region -> extruders -> { infill });
for my $fill ( @ { $island -> { fills } }) {
if ( $fill -> isa ( 'Slic3r::ExtrusionPath::Collection' )) {
$gcode .= $gcodegen -> extrude ( $_ , 'fill' )
for $fill -> chained_path ( $gcodegen -> last_pos );
} else {
$gcode .= $gcodegen -> extrude ( $fill , 'fill' ) ;
}
2012-10-24 19:38:18 +02:00
}
2013-03-10 17:40:19 +01:00
};
2013-03-19 11:58:03 +01:00
# give priority to infill if we were already using its extruder and it wouldn't
# be good for perimeters
2013-03-10 18:15:44 +01:00
if ( $ Slic3r:: Config -> infill_first
2013-03-19 11:58:03 +01:00
|| ( $gcodegen -> multiple_extruders && $region -> extruders -> { infill } eq $gcodegen -> extruder ) && $region -> extruders -> { infill } ne $region -> extruders -> { perimeter }) {
2013-03-10 17:40:19 +01:00
$extrude_fills -> ();
$extrude_perimeters -> ();
} else {
$extrude_perimeters -> ();
$extrude_fills -> ();
2012-09-22 19:04:36 +02:00
}
2012-07-20 15:02:25 +02:00
}
2012-05-20 20:07:39 +02:00
}
2011-09-26 10:52:58 +02:00
}
2012-06-23 18:31:24 +02:00
return if ! $gcode ;
2012-02-25 21:01:00 +01:00
2012-07-27 21:13:03 +02:00
my $fan_speed = $ Slic3r:: Config -> fan_always_on ? $ Slic3r:: Config -> min_fan_speed : 0 ;
2012-02-25 21:01:00 +01:00
my $speed_factor = 1 ;
2012-07-27 21:13:03 +02:00
if ( $ Slic3r:: Config -> cooling ) {
2012-06-28 14:44:54 +02:00
my $layer_time = $gcodegen -> elapsed_time ;
2013-03-10 15:36:52 +01:00
Slic3r:: debugf "Layer %d estimated printing time: %d seconds\n" , $layer -> id , $layer_time ;
2012-07-27 21:13:03 +02:00
if ( $layer_time < $ Slic3r:: Config -> slowdown_below_layer_time ) {
$fan_speed = $ Slic3r:: Config -> max_fan_speed ;
$speed_factor = $layer_time / $ Slic3r:: Config -> slowdown_below_layer_time ;
} elsif ( $layer_time < $ Slic3r:: Config -> fan_below_layer_time ) {
$fan_speed = $ Slic3r:: Config -> max_fan_speed - ( $ Slic3r:: Config -> max_fan_speed - $ Slic3r:: Config -> min_fan_speed )
* ( $layer_time - $ Slic3r:: Config -> slowdown_below_layer_time )
/ ($Slic3r::Config->fan_below_layer_time - $Slic3r::Config->slowdown_below_layer_time); #/
2012-02-25 21:01:00 +01:00
}
2012-02-25 21:56:36 +01:00
Slic3r:: debugf " fan = %d%%, speed = %d%%\n" , $fan_speed , $speed_factor * 100 ;
if ( $speed_factor < 1 ) {
2013-03-17 02:22:50 +01:00
$gcode =~ s/^(?=.*? [XY])(?=.*? E)(?!;_WIPE)(?<!;_BRIDGE_FAN_START\n)(G1 .*?F)(\d+(?:\.\d+)?)/
2012-02-25 21:56:36 +01:00
my $new_speed = $2 * $speed_factor;
$1 . sprintf("%.${dec}f", $new_speed < $min_print_speed ? $min_print_speed : $new_speed)
/gexm ;
}
2013-03-10 15:36:52 +01:00
$fan_speed = 0 if $layer -> id < $ Slic3r:: Config -> disable_fan_first_layers ;
2012-02-25 21:01:00 +01:00
}
2012-06-28 14:44:54 +02:00
$gcode = $gcodegen -> set_fan ( $fan_speed ) . $gcode ;
2012-02-25 21:01:00 +01:00
# bridge fan speed
2013-03-10 15:36:52 +01:00
if ( ! $ Slic3r:: Config -> cooling || $ Slic3r:: Config -> bridge_fan_speed == 0 || $layer -> id < $ Slic3r:: Config -> disable_fan_first_layers ) {
2012-05-20 20:07:39 +02:00
$gcode =~ s/^;_BRIDGE_FAN_(?:START|END)\n//gm ;
2012-02-25 21:01:00 +01:00
} else {
2012-07-27 21:13:03 +02:00
$gcode =~ s/^;_BRIDGE_FAN_START\n/ $gcodegen->set_fan($Slic3r::Config->bridge_fan_speed, 1) /gmex ;
2012-06-28 14:44:54 +02:00
$gcode =~ s/^;_BRIDGE_FAN_END\n/ $gcodegen->set_fan($fan_speed, 1) /gmex ;
2012-02-25 21:01:00 +01:00
}
2013-03-17 02:22:50 +01:00
$gcode =~ s/;_WIPE//g ;
2012-02-25 21:01:00 +01:00
2012-05-20 20:07:39 +02:00
return $gcode ;
};
# do all objects for each layer
2012-07-27 21:13:03 +02:00
if ( $ Slic3r:: Config -> complete_objects ) {
2012-05-20 20:07:39 +02:00
2012-05-21 20:19:30 +02:00
# print objects from the smallest to the tallest to avoid collisions
# when moving onto next object starting point
2012-05-21 20:23:17 +02:00
my @obj_idx = sort { $self -> objects -> [ $a ] -> layer_count <=> $self -> objects -> [ $b ] -> layer_count } 0 .. $# { $self -> objects };
2012-05-20 20:07:39 +02:00
my $finished_objects = 0 ;
2012-05-21 20:19:30 +02:00
for my $obj_idx ( @obj_idx ) {
2012-09-22 16:19:24 +02:00
for my $copy ( @ { $self -> objects -> [ $obj_idx ] -> copies }) {
2012-05-20 20:07:39 +02:00
# move to the origin position for the copy we're going to print.
# this happens before Z goes down to layer 0 again, so that
# no collision happens hopefully.
if ( $finished_objects > 0 ) {
2012-11-06 19:54:21 +01:00
$gcodegen -> set_shift ( map $shift [ $_ ] + unscale $copy -> [ $_ ], X , Y );
2012-06-28 14:44:54 +02:00
print $fh $gcodegen -> retract ;
print $fh $gcodegen -> G0 ( Slic3r::Point -> new ( 0 , 0 ), undef , 0 , 'move to origin position for next object' );
2012-05-20 20:07:39 +02:00
}
2013-03-10 15:36:52 +01:00
for my $layer ( @ { $self -> objects -> [ $obj_idx ] -> layers }) {
2012-05-20 20:07:39 +02:00
# if we are printing the bottom layer of an object, and we have already finished
# another one, set first layer temperatures. this happens before the Z move
# is triggered, so machine has more time to reach such temperatures
2013-03-10 15:36:52 +01:00
if ( $layer -> id == 0 && $finished_objects > 0 ) {
2012-07-27 21:13:03 +02:00
printf $fh $gcodegen -> set_bed_temperature ( $ Slic3r:: Config -> first_layer_bed_temperature ),
if $ Slic3r:: Config -> first_layer_bed_temperature ;
2012-08-22 17:58:38 +02:00
$print_first_layer_temperature -> ();
2012-05-20 20:07:39 +02:00
}
2013-03-10 15:36:52 +01:00
print $fh $extrude_layer -> ( $layer , [ $copy ]);
2012-05-20 20:07:39 +02:00
}
$finished_objects ++ ;
}
}
} else {
2013-03-10 15:36:52 +01:00
print $fh $extrude_layer -> ( $_ , $_ -> object -> copies )
for sort { $a -> print_z <=> $b -> print_z }
map @ { $_ -> layers }, @ { $self -> objects };
2011-09-03 20:47:38 +02:00
}
2011-12-20 15:29:15 +01:00
# save statistic data
2012-06-28 14:44:54 +02:00
$self -> total_extrusion_length ( $gcodegen -> total_extrusion_length );
2011-12-20 15:29:15 +01:00
2011-09-03 20:47:38 +02:00
# write end commands to file
2013-03-30 00:36:14 +01:00
print $fh $gcodegen -> retract if $gcodegen -> extruder ; # empty prints don't even set an extruder
2012-06-28 14:44:54 +02:00
print $fh $gcodegen -> set_fan ( 0 );
2012-07-27 21:13:03 +02:00
printf $fh "%s\n" , $ Slic3r:: Config -> replace_options ( $ Slic3r:: Config -> end_gcode );
2011-09-03 20:47:38 +02:00
2011-12-20 15:29:15 +01:00
printf $fh "; filament used = %.1fmm (%.1fcm3)\n" ,
$self -> total_extrusion_length , $self -> total_extrusion_volume ;
2012-11-18 19:53:52 +01:00
if ( $ Slic3r:: Config -> gcode_comments ) {
# append full config
print $fh "\n" ;
foreach my $opt_key ( sort keys % { $ Slic3r:: Config }) {
next if $ Slic3r::Config:: Options -> { $opt_key }{ shortcut };
next if $ Slic3r::Config:: Options -> { $opt_key }{ gui_only };
printf $fh "; %s = %s\n" , $opt_key , $ Slic3r:: Config -> serialize ( $opt_key );
}
}
2011-09-03 20:47:38 +02:00
# close our gcode file
close $fh ;
}
2011-12-20 15:29:15 +01:00
sub total_extrusion_volume {
my $self = shift ;
2012-09-23 02:40:25 +02:00
return $self -> total_extrusion_length * ( $self -> extruders -> [ 0 ] -> filament_diameter ** 2 ) * PI /4 / 1000 ;
2011-12-20 15:29:15 +01:00
}
2012-09-12 16:30:44 +02:00
# this method will return the supplied input file path after expanding its
2012-04-30 14:56:01 +02:00
# format variables with their values
sub expanded_output_filepath {
my $self = shift ;
2012-09-12 16:30:44 +02:00
my ( $path , $input_file ) = @_ ;
# if no input file was supplied, take the first one from our objects
$input_file ||= $self -> objects -> [ 0 ] -> input_file ;
2012-11-23 11:24:04 +01:00
return undef if ! defined $input_file ;
2012-04-30 14:56:01 +02:00
2012-08-01 17:12:16 +02: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 14:56:01 +02:00
# if no explicit output file was defined, we take the input
# file directory and append the specified filename format
2012-07-27 21:13:03 +02:00
$path ||= ( fileparse ( $input_file ))[ 1 ] . $ Slic3r:: Config -> output_filename_format ;
2012-04-30 14:56:01 +02:00
my $input_filename = my $input_filename_base = basename ( $input_file );
$input_filename_base =~ s/\.(?:stl|amf(?:\.xml)?)$//i ;
2012-07-27 21:13:03 +02:00
return $ Slic3r:: Config -> replace_options ( $path , {
2012-04-30 14:56:01 +02:00
input_filename => $input_filename ,
input_filename_base => $input_filename_base ,
2012-09-12 15:29:44 +02:00
% { $self -> extra_variables },
2012-04-30 14:56:01 +02:00
});
}
2011-09-01 21:06:28 +02:00
1 ;