From 191de5d0781698f1d30d63adf9cd129d3114bccf Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Wed, 29 Aug 2012 19:37:27 +0200
Subject: [PATCH 01/22] Unfinished work for decoupling GUI from the Print
 object (goal = more speed for manipulation, less memory usage)

---
 lib/Slic3r/Config.pm     |   9 ++
 lib/Slic3r/GUI/Plater.pm | 261 +++++++++++++++++++++------------------
 lib/Slic3r/Model.pm      |  90 +++++++++-----
 lib/Slic3r/Polyline.pm   |   9 +-
 lib/Slic3r/Print.pm      |  20 +--
 5 files changed, 223 insertions(+), 166 deletions(-)

diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm
index 97992d5b4..71363c726 100644
--- a/lib/Slic3r/Config.pm
+++ b/lib/Slic3r/Config.pm
@@ -1131,6 +1131,15 @@ sub replace_options {
     return $string;
 }
 
+# min object distance is max(duplicate_distance, clearance_radius)
+sub min_object_distance {
+    my $self = shift;
+    
+    return ($self->complete_objects && $self->extruder_clearance_radius > $self->duplicate_distance)
+        ? $self->extruder_clearance_radius
+        : $self->duplicate_distance;
+}
+
 # CLASS METHODS:
 
 sub write_ini {
diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index a1b8c3406..712b736b0 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -4,7 +4,7 @@ use warnings;
 use utf8;
 
 use File::Basename qw(basename dirname);
-use Math::ConvexHull qw(convex_hull);
+use List::Util qw(max sum);
 use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 scale unscale);
 use Slic3r::Geometry::Clipper qw(JT_ROUND);
 use threads::shared qw(shared_clone);
@@ -155,8 +155,9 @@ sub new {
     EVT_COMMAND($self, -1, $THUMBNAIL_DONE_EVENT, sub {
         my ($self, $event) = @_;
         my ($obj_idx, $thumbnail) = @{$event->GetData};
-        $self->{thumbnails}[$obj_idx] = $thumbnail;
-        $self->make_thumbnail2;
+        $self->{objects}[$obj_idx]->thumbnail($thumbnail);
+        $self->mesh(undef);
+        $self->on_thumbnail_made;
     });
     
     EVT_COMMAND($self, -1, $PROGRESS_BAR_EVENT, sub {
@@ -182,10 +183,7 @@ sub new {
     });
     
     $self->_update_bed_size;
-    $self->{print} = Slic3r::Print->new;
-    $self->{thumbnails} = [];       # polygons, each one aligned to 0,0
-    $self->{scale} = [];
-    $self->{object_previews} = [];  # [ obj_idx, copy_idx, positioned polygon ]
+    $self->{objects} = [];
     $self->{selected_objects} = [];
     $self->recenter;
     
@@ -295,12 +293,26 @@ sub load_file {
     
     my $process_dialog = Wx::ProgressDialog->new('Loading…', "Processing input file…", 100, $self, 0);
     $process_dialog->Pulse;
-    local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
-    $self->{print}->add_objects_from_file($input_file);
-    my $obj_idx = $#{$self->{print}->objects};
-    $process_dialog->Destroy;
     
-    $self->object_loaded($obj_idx);
+    local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
+    my $model = Slic3r::Model->read_from_file($input_file);
+    for my $i (0 .. $#{$model->objects}) {
+        my $object = Slic3r::GUI::Plater::Object->new(
+            name                    => basebane($input_file),
+            input_file              => $input_file,
+            input_file_object_id    => $i,
+            mesh                    => $model->objects->[$i]->mesh,
+            instances               => [
+                $model->objects->[$i]->instances
+                    ? (map $_->offset, @{$model->objects->[$i]->instances})
+                    : [0,0],
+            ],
+        );
+        push @{ $self->{objects} }, $object;
+        $self->object_loaded($#{ $self->{objects} });
+    }
+    
+    $process_dialog->Destroy;
     $self->statusbar->SetStatusText("Loaded $input_file");
 }
 
@@ -308,11 +320,10 @@ sub object_loaded {
     my $self = shift;
     my ($obj_idx, %params) = @_;
     
-    my $object = $self->{print}->objects->[$obj_idx];
-    $self->{list}->InsertStringItem($obj_idx, basename($object->input_file));
-    $self->{list}->SetItem($obj_idx, 1, "1");
-    $self->{list}->SetItem($obj_idx, 2, "100%");
-    push @{$self->{scale}}, 1;
+    my $object = $self->{objects}[$obj_idx];
+    $self->{list}->InsertStringItem($obj_idx, $object->name);
+    $self->{list}->SetItem($obj_idx, 1, $object->instances_count);
+    $self->{list}->SetItem($obj_idx, 2, ($object->scale * 100) . "%");
     
     $self->make_thumbnail($obj_idx);
     $self->arrange unless $params{no_arrange};
@@ -325,36 +336,13 @@ sub remove {
     my $self = shift;
     my ($obj_idx) = @_;
     
-    if (defined $obj_idx) {
-        $self->{print}->copies->[$obj_idx][$_] = undef
-            for 0 .. $#{ $self->{print}->copies->[$obj_idx] };
-    } else {
-        foreach my $pobj (@{$self->{selected_objects}}) {
-            my ($obj_idx, $copy_idx) = ($pobj->[0], $pobj->[1]);
-            $self->{print}->copies->[$obj_idx][$copy_idx] = undef;
-        }
+    # if no object index is supplied, remove the selected one
+    if (!defined $obj_idx) {
+        ($obj_idx, undef) = $self->selected_object;
     }
     
-    my @objects_to_remove = ();
-    for my $obj_idx (0 .. $#{$self->{print}->objects}) {
-        my $copies = $self->{print}->copies->[$obj_idx];
-        
-        # filter out removed copies
-        @$copies = grep defined $_, @$copies;
-        
-        # update copies count in list
-        $self->{list}->SetItem($obj_idx, 1, scalar @$copies);
-        
-        # if no copies are left, remove the object itself
-        push @objects_to_remove, $obj_idx if !@$copies;
-    }
-    for my $obj_idx (sort { $b <=> $a } @objects_to_remove) {
-        splice @{$self->{print}->objects}, $obj_idx, 1;
-        splice @{$self->{print}->copies}, $obj_idx, 1;
-        splice @{$self->{thumbnails}}, $obj_idx, 1;
-        splice @{$self->{scale}}, $obj_idx, 1;
-        $self->{list}->DeleteItem($obj_idx);
-    }
+    splice @{$self->{objects}}, $obj_idx, 1;
+    $self->{list}->DeleteItem($obj_idx);
     
     $self->{selected_objects} = [];
     $self->selection_changed(0);
@@ -366,10 +354,7 @@ sub remove {
 sub reset {
     my $self = shift;
     
-    @{$self->{print}->objects} = ();
-    @{$self->{print}->copies} = ();
-    @{$self->{thumbnails}} = ();
-    @{$self->{scale}} = ();
+    @{$self->{objects}} = ();
     $self->{list}->DeleteAllItems;
     
     $self->{selected_objects} = [];
@@ -381,21 +366,21 @@ sub reset {
 sub increase {
     my $self = shift;
     
-    my $obj_idx = $self->selected_object_idx;
-    my $copies = $self->{print}->copies->[$obj_idx];
-    push @$copies, [ $copies->[-1]->[X] + scale 10, $copies->[-1]->[Y] + scale 10 ];
-    $self->{list}->SetItem($obj_idx, 1, scalar @$copies);
+    my ($obj_idx, $object) = $self->selected_object;
+    my $instances = $object->instances;
+    push @$instances, [ $instances->[-1]->[X] + scale 10, $instances->[-1]->[Y] + scale 10 ];
+    $self->{list}->SetItem($obj_idx, 1, $object->instances_count);
     $self->arrange;
 }
 
 sub decrease {
     my $self = shift;
     
-    my $obj_idx = $self->selected_object_idx;
+    my ($obj_idx, $object) = $self->selected_object;
     $self->{selected_objects} = [ +(grep { $_->[0] == $obj_idx } @{$self->{object_previews}})[-1] ];
     $self->remove;
     
-    if ($self->{print}->objects->[$obj_idx]) {
+    if ($self->{objects}[$obj_idx]) {
         $self->{list}->Select($obj_idx, 0);
         $self->{list}->Select($obj_idx, 1);
     }
@@ -405,37 +390,14 @@ sub rotate {
     my $self = shift;
     my ($angle) = @_;
     
-    my $obj_idx = $self->selected_object_idx;
-    my $object = $self->{print}->objects->[$obj_idx];
+    my ($obj_idx, $object) = $self->selected_object;
     
     if (!defined $angle) {
-        $angle = Wx::GetNumberFromUser("", "Enter the rotation angle:", "Rotate", 0, -364, 364, $self);
+        $angle = Wx::GetNumberFromUser("", "Enter the rotation angle:", "Rotate", $object->rotate, -364, 364, $self);
         return if !$angle || $angle == -1;
     }
     
-    $self->statusbar->SetStatusText("Rotating object…");
-    $self->statusbar->StartBusy;
-    
-    # rotate, realign to 0,0 and update size
-    $object->mesh->rotate($angle);
-    $object->mesh->align_to_origin;
-    $object->size([ $object->mesh->size ]);
-    
-    $self->make_thumbnail($obj_idx);
-    $self->recenter;
-    $self->{canvas}->Refresh;
-    $self->statusbar->StopBusy;
-    $self->statusbar->SetStatusText("");
-}
-
-sub arrange {
-    my $self = shift;
-    
-    eval {
-        $self->{print}->arrange_objects;
-    };
-    # ignore arrange warnings on purpose
-    
+    $object->set_rotation($angle);
     $self->recenter;
     $self->{canvas}->Refresh;
 }
@@ -443,36 +405,41 @@ sub arrange {
 sub changescale {
     my $self = shift;
     
-    my $obj_idx = $self->selected_object_idx;
-    my $scale = $self->{scale}[$obj_idx];
+    my ($obj_idx, $object) = $self->selected_object;
+    
     # max scale factor should be above 2540 to allow importing files exported in inches
-    $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale", $scale*100, 0, 5000, $self);
+    $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale", $object->scale*100, 0, 5000, $self);
     return if !$scale || $scale == -1;
     
-    $self->statusbar->SetStatusText("Scaling object…");
-    $self->statusbar->StartBusy;
-    
-    my $object = $self->{print}->objects->[$obj_idx];
-    my $mesh = $object->mesh;
-    $mesh->scale($scale/100 / $self->{scale}[$obj_idx]);
-    $object->mesh->align_to_origin;
-    $object->size([ $object->mesh->size ]);
-    
-    $self->{scale}[$obj_idx] = $scale/100;
-    $self->{list}->SetItem($obj_idx, 2, "$scale%");
-    
-    $self->make_thumbnail($obj_idx);
+    $object->set_scale($scale);
     $self->arrange;
-    $self->statusbar->StopBusy;
-    $self->statusbar->SetStatusText("");
+}
+
+sub arrange {
+    my $self = shift;
+    
+    my $total_parts = sum(map $_->instances_count, @{$self->{objects}}) or return;
+    my @size = ();
+    for my $a (X,Y) {
+        $size[$a] = max(map $_->thumbnail->size->[$a], @{$self->{objects}});
+    }
+    
+    eval {
+        my $config = $self->skeinpanel->config;
+        my @positions = Slic3r::Geometry::arrange
+            ($total_parts, @size, @{$config->bed_size}, $config->min_object_distance);
+    };
+    # ignore arrange warnings on purpose
+    
+    $self->recenter;
+    $self->{canvas}->Refresh;
 }
 
 sub split_object {
     my $self = shift;
     
-    my $obj_idx = $self->selected_object_idx;
-    my $current_object = $self->{print}->objects->[$obj_idx];
-    my $current_copies_num = @{$self->{print}->copies->[$obj_idx]};
+    my ($obj_idx, $current_object) = $self->selected_object;
+    my $current_copies_num = $current_object->instances_count;
     my $mesh = $current_object->mesh->clone;
     $mesh->scale(&Slic3r::SCALING_FACTOR);
     
@@ -670,8 +637,10 @@ sub make_model {
     my $self = shift;
     
     my $model = Slic3r::Model->new;
-    for my $obj_idx (0 .. $#{$self->{print}->objects}) {
-        my $mesh = $self->{print}->objects->[$obj_idx]->mesh->clone;
+    for my $obj_idx (0 .. $#{$self->{objects}}) {
+        my $object = $self->{objects}[$obj_idx];
+        # TODO: reload file
+        my $mesh = $self->{print}->[$obj_idx]->mesh->clone;
         $mesh->scale(&Slic3r::SCALING_FACTOR);
         my $object = $model->add_object(vertices => $mesh->vertices);
         $object->add_volume(facets => $mesh->facets);
@@ -689,27 +658,21 @@ sub make_thumbnail {
     my ($obj_idx) = @_;
     
     my $cb = sub {
-        my $object = $self->{print}->objects->[$obj_idx];
-        my @points = map [ @$_[X,Y] ], @{$object->mesh->vertices};
-        my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points));
-        for (@$convex_hull) {
-            @$_ = map $self->to_pixel($_), @$_;
-        }
-        $convex_hull->simplify(0.3);
-        $self->{thumbnails}->[$obj_idx] = $convex_hull;  # ignored in multithread environment
+        my $object = $self->{objects}[$obj_idx];
+        my $thumbnail = $object->make_thumbnail;
         
         if ($Slic3r::have_threads) {
-            Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $THUMBNAIL_DONE_EVENT, shared_clone([ $obj_idx, $convex_hull ])));
+            Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $THUMBNAIL_DONE_EVENT, shared_clone([ $obj_idx, $thumbnail ])));
             threads->exit;
         } else {
-            $self->make_thumbnail2;
+            $self->on_thumbnail_made;
         }
     };
     
     $Slic3r::have_threads ? threads->create($cb)->detach : $cb->();
 }
 
-sub make_thumbnail2 {
+sub on_thumbnail_made {
     my $self = shift;
     $self->recenter;
     $self->{canvas}->Refresh;
@@ -719,8 +682,8 @@ sub recenter {
     my $self = shift;
     
     # calculate displacement needed to center the print
-    my @print_bb = $self->{print}->bounding_box;
-    @print_bb = (0,0,0,0) if !defined $print_bb[0];
+    my @print_bb = (0,0,0,0);
+    @print_bb =  if !defined $print_bb[0];
     $self->{shift} = [
         ($self->{canvas}->GetSize->GetWidth  - ($self->to_pixel($print_bb[X2] + $print_bb[X1]))) / 2,
         ($self->{canvas}->GetSize->GetHeight - ($self->to_pixel($print_bb[Y2] + $print_bb[Y1]))) / 2,
@@ -758,9 +721,6 @@ sub _update_bed_size {
     my $bed_largest_side = $bed_size->[X] > $bed_size->[Y] ? $bed_size->[X] : $bed_size->[Y];
     my $old_scaling_factor = $self->{scaling_factor};
     $self->{scaling_factor} = $canvas_side / $bed_largest_side;
-    if (defined $old_scaling_factor && $self->{scaling_factor} != $old_scaling_factor) {
-        $self->make_thumbnail($_) for 0..$#{$self->{thumbnails}};
-    }
 }
 
 # this is called on the canvas
@@ -936,9 +896,10 @@ sub selection_changed {
     }
 }
 
-sub selected_object_idx {
+sub selected_object {
     my $self = shift;
-    return $self->{selected_objects}[0] ? $self->{selected_objects}[0][0] : $self->{list}->GetFirstSelected;
+    my $obj_idx = $self->{selected_objects}[0] ? $self->{selected_objects}[0][0] : $self->{list}->GetFirstSelected;
+    return ($obj_idx, $self->{objects}[$obj_idx]),
 }
 
 sub statusbar {
@@ -989,4 +950,60 @@ sub OnDropFiles {
     $self->{window}->load_file($_) for @$filenames;
 }
 
+package Slic3r::GUI::Plater::Object;
+use Moo;
+
+use Math::ConvexHull qw(convex_hull);
+
+has 'name'                  => (is => 'rw', required => 1);
+has 'input_file'            => (is => 'rw', required => 1);
+has 'input_file_object_id'  => (is => 'rw', required => 1);
+has 'mesh'                  => (is => 'rw', required => 1, trigger => 1);
+has 'size'                  => (is => 'rw');
+has 'scale'                 => (is => 'rw', default => sub { 1 });
+has 'rotate'                => (is => 'rw', default => sub { 0 });
+has 'instances'             => (is => 'rw', default => sub { [] });
+has 'thumbnail'             => (is => 'rw');
+
+sub _trigger_mesh {
+    my $self = shift;
+    $self->size($mesh->size) if $self->mesh;
+}
+
+sub instances_count {
+    my $self = shift;
+    return scalar @{$self->instances};
+}
+
+sub make_thumbnail {
+    my $self = shift;
+    
+    my @points = map [ @$_[X,Y] ], @{$object->mesh->vertices};
+    my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points));
+    for (@$convex_hull) {
+        @$_ = map $self->to_pixel($_), @$_;
+    }
+    $convex_hull->simplify(0.3);
+    
+    $self->thumbnail($convex_hull);  # ignored in multi-threaded environments
+    $self->mesh(undef);
+    return $convex_hull;
+}
+
+sub set_rotation {
+    my $self = shift;
+    my ($angle) = @_;
+    
+    $self->thumbnail->rotate($angle - $self->rotate);
+    $self->rotate($angle);
+}
+
+sub set_scale {
+    my $self = shift;
+    my ($scale) = @_;
+    
+    $self->thumbnail->scale($scale - $self->scale);
+    $self->scale($scale);
+}
+
 1;
diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm
index b648d4b34..3a4aec49d 100644
--- a/lib/Slic3r/Model.pm
+++ b/lib/Slic3r/Model.pm
@@ -6,6 +6,18 @@ use Slic3r::Geometry qw(X Y Z);
 has 'materials' => (is => 'ro', default => sub { {} });
 has 'objects'   => (is => 'ro', default => sub { [] });
 
+sub read_from_file {
+    my $class = shift;
+    my ($input_file) = @_;
+    
+    my $model = $input_file =~ /\.stl$/i            ? Slic3r::Format::STL->read_file($input_file)
+              : $input_file =~ /\.obj$/i            ? Slic3r::Format::OBJ->read_file($input_file)
+              : $input_file =~ /\.amf(\.xml)?$/i    ? Slic3r::Format::AMF->read_file($input_file)
+              : die "Input file must have .stl, .obj or .amf(.xml) extension\n";
+    
+    return $model;
+}
+
 sub add_object {
     my $self = shift;
     
@@ -21,34 +33,15 @@ sub mesh {
     my $vertices = [];
     my $facets = [];
     foreach my $object (@{$self->objects}) {
-        my @instances = $object->instances ? @{$object->instances} : (undef);
-        foreach my $instance (@instances) {
-            my @vertices = @{$object->vertices};
-            if ($instance) {
-                # save Z coordinates, as rotation and translation discard them
-                my @z = map $_->[Z], @vertices;
-                
-                if ($instance->rotation) {
-                    # transform vertex coordinates
-                    my $rad = Slic3r::Geometry::deg2rad($instance->rotation);
-                    @vertices = Slic3r::Geometry::rotate_points($rad, undef, @vertices);
-                }
-                @vertices = Slic3r::Geometry::move_points($instance->offset, @vertices);
-                
-                # reapply Z coordinates
-                $vertices[$_][Z] = $z[$_] for 0 .. $#z;
-            }
-            
-            my $v_offset = @$vertices;
-            push @$vertices, @vertices;
-            foreach my $volume (@{$object->volumes}) {
-                push @$facets, map {
-                    my $f = [@$_];
-                    $f->[$_] += $v_offset for -3..-1;
-                    $f;
-                } @{$volume->facets};
-            }
-        }
+        my $mesh = $object->mesh;
+        
+        my $v_offset = @$vertices;
+        push @$vertices, @{$mesh->vertices};
+        push @$facets, map {
+            my $f = [@$_];
+            $f->[$_] += $v_offset for -3..-1;
+            $f;
+        } @{$mesh->facets};
     }
     
     return Slic3r::TriangleMesh->new(
@@ -87,6 +80,47 @@ sub add_instance {
     return $self->instances->[-1];
 }
 
+sub mesh {
+    my $self = shift;
+    
+    my $vertices = [];
+    my $facets = [];
+    
+    my @instances = $self->instances ? @{$self->instances} : (undef);
+    foreach my $instance (@instances) {
+        my @vertices = @{$self->vertices};
+        if ($instance) {
+            # save Z coordinates, as rotation and translation discard them
+            my @z = map $_->[Z], @vertices;
+            
+            if ($instance->rotation) {
+                # transform vertex coordinates
+                my $rad = Slic3r::Geometry::deg2rad($instance->rotation);
+                @vertices = Slic3r::Geometry::rotate_points($rad, undef, @vertices);
+            }
+            @vertices = Slic3r::Geometry::move_points($instance->offset, @vertices);
+            
+            # reapply Z coordinates
+            $vertices[$_][Z] = $z[$_] for 0 .. $#z;
+        }
+        
+        my $v_offset = @$vertices;
+        push @$vertices, @vertices;
+        foreach my $volume (@{$self->volumes}) {
+            push @$facets, map {
+                my $f = [@$_];
+                $f->[$_] += $v_offset for -3..-1;
+                $f;
+            } @{$volume->facets};
+        }
+    }
+    
+    return Slic3r::TriangleMesh->new(
+        vertices => $vertices,
+        facets   => $facets,
+    );
+}
+
 package Slic3r::Model::Volume;
 use Moo;
 
diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm
index da9cc0479..ce13e7786 100644
--- a/lib/Slic3r/Polyline.pm
+++ b/lib/Slic3r/Polyline.pm
@@ -4,7 +4,7 @@ use warnings;
 
 use Math::Clipper qw();
 use Scalar::Util qw(reftype);
-use Slic3r::Geometry qw(A B polyline_remove_parallel_continuous_edges polyline_remove_acute_vertices
+use Slic3r::Geometry qw(A B X Y MIN MAX polyline_remove_parallel_continuous_edges polyline_remove_acute_vertices
     polyline_lines move_points same_point);
 
 # the constructor accepts an array(ref) of points
@@ -140,6 +140,13 @@ sub bounding_box {
     return Slic3r::Geometry::bounding_box($self);
 }
 
+sub size {
+    my $self = shift;
+    
+    my @extents = $self->bounding_box;
+    return map $extents[$_][MAX] - $extents[$_][MIN], (X,Y);
+}
+
 sub rotate {
     my $self = shift;
     my ($angle, $center) = @_;
diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm
index bfa7c2c36..735a31681 100644
--- a/lib/Slic3r/Print.pm
+++ b/lib/Slic3r/Print.pm
@@ -3,6 +3,7 @@ use Moo;
 
 use File::Basename qw(basename fileparse);
 use File::Spec;
+use List::Util qw(max);
 use Math::ConvexHull 1.0.4 qw(convex_hull);
 use Slic3r::ExtrusionPath ':roles';
 use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 PI scale unscale move_points);
@@ -83,10 +84,7 @@ sub add_objects_from_file {
     my $self = shift;
     my ($input_file) = @_;
     
-    my $model = $input_file =~ /\.stl$/i            ? Slic3r::Format::STL->read_file($input_file)
-              : $input_file =~ /\.obj$/i            ? Slic3r::Format::OBJ->read_file($input_file)
-              : $input_file =~ /\.amf(\.xml)?$/i    ? Slic3r::Format::AMF->read_file($input_file)
-              : die "Input file must have .stl, .obj or .amf(.xml) extension\n";
+    my $model = Slic3r::Model->read_from_file($input_file);
     
     my @print_objects = $self->add_model($model);
     $_->input_file($input_file) for @print_objects;
@@ -231,19 +229,11 @@ sub arrange_objects {
     my $self = shift;
 
     my $total_parts = scalar map @$_, @{$self->copies};
-    my $partx = my $party = 0;
-    foreach my $object (@{$self->objects}) {
-        $partx = $object->size->[X] if $object->size->[X] > $partx;
-        $party = $object->size->[Y] if $object->size->[Y] > $party;
-    }
-    
-    # object distance is max(duplicate_distance, clearance_radius)
-    my $distance = $Slic3r::Config->complete_objects && $Slic3r::Config->extruder_clearance_radius > $Slic3r::Config->duplicate_distance
-        ? $Slic3r::Config->extruder_clearance_radius
-        : $Slic3r::Config->duplicate_distance;
+    my $partx = max(map $_->size->[X], @{$self->objects});
+    my $party = max(map $_->size->[Y], @{$self->objects});
     
     my @positions = Slic3r::Geometry::arrange
-        ($total_parts, $partx, $party, (map scale $_, @{$Slic3r::Config->bed_size}), scale $distance);
+        ($total_parts, $partx, $party, (map scale $_, @{$Slic3r::Config->bed_size}), scale $Slic3r::Config->min_object_distance);
     
     for my $obj_idx (0..$#{$self->objects}) {
         @{$self->copies->[$obj_idx]} = splice @positions, 0, scalar @{$self->copies->[$obj_idx]};

From 94a096f1d21d6d1e86e04b1fbef968ad452f1b1d Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Thu, 30 Aug 2012 23:04:56 +0200
Subject: [PATCH 02/22] Support legacy config files not containing
 first_layer_height #631

---
 lib/Slic3r/Print.pm | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm
index 735a31681..397bc9bea 100644
--- a/lib/Slic3r/Print.pm
+++ b/lib/Slic3r/Print.pm
@@ -43,6 +43,14 @@ sub _trigger_config {
     # store config in a handy place
     $Slic3r::Config = $self->config;
     
+    # 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);
+    
     # initialize extruder(s)
     $Slic3r::extruders = [];
     for my $t (0, map $_-1, map $self->config->get($_), qw(perimeter_extruder infill_extruder support_material_extruder)) {
@@ -72,12 +80,6 @@ sub _trigger_config {
     # 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';
-    
-    # legacy with existing config files
-    $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);
 }
 
 sub add_objects_from_file {

From 202bfa91c8e96cce10cc3194cd30d0093dc9a468 Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Thu, 30 Aug 2012 23:13:28 +0200
Subject: [PATCH 03/22] Adjust M-codes for temperature handling for Teacup.
 #539

---
 lib/Slic3r/GCode.pm | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm
index 94b065dec..499502696 100644
--- a/lib/Slic3r/GCode.pm
+++ b/lib/Slic3r/GCode.pm
@@ -421,25 +421,34 @@ sub set_temperature {
     
     return "" if $wait && $Slic3r::Config->gcode_flavor eq 'makerbot';
     
-    my ($code, $comment) = $wait
+    my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup')
         ? ('M109', 'wait for temperature to be reached')
         : ('M104', 'set temperature');
-    return sprintf "$code %s%d %s; $comment\n",
+    my $gcode = sprintf "$code %s%d %s; $comment\n",
         ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature,
         (defined $tool && $tool != $self->extruder_idx) ? "T$tool " : "";
+    
+    $gcode .= "M116 ; wait for temperature to be reached\n"
+        if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait;
+    
+    return $gcode;
 }
 
 sub set_bed_temperature {
     my $self = shift;
     my ($temperature, $wait) = @_;
     
-    my ($code, $comment) = $wait
+    my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup')
         ? (($Slic3r::Config->gcode_flavor eq 'makerbot' ? 'M109'
-            : $Slic3r::Config->gcode_flavor eq 'teacup' ? 'M109 P1'
             : 'M190'), 'wait for bed temperature to be reached')
         : ('M140', 'set bed temperature');
-    return sprintf "$code %s%d ; $comment\n",
+    my $gcode = sprintf "$code %s%d ; $comment\n",
         ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature;
+    
+    $gcode .= "M116 ; wait for bed temperature to be reached\n"
+        if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait;
+    
+    return $gcode;
 }
 
 1;

From d7e801fd32712244aebc7260f9db39fdaca8bee2 Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Thu, 30 Aug 2012 23:15:42 +0200
Subject: [PATCH 04/22] Releasing 0.9.2

---
 lib/Slic3r.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm
index 19117f4b8..085bd2123 100644
--- a/lib/Slic3r.pm
+++ b/lib/Slic3r.pm
@@ -7,7 +7,7 @@ use strict;
 use warnings;
 require v5.10;
 
-our $VERSION = "0.9.2-dev";
+our $VERSION = "0.9.2";
 
 our $debug = 0;
 sub debugf {

From 152c714b879743bb56c3a103862180ab94a39319 Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Fri, 31 Aug 2012 00:12:13 +0200
Subject: [PATCH 05/22] Bump version number

---
 lib/Slic3r.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm
index 085bd2123..746176f26 100644
--- a/lib/Slic3r.pm
+++ b/lib/Slic3r.pm
@@ -7,7 +7,7 @@ use strict;
 use warnings;
 require v5.10;
 
-our $VERSION = "0.9.2";
+our $VERSION = "0.9.3-dev";
 
 our $debug = 0;
 sub debugf {

From dc0d87f2bb5d13008841771b9a8232748b94bc36 Mon Sep 17 00:00:00 2001
From: Henrik Brix Andersen <henrik@brixandersen.dk>
Date: Sat, 1 Sep 2012 08:36:54 +0200
Subject: [PATCH 06/22] Fix MANIFEST. #656

---
 MANIFEST | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/MANIFEST b/MANIFEST
index 5a5a80df1..ff89293dd 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -15,7 +15,6 @@ lib/Slic3r/Fill/Flowsnake.pm
 lib/Slic3r/Fill/HilbertCurve.pm
 lib/Slic3r/Fill/Honeycomb.pm
 lib/Slic3r/Fill/Line.pm
-lib/Slic3r/Fill/Model.pm
 lib/Slic3r/Fill/OctagramSpiral.pm
 lib/Slic3r/Fill/PlanePath.pm
 lib/Slic3r/Fill/Rectilinear.pm
@@ -36,6 +35,7 @@ lib/Slic3r/GUI/SkeinPanel.pm
 lib/Slic3r/GUI/Tab.pm
 lib/Slic3r/Layer.pm
 lib/Slic3r/Line.pm
+lib/Slic3r/Model.pm
 lib/Slic3r/Point.pm
 lib/Slic3r/Polygon.pm
 lib/Slic3r/Polyline.pm

From 47c5d410ea52e2258b3c28a0316b72fce23f1c9f Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Thu, 6 Sep 2012 10:44:48 +0200
Subject: [PATCH 07/22] Remove the initial retraction compensation. Not only it
 is not very good for multi-head setups, but it also caused wrong first layer
 Z when lift was enabled. #655

---
 lib/Slic3r/Print.pm | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm
index 397bc9bea..f9aac1505 100644
--- a/lib/Slic3r/Print.pm
+++ b/lib/Slic3r/Print.pm
@@ -574,15 +574,6 @@ sub write_gcode {
     print $fh $gcodegen->set_tool(0);
     print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers;
     
-    # this spits out some platic at start from each extruder when they are first used;
-    # the primary extruder will compensate by the normal retraction length, while 
-    # the others will compensate for their toolchange length + restart extra.
-    # this is a temporary solution as all extruders should use some kind of skirt 
-    # to be put into a consistent state.
-    $_->retracted($_->retract_length_toolchange + $_->retract_restart_extra_toolchange)
-        for @{$Slic3r::extruders}[1 .. $#{$Slic3r::extruders}];
-    $gcodegen->retract;
-    
     # write start commands to file
     printf $fh $gcodegen->set_bed_temperature($Slic3r::Config->first_layer_bed_temperature, 1),
         if $Slic3r::Config->first_layer_bed_temperature && $Slic3r::Config->start_gcode !~ /M190/i;

From 3b38e392ad7c3e83fc67dd4c4b6c2bece0ceb11e Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Thu, 6 Sep 2012 10:56:42 +0200
Subject: [PATCH 08/22] Consider single walls as external perimeters. #661

---
 lib/Slic3r/Layer.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm
index cb47fa484..a6560b66e 100644
--- a/lib/Slic3r/Layer.pm
+++ b/lib/Slic3r/Layer.pm
@@ -318,7 +318,7 @@ sub make_perimeters {
     {
         my @thin_paths = ();
         my %properties = (
-            role            => EXTR_ROLE_PERIMETER,
+            role            => EXTR_ROLE_EXTERNAL_PERIMETER,
             flow_spacing    => $self->perimeter_flow->spacing,
         );
         for (@{ $self->thin_walls }) {

From 37ae2e1e2d067c22c47e78a08f6e47ef8ae48f56 Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Thu, 6 Sep 2012 11:01:44 +0200
Subject: [PATCH 09/22] Use support material extruder for brim. #653

---
 lib/Slic3r/Config.pm | 3 +++
 lib/Slic3r/Print.pm  | 1 +
 2 files changed, 4 insertions(+)

diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm
index 71363c726..312352312 100644
--- a/lib/Slic3r/Config.pm
+++ b/lib/Slic3r/Config.pm
@@ -178,6 +178,7 @@ our $Options = {
     # extruder mapping
     'perimeter_extruder' => {
         label   => 'Perimeter extruder',
+        tooltip => 'The extruder to use when printing perimeters.',
         cli     => 'perimeter-extruder=i',
         type    => 'i',
         aliases => [qw(perimeters_extruder)],
@@ -185,12 +186,14 @@ our $Options = {
     },
     'infill_extruder' => {
         label   => 'Infill extruder',
+        tooltip => 'The extruder to use when printing infill.',
         cli     => 'infill-extruder=i',
         type    => 'i',
         default => 1,
     },
     'support_material_extruder' => {
         label   => 'Support material extruder',
+        tooltip => 'The extruder to use when printing support material. This affects brim too.',
         cli     => 'support-material-extruder=i',
         type    => 'i',
         default => 1,
diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm
index f9aac1505..e38b20a71 100644
--- a/lib/Slic3r/Print.pm
+++ b/lib/Slic3r/Print.pm
@@ -643,6 +643,7 @@ sub write_gcode {
         
         # extrude brim
         if ($layer_id == 0 && !$brim_done) {
+            $gcode .= $gcodegen->set_tool($Slic3r::Config->support_material_extruder-1);
             $gcodegen->shift_x($shift[X]);
             $gcodegen->shift_y($shift[Y]);
             $gcode .= $gcodegen->extrude_loop($_, 'brim') for @{$self->brim};

From 6adf98edafd6d13d76252de6b6b6ca79292f07be Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Thu, 6 Sep 2012 11:28:24 +0200
Subject: [PATCH 10/22] Fix regression causing SVG/DLP support material to
 crash the application. #668

---
 lib/Slic3r/Print.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm
index e38b20a71..d0173a8a4 100644
--- a/lib/Slic3r/Print.pm
+++ b/lib/Slic3r/Print.pm
@@ -6,7 +6,7 @@ use File::Spec;
 use List::Util qw(max);
 use Math::ConvexHull 1.0.4 qw(convex_hull);
 use Slic3r::ExtrusionPath ':roles';
-use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 PI scale unscale move_points);
+use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 PI scale unscale move_points nearest_point);
 use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex offset JT_ROUND JT_SQUARE);
 use Time::HiRes qw(gettimeofday tv_interval);
 

From e89f64e9d313d74b6c1aa3ca0fd9afadaf50eba0 Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Tue, 11 Sep 2012 18:11:46 +0200
Subject: [PATCH 11/22] More unfinished work

---
 lib/Slic3r/GUI/Plater.pm | 36 ++++++++++++++++++++++--------------
 lib/Slic3r/Model.pm      |  2 ++
 2 files changed, 24 insertions(+), 14 deletions(-)

diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index 712b736b0..9488fc4bc 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -5,7 +5,7 @@ use utf8;
 
 use File::Basename qw(basename dirname);
 use List::Util qw(max sum);
-use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 scale unscale);
+use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2);
 use Slic3r::Geometry::Clipper qw(JT_ROUND);
 use threads::shared qw(shared_clone);
 use Wx qw(:bitmap :brush :button :cursor :dialog :filedialog :font :keycode :icon :id :listctrl :misc :panel :pen :sizer :toolbar :window);
@@ -368,7 +368,7 @@ sub increase {
     
     my ($obj_idx, $object) = $self->selected_object;
     my $instances = $object->instances;
-    push @$instances, [ $instances->[-1]->[X] + scale 10, $instances->[-1]->[Y] + scale 10 ];
+    push @$instances, [ $instances->[-1]->[X] + 10, $instances->[-1]->[Y] + 10 ];
     $self->{list}->SetItem($obj_idx, 1, $object->instances_count);
     $self->arrange;
 }
@@ -408,7 +408,7 @@ sub changescale {
     my ($obj_idx, $object) = $self->selected_object;
     
     # max scale factor should be above 2540 to allow importing files exported in inches
-    $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale", $object->scale*100, 0, 5000, $self);
+    my $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale", $object->scale*100, 0, 5000, $self);
     return if !$scale || $scale == -1;
     
     $object->set_scale($scale);
@@ -642,8 +642,8 @@ sub make_model {
         # TODO: reload file
         my $mesh = $self->{print}->[$obj_idx]->mesh->clone;
         $mesh->scale(&Slic3r::SCALING_FACTOR);
-        my $object = $model->add_object(vertices => $mesh->vertices);
-        $object->add_volume(facets => $mesh->facets);
+        my $model_object = $model->add_object(vertices => $mesh->vertices);
+        $model_object->add_volume(facets => $mesh->facets);
         for my $copy (@{$self->{print}->copies->[$obj_idx]}) {
             $object->add_instance(rotation => 0, offset => [ map unscale $_, @$copy ]);
         }
@@ -683,7 +683,15 @@ sub recenter {
     
     # calculate displacement needed to center the print
     my @print_bb = (0,0,0,0);
-    @print_bb =  if !defined $print_bb[0];
+    foreach my $object (@{$self->{objects}}) {
+        foreach my $instance (@{$object->instances}) {
+            my @bb = (@$instance, map $instance->[$_] + $object->size->[$_], X,Y);
+            $print_bb[X1] = $bb[X1] if $bb[X1] < $print_bb[X1];
+            $print_bb[Y1] = $bb[Y1] if $bb[Y1] < $print_bb[Y1];
+            $print_bb[X2] = $bb[X2] if $bb[X2] > $print_bb[X2];
+            $print_bb[Y2] = $bb[Y2] if $bb[Y2] > $print_bb[Y2];
+        }
+    }
     $self->{shift} = [
         ($self->{canvas}->GetSize->GetWidth  - ($self->to_pixel($print_bb[X2] + $print_bb[X1]))) / 2,
         ($self->{canvas}->GetSize->GetHeight - ($self->to_pixel($print_bb[Y2] + $print_bb[Y1]))) / 2,
@@ -727,7 +735,6 @@ sub _update_bed_size {
 sub repaint {
     my ($self, $event) = @_;
     my $parent = $self->GetParent;
-    my $print = $parent->{print};
     
     my $dc = Wx::PaintDC->new($self);
     my $size = $self->GetSize;
@@ -744,7 +751,7 @@ sub repaint {
     }
     
     # draw print center
-    if (@{$print->objects}) {
+    if (@{$parent->{objects}}) {
         $dc->SetPen($parent->{print_center_pen});
         $dc->DrawLine($size[X]/2, 0, $size[X]/2, $size[Y]);
         $dc->DrawLine(0, $size[Y]/2, $size[X], $size[Y]/2);
@@ -760,7 +767,7 @@ sub repaint {
     $dc->DrawRectangle(0, 0, @size);
     
     # draw text if plate is empty
-    if (!@{$print->objects}) {
+    if (@{$parent->{objects}}) {
         $dc->SetTextForeground(Wx::Colour->new(150,50,50));
         $dc->SetFont(Wx::Font->new(14, wxDEFAULT, wxNORMAL, wxNORMAL));
         $dc->DrawLabel(CANVAS_TEXT, Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
@@ -769,7 +776,7 @@ sub repaint {
     # draw thumbnails
     $dc->SetPen(wxBLACK_PEN);
     @{$parent->{object_previews}} = ();
-    for my $obj_idx (0 .. $#{$print->objects}) {
+    for my $obj_idx (0 .. $#{$parent->{objects}}) {
         next unless $parent->{thumbnails}[$obj_idx];
         for my $copy_idx (0 .. $#{$print->copies->[$obj_idx]}) {
             my $copy = $print->copies->[$obj_idx][$copy_idx];
@@ -909,12 +916,12 @@ sub statusbar {
 
 sub to_pixel {
     my $self = shift;
-    return unscale $_[0] * $self->{scaling_factor};
+    return $_[0] * $self->{scaling_factor};
 }
 
 sub to_scaled {
     my $self = shift;
-    return scale $_[0] / $self->{scaling_factor};
+    return $_[0] / $self->{scaling_factor};
 }
 
 sub _y {
@@ -954,6 +961,7 @@ package Slic3r::GUI::Plater::Object;
 use Moo;
 
 use Math::ConvexHull qw(convex_hull);
+use Slic3r::Geometry qw(X Y);
 
 has 'name'                  => (is => 'rw', required => 1);
 has 'input_file'            => (is => 'rw', required => 1);
@@ -967,7 +975,7 @@ has 'thumbnail'             => (is => 'rw');
 
 sub _trigger_mesh {
     my $self = shift;
-    $self->size($mesh->size) if $self->mesh;
+    $self->size($self->mesh->size) if $self->mesh;
 }
 
 sub instances_count {
@@ -978,7 +986,7 @@ sub instances_count {
 sub make_thumbnail {
     my $self = shift;
     
-    my @points = map [ @$_[X,Y] ], @{$object->mesh->vertices};
+    my @points = map [ @$_[X,Y] ], @{$self->mesh->vertices};
     my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points));
     for (@$convex_hull) {
         @$_ = map $self->to_pixel($_), @$_;
diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm
index 3a4aec49d..ead574aee 100644
--- a/lib/Slic3r/Model.pm
+++ b/lib/Slic3r/Model.pm
@@ -59,6 +59,8 @@ has 'attributes'    => (is => 'rw', default => sub { {} });
 package Slic3r::Model::Object;
 use Moo;
 
+use Slic3r::Geometry qw(X Y Z);
+
 has 'model'     => (is => 'ro', weak_ref => 1, required => 1);
 has 'vertices'  => (is => 'ro', default => sub { [] });
 has 'volumes'   => (is => 'ro', default => sub { [] });

From d848e2d4c9c84a71a96b8de963671ca2fd266cc5 Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Tue, 11 Sep 2012 16:02:26 +0200
Subject: [PATCH 12/22] Warn the user when Slic3r is being run under 5.16

---
 lib/Slic3r.pm | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm
index 746176f26..f6bd643c4 100644
--- a/lib/Slic3r.pm
+++ b/lib/Slic3r.pm
@@ -21,6 +21,9 @@ BEGIN {
     $have_threads = $Config{useithreads} && eval "use threads; use Thread::Queue; 1";
 }
 
+warn "Running Slic3r under Perl >= 5.16 is not supported nor recommended\n"
+    if $^V >= v5.16;
+
 use FindBin;
 our $var = "$FindBin::Bin/var";
 

From f314cedd8f63e8c100d8b35ac68af58a1e38fab9 Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Wed, 12 Sep 2012 12:13:43 +0200
Subject: [PATCH 13/22] Bugfix: extrusion width setting for support material
 was only affecting the path spacing but not the actual flow. #666

---
 lib/Slic3r/Fill.pm             | 2 +-
 lib/Slic3r/Fill/Concentric.pm  | 2 +-
 lib/Slic3r/Fill/Honeycomb.pm   | 3 ++-
 lib/Slic3r/Fill/PlanePath.pm   | 2 +-
 lib/Slic3r/Fill/Rectilinear.pm | 2 +-
 5 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm
index e30eec65f..7d0556f89 100644
--- a/lib/Slic3r/Fill.pm
+++ b/lib/Slic3r/Fill.pm
@@ -170,7 +170,7 @@ sub make_fill {
                             ? ($surface->surface_type == S_TYPE_TOP ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL)
                             : EXTR_ROLE_FILL),
                     depth_layers => $surface->depth_layers,
-                    flow_spacing => $params->{flow_spacing} || $flow_spacing,
+                    flow_spacing => $params->{flow_spacing} || (warn "Warning: no flow_spacing was returned by the infill engine, please report this to the developer\n"),
                 ), @paths,
             ],
         );
diff --git a/lib/Slic3r/Fill/Concentric.pm b/lib/Slic3r/Fill/Concentric.pm
index 94866e2ae..c0d172cfe 100644
--- a/lib/Slic3r/Fill/Concentric.pm
+++ b/lib/Slic3r/Fill/Concentric.pm
@@ -18,7 +18,7 @@ sub fill_surface {
     my $min_spacing = scale $params{flow_spacing};
     my $distance = $min_spacing / $params{density};
     
-    my $flow_spacing;
+    my $flow_spacing = $params{flow_spacing};
     if ($params{density} == 1) {
         $distance = $self->adjust_solid_spacing(
             width       => $bounding_box->[X2] - $bounding_box->[X1],
diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm
index 229c72568..1e407e2e6 100644
--- a/lib/Slic3r/Fill/Honeycomb.pm
+++ b/lib/Slic3r/Fill/Honeycomb.pm
@@ -94,7 +94,8 @@ sub fill_surface {
         paths => [ map Slic3r::ExtrusionPath->pack(polyline => $_, role => -1), @paths ],
     );
     
-    return {}, map $_->polyline, $collection->shortest_path;
+    return { flow_spacing => $params{flow_spacing} },
+        map $_->polyline, $collection->shortest_path;
 }
 
 1;
diff --git a/lib/Slic3r/Fill/PlanePath.pm b/lib/Slic3r/Fill/PlanePath.pm
index daeb13dea..d33b6e6fc 100644
--- a/lib/Slic3r/Fill/PlanePath.pm
+++ b/lib/Slic3r/Fill/PlanePath.pm
@@ -60,7 +60,7 @@ sub fill_surface {
     # paths must be rotated back
     $self->rotate_points_back(\@paths, $rotate_vector);
     
-    return {}, @paths;
+    return { flow_spacing => $params{flow_spacing} }, @paths;
 }
 
 1;
diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm
index 2de7872a6..bac965c15 100644
--- a/lib/Slic3r/Fill/Rectilinear.pm
+++ b/lib/Slic3r/Fill/Rectilinear.pm
@@ -22,7 +22,7 @@ sub fill_surface {
     my $distance_between_lines = $min_spacing / $params{density};
     my $line_oscillation = $distance_between_lines - $min_spacing;
     
-    my $flow_spacing;
+    my $flow_spacing = $params{flow_spacing};
     if ($params{density} == 1) {
         $distance_between_lines = $self->adjust_solid_spacing(
             width       => $bounding_box->[X2] - $bounding_box->[X1],

From e61055774ffa46712f6001a0511cb4c4b8dca255 Mon Sep 17 00:00:00 2001
From: Henrik Brix Andersen <henrik@brixandersen.dk>
Date: Wed, 12 Sep 2012 15:40:55 +0300
Subject: [PATCH 14/22] Move FAQs to the wiki, add link to Documentation

---
 README.markdown | 39 ++-------------------------------------
 1 file changed, 2 insertions(+), 37 deletions(-)

diff --git a/README.markdown b/README.markdown
index 46c779a83..1dfe6552b 100644
--- a/README.markdown
+++ b/README.markdown
@@ -9,8 +9,8 @@ A: Yes.
 Slic3r is a G-code generator for 3D printers. It's compatible with RepRaps,
 Makerbots, Ultimakers and many more machines.
 
-See the [project homepage](http://slic3r.org/) at slic3r.org
-for more information.
+See the [project homepage](http://slic3r.org/) at slic3r.org and the
+[documentation](https://github.com/alexrj/Slic3r/wiki/Documentation) on the Slic3r wiki for more information.
 
 ## What language is it written in?
 
@@ -275,38 +275,3 @@ If you want to change a preset file, just do
 If you want to slice a file overriding an option contained in your preset file:
 
     slic3r.pl --load config.ini --layer-height 0.25 file.stl
-
-## How can I integrate Slic3r with Pronterface?
-
-Put this into *slicecommand*:
-
-    slic3r.pl $s --load config.ini --output $o
-
-And this into *sliceoptscommand*:
-
-    slic3r.pl --load config.ini --ignore-nonexistent-config
-
-Replace `slic3r.pl` with the full path to the slic3r executable and `config.ini`
-with the full path of your config file (put it in your home directory or where
-you like).
-On Mac, the executable has a path like this:
-
-    /Applications/Slic3r.app/Contents/MacOS/slic3r
-
-## How can I specify a custom filename format for output G-code files?
-
-You can specify a filename format by using any of the config options. 
-Just enclose them in square brackets, and Slic3r will replace them upon
-exporting.
-The additional `[input_filename]` and `[input_filename_base]` options will
-be replaced by the input file name (in the second case, the .stl extension 
-is stripped).
-
-The default format is `[input_filename_base].gcode`, meaning that if you slice
-a *foo.stl* file, the output will be saved to *foo.gcode*.
-
-See below for more complex examples:
-
-    [input_filename_base]_h[layer_height]_p[perimeters]_s[solid_layers].gcode
-    [input_filename]_center[print_center]_[layer_height]layers.gcode
-

From 87912cb3b0a93eddbc9d32a8d75ae4d2ec2a76a7 Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Wed, 12 Sep 2012 15:19:47 +0200
Subject: [PATCH 15/22] Bugfix: only_retract_when_crossing_perimeters was
 almost not working. #680

---
 lib/Slic3r/GCode.pm    | 4 ++--
 lib/Slic3r/Geometry.pm | 2 ++
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm
index 499502696..4f29fa0b9 100644
--- a/lib/Slic3r/GCode.pm
+++ b/lib/Slic3r/GCode.pm
@@ -3,7 +3,7 @@ use Moo;
 
 use List::Util qw(first);
 use Slic3r::ExtrusionPath ':roles';
-use Slic3r::Geometry qw(scale unscale points_coincide PI X Y);
+use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y);
 
 has 'layer'              => (is => 'rw');
 has 'shift_x'            => (is => 'rw', default => sub {0} );
@@ -135,7 +135,7 @@ sub extrude_path {
     {
         my $travel = Slic3r::Line->new($self->last_pos, $path->points->[0]);
         if ($travel->length >= scale $self->extruder->retract_before_travel) {
-            if (!$Slic3r::Config->only_retract_when_crossing_perimeters || $path->role != EXTR_ROLE_FILL || !first { $_->expolygon->encloses_line($travel) } @{$self->layer->slices}) {
+            if (!$Slic3r::Config->only_retract_when_crossing_perimeters || $path->role != EXTR_ROLE_FILL || !first { $_->expolygon->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices}) {
                 $gcode .= $self->retract(travel_to => $path->points->[0]);
             }
         }
diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm
index 979a2eb0e..dfad17ad0 100644
--- a/lib/Slic3r/Geometry.pm
+++ b/lib/Slic3r/Geometry.pm
@@ -20,6 +20,7 @@ our @EXPORT_OK = qw(
     shortest_path collinear scale unscale merge_collinear_lines
     rad2deg_dir bounding_box_center line_intersects_any douglas_peucker
     polyline_remove_short_segments normal triangle_normal polygon_is_convex
+    scaled_epsilon
 );
 
 
@@ -38,6 +39,7 @@ use constant MAX => 1;
 our $parallel_degrees_limit = abs(deg2rad(3));
 
 sub epsilon () { 1E-4 }
+sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR }
 
 sub scale   ($) { $_[0] / &Slic3r::SCALING_FACTOR }
 sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR }

From 4fbb8ffb9d697895a3dfb943925496b3b64a4442 Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Wed, 12 Sep 2012 15:22:43 +0200
Subject: [PATCH 16/22] Replace 'scale epsilon' with 'scaled_epsilon'

---
 lib/Slic3r/Fill/Rectilinear.pm |  8 ++++----
 t/arcs.t                       | 10 +++++-----
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm
index bac965c15..121249eaa 100644
--- a/lib/Slic3r/Fill/Rectilinear.pm
+++ b/lib/Slic3r/Fill/Rectilinear.pm
@@ -3,7 +3,7 @@ use Moo;
 
 extends 'Slic3r::Fill::Base';
 
-use Slic3r::Geometry qw(X1 Y1 X2 Y2 A B X Y scale unscale epsilon);
+use Slic3r::Geometry qw(X1 Y1 X2 Y2 A B X Y scale unscale scaled_epsilon);
 
 sub fill_surface {
     my $self = shift;
@@ -36,7 +36,7 @@ sub fill_surface {
     my $x = $bounding_box->[X1];
     my $is_line_pattern = $self->isa('Slic3r::Fill::Line');
     my @vertical_lines = ();
-    for (my $i = 0; $x <= $bounding_box->[X2] + scale epsilon; $i++) {
+    for (my $i = 0; $x <= $bounding_box->[X2] + scaled_epsilon; $i++) {
         my $vertical_line = Slic3r::Line->new([$x, $bounding_box->[Y2]], [$x, $bounding_box->[Y1]]);
         if ($is_line_pattern && $i % 2) {
             $vertical_line->[A][X] += $line_oscillation;
@@ -49,7 +49,7 @@ sub fill_surface {
     # clip paths against a slightly offsetted expolygon, so that the first and last paths
     # are kept even if the expolygon has vertical sides
     my @paths = @{ Boost::Geometry::Utils::polygon_linestring_intersection(
-        +($expolygon->offset_ex(scale epsilon))[0]->boost_polygon,  # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object
+        +($expolygon->offset_ex(scaled_epsilon))[0]->boost_polygon,  # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object
         Boost::Geometry::Utils::linestring(@vertical_lines),
     ) };
     for (@paths) {
@@ -64,7 +64,7 @@ sub fill_surface {
         );
         @paths = ();
         
-        my $tolerance = 10 * scale epsilon;
+        my $tolerance = 10 * scaled_epsilon;
         my $diagonal_distance = $distance_between_lines * 5;
         my $can_connect = $is_line_pattern
             ? sub {
diff --git a/t/arcs.t b/t/arcs.t
index 1ffcd87b0..972620402 100644
--- a/t/arcs.t
+++ b/t/arcs.t
@@ -11,7 +11,7 @@ BEGIN {
 
 use Slic3r;
 use Slic3r::ExtrusionPath ':roles';
-use Slic3r::Geometry qw(epsilon scale X Y);
+use Slic3r::Geometry qw(scaled_epsilon scale X Y);
 
 {
     my $path = Slic3r::ExtrusionPath->new(polyline => Slic3r::Polyline->new(
@@ -61,17 +61,17 @@ use Slic3r::Geometry qw(epsilon scale X Y);
     isa_ok $collection2->paths->[0], 'Slic3r::ExtrusionPath::Arc', 'path';
     
     my $expected_length = scale 7.06858347057701;
-    ok abs($collection1->paths->[0]->length - $expected_length) < scale epsilon, 'cw oriented arc has correct length';
-    ok abs($collection2->paths->[0]->length - $expected_length) < scale epsilon, 'ccw oriented arc has correct length';
+    ok abs($collection1->paths->[0]->length - $expected_length) < scaled_epsilon, 'cw oriented arc has correct length';
+    ok abs($collection2->paths->[0]->length - $expected_length) < scaled_epsilon, 'ccw oriented arc has correct length';
 
     is $collection1->paths->[0]->orientation, 'cw', 'cw orientation was correctly detected';
     is $collection2->paths->[0]->orientation, 'ccw', 'ccw orientation was correctly detected';
     
     my $center1 = [ map sprintf('%.0f', $_), @{ $collection1->paths->[0]->center } ];
-    ok abs($center1->[X] - scale 10) < scale epsilon && abs($center1->[Y] - scale 10) < scale epsilon, 'center was correctly detected';
+    ok abs($center1->[X] - scale 10) < scaled_epsilon && abs($center1->[Y] - scale 10) < scaled_epsilon, 'center was correctly detected';
     
     my $center2 = [ map sprintf('%.0f', $_), @{ $collection2->paths->[0]->center } ];
-    ok abs($center2->[X] - scale 10) < scale epsilon && abs($center1->[Y] - scale 10) < scale epsilon, 'center was correctly detected';
+    ok abs($center2->[X] - scale 10) < scaled_epsilon && abs($center1->[Y] - scale 10) < scaled_epsilon, 'center was correctly detected';
 }
 
 #==========================================================

From 757515ba170b2f741528b94428ebf34b04e6afc0 Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Wed, 12 Sep 2012 15:29:44 +0200
Subject: [PATCH 17/22] New [print_preset], [filament_preset] and
 [printer_preset] variables. #675

---
 lib/Slic3r/GUI/Plater.pm | 2 ++
 lib/Slic3r/Print.pm      | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index 9488fc4bc..4996e68ec 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -475,6 +475,8 @@ sub export_gcode {
     
     # set this before spawning the thread because ->config needs GetParent and it's not available there
     $self->{print}->config($self->skeinpanel->config);
+    $self->{print}->extra_variables->{"${_}_preset"} = $self->skeinpanel->{options_tabs}{$_}->current_preset->{name}
+        for qw(print filament printer);
     
     # select output file
     $self->{output_file} = $main::opt{output};
diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm
index d0173a8a4..868b75846 100644
--- a/lib/Slic3r/Print.pm
+++ b/lib/Slic3r/Print.pm
@@ -11,6 +11,7 @@ use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex offset JT_ROUN
 use Time::HiRes qw(gettimeofday tv_interval);
 
 has 'config'                 => (is => 'rw', default => sub { Slic3r::Config->new_from_defaults }, trigger => 1);
+has 'extra_variables'        => (is => 'rw', default => sub {{}});
 has 'objects'                => (is => 'rw', default => sub {[]});
 has 'copies'                 => (is => 'rw', default => sub {[]});  # obj_idx => [copies...]
 has 'total_extrusion_length' => (is => 'rw');
@@ -808,6 +809,7 @@ sub expanded_output_filepath {
     return $Slic3r::Config->replace_options($path, {
         input_filename      => $input_filename,
         input_filename_base => $input_filename_base,
+        %{ $self->extra_variables },
     });
 }
 

From ca44cba6c376114a62e830552a2c2c207aa44836 Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Wed, 12 Sep 2012 15:40:57 +0200
Subject: [PATCH 18/22] Bugfix: --datadir was ignored. #651

---
 slic3r.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/slic3r.pl b/slic3r.pl
index 234ce5c24..e5c63b99e 100755
--- a/slic3r.pl
+++ b/slic3r.pl
@@ -71,11 +71,11 @@ if ($opt{save}) {
 # launch GUI
 my $gui;
 if (!@ARGV && !$opt{save} && eval "require Slic3r::GUI; 1") {
-    $gui = Slic3r::GUI->new;
     {
         no warnings 'once';
         $Slic3r::GUI::datadir = $opt{datadir} if $opt{datadir};
     }
+    $gui = Slic3r::GUI->new;
     $gui->{skeinpanel}->load_config_file($_) for @{$opt{load}};
     $gui->{skeinpanel}->load_config($cli_config);
     $gui->MainLoop;

From f659dcf0888f7e1b3ffc72d08971b1a8896f06fb Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Wed, 12 Sep 2012 15:53:24 +0200
Subject: [PATCH 19/22] Patch for --gcode-arcs (kindly submitted by Paul Howes)

---
 lib/Slic3r/ExtrusionPath.pm | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/lib/Slic3r/ExtrusionPath.pm b/lib/Slic3r/ExtrusionPath.pm
index a006d8712..224cdf4be 100644
--- a/lib/Slic3r/ExtrusionPath.pm
+++ b/lib/Slic3r/ExtrusionPath.pm
@@ -187,10 +187,14 @@ sub detect_arcs {
             my $s2_angle = $s2->atan;
             my $s3_angle = $s3->atan;
             $s1_angle += 2*PI if $s1_angle < 0;
-            $s2_angle += 2*PI if $s2_angle < 0;
-            $s3_angle += 2*PI if $s3_angle < 0;
             my $s1s2_angle = $s2_angle - $s1_angle;
             my $s2s3_angle = $s3_angle - $s2_angle;
+            # allow -ve angles but constrain angles differences to 0<difference<2PI
+            $s1s2_angle -= 2*PI if ($s1s2_angle > 2*PI);
+            $s1s2_angle += 2*PI if ($s1s2_angle < -2*PI);
+            $s2s3_angle -= 2*PI if ($s2s3_angle > 2*PI);
+            $s2s3_angle += 2*PI if ($s2s3_angle < -2*PI);
+
             next if abs($s1s2_angle - $s2s3_angle) > $Slic3r::Geometry::parallel_degrees_limit;
             next if abs($s1s2_angle) < $Slic3r::Geometry::parallel_degrees_limit;     # ignore parallel lines
             next if $s1s2_angle > $max_angle;  # ignore too sharp vertices
@@ -203,10 +207,15 @@ sub detect_arcs {
                 my $line = Slic3r::Line->new($points[$j], $points[$j+1]);
                 last if abs($line->length - $s1_len) > $len_epsilon;
                 my $line_angle = $line->atan;
-                $line_angle += 2*PI if $line_angle < 0;
                 my $anglediff = $line_angle - $last_line_angle;
+                # allow -ve angles but constrain angle differences to 0<difference<2PI
+                $anglediff -= 2*PI if ($anglediff > 2*PI);
+                $anglediff += 2*PI if ($anglediff < -2*PI);
+
                 last if abs($s1s2_angle - $anglediff) > $Slic3r::Geometry::parallel_degrees_limit;
-                
+                # Do not try to be too ambitious. Just detect arcs up to 60 degrees.
+                # The algorithm for finding the center is not accurate enough for more than this
+                last if abs($s1_angle - $line_angle) > PI/6; 
                 # point $j+1 belongs to the arc
                 $arc_points[-1] = $points[$j+1];
                 $last_j = $j+1;

From 8382eeef0a1a18ff728a49019ce24ab5e631dadc Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Wed, 12 Sep 2012 15:58:01 +0200
Subject: [PATCH 20/22] Revert "Patch for --gcode-arcs (kindly submitted by
 Paul Howes)"

This reverts commit f4260ae93d25df113cbbb9c1995c508c44587d56.
---
 lib/Slic3r/ExtrusionPath.pm | 17 ++++-------------
 1 file changed, 4 insertions(+), 13 deletions(-)

diff --git a/lib/Slic3r/ExtrusionPath.pm b/lib/Slic3r/ExtrusionPath.pm
index 224cdf4be..a006d8712 100644
--- a/lib/Slic3r/ExtrusionPath.pm
+++ b/lib/Slic3r/ExtrusionPath.pm
@@ -187,14 +187,10 @@ sub detect_arcs {
             my $s2_angle = $s2->atan;
             my $s3_angle = $s3->atan;
             $s1_angle += 2*PI if $s1_angle < 0;
+            $s2_angle += 2*PI if $s2_angle < 0;
+            $s3_angle += 2*PI if $s3_angle < 0;
             my $s1s2_angle = $s2_angle - $s1_angle;
             my $s2s3_angle = $s3_angle - $s2_angle;
-            # allow -ve angles but constrain angles differences to 0<difference<2PI
-            $s1s2_angle -= 2*PI if ($s1s2_angle > 2*PI);
-            $s1s2_angle += 2*PI if ($s1s2_angle < -2*PI);
-            $s2s3_angle -= 2*PI if ($s2s3_angle > 2*PI);
-            $s2s3_angle += 2*PI if ($s2s3_angle < -2*PI);
-
             next if abs($s1s2_angle - $s2s3_angle) > $Slic3r::Geometry::parallel_degrees_limit;
             next if abs($s1s2_angle) < $Slic3r::Geometry::parallel_degrees_limit;     # ignore parallel lines
             next if $s1s2_angle > $max_angle;  # ignore too sharp vertices
@@ -207,15 +203,10 @@ sub detect_arcs {
                 my $line = Slic3r::Line->new($points[$j], $points[$j+1]);
                 last if abs($line->length - $s1_len) > $len_epsilon;
                 my $line_angle = $line->atan;
+                $line_angle += 2*PI if $line_angle < 0;
                 my $anglediff = $line_angle - $last_line_angle;
-                # allow -ve angles but constrain angle differences to 0<difference<2PI
-                $anglediff -= 2*PI if ($anglediff > 2*PI);
-                $anglediff += 2*PI if ($anglediff < -2*PI);
-
                 last if abs($s1s2_angle - $anglediff) > $Slic3r::Geometry::parallel_degrees_limit;
-                # Do not try to be too ambitious. Just detect arcs up to 60 degrees.
-                # The algorithm for finding the center is not accurate enough for more than this
-                last if abs($s1_angle - $line_angle) > PI/6; 
+                
                 # point $j+1 belongs to the arc
                 $arc_points[-1] = $points[$j+1];
                 $last_j = $j+1;

From e40f32995f034962a7310dcbaf6ddc6eb1327cab Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Wed, 12 Sep 2012 16:30:44 +0200
Subject: [PATCH 21/22] Finish GUI decoupling

---
 lib/Slic3r/Format/AMF.pm        |   2 +-
 lib/Slic3r/Format/AMF/Parser.pm |   4 +-
 lib/Slic3r/GUI/Plater.pm        | 218 +++++++++++++++++++++-----------
 lib/Slic3r/GUI/SkeinPanel.pm    |   4 +-
 lib/Slic3r/Geometry.pm          |  11 +-
 lib/Slic3r/Model.pm             |  61 +++------
 lib/Slic3r/Polygon.pm           |   4 +-
 lib/Slic3r/Polyline.pm          |  22 +++-
 lib/Slic3r/Print.pm             |  17 ++-
 lib/Slic3r/TriangleMesh.pm      |  20 +++
 t/fill.t                        |   2 +-
 11 files changed, 223 insertions(+), 142 deletions(-)

diff --git a/lib/Slic3r/Format/AMF.pm b/lib/Slic3r/Format/AMF.pm
index 552f1fdbd..78d2656c5 100644
--- a/lib/Slic3r/Format/AMF.pm
+++ b/lib/Slic3r/Format/AMF.pm
@@ -72,7 +72,7 @@ sub write_file {
             foreach my $instance (@{$object->instances}) {
                 $instances .= sprintf qq{    <instance objectid="%d">\n}, $object_id;
                 $instances .= sprintf qq{      <deltax>%s</deltax>\n}, $instance->offset->[X];
-                $instances .= sprintf qq{      <deltax>%s</deltax>\n}, $instance->offset->[Y];
+                $instances .= sprintf qq{      <deltay>%s</deltay>\n}, $instance->offset->[Y];
                 $instances .= sprintf qq{      <rz>%s</rz>\n}, $instance->rotation;
                 $instances .= sprintf qq{    </instance>\n};
             }
diff --git a/lib/Slic3r/Format/AMF/Parser.pm b/lib/Slic3r/Format/AMF/Parser.pm
index 93b35765e..5e0b28178 100644
--- a/lib/Slic3r/Format/AMF/Parser.pm
+++ b/lib/Slic3r/Format/AMF/Parser.pm
@@ -107,7 +107,7 @@ sub end_document {
     
     foreach my $object_id (keys %{ $self->{_instances} }) {
         my $new_object_id = $self->{_objects_map}{$object_id};
-        if (!$new_object_id) {
+        if (!defined $new_object_id) {
             warn "Undefined object $object_id referenced in constellation\n";
             next;
         }
@@ -115,7 +115,7 @@ sub end_document {
         foreach my $instance (@{ $self->{_instances}{$object_id} }) {
             $self->{_model}->objects->[$new_object_id]->add_instance(
                 rotation => $instance->{rz} || 0,
-                offset   => [ $instance->{deltax} || 0, $instance->{deltay} ],
+                offset   => [ $instance->{deltax} || 0, $instance->{deltay} || 0 ],
             );
         }
     }
diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index 4996e68ec..89db0241f 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -5,6 +5,7 @@ use utf8;
 
 use File::Basename qw(basename dirname);
 use List::Util qw(max sum);
+use Math::ConvexHull qw(convex_hull);
 use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2);
 use Slic3r::Geometry::Clipper qw(JT_ROUND);
 use threads::shared qw(shared_clone);
@@ -39,6 +40,8 @@ sub new {
     $self->{config} = Slic3r::Config->new_from_defaults(qw(
         bed_size print_center complete_objects extruder_clearance_radius skirts skirt_distance
     ));
+    $self->{objects} = [];
+    $self->{selected_objects} = [];
     
     $self->{canvas} = Wx::Panel->new($self, -1, wxDefaultPosition, CANVAS_SIZE, wxTAB_TRAVERSAL);
     $self->{canvas}->SetBackgroundColour(Wx::wxWHITE);
@@ -183,8 +186,6 @@ sub new {
     });
     
     $self->_update_bed_size;
-    $self->{objects} = [];
-    $self->{selected_objects} = [];
     $self->recenter;
     
     {
@@ -298,7 +299,7 @@ sub load_file {
     my $model = Slic3r::Model->read_from_file($input_file);
     for my $i (0 .. $#{$model->objects}) {
         my $object = Slic3r::GUI::Plater::Object->new(
-            name                    => basebane($input_file),
+            name                    => basename($input_file),
             input_file              => $input_file,
             input_file_object_id    => $i,
             mesh                    => $model->objects->[$i]->mesh,
@@ -308,8 +309,13 @@ sub load_file {
                     : [0,0],
             ],
         );
+        
+        # we only consider the rotation of the first instance for now
+        $object->set_rotation($model->objects->[$i]->instances->[0]->rotation)
+            if $model->objects->[$i]->instances;
+        
         push @{ $self->{objects} }, $object;
-        $self->object_loaded($#{ $self->{objects} });
+        $self->object_loaded($#{ $self->{objects} }, no_arrange => (@{$object->instances} > 1));
     }
     
     $process_dialog->Destroy;
@@ -377,13 +383,18 @@ sub decrease {
     my $self = shift;
     
     my ($obj_idx, $object) = $self->selected_object;
-    $self->{selected_objects} = [ +(grep { $_->[0] == $obj_idx } @{$self->{object_previews}})[-1] ];
-    $self->remove;
+    if ($object->instances_count >= 2) {
+        pop @{$object->instances};
+    } else {
+        $self->remove;
+    }
     
     if ($self->{objects}[$obj_idx]) {
         $self->{list}->Select($obj_idx, 0);
         $self->{list}->Select($obj_idx, 1);
     }
+    $self->recenter;
+    $self->{canvas}->Refresh;
 }
 
 sub rotate {
@@ -397,7 +408,7 @@ sub rotate {
         return if !$angle || $angle == -1;
     }
     
-    $object->set_rotation($angle);
+    $object->set_rotation($object->rotate + $angle);
     $self->recenter;
     $self->{canvas}->Refresh;
 }
@@ -411,7 +422,8 @@ sub changescale {
     my $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale", $object->scale*100, 0, 5000, $self);
     return if !$scale || $scale == -1;
     
-    $object->set_scale($scale);
+    $self->{list}->SetItem($obj_idx, 2, "$scale%");
+    $object->set_scale($scale / 100);
     $self->arrange;
 }
 
@@ -421,13 +433,16 @@ sub arrange {
     my $total_parts = sum(map $_->instances_count, @{$self->{objects}}) or return;
     my @size = ();
     for my $a (X,Y) {
-        $size[$a] = max(map $_->thumbnail->size->[$a], @{$self->{objects}});
+        $size[$a] = $self->to_units(max(map $_->thumbnail->size->[$a], @{$self->{objects}}));
     }
     
     eval {
         my $config = $self->skeinpanel->config;
         my @positions = Slic3r::Geometry::arrange
-            ($total_parts, @size, @{$config->bed_size}, $config->min_object_distance);
+            ($total_parts, @size, @{$config->bed_size}, $config->min_object_distance, $self->skeinpanel->config);
+        
+        @$_ = @{shift @positions}
+            for map @{$_->instances}, @{$self->{objects}};
     };
     # ignore arrange warnings on purpose
     
@@ -440,8 +455,7 @@ sub split_object {
     
     my ($obj_idx, $current_object) = $self->selected_object;
     my $current_copies_num = $current_object->instances_count;
-    my $mesh = $current_object->mesh->clone;
-    $mesh->scale(&Slic3r::SCALING_FACTOR);
+    my $mesh = $current_object->get_mesh;
     
     my @new_meshes = $mesh->split_mesh;
     if (@new_meshes == 1) {
@@ -455,11 +469,15 @@ sub split_object {
     $self->remove($obj_idx);
     
     foreach my $mesh (@new_meshes) {
-        my $object = $self->{print}->add_object_from_mesh($mesh);
-        $object->input_file($current_object->input_file);
-        my $new_obj_idx = $#{$self->{print}->objects};
-        push @{$self->{print}->copies->[$new_obj_idx]}, [0,0] for 2..$current_copies_num;
-        $self->object_loaded($new_obj_idx, no_arrange => 1);
+        my $object = Slic3r::GUI::Plater::Object->new(
+            name                    => basename($current_object->input_file),
+            input_file              => $current_object->input_file,
+            input_file_object_id    => undef,
+            mesh                    => $mesh,
+            instances               => [ map [0,0], 1..$current_copies_num ],
+        );
+        push @{ $self->{objects} }, $object;
+        $self->object_loaded($#{ $self->{objects} }, no_arrange => 1);
     }
     
     $self->arrange;
@@ -473,15 +491,13 @@ sub export_gcode {
         return;
     }
     
-    # set this before spawning the thread because ->config needs GetParent and it's not available there
-    $self->{print}->config($self->skeinpanel->config);
-    $self->{print}->extra_variables->{"${_}_preset"} = $self->skeinpanel->{options_tabs}{$_}->current_preset->{name}
-        for qw(print filament printer);
+    # get config before spawning the thread because ->config needs GetParent and it's not available there
+    my $print = $self->_init_print;
     
     # select output file
     $self->{output_file} = $main::opt{output};
     {
-        $self->{output_file} = $self->{print}->expanded_output_filepath($self->{output_file});
+        $self->{output_file} = $print->expanded_output_filepath($self->{output_file}, $self->{objects}[0]->input_file);
         my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', dirname($self->{output_file}),
             basename($self->{output_file}), $Slic3r::GUI::SkeinPanel::gcode_wildcard, wxFD_SAVE);
         if ($dlg->ShowModal != wxID_OK) {
@@ -496,6 +512,7 @@ sub export_gcode {
     if ($Slic3r::have_threads) {
         $self->{export_thread} = threads->create(sub {
             $self->export_gcode2(
+                $print,
                 $self->{output_file},
                 progressbar     => sub { Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROGRESS_BAR_EVENT, shared_clone([@_]))) },
                 message_dialog  => sub { Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $MESSAGE_DIALOG_EVENT, shared_clone([@_]))) },
@@ -516,6 +533,7 @@ sub export_gcode {
         });
     } else {
         $self->export_gcode2(
+            $print,
             $self->{output_file},
             progressbar => sub {
                 my ($percent, $message) = @_;
@@ -529,9 +547,20 @@ sub export_gcode {
     }
 }
 
+sub _init_print {
+    my $self = shift;
+    
+    return Slic3r::Print->new(
+        config => $self->skeinpanel->config,
+        extra_variables => {
+            map { $_ => $self->skeinpanel->{options_tabs}{$_}->current_preset->{name} } qw(print filament printer),
+        },
+    );
+}
+
 sub export_gcode2 {
     my $self = shift;
-    my ($output_file, %params) = @_;
+    my ($print, $output_file, %params) = @_;
     $Slic3r::Geometry::Clipper::clipper = Math::Clipper->new;
     local $SIG{'KILL'} = sub {
         Slic3r::debugf "Exporting cancelled; exiting thread...\n";
@@ -539,8 +568,8 @@ sub export_gcode2 {
     } if $Slic3r::have_threads;
     
     eval {
-        my $print = $self->{print};
         $print->config->validate;
+        $print->add_model($self->make_model);
         $print->validate;
         
         {
@@ -549,7 +578,6 @@ sub export_gcode2 {
             my %params = (
                 output_file => $output_file,
                 status_cb   => sub { $params{progressbar}->(@_) },
-                keep_meshes => 1,
             );
             if ($params{export_svg}) {
                 $print->export_svg(%params);
@@ -621,7 +649,7 @@ sub _get_export_file {
     
     my $output_file = $main::opt{output};
     {
-        $output_file = $self->{print}->expanded_output_filepath($output_file);
+        $output_file = $self->_init_print->expanded_output_filepath($output_file, $self->{objects}[0]->input_file);
         $output_file =~ s/\.gcode$/$suffix/i;
         my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file),
             basename($output_file), $Slic3r::GUI::SkeinPanel::model_wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
@@ -639,18 +667,21 @@ sub make_model {
     my $self = shift;
     
     my $model = Slic3r::Model->new;
-    for my $obj_idx (0 .. $#{$self->{objects}}) {
-        my $object = $self->{objects}[$obj_idx];
-        # TODO: reload file
-        my $mesh = $self->{print}->[$obj_idx]->mesh->clone;
-        $mesh->scale(&Slic3r::SCALING_FACTOR);
-        my $model_object = $model->add_object(vertices => $mesh->vertices);
-        $model_object->add_volume(facets => $mesh->facets);
-        for my $copy (@{$self->{print}->copies->[$obj_idx]}) {
-            $object->add_instance(rotation => 0, offset => [ map unscale $_, @$copy ]);
-        }
+    foreach my $object (@{$self->{objects}}) {
+        my $mesh = $object->get_mesh;
+        $mesh->scale($object->scale);
+        my $model_object = $model->add_object(
+            vertices    => $mesh->vertices,
+            input_file  => $object->input_file,
+        );
+        $model_object->add_volume(
+            facets      => $mesh->facets,
+        );
+        $model_object->add_instance(
+            rotation    => $object->rotate,
+            offset      => [ @$_ ],
+        ) for @{$object->instances};
     }
-    # TODO: $model->align_to_origin;
     
     return $model;
 }
@@ -661,7 +692,7 @@ sub make_thumbnail {
     
     my $cb = sub {
         my $object = $self->{objects}[$obj_idx];
-        my $thumbnail = $object->make_thumbnail;
+        my $thumbnail = $object->make_thumbnail(scaling_factor => $self->{scaling_factor});
         
         if ($Slic3r::have_threads) {
             Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $THUMBNAIL_DONE_EVENT, shared_clone([ $obj_idx, $thumbnail ])));
@@ -683,20 +714,21 @@ sub on_thumbnail_made {
 sub recenter {
     my $self = shift;
     
+    return unless @{$self->{objects}};
+    
     # calculate displacement needed to center the print
-    my @print_bb = (0,0,0,0);
-    foreach my $object (@{$self->{objects}}) {
-        foreach my $instance (@{$object->instances}) {
-            my @bb = (@$instance, map $instance->[$_] + $object->size->[$_], X,Y);
-            $print_bb[X1] = $bb[X1] if $bb[X1] < $print_bb[X1];
-            $print_bb[Y1] = $bb[Y1] if $bb[Y1] < $print_bb[Y1];
-            $print_bb[X2] = $bb[X2] if $bb[X2] > $print_bb[X2];
-            $print_bb[Y2] = $bb[Y2] if $bb[Y2] > $print_bb[Y2];
-        }
-    }
+    my @print_bb = Slic3r::Geometry::bounding_box([
+        map {
+            my $obj = $_;
+            map {
+                my $instance = $_;
+                $instance, [ map $instance->[$_] + $obj->rotated_size->[$_], X,Y ];
+            } @{$obj->instances};
+        } @{$self->{objects}},
+    ]);
     $self->{shift} = [
-        ($self->{canvas}->GetSize->GetWidth  - ($self->to_pixel($print_bb[X2] + $print_bb[X1]))) / 2,
-        ($self->{canvas}->GetSize->GetHeight - ($self->to_pixel($print_bb[Y2] + $print_bb[Y1]))) / 2,
+        ($self->{canvas}->GetSize->GetWidth  - $self->to_pixel($print_bb[X2] + $print_bb[X1])) / 2,
+        ($self->{canvas}->GetSize->GetHeight - $self->to_pixel($print_bb[Y2] + $print_bb[Y1])) / 2,
     ];
 }
 
@@ -729,7 +761,6 @@ sub _update_bed_size {
     my $bed_size = $self->{config}->bed_size;
     my $canvas_side = CANVAS_SIZE->[X];  # when the canvas is not rendered yet, its GetSize() method returns 0,0
     my $bed_largest_side = $bed_size->[X] > $bed_size->[Y] ? $bed_size->[X] : $bed_size->[Y];
-    my $old_scaling_factor = $self->{scaling_factor};
     $self->{scaling_factor} = $canvas_side / $bed_largest_side;
 }
 
@@ -769,7 +800,7 @@ sub repaint {
     $dc->DrawRectangle(0, 0, @size);
     
     # draw text if plate is empty
-    if (@{$parent->{objects}}) {
+    if (!@{$parent->{objects}}) {
         $dc->SetTextForeground(Wx::Colour->new(150,50,50));
         $dc->SetFont(Wx::Font->new(14, wxDEFAULT, wxNORMAL, wxNORMAL));
         $dc->DrawLabel(CANVAS_TEXT, Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
@@ -779,14 +810,15 @@ sub repaint {
     $dc->SetPen(wxBLACK_PEN);
     @{$parent->{object_previews}} = ();
     for my $obj_idx (0 .. $#{$parent->{objects}}) {
-        next unless $parent->{thumbnails}[$obj_idx];
-        for my $copy_idx (0 .. $#{$print->copies->[$obj_idx]}) {
-            my $copy = $print->copies->[$obj_idx][$copy_idx];
-            push @{$parent->{object_previews}}, [ $obj_idx, $copy_idx, $parent->{thumbnails}[$obj_idx]->clone ];
-            $parent->{object_previews}->[-1][2]->translate(map $parent->to_pixel($copy->[$_]) + $parent->{shift}[$_], (X,Y));
+        my $object = $parent->{objects}[$obj_idx];
+        next unless $object->thumbnail;
+        for my $instance_idx (0 .. $#{$object->instances}) {
+            my $instance = $object->instances->[$instance_idx];
+            push @{$parent->{object_previews}}, [ $obj_idx, $instance_idx, $object->thumbnail->clone ];
+            $parent->{object_previews}->[-1][2]->translate(map $parent->to_pixel($instance->[$_]) + $parent->{shift}[$_], (X,Y));
             
             my $drag_object = $self->{drag_object};
-            if (defined $drag_object && $obj_idx == $drag_object->[0] && $copy_idx == $drag_object->[1]) {
+            if (defined $drag_object && $obj_idx == $drag_object->[0] && $instance_idx == $drag_object->[1]) {
                 $dc->SetBrush($parent->{dragged_brush});
             } elsif (grep { $_->[0] == $obj_idx } @{$parent->{selected_objects}}) {
                 $dc->SetBrush($parent->{selected_brush});
@@ -796,7 +828,7 @@ sub repaint {
             $dc->DrawPolygon($parent->_y($parent->{object_previews}->[-1][2]), 0, 0);
             
             # if sequential printing is enabled and we have more than one object
-            if ($parent->{config}->complete_objects && (map @$_, @{$print->copies}) > 1) {
+            if ($parent->{config}->complete_objects && (map @{$_->instances}, @{$parent->{objects}}) > 1) {
                 my $clearance = +($parent->{object_previews}->[-1][2]->offset($parent->{config}->extruder_clearance_radius / 2 * $parent->{scaling_factor}, 1, JT_ROUND))[0];
                 $dc->SetPen($parent->{clearance_pen});
                 $dc->SetBrush($parent->{transparent_brush});
@@ -820,7 +852,6 @@ sub repaint {
 sub mouse_event {
     my ($self, $event) = @_;
     my $parent = $self->GetParent;
-    my $print = $parent->{print};
     
     my $point = $event->GetPosition;
     my $pos = $parent->_y([[$point->x, $point->y]])->[0]; #]]
@@ -829,12 +860,13 @@ sub mouse_event {
         $parent->{list}->Select($parent->{list}->GetFirstSelected, 0);
         $parent->selection_changed(0);
         for my $preview (@{$parent->{object_previews}}) {
-            if ($preview->[2]->encloses_point($pos)) {
-                $parent->{selected_objects} = [$preview];
-                $parent->{list}->Select($preview->[0], 1);
+            my ($obj_idx, $instance_idx, $thumbnail) = @$preview;
+            if ($thumbnail->encloses_point($pos)) {
+                $parent->{selected_objects} = [ [$obj_idx, $instance_idx] ];
+                $parent->{list}->Select($obj_idx, 1);
                 $parent->selection_changed(1);
-                my $copy = $print->copies->[ $preview->[0] ]->[ $preview->[1] ];
-                $self->{drag_start_pos} = [ map $pos->[$_] - $parent->{shift}[$_] - $parent->to_pixel($copy->[$_]), X,Y ];   # displacement between the click and the copy's origin
+                my $instance = $parent->{objects}[$obj_idx]->instances->[$instance_idx];
+                $self->{drag_start_pos} = [ map $pos->[$_] - $parent->{shift}[$_] - $parent->to_pixel($instance->[$_]), X,Y ];   # displacement between the click and the instance origin
                 $self->{drag_object} = $preview;
             }
         }
@@ -847,9 +879,11 @@ sub mouse_event {
         $self->SetCursor(wxSTANDARD_CURSOR);
     } elsif ($event->Dragging) {
         return if !$self->{drag_start_pos}; # concurrency problems
-        for my $obj ($self->{drag_object}) {
-            my $copy = $print->copies->[ $obj->[0] ]->[ $obj->[1] ];
-            $copy->[$_] = $parent->to_scaled($pos->[$_] - $self->{drag_start_pos}[$_] - $parent->{shift}[$_]) for X,Y;
+        for my $preview ($self->{drag_object}) {
+            my ($obj_idx, $instance_idx, $thumbnail) = @$preview;
+            my $instance = $parent->{objects}[$obj_idx]->instances->[$instance_idx];
+            $instance->[$_] = $parent->to_units($pos->[$_] - $self->{drag_start_pos}[$_] - $parent->{shift}[$_]) for X,Y;
+            $instance = $parent->_y([$instance])->[0];
             $parent->Refresh;
         }
     } elsif ($event->Moving) {
@@ -886,7 +920,7 @@ sub list_item_selected {
 sub object_list_changed {
     my $self = shift;
     
-    my $method = $self->{print} && @{$self->{print}->objects} ? 'Enable' : 'Disable';
+    my $method = @{$self->{objects}} ? 'Enable' : 'Disable';
     $self->{"btn_$_"}->$method
         for grep $self->{"btn_$_"}, qw(reset arrange export_gcode export_stl);
 }
@@ -921,7 +955,7 @@ sub to_pixel {
     return $_[0] * $self->{scaling_factor};
 }
 
-sub to_scaled {
+sub to_units {
     my $self = shift;
     return $_[0] / $self->{scaling_factor};
 }
@@ -967,17 +1001,25 @@ use Slic3r::Geometry qw(X Y);
 
 has 'name'                  => (is => 'rw', required => 1);
 has 'input_file'            => (is => 'rw', required => 1);
-has 'input_file_object_id'  => (is => 'rw', required => 1);
+has 'input_file_object_id'  => (is => 'rw');  # undef means keep mesh
 has 'mesh'                  => (is => 'rw', required => 1, trigger => 1);
 has 'size'                  => (is => 'rw');
 has 'scale'                 => (is => 'rw', default => sub { 1 });
 has 'rotate'                => (is => 'rw', default => sub { 0 });
-has 'instances'             => (is => 'rw', default => sub { [] });
+has 'instances'             => (is => 'rw', default => sub { [] }); # upward Y axis
 has 'thumbnail'             => (is => 'rw');
 
 sub _trigger_mesh {
     my $self = shift;
-    $self->size($self->mesh->size) if $self->mesh;
+    $self->size([$self->mesh->size]) if $self->mesh;
+}
+
+sub get_mesh {
+    my $self = shift;
+    
+    return $self->mesh->clone if $self->mesh;
+    my $model = Slic3r::Model->read_from_file($self->input_file);
+    return $model->objects->[$self->input_file_object_id]->mesh;
 }
 
 sub instances_count {
@@ -987,16 +1029,21 @@ sub instances_count {
 
 sub make_thumbnail {
     my $self = shift;
+    my %params = @_;
     
     my @points = map [ @$_[X,Y] ], @{$self->mesh->vertices};
     my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points));
     for (@$convex_hull) {
-        @$_ = map $self->to_pixel($_), @$_;
+        @$_ = map $_ * $params{scaling_factor}, @$_;
     }
     $convex_hull->simplify(0.3);
+    $convex_hull->rotate(Slic3r::Geometry::deg2rad($self->rotate));
+    $convex_hull->scale($self->scale);
+    $convex_hull->align_to_origin;
     
     $self->thumbnail($convex_hull);  # ignored in multi-threaded environments
-    $self->mesh(undef);
+    $self->mesh(undef) if defined $self->input_file_object_id;
+    
     return $convex_hull;
 }
 
@@ -1004,7 +1051,10 @@ sub set_rotation {
     my $self = shift;
     my ($angle) = @_;
     
-    $self->thumbnail->rotate($angle - $self->rotate);
+    if ($self->thumbnail) {
+        $self->thumbnail->rotate(Slic3r::Geometry::deg2rad($angle - $self->rotate));
+        $self->thumbnail->align_to_origin;
+    }
     $self->rotate($angle);
 }
 
@@ -1012,8 +1062,22 @@ sub set_scale {
     my $self = shift;
     my ($scale) = @_;
     
-    $self->thumbnail->scale($scale - $self->scale);
+    my $factor = $scale / $self->scale;
+    return if $factor == 1;
+    $self->size->[$_] *= $factor for X,Y;
+    if ($self->thumbnail) {
+        $self->thumbnail->scale($factor);
+        $self->thumbnail->align_to_origin;
+    }
     $self->scale($scale);
 }
 
+sub rotated_size {
+    my $self = shift;
+    
+    return Slic3r::Polygon->new([0,0], [$self->size->[X], 0], [@{$self->size}], [0, $self->size->[Y]])
+        ->rotate(Slic3r::Geometry::deg2rad($self->rotate))
+        ->size;
+}
+
 1;
diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm
index babd1cfbc..6ef5a64de 100644
--- a/lib/Slic3r/GUI/SkeinPanel.pm
+++ b/lib/Slic3r/GUI/SkeinPanel.pm
@@ -57,8 +57,8 @@ sub do_slice {
         $config->validate;
 
         # confirm slicing of more than one copies
-        my $copies = $Slic3r::Config->duplicate_grid->[X] * $Slic3r::Config->duplicate_grid->[Y];
-        $copies = $Slic3r::Config->duplicate if $Slic3r::Config->duplicate > 1;
+        my $copies = $config->duplicate_grid->[X] * $config->duplicate_grid->[Y];
+        $copies = $config->duplicate if $config->duplicate > 1;
         if ($copies > 1) {
             my $confirmation = Wx::MessageDialog->new($self, "Are you sure you want to slice $copies copies?",
                                                       'Multiple Copies', wxICON_QUESTION | wxOK | wxCANCEL);
diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm
index dfad17ad0..cd1e59b32 100644
--- a/lib/Slic3r/Geometry.pm
+++ b/lib/Slic3r/Geometry.pm
@@ -882,7 +882,7 @@ sub douglas_peucker2 {
 }
 
 sub arrange {
-    my ($total_parts, $partx, $party, $areax, $areay, $dist) = @_;
+    my ($total_parts, $partx, $party, $areax, $areay, $dist, $Config) = @_;
     
     my $linint = sub {
         my ($value, $oldmin, $oldmax, $newmin, $newmax) = @_;
@@ -895,8 +895,13 @@ sub arrange {
     
     # margin needed for the skirt
     my $skirt_margin;		
-    if ($Slic3r::Config->skirts > 0) {
-        $skirt_margin = ($Slic3r::flow->spacing * $Slic3r::Config->skirts + $Slic3r::Config->skirt_distance) * 2;
+    if ($Config->skirts > 0) {
+        my $flow = Slic3r::Flow->new(
+            layer_height    => $Config->first_layer_height,
+            nozzle_diameter => $Config->nozzle_diameter->[0],  # TODO: actually look for the extruder used for skirt
+            width           => $Config->first_layer_extrusion_width,
+        );
+        $skirt_margin = ($flow->spacing * $Config->skirts + $Config->skirt_distance) * 2;
     } else {
         $skirt_margin = 0;		
     }
diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm
index ead574aee..2d7a33155 100644
--- a/lib/Slic3r/Model.pm
+++ b/lib/Slic3r/Model.pm
@@ -30,24 +30,21 @@ sub add_object {
 sub mesh {
     my $self = shift;
     
-    my $vertices = [];
-    my $facets = [];
+    my @meshes = ();
     foreach my $object (@{$self->objects}) {
-        my $mesh = $object->mesh;
-        
-        my $v_offset = @$vertices;
-        push @$vertices, @{$mesh->vertices};
-        push @$facets, map {
-            my $f = [@$_];
-            $f->[$_] += $v_offset for -3..-1;
-            $f;
-        } @{$mesh->facets};
+        my @instances = $object->instances ? @{$object->instances} : (undef);
+        foreach my $instance (@instances) {
+            my $mesh = $object->mesh->clone;
+            if ($instance) {
+                $mesh->rotate($instance->rotation);
+                $mesh->align_to_origin;
+                $mesh->move(@{$instance->offset});
+            }
+            push @meshes, $mesh;
+        }
     }
     
-    return Slic3r::TriangleMesh->new(
-        vertices => $vertices,
-        facets   => $facets,
-    );
+    return Slic3r::TriangleMesh->merge(@meshes);
 }
 
 package Slic3r::Model::Material;
@@ -61,6 +58,7 @@ use Moo;
 
 use Slic3r::Geometry qw(X Y Z);
 
+has 'input_file' => (is => 'rw');
 has 'model'     => (is => 'ro', weak_ref => 1, required => 1);
 has 'vertices'  => (is => 'ro', default => sub { [] });
 has 'volumes'   => (is => 'ro', default => sub { [] });
@@ -88,38 +86,11 @@ sub mesh {
     my $vertices = [];
     my $facets = [];
     
-    my @instances = $self->instances ? @{$self->instances} : (undef);
-    foreach my $instance (@instances) {
-        my @vertices = @{$self->vertices};
-        if ($instance) {
-            # save Z coordinates, as rotation and translation discard them
-            my @z = map $_->[Z], @vertices;
-            
-            if ($instance->rotation) {
-                # transform vertex coordinates
-                my $rad = Slic3r::Geometry::deg2rad($instance->rotation);
-                @vertices = Slic3r::Geometry::rotate_points($rad, undef, @vertices);
-            }
-            @vertices = Slic3r::Geometry::move_points($instance->offset, @vertices);
-            
-            # reapply Z coordinates
-            $vertices[$_][Z] = $z[$_] for 0 .. $#z;
-        }
-        
-        my $v_offset = @$vertices;
-        push @$vertices, @vertices;
-        foreach my $volume (@{$self->volumes}) {
-            push @$facets, map {
-                my $f = [@$_];
-                $f->[$_] += $v_offset for -3..-1;
-                $f;
-            } @{$volume->facets};
-        }
-    }
+    
     
     return Slic3r::TriangleMesh->new(
-        vertices => $vertices,
-        facets   => $facets,
+        vertices => $self->vertices,
+        facets   => [ map @{$_->facets}, @{$self->volumes} ],
     );
 }
 
diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm
index ba09dcc2d..ee6f193d9 100644
--- a/lib/Slic3r/Polygon.pm
+++ b/lib/Slic3r/Polygon.pm
@@ -6,7 +6,7 @@ use warnings;
 use parent 'Slic3r::Polyline';
 
 use Slic3r::Geometry qw(polygon_lines polygon_remove_parallel_continuous_edges
-    scale polygon_remove_acute_vertices polygon_segment_having_point point_in_polygon);
+    polygon_remove_acute_vertices polygon_segment_having_point point_in_polygon);
 use Slic3r::Geometry::Clipper qw(JT_MITER);
 
 sub lines {
@@ -111,7 +111,7 @@ sub is_printable {
     # detect them and we would be discarding them.
     my $p = $self->clone;
     $p->make_counter_clockwise;
-    return $p->offset(scale($flow_width || $Slic3r::flow->width) / 2) ? 1 : 0;
+    return $p->offset(Slic3r::Geometry::scale($flow_width || $Slic3r::flow->width) / 2) ? 1 : 0;
 }
 
 sub is_valid {
diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm
index ce13e7786..474b55d94 100644
--- a/lib/Slic3r/Polyline.pm
+++ b/lib/Slic3r/Polyline.pm
@@ -4,7 +4,7 @@ use warnings;
 
 use Math::Clipper qw();
 use Scalar::Util qw(reftype);
-use Slic3r::Geometry qw(A B X Y MIN MAX polyline_remove_parallel_continuous_edges polyline_remove_acute_vertices
+use Slic3r::Geometry qw(A B X Y X1 X2 Y1 Y2 polyline_remove_parallel_continuous_edges polyline_remove_acute_vertices
     polyline_lines move_points same_point);
 
 # the constructor accepts an array(ref) of points
@@ -144,7 +144,13 @@ sub size {
     my $self = shift;
     
     my @extents = $self->bounding_box;
-    return map $extents[$_][MAX] - $extents[$_][MIN], (X,Y);
+    return [$extents[X2] - $extents[X1], $extents[Y2] - $extents[Y1]];
+}
+
+sub align_to_origin {
+    my $self = shift;
+    my @bb = $self->bounding_box;
+    return $self->translate(-$bb[X1], -$bb[Y1]);
 }
 
 sub rotate {
@@ -163,4 +169,16 @@ sub translate {
     return $self;
 }
 
+sub scale {
+    my $self = shift;
+    my ($factor) = @_;
+    return if $factor == 1;
+    
+    # transform point coordinates
+    foreach my $point (@$self) {
+        $point->[$_] *= $factor for X,Y;
+    }
+    return $self;
+}
+
 1;
diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm
index 868b75846..8accf1dd9 100644
--- a/lib/Slic3r/Print.pm
+++ b/lib/Slic3r/Print.pm
@@ -108,11 +108,11 @@ sub add_model {
             $mesh->rotate($object->instances->[0]->rotation);
         }
         
-        push @print_objects, $self->add_object_from_mesh($mesh);
+        push @print_objects, $self->add_object_from_mesh($mesh, input_file => $object->input_file);
         
         if ($object->instances) {
             # replace the default [0,0] instance with the custom ones
-            @{$self->copies->[-1]} = map [ scale $_->offset->[X], scale $_->offset->[X] ], @{$object->instances};
+            @{$self->copies->[-1]} = map [ scale $_->offset->[X], scale $_->offset->[Y] ], @{$object->instances};
         }
     }
     
@@ -121,7 +121,7 @@ sub add_model {
 
 sub add_object_from_mesh {
     my $self = shift;
-    my ($mesh) = @_;
+    my ($mesh, %attributes) = @_;
     
     $mesh->rotate($Slic3r::Config->rotate);
     $mesh->scale($Slic3r::Config->scale / &Slic3r::SCALING_FACTOR);
@@ -131,6 +131,7 @@ sub add_object_from_mesh {
     my $object = Slic3r::Print::Object->new(
         mesh => $mesh,
         size => [ $mesh->size ],
+        %attributes,
     );
     
     push @{$self->objects}, $object;
@@ -236,7 +237,7 @@ sub arrange_objects {
     my $party = max(map $_->size->[Y], @{$self->objects});
     
     my @positions = Slic3r::Geometry::arrange
-        ($total_parts, $partx, $party, (map scale $_, @{$Slic3r::Config->bed_size}), scale $Slic3r::Config->min_object_distance);
+        ($total_parts, $partx, $party, (map scale $_, @{$Slic3r::Config->bed_size}), scale $Slic3r::Config->min_object_distance, $self->config);
     
     for my $obj_idx (0..$#{$self->objects}) {
         @{$self->copies->[$obj_idx]} = splice @positions, 0, scalar @{$self->copies->[$obj_idx]};
@@ -788,11 +789,14 @@ sub total_extrusion_volume {
     return $self->total_extrusion_length * ($Slic3r::extruders->[0]->filament_diameter**2) * PI/4 / 1000;
 }
 
-# this method will return the value of $self->output_file after expanding its
+# this method will return the supplied input file path after expanding its
 # format variables with their values
 sub expanded_output_filepath {
     my $self = shift;
-    my ($path) = @_;
+    my ($path, $input_file) = @_;
+    
+    # if no input file was supplied, take the first one from our objects
+    $input_file ||= $self->objects->[0]->input_file;
     
     # if output path is an existing directory, we take that and append
     # the specified filename format
@@ -800,7 +804,6 @@ sub expanded_output_filepath {
 
     # if no explicit output file was defined, we take the input
     # file directory and append the specified filename format
-    my $input_file = $self->objects->[0]->input_file;
     $path ||= (fileparse($input_file))[1] . $Slic3r::Config->output_filename_format;
     
     my $input_filename = my $input_filename_base = basename($input_file);
diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm
index 6f6589450..b17148fa8 100644
--- a/lib/Slic3r/TriangleMesh.pm
+++ b/lib/Slic3r/TriangleMesh.pm
@@ -70,6 +70,26 @@ sub BUILD {
     }
 }
 
+sub merge {
+    my $class = shift;
+    my @meshes = @_;
+    
+    my $vertices = [];
+    my $facets = [];
+    
+    foreach my $mesh (@meshes) {
+        my $v_offset = @$vertices;
+        push @$vertices, @{$mesh->vertices};
+        push @$facets, map {
+            my $f = [@$_];
+            $f->[$_] += $v_offset for -3..-1;
+            $f;
+        } @{$mesh->facets};
+    }
+    
+    return $class->new(vertices => $vertices, facets => $facets);
+}
+
 sub clone {
     my $self = shift;
     return (ref $self)->new(
diff --git a/t/fill.t b/t/fill.t
index 2a7ef1f93..dd8cdf938 100644
--- a/t/fill.t
+++ b/t/fill.t
@@ -36,7 +36,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
         expolygon       => Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,50], [0,50] ]),
     );
     foreach my $angle (0, 45) {
-        $surface->expolygon->rotate($angle, [0,0]);
+        $surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]);
         my ($params, @paths) = $filler->fill_surface($surface, flow_spacing => 0.69, density => 0.4);
         is scalar @paths, 1, 'one continuous path';
     }

From f638558167c3a8cf0b7fef62f560c3e2b1bdedef Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Fri, 21 Sep 2012 15:35:32 +0200
Subject: [PATCH 22/22] Preserve position when splitting objects in plater

---
 lib/Slic3r/GUI/Plater.pm   | 9 ++++++---
 lib/Slic3r/TriangleMesh.pm | 6 +++---
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index 89db0241f..5f83117c4 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -6,7 +6,7 @@ use utf8;
 use File::Basename qw(basename dirname);
 use List::Util qw(max sum);
 use Math::ConvexHull qw(convex_hull);
-use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2);
+use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX);
 use Slic3r::Geometry::Clipper qw(JT_ROUND);
 use threads::shared qw(shared_clone);
 use Wx qw(:bitmap :brush :button :cursor :dialog :filedialog :font :keycode :icon :id :listctrl :misc :panel :pen :sizer :toolbar :window);
@@ -456,6 +456,7 @@ sub split_object {
     my ($obj_idx, $current_object) = $self->selected_object;
     my $current_copies_num = $current_object->instances_count;
     my $mesh = $current_object->get_mesh;
+    $mesh->align_to_origin;
     
     my @new_meshes = $mesh->split_mesh;
     if (@new_meshes == 1) {
@@ -469,18 +470,20 @@ sub split_object {
     $self->remove($obj_idx);
     
     foreach my $mesh (@new_meshes) {
+        my @extents = $mesh->extents;
         my $object = Slic3r::GUI::Plater::Object->new(
             name                    => basename($current_object->input_file),
             input_file              => $current_object->input_file,
             input_file_object_id    => undef,
             mesh                    => $mesh,
-            instances               => [ map [0,0], 1..$current_copies_num ],
+            instances               => [ map [$extents[X][MIN], $extents[Y][MIN]], 1..$current_copies_num ],
         );
         push @{ $self->{objects} }, $object;
         $self->object_loaded($#{ $self->{objects} }, no_arrange => 1);
     }
     
-    $self->arrange;
+    $self->recenter;
+    $self->{canvas}->Refresh;
 }
 
 sub export_gcode {
diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm
index b17148fa8..7f9d929b1 100644
--- a/lib/Slic3r/TriangleMesh.pm
+++ b/lib/Slic3r/TriangleMesh.pm
@@ -355,7 +355,7 @@ sub align_to_origin {
     
     # calculate the displacements needed to 
     # have lowest value for each axis at coordinate 0
-    my @extents = $self->bounding_box;
+    my @extents = $self->extents;
     $self->move(map -$extents[$_][MIN], X,Y,Z);
 }
 
@@ -379,7 +379,7 @@ sub duplicate {
     $self->BUILD;
 }
 
-sub bounding_box {
+sub extents {
     my $self = shift;
     my @extents = (map [undef, undef], X,Y,Z);
     foreach my $vertex (@{$self->vertices}) {
@@ -394,7 +394,7 @@ sub bounding_box {
 sub size {
     my $self = shift;
     
-    my @extents = $self->bounding_box;
+    my @extents = $self->extents;
     return map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z);
 }