diff --git a/MANIFEST b/MANIFEST
index c4c53288c..2cbf9fc2c 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -34,12 +34,14 @@ lib/Slic3r/GUI/Plater.pm
 lib/Slic3r/GUI/SkeinPanel.pm
 lib/Slic3r/GUI/Tab.pm
 lib/Slic3r/Layer.pm
+lib/Slic3r/Layer/Region.pm
 lib/Slic3r/Line.pm
 lib/Slic3r/Model.pm
 lib/Slic3r/Point.pm
 lib/Slic3r/Polygon.pm
 lib/Slic3r/Polyline.pm
 lib/Slic3r/Print.pm
+lib/Slic3r/Print/Region.pm
 lib/Slic3r/Print/Object.pm
 lib/Slic3r/Surface.pm
 lib/Slic3r/SVG.pm
diff --git a/README.markdown b/README.markdown
index 1dfe6552b..5ca1ac240 100644
--- a/README.markdown
+++ b/README.markdown
@@ -153,7 +153,9 @@ The author of the Silk icon set is Mark James.
         --first-layer-height Layer height for first layer (mm or %, default: 100%)
         --infill-every-layers
                             Infill every N layers (default: 1)
-      
+        --solid-infill-every-layers
+                            Force a solid layer every N layers (default: 0)
+    
       Print options:
         --perimeters        Number of perimeters/horizontal skins (range: 0+, default: 3)
         --solid-layers      Number of solid layers to do for top/bottom surfaces
diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm
index f6bd643c4..e80357767 100644
--- a/lib/Slic3r.pm
+++ b/lib/Slic3r.pm
@@ -7,7 +7,7 @@ use strict;
 use warnings;
 require v5.10;
 
-our $VERSION = "0.9.3-dev";
+our $VERSION = "0.9.4-dev";
 
 our $debug = 0;
 sub debugf {
@@ -43,6 +43,7 @@ use Slic3r::Format::STL;
 use Slic3r::GCode;
 use Slic3r::Geometry qw(PI);
 use Slic3r::Layer;
+use Slic3r::Layer::Region;
 use Slic3r::Line;
 use Slic3r::Model;
 use Slic3r::Point;
@@ -50,6 +51,7 @@ use Slic3r::Polygon;
 use Slic3r::Polyline;
 use Slic3r::Print;
 use Slic3r::Print::Object;
+use Slic3r::Print::Region;
 use Slic3r::Surface;
 use Slic3r::TriangleMesh;
 eval "use Slic3r::Build";
@@ -63,15 +65,15 @@ use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI;
 # process.  They should belong to the Print object, but we are keeping 
 # them here because it makes accessing them slightly faster.
 our $Config;
-our $extruders;
-our ($flow, $first_layer_flow, $perimeter_flow, $infill_flow, $support_material_flow);
+our $flow;
 
 sub parallelize {
     my %params = @_;
     
     if (!$params{disable} && $Slic3r::have_threads && $Config->threads > 1) {
+        my @items = (ref $params{items} eq 'CODE') ? $params{items}->() : @{$params{items}};
         my $q = Thread::Queue->new;
-        $q->enqueue(@{ $params{items} }, (map undef, 1..$Config->threads));
+        $q->enqueue(@items, (map undef, 1..$Config->threads));
         
         my $thread_cb = sub { $params{thread_cb}->($q) };
         foreach my $th (map threads->create($thread_cb), 1..$Config->threads) {
diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm
index 312352312..1c78843fd 100644
--- a/lib/Slic3r/Config.pm
+++ b/lib/Slic3r/Config.pm
@@ -3,6 +3,8 @@ use strict;
 use warnings;
 use utf8;
 
+use List::Util qw(first);
+
 use constant PI => 4 * atan2(1, 1);
 
 # cemetery of old config settings
@@ -351,6 +353,15 @@ our $Options = {
         min     => 1,
         default => 1,
     },
+    'solid_infill_every_layers' => {
+        label   => 'Solid infill every',
+        tooltip => 'This feature allows to force a solid layer every given number of layers. Zero to disable.',
+        sidetext => 'layers',
+        cli     => 'solid-infill-every-layers=i',
+        type    => 'i',
+        min     => 0,
+        default => 0,
+    },
     
     # flow options
     'extrusion_width' => {
@@ -944,6 +955,9 @@ sub set {
         $opt_key =~ s/^bottom_layer_speed$/first_layer_speed/;
         $value = $value =~ /^\d+(?:\.\d+)?$/ && $value != 0 ? ($value*100) . "%" : 0;
     }
+    if ($opt_key eq 'threads' && !$Slic3r::have_threads) {
+        $value = 1;
+    }
     
     if (!exists $Options->{$opt_key}) {
         $opt_key = +(grep { $Options->{$_}{aliases} && grep $_ eq $opt_key, @{$Options->{$_}{aliases}} } keys %$Options)[0]
@@ -1051,15 +1065,18 @@ sub validate {
     
     # --fill-pattern
     die "Invalid value for --fill-pattern\n"
-        if !exists $Slic3r::Fill::FillTypes{$self->fill_pattern};
+        if !first { $_ eq $self->fill_pattern } @{$Options->{fill_pattern}{values}};
     
     # --solid-fill-pattern
     die "Invalid value for --solid-fill-pattern\n"
-        if !exists $Slic3r::Fill::FillTypes{$self->solid_fill_pattern};
+        if !first { $_ eq $self->solid_fill_pattern } @{$Options->{solid_fill_pattern}{values}};
     
     # --fill-density
     die "Invalid value for --fill-density\n"
         if $self->fill_density < 0 || $self->fill_density > 1;
+    die "The selected fill pattern is not supposed to work at 100% density\n"
+        if $self->fill_density == 1
+            && !first { $_ eq $self->fill_pattern } @{$Options->{solid_fill_pattern}{values}};
     
     # --infill-every-layers
     die "Invalid value for --infill-every-layers\n"
diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm
index 26f0d471b..6a3e946c1 100644
--- a/lib/Slic3r/ExPolygon.pm
+++ b/lib/Slic3r/ExPolygon.pm
@@ -82,14 +82,6 @@ sub safety_offset {
     );
 }
 
-sub offset_ex {
-    my $self = shift;
-    my @offsets = $self->offset(@_);
-    
-    # apply holes to the right contours
-    return @{ union_ex(\@offsets) };
-}
-
 sub noncollapsing_offset_ex {
     my $self = shift;
     my ($distance, @params) = @_;
diff --git a/lib/Slic3r/Extruder.pm b/lib/Slic3r/Extruder.pm
index 4b7b6b19a..3b77a3497 100644
--- a/lib/Slic3r/Extruder.pm
+++ b/lib/Slic3r/Extruder.pm
@@ -9,7 +9,9 @@ use constant OPTIONS => [qw(
     retract_length retract_lift retract_speed retract_restart_extra retract_before_travel
     retract_length_toolchange retract_restart_extra_toolchange
 )];
-has $_ => (is => 'ro', required => 1) for @{&OPTIONS};
+
+has 'id'    => (is => 'rw', required => 1);
+has $_      => (is => 'ro', required => 1) for @{&OPTIONS};
 
 has 'retracted'                 => (is => 'rw', default => sub {0} );
 has 'e_per_mm3'                 => (is => 'lazy');
diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm
index 5c694d95e..4ae1f5415 100644
--- a/lib/Slic3r/Fill.pm
+++ b/lib/Slic3r/Fill.pm
@@ -99,7 +99,7 @@ sub make_fill {
     
     # add spacing between adjacent surfaces
     {
-        my $distance = scale $layer->infill_flow->spacing / 2;
+        my $distance = $layer->infill_flow->scaled_spacing / 2;
         my @offsets = ();
         foreach my $surface (@surfaces) {
             my $expolygon = $surface->expolygon;
diff --git a/lib/Slic3r/Fill/Concentric.pm b/lib/Slic3r/Fill/Concentric.pm
index c0d172cfe..699b526d9 100644
--- a/lib/Slic3r/Fill/Concentric.pm
+++ b/lib/Slic3r/Fill/Concentric.pm
@@ -63,7 +63,7 @@ sub fill_surface {
         my $path = $loop->split_at_index($index);
         
         # clip the path to avoid the extruder to get exactly on the first point of the loop
-        $path->clip_end(scale($self->layer ? $self->layer->flow->width : $Slic3r::flow->width) * 0.15);
+        $path->clip_end($self->layer ? $self->layer->flow->scaled_width : $Slic3r::flow->scaled_width * 0.15);
         
         push @paths, $path->points if @{$path->points};
     }
diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm
index 1e407e2e6..70dd1e2ad 100644
--- a/lib/Slic3r/Fill/Honeycomb.pm
+++ b/lib/Slic3r/Fill/Honeycomb.pm
@@ -21,7 +21,7 @@ sub fill_surface {
     # infill math
     my $min_spacing = scale $params{flow_spacing};
     my $distance = $min_spacing / $params{density};
-    my $overlap_distance = scale($self->layer ? $self->layer->flow->width : $Slic3r::flow->width) * 0.4;
+    my $overlap_distance = $self->layer ? $self->layer->flow->scaled_width : $Slic3r::flow->scaled_width * 0.4;
     
     my $cache_id = sprintf "d%s_s%s_a%s",
         $params{density}, $params{flow_spacing}, $rotate_vector->[0][0];
diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm
index 121249eaa..b4569dbc8 100644
--- a/lib/Slic3r/Fill/Rectilinear.pm
+++ b/lib/Slic3r/Fill/Rectilinear.pm
@@ -31,7 +31,7 @@ sub fill_surface {
         $flow_spacing = unscale $distance_between_lines;
     }
     
-    my $overlap_distance = scale($self->layer ? $self->layer->flow->width : $Slic3r::flow->width) * 0.4;
+    my $overlap_distance = $self->layer ? $self->layer->flow->scaled_width : $Slic3r::flow->scaled_width * 0.4;
     
     my $x = $bounding_box->[X1];
     my $is_line_pattern = $self->isa('Slic3r::Fill::Line');
diff --git a/lib/Slic3r/Flow.pm b/lib/Slic3r/Flow.pm
index 5e8f23a2b..a035b9d37 100644
--- a/lib/Slic3r/Flow.pm
+++ b/lib/Slic3r/Flow.pm
@@ -1,13 +1,15 @@
 package Slic3r::Flow;
 use Moo;
 
-use Slic3r::Geometry qw(PI);
+use Slic3r::Geometry qw(PI scale);
 
 has 'nozzle_diameter'   => (is => 'ro', required => 1);
 has 'layer_height'      => (is => 'ro', default => sub { $Slic3r::Config->layer_height });
 
 has 'width'             => (is => 'rwp', builder => 1);
 has 'spacing'           => (is => 'lazy');
+has 'scaled_width'      => (is => 'lazy');
+has 'scaled_spacing'    => (is => 'lazy');
 
 sub BUILD {
     my $self = shift;
@@ -65,4 +67,14 @@ sub clone {
     );
 }
 
+sub _build_scaled_width {
+    my $self = shift;
+    return scale $self->width;
+}
+
+sub _build_scaled_spacing {
+    my $self = shift;
+    return scale $self->spacing;
+}
+
 1;
diff --git a/lib/Slic3r/Format/AMF.pm b/lib/Slic3r/Format/AMF.pm
index 78d2656c5..d654ca1a5 100644
--- a/lib/Slic3r/Format/AMF.pm
+++ b/lib/Slic3r/Format/AMF.pm
@@ -35,8 +35,8 @@ sub write_file {
     for my $material_id (sort keys %{ $model->materials }) {
         my $material = $model->materials->{$material_id};
         printf $fh qq{  <material id="%d">\n}, $material_id;
-        for (keys %$material) {
-             printf $fh qq{    <metadata type=\"%s\">%s</metadata>\n}, $_, $material->{$_};
+        for (keys %{$material->attributes}) {
+             printf $fh qq{    <metadata type=\"%s\">%s</metadata>\n}, $_, $material->attributes->{$_};
         }
         printf $fh qq{  </material>\n};
     }
diff --git a/lib/Slic3r/Format/AMF/Parser.pm b/lib/Slic3r/Format/AMF/Parser.pm
index 5e0b28178..173dfc091 100644
--- a/lib/Slic3r/Format/AMF/Parser.pm
+++ b/lib/Slic3r/Format/AMF/Parser.pm
@@ -37,10 +37,10 @@ sub start_element {
         $self->{_vertex_idx} = $1-1;
     } elsif ($data->{LocalName} eq 'material') {
         my $material_id = $self->_get_attribute($data, 'id') || '_';
-        $self->{_material} = $self->{_model}->materials->{ $material_id } = {};
+        $self->{_material} = $self->{_model}->set_material($material_id);
     } elsif ($data->{LocalName} eq 'metadata' && $self->{_tree}[-1] eq 'material') {
         $self->{_material_metadata_type} = $self->_get_attribute($data, 'type');
-        $self->{_material}{ $self->{_material_metadata_type} } = "";
+        $self->{_material}->attributes->{ $self->{_material_metadata_type} } = "";
     } elsif ($data->{LocalName} eq 'constellation') {
         $self->{_constellation} = 1; # we merge all constellations as we don't support more than one
     } elsif ($data->{LocalName} eq 'instance' && $self->{_constellation}) {
@@ -63,7 +63,7 @@ sub characters {
     } elsif ($self->{_triangle} && defined $self->{_vertex_idx}) {
         $self->{_triangle}[ $self->{_vertex_idx} ] .= $data->{Data};
     } elsif ($self->{_material_metadata_type}) {
-        $self->{_material}{ $self->{_material_metadata_type} } .= $data->{Data};
+        $self->{_material}->attributes->{ $self->{_material_metadata_type} } .= $data->{Data};
     } elsif ($self->{_instance_property}) {
         $self->{_instance}{ $self->{_instance_property} } .= $data->{Data};
     }
diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm
index 4f29fa0b9..a135be271 100644
--- a/lib/Slic3r/GCode.pm
+++ b/lib/Slic3r/GCode.pm
@@ -5,13 +5,14 @@ use List::Util qw(first);
 use Slic3r::ExtrusionPath ':roles';
 use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y);
 
+has 'multiple_extruders' => (is => 'ro', default => sub {0} );
 has 'layer'              => (is => 'rw');
 has 'shift_x'            => (is => 'rw', default => sub {0} );
 has 'shift_y'            => (is => 'rw', default => sub {0} );
 has 'z'                  => (is => 'rw', default => sub {0} );
 has 'speed'              => (is => 'rw');
 
-has 'extruder_idx'       => (is => 'rw');
+has 'extruder'           => (is => 'rw');
 has 'extrusion_distance' => (is => 'rw', default => sub {0} );
 has 'elapsed_time'       => (is => 'rw', default => sub {0} );  # seconds
 has 'total_extrusion_length' => (is => 'rw', default => sub {0} );
@@ -49,11 +50,6 @@ my %role_speeds = (
     &EXTR_ROLE_SUPPORTMATERIAL              => 'perimeter',
 );
 
-sub extruder {
-    my $self = shift;
-    return $Slic3r::extruders->[$self->extruder_idx];
-}
-
 sub change_layer {
     my $self = shift;
     my ($layer) = @_;
@@ -93,7 +89,6 @@ sub extrude_loop {
     # or randomize if requested
     my $last_pos = $self->last_pos;
     if ($Slic3r::Config->randomize_start && $loop->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER) {
-        srand $self->layer->id * 10;
         $last_pos = Slic3r::Point->new(scale $Slic3r::Config->print_center->[X], scale $Slic3r::Config->bed_size->[Y]);
         $last_pos->rotate(rand(2*PI), $Slic3r::Config->print_center);
     }
@@ -105,7 +100,7 @@ sub extrude_loop {
     # clip the path to avoid the extruder to get exactly on the first point of the loop;
     # if polyline was shorter than the clipping distance we'd get a null polyline, so
     # we discard it in that case
-    $extrusion_path->clip_end(scale($self->layer ? $self->layer->flow->width : $Slic3r::flow->width) * 0.15);
+    $extrusion_path->clip_end($self->layer ? $self->layer->flow->scaled_width : $Slic3r::flow->scaled_width * 0.15);
     return '' if !@{$extrusion_path->polyline};
     
     # extrude along the path
@@ -135,7 +130,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, scaled_epsilon) } @{$self->layer->slices}) {
+            if (!$Slic3r::Config->only_retract_when_crossing_perimeters || $path->role != EXTR_ROLE_FILL || !first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices}) {
                 $gcode .= $self->retract(travel_to => $path->points->[0]);
             }
         }
@@ -374,26 +369,26 @@ sub _Gx {
     return "$gcode\n";
 }
 
-sub set_tool {
+sub set_extruder {
     my $self = shift;
-    my ($tool) = @_;
+    my ($extruder) = @_;
     
-    # return nothing if this tool was already selected
-    return "" if (defined $self->extruder_idx) && ($self->extruder_idx == $tool);
+    # return nothing if this extruder was already selected
+    return "" if (defined $self->extruder) && ($self->extruder->id == $extruder);
     
     # if we are running a single-extruder setup, just set the extruder and return nothing
-    if (@{$Slic3r::extruders} == 1) {
-        $self->extruder_idx($tool);
+    if (!$self->multiple_extruders) {
+        $self->extruder($extruder);
         return "";
     }
     
-    # trigger retraction on the current tool (if any) 
+    # trigger retraction on the current extruder (if any) 
     my $gcode = "";
-    $gcode .= $self->retract(toolchange => 1) if defined $self->extruder_idx;
+    $gcode .= $self->retract(toolchange => 1) if defined $self->extruder;
     
-    # set the new tool
-    $self->extruder_idx($tool);
-    $gcode .= sprintf "T%d%s\n", $tool, ($Slic3r::Config->gcode_comments ? ' ; change tool' : '');
+    # set the new extruder
+    $self->extruder($extruder);
+    $gcode .= sprintf "T%d%s\n", $extruder->id, ($Slic3r::Config->gcode_comments ? ' ; change extruder' : '');
     $gcode .= $self->reset_e;
     
     return $gcode;
@@ -426,7 +421,7 @@ sub set_temperature {
         : ('M104', 'set temperature');
     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 " : "";
+        (defined $tool && $tool != $self->extruder->id) ? "T$tool " : "";
     
     $gcode .= "M116 ; wait for temperature to be reached\n"
         if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait;
diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm
index 887d35320..8bfd4d1fc 100644
--- a/lib/Slic3r/GUI.pm
+++ b/lib/Slic3r/GUI.pm
@@ -21,6 +21,7 @@ use constant MI_QUICK_SLICE   => &Wx::NewId;
 use constant MI_REPEAT_QUICK  => &Wx::NewId;
 use constant MI_QUICK_SAVE_AS => &Wx::NewId;
 use constant MI_SLICE_SVG     => &Wx::NewId;
+use constant MI_COMBINE_STLS  => &Wx::NewId;
 
 use constant MI_PLATER_EXPORT_GCODE => &Wx::NewId;
 use constant MI_PLATER_EXPORT_STL   => &Wx::NewId;
@@ -33,6 +34,7 @@ use constant MI_TAB_PRINTER   => &Wx::NewId;
 
 use constant MI_CONF_WIZARD   => &Wx::NewId;
 use constant MI_WEBSITE       => &Wx::NewId;
+use constant MI_DOCUMENTATION => &Wx::NewId;
 
 our $datadir;
 our $Settings;
@@ -90,6 +92,8 @@ sub OnInit {
         $fileMenu->AppendSeparator();
         $fileMenu->Append(MI_SLICE_SVG, "Slice to SV&G…\tCtrl+G", 'Slice file to SVG');
         $fileMenu->AppendSeparator();
+        $fileMenu->Append(MI_COMBINE_STLS, "Combine multi-material STL files…", 'Combine multiple STL files into a single multi-material AMF file');
+        $fileMenu->AppendSeparator();
         $fileMenu->Append(wxID_EXIT, "&Quit", 'Quit Slic3r');
         EVT_MENU($frame, MI_LOAD_CONF, sub { $self->{skeinpanel}->load_config_file });
         EVT_MENU($frame, MI_EXPORT_CONF, sub { $self->{skeinpanel}->export_config });
@@ -99,6 +103,7 @@ sub OnInit {
         EVT_MENU($frame, MI_QUICK_SAVE_AS, sub { $self->{skeinpanel}->do_slice(save_as => 1);
                                                  $repeat->Enable(defined $Slic3r::GUI::SkeinPanel::last_input_file) });
         EVT_MENU($frame, MI_SLICE_SVG, sub { $self->{skeinpanel}->do_slice(save_as => 1, export_svg => 1) });
+        EVT_MENU($frame, MI_COMBINE_STLS, sub { $self->{skeinpanel}->combine_stls });
         EVT_MENU($frame, wxID_EXIT, sub {$_[0]->Close(0)});
     }
     
@@ -130,10 +135,14 @@ sub OnInit {
     my $helpMenu = Wx::Menu->new;
     {
         $helpMenu->Append(MI_CONF_WIZARD, "&Configuration $Slic3r::GUI::ConfigWizard::wizard…", "Run Configuration $Slic3r::GUI::ConfigWizard::wizard");
+        $helpMenu->AppendSeparator();
         $helpMenu->Append(MI_WEBSITE, "Slic3r &Website", 'Open the Slic3r website in your browser');
+        $helpMenu->Append(MI_DOCUMENTATION, "&Documentation", 'Open the Slic3r documentation in your browser');
+        $helpMenu->AppendSeparator();
         $helpMenu->Append(wxID_ABOUT, "&About Slic3r", 'Show about dialog');
         EVT_MENU($frame, MI_CONF_WIZARD, sub { $self->{skeinpanel}->config_wizard });
         EVT_MENU($frame, MI_WEBSITE, sub { Wx::LaunchDefaultBrowser('http://slic3r.org/') });
+        EVT_MENU($frame, MI_DOCUMENTATION, sub { Wx::LaunchDefaultBrowser('https://github.com/alexrj/Slic3r/wiki/Documentation') });
         EVT_MENU($frame, wxID_ABOUT, \&about);
     }
     
diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index 5f83117c4..37dd165fa 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -158,9 +158,8 @@ sub new {
     EVT_COMMAND($self, -1, $THUMBNAIL_DONE_EVENT, sub {
         my ($self, $event) = @_;
         my ($obj_idx, $thumbnail) = @{$event->GetData};
-        $self->{objects}[$obj_idx]->thumbnail($thumbnail);
-        $self->mesh(undef);
-        $self->on_thumbnail_made;
+        $self->{objects}[$obj_idx]->thumbnail($thumbnail->clone);
+        $self->on_thumbnail_made($obj_idx);
     });
     
     EVT_COMMAND($self, -1, $PROGRESS_BAR_EVENT, sub {
@@ -275,7 +274,7 @@ sub load {
     my $self = shift;
     
     my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || '';
-    my $dialog = Wx::FileDialog->new($self, 'Choose one or more files (STL/OBJ/AMF):', $dir, "", $Slic3r::GUI::SkeinPanel::model_wildcard, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
+    my $dialog = Wx::FileDialog->new($self, 'Choose one or more files (STL/OBJ/AMF):', $dir, "", &Slic3r::GUI::SkeinPanel::MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
     if ($dialog->ShowModal != wxID_OK) {
         $dialog->Destroy;
         return;
@@ -433,7 +432,7 @@ sub arrange {
     my $total_parts = sum(map $_->instances_count, @{$self->{objects}}) or return;
     my @size = ();
     for my $a (X,Y) {
-        $size[$a] = $self->to_units(max(map $_->thumbnail->size->[$a], @{$self->{objects}}));
+        $size[$a] = max(map $_->rotated_size->[$a], @{$self->{objects}});
     }
     
     eval {
@@ -502,7 +501,7 @@ sub export_gcode {
     {
         $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);
+            basename($self->{output_file}), &Slic3r::GUI::SkeinPanel::FILE_WILDCARDS->{gcode}, wxFD_SAVE);
         if ($dlg->ShowModal != wxID_OK) {
             $dlg->Destroy;
             return;
@@ -529,7 +528,7 @@ sub export_gcode {
             );
         });
         $self->statusbar->SetCancelCallback(sub {
-            $self->{export_thread}->kill('KILL');
+            $self->{export_thread}->kill('KILL')->join;
             $self->{export_thread} = undef;
             $self->statusbar->StopBusy;
             $self->statusbar->SetStatusText("Export cancelled");
@@ -556,7 +555,7 @@ sub _init_print {
     return Slic3r::Print->new(
         config => $self->skeinpanel->config,
         extra_variables => {
-            map { $_ => $self->skeinpanel->{options_tabs}{$_}->current_preset->{name} } qw(print filament printer),
+            map { +"${_}_preset" => $self->skeinpanel->{options_tabs}{$_}->current_preset->{name} } qw(print filament printer),
         },
     );
 }
@@ -601,7 +600,6 @@ sub export_gcode2 {
         }
         $message .= ".";
         $params{on_completed}->($message);
-        $print->cleanup;
     };
     $params{catch_error}->();
 }
@@ -655,7 +653,7 @@ sub _get_export_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);
+            basename($output_file), &Slic3r::GUI::SkeinPanel::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
         if ($dlg->ShowModal != wxID_OK) {
             $dlg->Destroy;
             return undef;
@@ -701,7 +699,7 @@ sub make_thumbnail {
             Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $THUMBNAIL_DONE_EVENT, shared_clone([ $obj_idx, $thumbnail ])));
             threads->exit;
         } else {
-            $self->on_thumbnail_made;
+            $self->on_thumbnail_made($obj_idx);
         }
     };
     
@@ -710,6 +708,9 @@ sub make_thumbnail {
 
 sub on_thumbnail_made {
     my $self = shift;
+    my ($obj_idx) = @_;
+    
+    $self->{objects}[$obj_idx]->free_mesh;
     $self->recenter;
     $self->{canvas}->Refresh;
 }
@@ -1017,6 +1018,14 @@ sub _trigger_mesh {
     $self->size([$self->mesh->size]) if $self->mesh;
 }
 
+sub free_mesh {
+    my $self = shift;
+    
+    # only delete mesh from memory if we can retrieve it from the original file
+    return unless $self->input_file && $self->input_file_object_id;
+    $self->mesh(undef);
+}
+
 sub get_mesh {
     my $self = shift;
     
diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm
index 6ef5a64de..8cdc8d7b2 100644
--- a/lib/Slic3r/GUI/SkeinPanel.pm
+++ b/lib/Slic3r/GUI/SkeinPanel.pm
@@ -13,6 +13,16 @@ our $last_input_file;
 our $last_output_file;
 our $last_config;
 
+use constant FILE_WILDCARDS => {
+    stl     => 'STL files (*.stl)|*.stl;*.STL',
+    obj     => 'OBJ files (*.obj)|*.obj;*.OBJ',
+    amf     => 'AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML',
+    ini     => 'INI files *.ini|*.ini;*.INI',
+    gcode   => 'G-code files *.gcode|*.gcode;*.GCODE|G-code files *.g|*.g;*.G',
+    svg     => 'SVG files *.svg|*.svg;*.SVG',
+};
+use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(stl obj amf)};
+
 sub new {
     my $class = shift;
     my ($parent) = @_;
@@ -41,11 +51,6 @@ sub new {
     return $self;
 }
 
-our $model_wildcard = "STL files (*.stl)|*.stl;*.STL|OBJ files (*.obj)|*.obj;*.OBJ|AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML";
-our $ini_wildcard = "INI files *.ini|*.ini;*.INI";
-our $gcode_wildcard = "G-code files *.gcode|*.gcode;*.GCODE|G-code files *.g|*.g;*.G";
-our $svg_wildcard = "SVG files *.svg|*.svg;*.SVG";
-
 sub do_slice {
     my $self = shift;
     my %params = @_;
@@ -70,7 +75,7 @@ sub do_slice {
 
         my $input_file;
         if (!$params{reslice}) {
-            my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF):', $dir, "", $model_wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+            my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF):', $dir, "", MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
             if ($dialog->ShowModal != wxID_OK) {
                 $dialog->Destroy;
                 return;
@@ -96,7 +101,7 @@ sub do_slice {
         Slic3r::GUI->save_settings;
         
         my $print = Slic3r::Print->new(config => $config);
-        $print->add_objects_from_file($input_file);
+        $print->add_model(Slic3r::Model->read_from_file($input_file));
         $print->validate;
 
         # select output file
@@ -107,7 +112,7 @@ sub do_slice {
             $output_file = $print->expanded_output_filepath($output_file);
             $output_file =~ s/\.gcode$/.svg/i if $params{export_svg};
             my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', dirname($output_file),
-                basename($output_file), $params{export_svg} ? $svg_wildcard : $gcode_wildcard, wxFD_SAVE);
+                basename($output_file), $params{export_svg} ? FILE_WILDCARDS->{svg} : FILE_WILDCARDS->{gcode}, wxFD_SAVE);
             if ($dlg->ShowModal != wxID_OK) {
                 $dlg->Destroy;
                 return;
@@ -172,7 +177,7 @@ sub export_config {
     my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || '';
     my $filename = $last_config ? basename($last_config) : "config.ini";
     my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $dir, $filename, 
-        $ini_wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
+        FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
     if ($dlg->ShowModal == wxID_OK) {
         my $file = $dlg->GetPath;
         $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file);
@@ -191,7 +196,7 @@ sub load_config_file {
         return unless $self->check_unsaved_changes;
         my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || '';
         my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini", 
-                $ini_wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+                FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
         return unless $dlg->ShowModal == wxID_OK;
         ($file) = $dlg->GetPaths;
         $dlg->Destroy;
@@ -221,6 +226,58 @@ sub config_wizard {
     }
 }
 
+sub combine_stls {
+    my $self = shift;
+    
+    # get input files
+    my @input_files = ();
+    my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || '';
+    {
+        my $dlg_message = 'Choose one or more files to combine (STL/OBJ)';
+        while (1) {
+            my $dialog = Wx::FileDialog->new($self, "$dlg_message:", $dir, "", MODEL_WILDCARD, 
+                wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
+            if ($dialog->ShowModal != wxID_OK) {
+                $dialog->Destroy;
+                last;
+            }
+            push @input_files, $dialog->GetPaths;
+            $dialog->Destroy;
+            $dlg_message .= " or hit Cancel if you have finished";
+            $dir = dirname($input_files[0]);
+        }
+        return if !@input_files;
+    }
+    
+    # get output file
+    my $output_file = $input_files[0];
+    {
+        $output_file =~ s/\.(?:stl|obj)$/.amf.xml/i;
+        my $dlg = Wx::FileDialog->new($self, 'Save multi-material AMF file as:', dirname($output_file),
+            basename($output_file), FILE_WILDCARDS->{amf}, wxFD_SAVE);
+        if ($dlg->ShowModal != wxID_OK) {
+            $dlg->Destroy;
+            return;
+        }
+        $output_file = $dlg->GetPath;
+    }
+    
+    my @models = map Slic3r::Model->read_from_file($_), @input_files;
+    my $new_model = Slic3r::Model->new;
+    my $new_object = $new_model->add_object;
+    for my $m (0 .. $#models) {
+        my $model = $models[$m];
+        $new_model->set_material($m, { Name => basename($input_files[$m]) });
+        $new_object->add_volume(
+            material_id => $m,
+            facets      => $model->objects->[0]->volumes->[0]->facets,
+            vertices    => $model->objects->[0]->vertices,
+        );
+    }
+    
+    Slic3r::Format::AMF->write_file($output_file, $new_model);
+}
+
 =head2 config
 
 This method collects all config values from the tabs and merges them into a single config object.
diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm
index 16107b548..69eab8820 100644
--- a/lib/Slic3r/GUI/Tab.pm
+++ b/lib/Slic3r/GUI/Tab.pm
@@ -401,7 +401,7 @@ sub build {
         },
         {
             title => 'Advanced',
-            options => [qw(infill_every_layers fill_angle solid_infill_below_area only_retract_when_crossing_perimeters)],
+            options => [qw(infill_every_layers solid_infill_every_layers fill_angle solid_infill_below_area only_retract_when_crossing_perimeters)],
         },
     ]);
     
diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm
index cd1e59b32..b5bec3a8d 100644
--- a/lib/Slic3r/Geometry.pm
+++ b/lib/Slic3r/Geometry.pm
@@ -20,7 +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
+    scaled_epsilon bounding_box_3D size_3D
 );
 
 
@@ -242,6 +242,7 @@ sub polygon_lines {
 sub nearest_point {
     my ($point, $points) = @_;
     my $index = nearest_point_index(@_);
+    return undef if !defined $index;
     return $points->[$index];
 }
 
@@ -706,6 +707,27 @@ sub bounding_box_intersect {
     return 1;
 }
 
+# 3D
+sub bounding_box_3D {
+    my ($points) = @_;
+    
+    my @extents = (map [undef, undef], X,Y,Z);
+    foreach my $point (@$points) {
+        for (X,Y,Z) {
+            $extents[$_][MIN] = $point->[$_] if !defined $extents[$_][MIN] || $point->[$_] < $extents[$_][MIN];
+            $extents[$_][MAX] = $point->[$_] if !defined $extents[$_][MAX] || $point->[$_] > $extents[$_][MAX];
+        }
+    }
+    return @extents;
+}
+
+sub size_3D {
+    my ($points) = @_;
+    
+    my @extents = bounding_box_3D($points);
+    return map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z);
+}
+
 sub angle3points {
     my ($p1, $p2, $p3) = @_;
     # p1 is the center
@@ -897,9 +919,9 @@ sub arrange {
     my $skirt_margin;		
     if ($Config->skirts > 0) {
         my $flow = Slic3r::Flow->new(
-            layer_height    => $Config->first_layer_height,
+            layer_height    => $Config->get_value('first_layer_height'),
             nozzle_diameter => $Config->nozzle_diameter->[0],  # TODO: actually look for the extruder used for skirt
-            width           => $Config->first_layer_extrusion_width,
+            width           => $Config->get_value('first_layer_extrusion_width'),
         );
         $skirt_margin = ($flow->spacing * $Config->skirts + $Config->skirt_distance) * 2;
     } else {
diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm
index 2c1bc52ba..bc65064a9 100644
--- a/lib/Slic3r/Layer.pm
+++ b/lib/Slic3r/Layer.pm
@@ -1,60 +1,24 @@
 package Slic3r::Layer;
 use Moo;
 
-use Math::Clipper ':all';
-use Slic3r::ExtrusionPath ':roles';
-use Slic3r::Geometry qw(scale unscale collinear X Y A B PI rad2deg_dir bounding_box_center shortest_path);
-use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex xor_ex is_counter_clockwise);
-use Slic3r::Surface ':types';
+use Slic3r::Geometry::Clipper qw(union_ex);
 
-# a sequential number of layer, starting at 0
-has 'id' => (
-    is          => 'rw',
-    #isa         => 'Int',
-    required    => 1,
-);
+has 'id'                => (is => 'rw', required => 1); # sequential number of layer, 0-based
+has 'object'            => (is => 'ro', weak_ref => 1, required => 1);
+has 'regions'           => (is => 'ro', default => sub { [] });
+has 'slicing_errors'    => (is => 'rw');
 
-has 'slicing_errors' => (is => 'rw');
+has 'slice_z'           => (is => 'lazy');
+has 'print_z'           => (is => 'lazy');
+has 'height'            => (is => 'lazy');
+has 'flow'              => (is => 'ro', default => sub { $Slic3r::flow });
 
-has 'slice_z' => (is => 'lazy');
-has 'print_z' => (is => 'lazy');
-has 'height'  => (is => 'lazy');
-has 'flow'    => (is => 'lazy');
-has 'perimeter_flow' => (is => 'lazy');
-has 'infill_flow'     => (is => 'lazy');
-
-# collection of spare segments generated by slicing the original geometry;
-# these need to be merged in continuos (closed) polylines
-has 'lines' => (is => 'rw', default => sub { [] });
-
-# collection of surfaces generated by slicing the original geometry
-has 'slices' => (is => 'rw');
-
-# collection of polygons or polylines representing thin walls contained 
-# in the original geometry
-has 'thin_walls' => (is => 'rw');
-
-# collection of polygons or polylines representing thin infill regions that
-# need to be filled with a medial axis
-has 'thin_fills' => (is => 'rw');
-
-# collection of expolygons generated by offsetting the innermost perimeter(s)
-# they represent boundaries of areas to fill, typed (top/bottom/internal)
-has 'surfaces' => (is => 'rw');
-
-# collection of surfaces for infill generation. the difference between surfaces
-# fill_surfaces is that this one honors fill_density == 0 and turns small internal
-# surfaces into solid ones
-has 'fill_surfaces' => (is => 'rw');
-
-# ordered collection of extrusion paths/loops to build all perimeters
-has 'perimeters' => (is => 'rw');
+# collection of expolygons generated by slicing the original geometry;
+# also known as 'islands' (all regions are merged here)
+has 'slices'            => (is => 'rw');
 
 # ordered collection of extrusion paths to fill surfaces for support material
-has 'support_fills' => (is => 'rw');
-
-# ordered collection of extrusion paths to fill surfaces
-has 'fills' => (is => 'rw');
+has 'support_fills'     => (is => 'rw');
 
 # Z used for slicing
 sub _build_slice_z {
@@ -78,522 +42,37 @@ sub _build_height {
     return $self->id == 0 ? $Slic3r::Config->get_value('first_layer_height') : $Slic3r::Config->layer_height;
 }
 
-sub _build_flow {
+sub region {
     my $self = shift;
-    return $self->id == 0 && $Slic3r::first_layer_flow
-        ? $Slic3r::first_layer_flow
-        : $Slic3r::flow;
-}
-
-sub _build_perimeter_flow {
-    my $self = shift;
-    return $self->id == 0 && $Slic3r::first_layer_flow
-        ? $Slic3r::first_layer_flow
-        : $Slic3r::perimeter_flow;
-}
-
-sub _build_infill_flow {
-    my $self = shift;
-    return $self->id == 0 && $Slic3r::first_layer_flow
-        ? $Slic3r::first_layer_flow
-        : $Slic3r::infill_flow;
-}
-
-# build polylines from lines
-sub make_surfaces {
-    my $self = shift;
-    my ($loops) = @_;
+    my ($region_id) = @_;
     
-    {
-        my $safety_offset = scale 0.1;
-        # merge everything
-        my $expolygons = [ map $_->offset_ex(-$safety_offset), @{union_ex(safety_offset($loops, $safety_offset))} ];
-        
-        Slic3r::debugf "  %d surface(s) having %d holes detected from %d polylines\n",
-            scalar(@$expolygons), scalar(map $_->holes, @$expolygons), scalar(@$loops);
-        
-        $self->slices([
-            map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL),
-                @$expolygons
-        ]);
-    }
-    
-    # the contours must be offsetted by half extrusion width inwards
-    {
-        my $distance = scale $self->perimeter_flow->width / 2;
-        my @surfaces = @{$self->slices};
-        @{$self->slices} = ();
-        foreach my $surface (@surfaces) {
-            push @{$self->slices}, map Slic3r::Surface->new
-                (expolygon => $_, surface_type => S_TYPE_INTERNAL),
-                map $_->offset_ex(+$distance),
-                $surface->expolygon->offset_ex(-2*$distance);
-        }
-        
-        # now detect thin walls by re-outgrowing offsetted surfaces and subtracting
-        # them from the original slices
-        my $outgrown = Math::Clipper::offset([ map $_->p, @{$self->slices} ], $distance);
-        my $diff = diff_ex(
-            [ map $_->p, @surfaces ],
-            $outgrown,
-            1,
-        );
-        
-        $self->thin_walls([]);
-        if (@$diff) {
-            my $area_threshold = scale($self->perimeter_flow->spacing) ** 2;
-            @$diff = grep $_->area > ($area_threshold), @$diff;
-            
-            @{$self->thin_walls} = map $_->medial_axis(scale $self->perimeter_flow->width), @$diff;
-            
-            Slic3r::debugf "  %d thin walls detected\n", scalar(@{$self->thin_walls}) if @{$self->thin_walls};
-        }
-    }
-    
-    if (0) {
-        require "Slic3r/SVG.pm";
-        Slic3r::SVG::output(undef, "surfaces.svg",
-            polygons        => [ map $_->contour, @{$self->slices} ],
-            red_polygons    => [ map $_->p, map @{$_->holes}, @{$self->slices} ],
+    if (!defined $self->regions->[$region_id]) {
+        $self->regions->[$region_id] = Slic3r::Layer::Region->new(
+            layer   => $self,
+            region  => $self->object->print->regions->[$region_id],
         );
     }
+    return $self->regions->[$region_id];
+}
+
+# merge all regions' slices to get islands
+sub make_slices {
+    my $self = shift;
+    
+    # optimization for single-region layers
+    my @regions_with_slices = grep { @{$_->slices} } @{$self->regions};
+    if (@regions_with_slices == 1) {
+        $self->slices([ map $_->expolygon, @{$regions_with_slices[0]->slices} ]);
+        return;
+    }
+    
+    $self->slices(union_ex([ map $_->p, map @{$_->slices}, @{$self->regions} ]));
 }
 
 sub make_perimeters {
     my $self = shift;
     Slic3r::debugf "Making perimeters for layer %d\n", $self->id;
-    
-    my $gap_area_threshold = scale($self->perimeter_flow->width)** 2;
-    
-    # this array will hold one arrayref per original surface (island);
-    # each item of this arrayref is an arrayref representing a depth (from outer
-    # perimeters to inner); each item of this arrayref is an ExPolygon:
-    # @perimeters = (
-    #    [ # first island
-    #        [ Slic3r::ExPolygon, Slic3r::ExPolygon... ],  #depth 0: outer loop
-    #        [ Slic3r::ExPolygon, Slic3r::ExPolygon... ],  #depth 1: inner loop
-    #    ],
-    #    [ # second island
-    #        ...
-    #    ]
-    # )
-    my @perimeters = ();  # one item per depth; each item
-    
-    # organize islands using a shortest path search
-    my @surfaces = @{shortest_path([
-        map [ $_->contour->[0], $_ ], @{$self->slices},
-    ])};
-    
-    $self->perimeters([]);
-    $self->surfaces([]);
-    $self->thin_fills([]);
-    
-    # for each island:
-    foreach my $surface (@surfaces) {
-        my @last_offsets = ($surface->expolygon);
-        
-        # experimental hole compensation (see ArcCompensation in the RepRap wiki)
-        if (0) {
-            foreach my $hole ($last_offsets[0]->holes) {
-                my $circumference = abs($hole->length);
-                next unless $circumference <= &Slic3r::SMALL_PERIMETER_LENGTH;
-                # this compensation only works for circular holes, while it would 
-                # overcompensate for hexagons and other shapes having straight edges.
-                # so we require a minimum number of vertices.
-                next unless $circumference / @$hole >= scale 3 * $Slic3r::flow->width;
-                
-                # revert the compensation done in make_surfaces() and get the actual radius
-                # of the hole
-                my $radius = ($circumference / PI / 2) - scale $self->perimeter_flow->spacing/2;
-                my $new_radius = (scale($self->perimeter_flow->width) + sqrt((scale($self->perimeter_flow->width)**2) + (4*($radius**2)))) / 2;
-                # holes are always turned to contours, so reverse point order before and after
-                $hole->reverse;
-                my @offsetted = $hole->offset(+ ($new_radius - $radius));
-                # skip arc compensation when hole is not round (thus leads to multiple offsets)
-                @$hole = map Slic3r::Point->new($_), @{ $offsetted[0] } if @offsetted == 1;
-                $hole->reverse;
-            }
-        }
-        
-        my $distance = scale $self->perimeter_flow->spacing;
-        my @gaps = ();
-        
-        # generate perimeters inwards (loop 0 is the external one)
-        my $loop_number = $Slic3r::Config->perimeters + ($surface->additional_inner_perimeters || 0);
-        push @perimeters, [[@last_offsets]];
-        for (my $loop = 1; $loop < $loop_number; $loop++) {
-            # offsetting a polygon can result in one or many offset polygons
-            my @new_offsets = ();
-            foreach my $expolygon (@last_offsets) {
-                my @offsets = map $_->offset_ex(+0.5*$distance), $expolygon->noncollapsing_offset_ex(-1.5*$distance);
-                push @new_offsets, @offsets;
-                
-                # where the above check collapses the expolygon, then there's no room for an inner loop
-                # and we can extract the gap for later processing
-                my $diff = diff_ex(
-                    [ map @$_, $expolygon->offset_ex(-0.5*$distance) ],
-                    [ map @$_, map $_->offset_ex(+0.5*$distance), @offsets ],  # should these be offsetted in a single pass?
-                );
-                push @gaps, grep $_->area >= $gap_area_threshold, @$diff;
-            }
-            @last_offsets = @new_offsets;
-            
-            last if !@last_offsets;
-            push @{ $perimeters[-1] }, [@last_offsets];
-        }
-        
-        # create one more offset to be used as boundary for fill
-        {
-            my @fill_boundaries = map $_->offset_ex(-$distance), @last_offsets;
-            $_->simplify(scale &Slic3r::RESOLUTION) for @fill_boundaries;
-            push @{ $self->surfaces }, @fill_boundaries;
-        }
-        
-        # fill gaps using dynamic extrusion width
-        {
-            # detect the small gaps that we need to treat like thin polygons,
-            # thus generating the skeleton and using it to fill them
-            my $w = $self->perimeter_flow->width;
-            my @widths = (1.5 * $w, $w, 0.5 * $w, 0.2 * $w);
-            foreach my $width (@widths) {
-                my $scaled_width = scale $width;
-                
-                # extract the gaps having this width
-                my @this_width = map $_->offset_ex(+0.5*$scaled_width), map $_->noncollapsing_offset_ex(-0.5*$scaled_width), @gaps;
-                
-                # fill them
-                my %path_args = (
-                    role            => EXTR_ROLE_SOLIDFILL,
-                    flow_spacing    => $self->perimeter_flow->clone(width => $width)->spacing,
-                );
-                push @{ $self->thin_fills }, map {
-                    $_->isa('Slic3r::Polygon')
-                        ? (map $_->pack, Slic3r::ExtrusionLoop->new(polygon => $_, %path_args)->split_at_first_point)  # we should keep these as loops
-                        : Slic3r::ExtrusionPath->pack(polyline => $_, %path_args),
-                } map $_->medial_axis($scaled_width), @this_width;
-            
-                Slic3r::debugf "  %d gaps filled with extrusion width = %s\n", scalar @this_width, $width
-                    if @{ $self->thin_fills };
-                
-                # check what's left
-                @gaps = @{diff_ex(
-                    [ map @$_, @gaps ],
-                    [ map @$_, @this_width ],
-                )};
-            }
-        }
-    }
-    
-    # process one island (original surface) at time
-    foreach my $island (@perimeters) {
-        # do holes starting from innermost one
-        my @holes = ();
-        my %is_external = ();
-        my @hole_depths = map [ map $_->holes, @$_ ], @$island;
-        
-        # organize the outermost hole loops using a shortest path search
-        @{$hole_depths[0]} = @{shortest_path([
-            map [ $_->[0], $_ ], @{$hole_depths[0]},
-        ])};
-        
-        CYCLE: while (map @$_, @hole_depths) {
-            shift @hole_depths while !@{$hole_depths[0]};
-            
-            # take first available hole
-            push @holes, shift @{$hole_depths[0]};
-            $is_external{$#holes} = 1;
-            
-            my $current_depth = 0;
-            while (1) {
-                $current_depth++;
-                
-                # look for the hole containing this one if any
-                next CYCLE if !$hole_depths[$current_depth];
-                my $parent_hole;
-                for (@{$hole_depths[$current_depth]}) {
-                    if ($_->encloses_point($holes[-1]->[0])) {
-                        $parent_hole = $_;
-                        last;
-                    }
-                }
-                next CYCLE if !$parent_hole;
-                
-                # look for other holes contained in such parent
-                for (@{$hole_depths[$current_depth-1]}) {
-                    if ($parent_hole->encloses_point($_->[0])) {
-                        # we have a sibling, so let's move onto next iteration
-                        next CYCLE;
-                    }
-                }
-                
-                push @holes, $parent_hole;
-                @{$hole_depths[$current_depth]} = grep $_ ne $parent_hole, @{$hole_depths[$current_depth]};
-            }
-        }
-        
-        # do holes, then contours starting from innermost one
-        $self->_add_perimeter($holes[$_], $is_external{$_} ? EXTR_ROLE_EXTERNAL_PERIMETER : undef)
-            for reverse 0 .. $#holes;
-        for my $depth (reverse 0 .. $#$island) {
-            my $role = $depth == $#$island ? EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER
-                : $depth == 0 ? EXTR_ROLE_EXTERNAL_PERIMETER
-                : EXTR_ROLE_PERIMETER;
-            $self->_add_perimeter($_, $role) for map $_->contour, @{$island->[$depth]};
-        }
-    }
-    
-    # add thin walls as perimeters
-    {
-        my @thin_paths = ();
-        my %properties = (
-            role            => EXTR_ROLE_EXTERNAL_PERIMETER,
-            flow_spacing    => $self->perimeter_flow->spacing,
-        );
-        for (@{ $self->thin_walls }) {
-            push @thin_paths, $_->isa('Slic3r::Polygon')
-                ? Slic3r::ExtrusionLoop->pack(polygon => $_, %properties)
-                : Slic3r::ExtrusionPath->pack(polyline => $_, %properties);
-        }
-        my $collection = Slic3r::ExtrusionPath::Collection->new(paths => \@thin_paths);
-        push @{ $self->perimeters }, $collection->shortest_path;
-    }
-}
-
-sub _add_perimeter {
-    my $self = shift;
-    my ($polygon, $role) = @_;
-    
-    return unless $polygon->is_printable($self->perimeter_flow->width);
-    push @{ $self->perimeters }, Slic3r::ExtrusionLoop->pack(
-        polygon         => $polygon,
-        role            => (abs($polygon->length) <= &Slic3r::SMALL_PERIMETER_LENGTH) ? EXTR_ROLE_SMALLPERIMETER : ($role // EXTR_ROLE_PERIMETER),  #/
-        flow_spacing    => $self->perimeter_flow->spacing,
-    );
-}
-
-sub prepare_fill_surfaces {
-    my $self = shift;
-    
-    my @surfaces = @{$self->surfaces};
-    
-    # if no solid layers are requested, turn top/bottom surfaces to internal
-    # note that this modifies $self->surfaces in place
-    if ($Slic3r::Config->solid_layers == 0) {
-        $_->surface_type(S_TYPE_INTERNAL) for grep $_->surface_type != S_TYPE_INTERNAL, @surfaces;
-    }
-    
-    # if hollow object is requested, remove internal surfaces
-    if ($Slic3r::Config->fill_density == 0) {
-        @surfaces = grep $_->surface_type != S_TYPE_INTERNAL, @surfaces;
-    }
-    
-    # remove unprintable regions (they would slow down the infill process and also cause
-    # some weird failures during bridge neighbor detection)
-    {
-        my $distance = scale $self->infill_flow->spacing / 2;
-        @surfaces = map {
-            my $surface = $_;
-            
-            # offset inwards
-            my @offsets = $surface->expolygon->offset_ex(-$distance);
-            @offsets = @{union_ex(Math::Clipper::offset([ map @$_, @offsets ], $distance, 100, JT_MITER))};
-            map Slic3r::Surface->new(
-                expolygon => $_,
-                surface_type => $surface->surface_type,
-            ), @offsets;
-        } @surfaces;
-    }
-        
-    # turn too small internal regions into solid regions
-    {
-        my $min_area = scale scale $Slic3r::Config->solid_infill_below_area; # scaling an area requires two calls!
-        my @small = grep $_->surface_type == S_TYPE_INTERNAL && $_->expolygon->contour->area <= $min_area, @surfaces;
-        $_->surface_type(S_TYPE_INTERNALSOLID) for @small;
-        Slic3r::debugf "identified %d small solid surfaces at layer %d\n", scalar(@small), $self->id if @small > 0;
-    }
-    
-    $self->fill_surfaces([@surfaces]);
-}
-
-# make bridges printable
-sub process_bridges {
-    my $self = shift;
-    
-    # no bridges are possible if we have no internal surfaces
-    return if $Slic3r::Config->fill_density == 0;
-    
-    my @bridges = ();
-    
-    # a bottom surface on a layer > 0 is either a bridge or a overhang 
-    # or a combination of both; any top surface is a candidate for
-    # reverse bridge processing
-    
-    my @solid_surfaces = grep {
-        ($_->surface_type == S_TYPE_BOTTOM && $self->id > 0) || $_->surface_type == S_TYPE_TOP
-    } @{$self->fill_surfaces} or return;
-    
-    my @internal_surfaces = grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @{$self->slices};
-    
-    SURFACE: foreach my $surface (@solid_surfaces) {
-        my $expolygon = $surface->expolygon->safety_offset;
-        my $description = $surface->surface_type == S_TYPE_BOTTOM ? 'bridge/overhang' : 'reverse bridge';
-        
-        # offset the contour and intersect it with the internal surfaces to discover 
-        # which of them has contact with our bridge
-        my @supporting_surfaces = ();
-        my ($contour_offset) = $expolygon->contour->offset(scale $self->flow->spacing * sqrt(2));
-        foreach my $internal_surface (@internal_surfaces) {
-            my $intersection = intersection_ex([$contour_offset], [$internal_surface->p]);
-            if (@$intersection) {
-                push @supporting_surfaces, $internal_surface;
-            }
-        }
-        
-        if (0) {
-            require "Slic3r/SVG.pm";
-            Slic3r::SVG::output(undef, "bridge_surfaces.svg",
-                green_polygons  => [ map $_->p, @supporting_surfaces ],
-                red_polygons    => [ @$expolygon ],
-            );
-        }
-        
-        Slic3r::debugf "Found $description on layer %d with %d support(s)\n", 
-            $self->id, scalar(@supporting_surfaces);
-        
-        next SURFACE unless @supporting_surfaces;
-        
-        my $bridge_angle = undef;
-        if ($surface->surface_type == S_TYPE_BOTTOM) {
-            # detect optimal bridge angle
-            
-            my $bridge_over_hole = 0;
-            my @edges = ();  # edges are POLYLINES
-            foreach my $supporting_surface (@supporting_surfaces) {
-                my @surface_edges = map $_->clip_with_polygon($contour_offset),
-                    ($supporting_surface->contour, $supporting_surface->holes);
-                
-                if (@supporting_surfaces == 1 && @surface_edges == 1
-                    && @{$supporting_surface->contour} == @{$surface_edges[0]}) {
-                    $bridge_over_hole = 1;
-                }
-                push @edges, grep { @$_ } @surface_edges;
-            }
-            Slic3r::debugf "  Bridge is supported on %d edge(s)\n", scalar(@edges);
-            Slic3r::debugf "  and covers a hole\n" if $bridge_over_hole;
-            
-            if (0) {
-                require "Slic3r/SVG.pm";
-                Slic3r::SVG::output(undef, "bridge_edges.svg",
-                    polylines       => [ map $_->p, @edges ],
-                );
-            }
-            
-            if (@edges == 2) {
-                my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges;
-                my @midpoints = map $_->midpoint, @chords;
-                my $line_between_midpoints = Slic3r::Line->new(@midpoints);
-                $bridge_angle = rad2deg_dir($line_between_midpoints->direction);
-            } elsif (@edges == 1) {
-                # TODO: this case includes both U-shaped bridges and plain overhangs;
-                # we need a trapezoidation algorithm to detect the actual bridged area
-                # and separate it from the overhang area.
-                # in the mean time, we're treating as overhangs all cases where
-                # our supporting edge is a straight line
-                if (@{$edges[0]} > 2) {
-                    my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]);
-                    $bridge_angle = rad2deg_dir($line->direction);
-                }
-            } elsif (@edges) {
-                my $center = bounding_box_center([ map @$_, @edges ]);
-                my $x = my $y = 0;
-                foreach my $point (map @$, @edges) {
-                    my $line = Slic3r::Line->new($center, $point);
-                    my $dir = $line->direction;
-                    my $len = $line->length;
-                    $x += cos($dir) * $len;
-                    $y += sin($dir) * $len;
-                }
-                $bridge_angle = rad2deg_dir(atan2($y, $x));
-            }
-            
-            Slic3r::debugf "  Optimal infill angle of bridge on layer %d is %d degrees\n",
-                $self->id, $bridge_angle if defined $bridge_angle;
-        }
-        
-        # now, extend our bridge by taking a portion of supporting surfaces
-        {
-            # offset the bridge by the specified amount of mm (minimum 3)
-            my $bridge_overlap = scale 3;
-            my ($bridge_offset) = $expolygon->contour->offset($bridge_overlap);
-            
-            # calculate the new bridge
-            my $intersection = intersection_ex(
-                [ @$expolygon, map $_->p, @supporting_surfaces ],
-                [ $bridge_offset ],
-            );
-            
-            push @bridges, map Slic3r::Surface->new(
-                expolygon => $_,
-                surface_type => $surface->surface_type,
-                bridge_angle => $bridge_angle,
-            ), @$intersection;
-        }
-    }
-    
-    # now we need to merge bridges to avoid overlapping
-    {
-        # build a list of unique bridge types
-        my @surface_groups = Slic3r::Surface->group(@bridges);
-        
-        # merge bridges of the same type, removing any of the bridges already merged;
-        # the order of @surface_groups determines the priority between bridges having 
-        # different surface_type or bridge_angle
-        @bridges = ();
-        foreach my $surfaces (@surface_groups) {
-            my $union = union_ex([ map $_->p, @$surfaces ]);
-            my $diff = diff_ex(
-                [ map @$_, @$union ],
-                [ map $_->p, @bridges ],
-            );
-            
-            push @bridges, map Slic3r::Surface->new(
-                expolygon => $_,
-                surface_type => $surfaces->[0]->surface_type,
-                bridge_angle => $surfaces->[0]->bridge_angle,
-            ), @$union;
-        }
-    }
-    
-    # apply bridges to layer
-    {
-        my @surfaces = @{$self->fill_surfaces};
-        @{$self->fill_surfaces} = ();
-        
-        # intersect layer surfaces with bridges to get actual bridges
-        foreach my $bridge (@bridges) {
-            my $actual_bridge = intersection_ex(
-                [ map $_->p, @surfaces ],
-                [ $bridge->p ],
-            );
-            
-            push @{$self->fill_surfaces}, map Slic3r::Surface->new(
-                expolygon => $_,
-                surface_type => $bridge->surface_type,
-                bridge_angle => $bridge->bridge_angle,
-            ), @$actual_bridge;
-        }
-        
-        # difference between layer surfaces and bridges are the other surfaces
-        foreach my $group (Slic3r::Surface->group(@surfaces)) {
-            my $difference = diff_ex(
-                [ map $_->p, @$group ],
-                [ map $_->p, @bridges ],
-            );
-            push @{$self->fill_surfaces}, map Slic3r::Surface->new(
-                expolygon => $_,
-                surface_type => $group->[0]->surface_type), @$difference;
-        }
-    }
+    $_->make_perimeters for @{$self->regions};
 }
 
 1;
diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm
new file mode 100644
index 000000000..a25ef993f
--- /dev/null
+++ b/lib/Slic3r/Layer/Region.pm
@@ -0,0 +1,561 @@
+package Slic3r::Layer::Region;
+use Moo;
+
+use Math::Clipper ':all';
+use Slic3r::ExtrusionPath ':roles';
+use Slic3r::Geometry qw(scale shortest_path);
+use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex);
+use Slic3r::Surface ':types';
+
+has 'layer' => (
+    is          => 'ro',
+    weak_ref    => 1,
+    required    => 1,
+    handles     => [qw(id slice_z print_z height flow)],
+);
+has 'region'            => (is => 'ro', required => 1);
+has 'perimeter_flow'    => (is => 'lazy');
+has 'infill_flow'       => (is => 'lazy');
+
+# collection of spare segments generated by slicing the original geometry;
+# these need to be merged in continuos (closed) polylines
+has 'lines' => (is => 'rw', default => sub { [] });
+
+# collection of surfaces generated by slicing the original geometry
+has 'slices' => (is => 'rw', default => sub { [] });
+
+# collection of polygons or polylines representing thin walls contained 
+# in the original geometry
+has 'thin_walls' => (is => 'rw', default => sub { [] });
+
+# collection of polygons or polylines representing thin infill regions that
+# need to be filled with a medial axis
+has 'thin_fills' => (is => 'rw', default => sub { [] });
+
+# collection of expolygons generated by offsetting the innermost perimeter(s)
+# they represent boundaries of areas to fill, typed (top/bottom/internal)
+has 'surfaces' => (is => 'rw', default => sub { [] });
+
+# collection of surfaces for infill generation. the difference between surfaces
+# fill_surfaces is that this one honors fill_density == 0 and turns small internal
+# surfaces into solid ones
+has 'fill_surfaces' => (is => 'rw', default => sub { [] });
+
+# ordered collection of extrusion paths/loops to build all perimeters
+has 'perimeters' => (is => 'rw', default => sub { [] });
+
+# ordered collection of extrusion paths to fill surfaces
+has 'fills' => (is => 'rw', default => sub { [] });
+
+sub _build_perimeter_flow {
+    my $self = shift;
+    return $self->id == 0
+        ? $self->region->first_layer_flows->{perimeter}
+        : $self->region->flows->{perimeter}
+}
+
+sub _build_infill_flow {
+    my $self = shift;
+    return $self->id == 0
+        ? $self->region->first_layer_flows->{infill}
+        : $self->region->flows->{infill}
+}
+
+# build polylines from lines
+sub make_surfaces {
+    my $self = shift;
+    my ($loops) = @_;
+    
+    return if !@$loops;    
+    {
+        my $safety_offset = scale 0.1;
+        # merge everything
+        my $expolygons = [ map $_->offset_ex(-$safety_offset), @{union_ex(safety_offset($loops, $safety_offset))} ];
+        
+        Slic3r::debugf "  %d surface(s) having %d holes detected from %d polylines\n",
+            scalar(@$expolygons), scalar(map $_->holes, @$expolygons), scalar(@$loops);
+        
+        $self->slices([
+            map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL),
+                @$expolygons
+        ]);
+    }
+    
+    # the contours must be offsetted by half extrusion width inwards
+    {
+        my $distance = $self->perimeter_flow->scaled_width / 2;
+        my @surfaces = @{$self->slices};
+        @{$self->slices} = ();
+        foreach my $surface (@surfaces) {
+            push @{$self->slices}, map Slic3r::Surface->new
+                (expolygon => $_, surface_type => S_TYPE_INTERNAL),
+                map $_->offset_ex(+$distance),
+                $surface->expolygon->offset_ex(-2*$distance);
+        }
+        
+        # now detect thin walls by re-outgrowing offsetted surfaces and subtracting
+        # them from the original slices
+        my $outgrown = Math::Clipper::offset([ map $_->p, @{$self->slices} ], $distance);
+        my $diff = diff_ex(
+            [ map $_->p, @surfaces ],
+            $outgrown,
+            1,
+        );
+        
+        $self->thin_walls([]);
+        if (@$diff) {
+            my $area_threshold = $self->perimeter_flow->scaled_spacing ** 2;
+            @$diff = grep $_->area > ($area_threshold), @$diff;
+            
+            @{$self->thin_walls} = map $_->medial_axis($self->perimeter_flow->scaled_width), @$diff;
+            
+            Slic3r::debugf "  %d thin walls detected\n", scalar(@{$self->thin_walls}) if @{$self->thin_walls};
+        }
+    }
+    
+    if (0) {
+        require "Slic3r/SVG.pm";
+        Slic3r::SVG::output(undef, "surfaces.svg",
+            polygons        => [ map $_->contour, @{$self->slices} ],
+            red_polygons    => [ map $_->p, map @{$_->holes}, @{$self->slices} ],
+        );
+    }
+}
+
+sub make_perimeters {
+    my $self = shift;
+    
+    my $gap_area_threshold = $self->perimeter_flow->scaled_width ** 2;
+    
+    # this array will hold one arrayref per original surface (island);
+    # each item of this arrayref is an arrayref representing a depth (from outer
+    # perimeters to inner); each item of this arrayref is an ExPolygon:
+    # @perimeters = (
+    #    [ # first island
+    #        [ Slic3r::ExPolygon, Slic3r::ExPolygon... ],  #depth 0: outer loop
+    #        [ Slic3r::ExPolygon, Slic3r::ExPolygon... ],  #depth 1: inner loop
+    #    ],
+    #    [ # second island
+    #        ...
+    #    ]
+    # )
+    my @perimeters = ();  # one item per depth; each item
+    
+    # organize islands using a shortest path search
+    my @surfaces = @{shortest_path([
+        map [ $_->contour->[0], $_ ], @{$self->slices},
+    ])};
+    
+    $self->perimeters([]);
+    $self->surfaces([]);
+    $self->thin_fills([]);
+    
+    # for each island:
+    foreach my $surface (@surfaces) {
+        my @last_offsets = ($surface->expolygon);
+        
+        # experimental hole compensation (see ArcCompensation in the RepRap wiki)
+        if (0) {
+            foreach my $hole ($last_offsets[0]->holes) {
+                my $circumference = abs($hole->length);
+                next unless $circumference <= &Slic3r::SMALL_PERIMETER_LENGTH;
+                # this compensation only works for circular holes, while it would 
+                # overcompensate for hexagons and other shapes having straight edges.
+                # so we require a minimum number of vertices.
+                next unless $circumference / @$hole >= 3 * $Slic3r::flow->scaled_width;
+                
+                # revert the compensation done in make_surfaces() and get the actual radius
+                # of the hole
+                my $radius = ($circumference / PI / 2) - $self->perimeter_flow->scaled_spacing/2;
+                my $new_radius = ($self->perimeter_flow->scaled_width + sqrt(($self->perimeter_flow->scaled_width ** 2) + (4*($radius**2)))) / 2;
+                # holes are always turned to contours, so reverse point order before and after
+                $hole->reverse;
+                my @offsetted = $hole->offset(+ ($new_radius - $radius));
+                # skip arc compensation when hole is not round (thus leads to multiple offsets)
+                @$hole = map Slic3r::Point->new($_), @{ $offsetted[0] } if @offsetted == 1;
+                $hole->reverse;
+            }
+        }
+        
+        my $distance = $self->perimeter_flow->scaled_spacing;
+        my @gaps = ();
+        
+        # generate perimeters inwards (loop 0 is the external one)
+        my $loop_number = $Slic3r::Config->perimeters + ($surface->additional_inner_perimeters || 0);
+        push @perimeters, [[@last_offsets]];
+        for (my $loop = 1; $loop < $loop_number; $loop++) {
+            # offsetting a polygon can result in one or many offset polygons
+            my @new_offsets = ();
+            foreach my $expolygon (@last_offsets) {
+                my @offsets = map $_->offset_ex(+0.5*$distance), $expolygon->noncollapsing_offset_ex(-1.5*$distance);
+                push @new_offsets, @offsets;
+                
+                # where the above check collapses the expolygon, then there's no room for an inner loop
+                # and we can extract the gap for later processing
+                my $diff = diff_ex(
+                    [ map @$_, $expolygon->offset_ex(-0.5*$distance) ],
+                    [ map @$_, map $_->offset_ex(+0.5*$distance), @offsets ],  # should these be offsetted in a single pass?
+                );
+                push @gaps, grep $_->area >= $gap_area_threshold, @$diff;
+            }
+            @last_offsets = @new_offsets;
+            
+            last if !@last_offsets;
+            push @{ $perimeters[-1] }, [@last_offsets];
+        }
+        
+        # create one more offset to be used as boundary for fill
+        {
+            my @fill_boundaries = map $_->offset_ex(-$distance), @last_offsets;
+            $_->simplify(scale &Slic3r::RESOLUTION) for @fill_boundaries;
+            push @{ $self->surfaces }, @fill_boundaries;
+        }
+        
+        # fill gaps using dynamic extrusion width
+        {
+            # detect the small gaps that we need to treat like thin polygons,
+            # thus generating the skeleton and using it to fill them
+            my $w = $self->perimeter_flow->width;
+            my @widths = (1.5 * $w, $w, 0.5 * $w, 0.2 * $w);
+            foreach my $width (@widths) {
+                my $scaled_width = scale $width;
+                
+                # extract the gaps having this width
+                my @this_width = map $_->offset_ex(+0.5*$scaled_width), map $_->noncollapsing_offset_ex(-0.5*$scaled_width), @gaps;
+                
+                # fill them
+                my %path_args = (
+                    role            => EXTR_ROLE_SOLIDFILL,
+                    flow_spacing    => $self->perimeter_flow->clone(width => $width)->spacing,
+                );
+                push @{ $self->thin_fills }, map {
+                    $_->isa('Slic3r::Polygon')
+                        ? (map $_->pack, Slic3r::ExtrusionLoop->new(polygon => $_, %path_args)->split_at_first_point)  # we should keep these as loops
+                        : Slic3r::ExtrusionPath->pack(polyline => $_, %path_args),
+                } map $_->medial_axis($scaled_width), @this_width;
+            
+                Slic3r::debugf "  %d gaps filled with extrusion width = %s\n", scalar @this_width, $width
+                    if @{ $self->thin_fills };
+                
+                # check what's left
+                @gaps = @{diff_ex(
+                    [ map @$_, @gaps ],
+                    [ map @$_, @this_width ],
+                )};
+            }
+        }
+    }
+    
+    # process one island (original surface) at time
+    foreach my $island (@perimeters) {
+        # do holes starting from innermost one
+        my @holes = ();
+        my %is_external = ();
+        my @hole_depths = map [ map $_->holes, @$_ ], @$island;
+        
+        # organize the outermost hole loops using a shortest path search
+        @{$hole_depths[0]} = @{shortest_path([
+            map [ $_->[0], $_ ], @{$hole_depths[0]},
+        ])};
+        
+        CYCLE: while (map @$_, @hole_depths) {
+            shift @hole_depths while !@{$hole_depths[0]};
+            
+            # take first available hole
+            push @holes, shift @{$hole_depths[0]};
+            $is_external{$#holes} = 1;
+            
+            my $current_depth = 0;
+            while (1) {
+                $current_depth++;
+                
+                # look for the hole containing this one if any
+                next CYCLE if !$hole_depths[$current_depth];
+                my $parent_hole;
+                for (@{$hole_depths[$current_depth]}) {
+                    if ($_->encloses_point($holes[-1]->[0])) {
+                        $parent_hole = $_;
+                        last;
+                    }
+                }
+                next CYCLE if !$parent_hole;
+                
+                # look for other holes contained in such parent
+                for (@{$hole_depths[$current_depth-1]}) {
+                    if ($parent_hole->encloses_point($_->[0])) {
+                        # we have a sibling, so let's move onto next iteration
+                        next CYCLE;
+                    }
+                }
+                
+                push @holes, $parent_hole;
+                @{$hole_depths[$current_depth]} = grep $_ ne $parent_hole, @{$hole_depths[$current_depth]};
+            }
+        }
+        
+        # do holes, then contours starting from innermost one
+        $self->_add_perimeter($holes[$_], $is_external{$_} ? EXTR_ROLE_EXTERNAL_PERIMETER : undef)
+            for reverse 0 .. $#holes;
+        for my $depth (reverse 0 .. $#$island) {
+            my $role = $depth == $#$island ? EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER
+                : $depth == 0 ? EXTR_ROLE_EXTERNAL_PERIMETER
+                : EXTR_ROLE_PERIMETER;
+            $self->_add_perimeter($_, $role) for map $_->contour, @{$island->[$depth]};
+        }
+    }
+    
+    # add thin walls as perimeters
+    {
+        my @thin_paths = ();
+        my %properties = (
+            role            => EXTR_ROLE_EXTERNAL_PERIMETER,
+            flow_spacing    => $self->perimeter_flow->spacing,
+        );
+        for (@{ $self->thin_walls }) {
+            push @thin_paths, $_->isa('Slic3r::Polygon')
+                ? Slic3r::ExtrusionLoop->pack(polygon => $_, %properties)
+                : Slic3r::ExtrusionPath->pack(polyline => $_, %properties);
+        }
+        my $collection = Slic3r::ExtrusionPath::Collection->new(paths => \@thin_paths);
+        push @{ $self->perimeters }, $collection->shortest_path;
+    }
+}
+
+sub _add_perimeter {
+    my $self = shift;
+    my ($polygon, $role) = @_;
+    
+    return unless $polygon->is_printable($self->perimeter_flow->width);
+    push @{ $self->perimeters }, Slic3r::ExtrusionLoop->pack(
+        polygon         => $polygon,
+        role            => (abs($polygon->length) <= &Slic3r::SMALL_PERIMETER_LENGTH) ? EXTR_ROLE_SMALLPERIMETER : ($role // EXTR_ROLE_PERIMETER),  #/
+        flow_spacing    => $self->perimeter_flow->spacing,
+    );
+}
+
+sub prepare_fill_surfaces {
+    my $self = shift;
+    
+    my @surfaces = @{$self->surfaces};
+    
+    # if no solid layers are requested, turn top/bottom surfaces to internal
+    # note that this modifies $self->surfaces in place
+    if ($Slic3r::Config->solid_layers == 0) {
+        $_->surface_type(S_TYPE_INTERNAL) for grep $_->surface_type != S_TYPE_INTERNAL, @surfaces;
+    }
+    
+    # if hollow object is requested, remove internal surfaces
+    if ($Slic3r::Config->fill_density == 0) {
+        @surfaces = grep $_->surface_type != S_TYPE_INTERNAL, @surfaces;
+    }
+    
+    # remove unprintable regions (they would slow down the infill process and also cause
+    # some weird failures during bridge neighbor detection)
+    {
+        my $distance = $self->infill_flow->scaled_spacing / 2;
+        @surfaces = map {
+            my $surface = $_;
+            
+            # offset inwards
+            my @offsets = $surface->expolygon->offset_ex(-$distance);
+            @offsets = @{union_ex(Math::Clipper::offset([ map @$_, @offsets ], $distance, 100, JT_MITER))};
+            map Slic3r::Surface->new(
+                expolygon => $_,
+                surface_type => $surface->surface_type,
+            ), @offsets;
+        } @surfaces;
+    }
+        
+    # turn too small internal regions into solid regions
+    {
+        my $min_area = scale scale $Slic3r::Config->solid_infill_below_area; # scaling an area requires two calls!
+        my @small = grep $_->surface_type == S_TYPE_INTERNAL && $_->expolygon->contour->area <= $min_area, @surfaces;
+        $_->surface_type(S_TYPE_INTERNALSOLID) for @small;
+        Slic3r::debugf "identified %d small solid surfaces at layer %d\n", scalar(@small), $self->id if @small > 0;
+    }
+    
+    $self->fill_surfaces([@surfaces]);
+}
+
+# make bridges printable
+sub process_bridges {
+    my $self = shift;
+    
+    # no bridges are possible if we have no internal surfaces
+    return if $Slic3r::Config->fill_density == 0;
+    
+    my @bridges = ();
+    
+    # a bottom surface on a layer > 0 is either a bridge or a overhang 
+    # or a combination of both; any top surface is a candidate for
+    # reverse bridge processing
+    
+    my @solid_surfaces = grep {
+        ($_->surface_type == S_TYPE_BOTTOM && $self->id > 0) || $_->surface_type == S_TYPE_TOP
+    } @{$self->fill_surfaces} or return;
+    
+    my @internal_surfaces = grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @{$self->slices};
+    
+    SURFACE: foreach my $surface (@solid_surfaces) {
+        my $expolygon = $surface->expolygon->safety_offset;
+        my $description = $surface->surface_type == S_TYPE_BOTTOM ? 'bridge/overhang' : 'reverse bridge';
+        
+        # offset the contour and intersect it with the internal surfaces to discover 
+        # which of them has contact with our bridge
+        my @supporting_surfaces = ();
+        my ($contour_offset) = $expolygon->contour->offset(scale $self->flow->spacing * sqrt(2));
+        foreach my $internal_surface (@internal_surfaces) {
+            my $intersection = intersection_ex([$contour_offset], [$internal_surface->p]);
+            if (@$intersection) {
+                push @supporting_surfaces, $internal_surface;
+            }
+        }
+        
+        if (0) {
+            require "Slic3r/SVG.pm";
+            Slic3r::SVG::output(undef, "bridge_surfaces.svg",
+                green_polygons  => [ map $_->p, @supporting_surfaces ],
+                red_polygons    => [ @$expolygon ],
+            );
+        }
+        
+        Slic3r::debugf "Found $description on layer %d with %d support(s)\n", 
+            $self->id, scalar(@supporting_surfaces);
+        
+        next SURFACE unless @supporting_surfaces;
+        
+        my $bridge_angle = undef;
+        if ($surface->surface_type == S_TYPE_BOTTOM) {
+            # detect optimal bridge angle
+            
+            my $bridge_over_hole = 0;
+            my @edges = ();  # edges are POLYLINES
+            foreach my $supporting_surface (@supporting_surfaces) {
+                my @surface_edges = map $_->clip_with_polygon($contour_offset),
+                    ($supporting_surface->contour, $supporting_surface->holes);
+                
+                if (@supporting_surfaces == 1 && @surface_edges == 1
+                    && @{$supporting_surface->contour} == @{$surface_edges[0]}) {
+                    $bridge_over_hole = 1;
+                }
+                push @edges, grep { @$_ } @surface_edges;
+            }
+            Slic3r::debugf "  Bridge is supported on %d edge(s)\n", scalar(@edges);
+            Slic3r::debugf "  and covers a hole\n" if $bridge_over_hole;
+            
+            if (0) {
+                require "Slic3r/SVG.pm";
+                Slic3r::SVG::output(undef, "bridge_edges.svg",
+                    polylines       => [ map $_->p, @edges ],
+                );
+            }
+            
+            if (@edges == 2) {
+                my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges;
+                my @midpoints = map $_->midpoint, @chords;
+                my $line_between_midpoints = Slic3r::Line->new(@midpoints);
+                $bridge_angle = Slic3r::Geometry::rad2deg_dir($line_between_midpoints->direction);
+            } elsif (@edges == 1) {
+                # TODO: this case includes both U-shaped bridges and plain overhangs;
+                # we need a trapezoidation algorithm to detect the actual bridged area
+                # and separate it from the overhang area.
+                # in the mean time, we're treating as overhangs all cases where
+                # our supporting edge is a straight line
+                if (@{$edges[0]} > 2) {
+                    my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]);
+                    $bridge_angle = Slic3r::Geometry::rad2deg_dir($line->direction);
+                }
+            } elsif (@edges) {
+                my $center = Slic3r::Geometry::bounding_box_center([ map @$_, @edges ]);
+                my $x = my $y = 0;
+                foreach my $point (map @$, @edges) {
+                    my $line = Slic3r::Line->new($center, $point);
+                    my $dir = $line->direction;
+                    my $len = $line->length;
+                    $x += cos($dir) * $len;
+                    $y += sin($dir) * $len;
+                }
+                $bridge_angle = Slic3r::Geometry::rad2deg_dir(atan2($y, $x));
+            }
+            
+            Slic3r::debugf "  Optimal infill angle of bridge on layer %d is %d degrees\n",
+                $self->id, $bridge_angle if defined $bridge_angle;
+        }
+        
+        # now, extend our bridge by taking a portion of supporting surfaces
+        {
+            # offset the bridge by the specified amount of mm (minimum 3)
+            my $bridge_overlap = scale 3;
+            my ($bridge_offset) = $expolygon->contour->offset($bridge_overlap);
+            
+            # calculate the new bridge
+            my $intersection = intersection_ex(
+                [ @$expolygon, map $_->p, @supporting_surfaces ],
+                [ $bridge_offset ],
+            );
+            
+            push @bridges, map Slic3r::Surface->new(
+                expolygon => $_,
+                surface_type => $surface->surface_type,
+                bridge_angle => $bridge_angle,
+            ), @$intersection;
+        }
+    }
+    
+    # now we need to merge bridges to avoid overlapping
+    {
+        # build a list of unique bridge types
+        my @surface_groups = Slic3r::Surface->group(@bridges);
+        
+        # merge bridges of the same type, removing any of the bridges already merged;
+        # the order of @surface_groups determines the priority between bridges having 
+        # different surface_type or bridge_angle
+        @bridges = ();
+        foreach my $surfaces (@surface_groups) {
+            my $union = union_ex([ map $_->p, @$surfaces ]);
+            my $diff = diff_ex(
+                [ map @$_, @$union ],
+                [ map $_->p, @bridges ],
+            );
+            
+            push @bridges, map Slic3r::Surface->new(
+                expolygon => $_,
+                surface_type => $surfaces->[0]->surface_type,
+                bridge_angle => $surfaces->[0]->bridge_angle,
+            ), @$union;
+        }
+    }
+    
+    # apply bridges to layer
+    {
+        my @surfaces = @{$self->fill_surfaces};
+        @{$self->fill_surfaces} = ();
+        
+        # intersect layer surfaces with bridges to get actual bridges
+        foreach my $bridge (@bridges) {
+            my $actual_bridge = intersection_ex(
+                [ map $_->p, @surfaces ],
+                [ $bridge->p ],
+            );
+            
+            push @{$self->fill_surfaces}, map Slic3r::Surface->new(
+                expolygon => $_,
+                surface_type => $bridge->surface_type,
+                bridge_angle => $bridge->bridge_angle,
+            ), @$actual_bridge;
+        }
+        
+        # difference between layer surfaces and bridges are the other surfaces
+        foreach my $group (Slic3r::Surface->group(@surfaces)) {
+            my $difference = diff_ex(
+                [ map $_->p, @$group ],
+                [ map $_->p, @bridges ],
+            );
+            push @{$self->fill_surfaces}, map Slic3r::Surface->new(
+                expolygon => $_,
+                surface_type => $group->[0]->surface_type), @$difference;
+        }
+    }
+}
+
+1;
diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm
index 2d7a33155..1e2a6b2ba 100644
--- a/lib/Slic3r/Model.pm
+++ b/lib/Slic3r/Model.pm
@@ -15,6 +15,7 @@ sub read_from_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";
     
+    $_->input_file($input_file) for @{$model->objects};
     return $model;
 }
 
@@ -26,6 +27,16 @@ sub add_object {
     return $object;
 }
 
+sub set_material {
+    my $self = shift;
+    my ($material_id, $attributes) = @_;
+    
+    return $self->materials->{$material_id} = Slic3r::Model::Region->new(
+        model       => $self,
+        attributes  => $attributes || {},
+    );
+}
+
 # flattens everything to a single mesh
 sub mesh {
     my $self = shift;
@@ -47,7 +58,7 @@ sub mesh {
     return Slic3r::TriangleMesh->merge(@meshes);
 }
 
-package Slic3r::Model::Material;
+package Slic3r::Model::Region;
 use Moo;
 
 has 'model'         => (is => 'ro', weak_ref => 1, required => 1);
@@ -66,8 +77,20 @@ has 'instances' => (is => 'rw');
 
 sub add_volume {
     my $self = shift;
+    my %args = @_;
     
-    my $volume = Slic3r::Model::Volume->new(object => $self, @_);
+    if (my $vertices = delete $args{vertices}) {
+        my $v_offset = @{$self->vertices};
+        push @{$self->vertices}, @$vertices;
+        
+        @{$args{facets}} = map {
+            my $f = [@$_];
+            $f->[$_] += $v_offset for -3..-1;
+            $f;
+        } @{$args{facets}};
+    }
+    
+    my $volume = Slic3r::Model::Volume->new(object => $self, %args);
     push @{$self->volumes}, $volume;
     return $volume;
 }
@@ -83,11 +106,6 @@ sub add_instance {
 sub mesh {
     my $self = shift;
     
-    my $vertices = [];
-    my $facets = [];
-    
-    
-    
     return Slic3r::TriangleMesh->new(
         vertices => $self->vertices,
         facets   => [ map @{$_->facets}, @{$self->volumes} ],
diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm
index 474b55d94..a870cd37b 100644
--- a/lib/Slic3r/Polyline.pm
+++ b/lib/Slic3r/Polyline.pm
@@ -88,7 +88,7 @@ sub length {
 # this only applies to polylines
 sub grow {
     my $self = shift;
-    return Slic3r::Polygon->new(@$self, CORE::reverse @$self[1..-2])->offset(@_);
+    return Slic3r::Polygon->new(@$self, CORE::reverse @$self[1..($#$self-1)])->offset(@_);
 }
 
 sub nearest_point_to {
diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm
index 8accf1dd9..9231fa9cc 100644
--- a/lib/Slic3r/Print.pm
+++ b/lib/Slic3r/Print.pm
@@ -6,16 +6,19 @@ 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 nearest_point);
+use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN 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);
 
 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');
-has 'processing_time'        => (is => 'rw', required => 0);
+has 'processing_time'        => (is => 'rw');
+has 'extruders'              => (is => 'rw', default => sub {[]});
+has 'regions'                => (is => 'rw', default => sub {[]});
+has 'support_material_flow'  => (is => 'rw');
+has 'first_layer_support_material_flow' => (is => 'rw');
 
 # ordered collection of extrusion paths to build skirt loops
 has 'skirt' => (
@@ -52,91 +55,77 @@ sub _trigger_config {
     $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)) {
-        $Slic3r::extruders->[$t] ||= Slic3r::Extruder->new(
-            map { $_ =>  $self->config->get($_)->[$t] // $self->config->get($_)->[0] } #/
-                @{&Slic3r::Extruder::OPTIONS}
-        );
-    }
-    
-    # calculate flow
-    $Slic3r::flow = $Slic3r::extruders->[0]->make_flow(width => $self->config->extrusion_width);
-    if ($self->config->first_layer_extrusion_width) {
-        $Slic3r::first_layer_flow = $Slic3r::extruders->[0]->make_flow(
-            layer_height => $self->config->get_value('first_layer_height'),
-            width        => $self->config->first_layer_extrusion_width,
-        );
-    }
-    for (qw(perimeter infill support_material)) {
-        no strict 'refs';
-        ${"Slic3r::${_}_flow"} = $Slic3r::extruders->[ $self->config->get("${_}_extruder")-1 ]
-            ->make_flow(width => $self->config->get("${_}_extrusion_width") || $self->config->extrusion_width);
-    }
-    
-    Slic3r::debugf "Default flow width = %s (spacing = %s)\n",
-        $Slic3r::flow->width, $Slic3r::flow->spacing;
-    
     # 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';
 }
 
-sub add_objects_from_file {
-    my $self = shift;
-    my ($input_file) = @_;
-    
-    my $model = Slic3r::Model->read_from_file($input_file);
-    
-    my @print_objects = $self->add_model($model);
-    $_->input_file($input_file) for @print_objects;
-}
-
 sub add_model {
     my $self = shift;
     my ($model) = @_;
     
-    my @print_objects = ();
-    foreach my $object (@{ $model->objects }) {
-        my $mesh = $object->volumes->[0]->mesh;
-        $mesh->check_manifoldness;
-        
-        if ($object->instances) {
-            # we ignore the per-instance rotation currently and only 
-            # consider the first one
-            $mesh->rotate($object->instances->[0]->rotation);
-        }
-        
-        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->[Y] ], @{$object->instances};
+    # append/merge materials and preserve a mapping between the original material ID
+    # and our numeric material index
+    my %materials = ();
+    {
+        my @material_ids = sort keys %{$model->materials};
+        @material_ids = (0) if !@material_ids;
+        for (my $i = $self->regions_count; $i < @material_ids; $i++) {
+            push @{$self->regions}, Slic3r::Print::Region->new;
+            $materials{$material_ids[$i]} = $#{$self->regions};
         }
     }
     
-    return @print_objects;
-}
-
-sub add_object_from_mesh {
-    my $self = shift;
-    my ($mesh, %attributes) = @_;
-    
-    $mesh->rotate($Slic3r::Config->rotate);
-    $mesh->scale($Slic3r::Config->scale / &Slic3r::SCALING_FACTOR);
-    $mesh->align_to_origin;
-    
-    # initialize print object
-    my $object = Slic3r::Print::Object->new(
-        mesh => $mesh,
-        size => [ $mesh->size ],
-        %attributes,
-    );
-    
-    push @{$self->objects}, $object;
-    push @{$self->copies}, [[0, 0]];
-    return $object;
+    foreach my $object (@{ $model->objects }) {
+        my @meshes = ();  # by region_id
+        
+        foreach my $volume (@{$object->volumes}) {
+            # should the object contain multiple volumes of the same material, merge them
+            my $region_id = defined $volume->material_id ? $materials{$volume->material_id} : 0;
+            my $mesh = $volume->mesh->clone;
+            $meshes[$region_id] = $meshes[$region_id]
+                ? Slic3r::TriangleMesh->merge($meshes[$region_id], $mesh)
+                : $mesh;
+        }
+        
+        foreach my $mesh (@meshes) {
+            next unless $mesh;
+            $mesh->check_manifoldness;
+            
+            if ($object->instances) {
+                # we ignore the per-instance rotation currently and only 
+                # consider the first one
+                $mesh->rotate($object->instances->[0]->rotation);
+            }
+            
+            $mesh->rotate($Slic3r::Config->rotate);
+            $mesh->scale($Slic3r::Config->scale / &Slic3r::SCALING_FACTOR);
+        }
+        
+        my $complete_mesh = Slic3r::TriangleMesh->merge(grep defined $_, @meshes);
+        
+        # initialize print object
+        my $print_object = Slic3r::Print::Object->new(
+            print       => $self,
+            meshes      => [ @meshes ],
+            size        => [ $complete_mesh->size ],
+            input_file  => $object->input_file
+        );
+        push @{$self->objects}, $print_object;
+        
+        # align object to origin
+        {
+            my @extents = $complete_mesh->extents;
+            foreach my $mesh (grep defined $_, @meshes) {
+                $mesh->move(map -$extents[$_][MIN], X,Y,Z);
+            }
+        }
+        
+        if ($object->instances) {
+            # replace the default [0,0] instance with the custom ones
+            @{$print_object->copies} = map [ scale $_->offset->[X], scale $_->offset->[Y] ], @{$object->instances};
+        }
+    }
 }
 
 sub validate {
@@ -149,11 +138,11 @@ sub validate {
             for my $obj_idx (0 .. $#{$self->objects}) {
                 my $clearance;
                 {
-                    my @points = map [ @$_[X,Y] ], @{$self->objects->[$obj_idx]->mesh->vertices};
+                    my @points = map [ @$_[X,Y] ], map @{$_->vertices}, @{$self->objects->[$obj_idx]->meshes};
                     my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points));
                     $clearance = +($convex_hull->offset(scale $Slic3r::Config->extruder_clearance_radius / 2, 1, JT_ROUND))[0];
                 }
-                for my $copy (@{$self->copies->[$obj_idx]}) {
+                for my $copy (@{$self->objects->[$obj_idx]->copies}) {
                     my $copy_clearance = $clearance->clone;
                     $copy_clearance->translate(@$copy);
                     if (@{ intersection_ex(\@a, [$copy_clearance]) }) {
@@ -168,30 +157,79 @@ sub validate {
         {
             my @obj_copies = $self->object_copies;
             pop @obj_copies;  # ignore the last copy: its height doesn't matter
-            if (grep { +($self->objects->[$_->[0]]->mesh->size)[Z] > scale $Slic3r::Config->extruder_clearance_height } @obj_copies) {
+            my $scaled_clearance = scale $Slic3r::Config->extruder_clearance_height;
+            if (grep { +($_->size)[Z] > $scaled_clearance } map @{$self->objects->[$_->[0]]->meshes}, @obj_copies) {
                 die "Some objects are too tall and cannot be printed without extruder collisions.\n";
             }
         }
     }
 }
 
+sub init_extruders {
+    my $self = shift;
+    
+    # map regions to extruders (ghetto mapping for now)
+    my %extruder_mapping = map { $_ => $_ } 0..$#{$self->regions};
+    
+    # initialize all extruder(s) we need
+    my @used_extruders = (
+        0,
+        (map $self->config->get("${_}_extruder")-1, qw(perimeter infill support_material)),
+        (values %extruder_mapping),
+    );
+    for my $extruder_id (keys %{{ map {$_ => 1} @used_extruders }}) {
+        $self->extruders->[$extruder_id] = Slic3r::Extruder->new(
+            id => $extruder_id,
+            map { $_ => $self->config->get($_)->[$extruder_id] // $self->config->get($_)->[0] } #/
+                @{&Slic3r::Extruder::OPTIONS}
+        );
+    }
+    
+    # calculate regions' flows
+    $Slic3r::flow = $self->extruders->[0]->make_flow(width => $self->config->extrusion_width);
+    for my $region_id (0 .. $#{$self->regions}) {
+        my $region = $self->regions->[$region_id];
+        
+        # per-role extruders and flows
+        for (qw(perimeter infill)) {
+            $region->extruders->{$_} = ($self->regions_count > 1)
+                ? $self->extruders->[$extruder_mapping{$region_id}]
+                : $self->extruders->[$self->config->get("${_}_extruder")-1];
+            $region->flows->{$_} = $region->extruders->{$_}->make_flow(
+                width => $self->config->get("${_}_extrusion_width") || $self->config->extrusion_width,
+            );
+            $region->first_layer_flows->{$_} = $region->extruders->{$_}->make_flow(
+                layer_height    => $self->config->get_value('first_layer_height'),
+                width           => $self->config->first_layer_extrusion_width,
+            );
+        }
+    }
+    
+    # calculate support material flow
+    if ($self->config->support_material) {
+        my $extruder = $self->extruders->[$self->config->support_material_extruder-1];
+        $self->support_material_flow($extruder->make_flow(
+            width => $self->config->support_material_extrusion_width || $self->config->extrusion_width,
+        ));
+        $self->first_layer_support_material_flow($extruder->make_flow(
+            layer_height    => $self->config->get_value('first_layer_height'),
+            width           => $self->config->first_layer_extrusion_width,
+        ));
+    }
+    
+    Slic3r::debugf "Default flow width = %s (spacing = %s)\n",
+        $Slic3r::flow->width, $Slic3r::flow->spacing;
+}
+
 sub object_copies {
     my $self = shift;
     my @oc = ();
     for my $obj_idx (0 .. $#{$self->objects}) {
-        push @oc, map [ $obj_idx, $_ ], @{$self->copies->[$obj_idx]};
+        push @oc, map [ $obj_idx, $_ ], @{$self->objects->[$obj_idx]->copies};
     }
     return @oc;
 }
 
-sub cleanup {
-    my $self = shift;
-    $_->cleanup for @{$self->objects};
-    @{$self->skirt} = ();
-    $self->total_extrusion_length(0);
-    $self->processing_time(0);
-}
-
 sub layer_count {
     my $self = shift;
     my $count = 0;
@@ -201,6 +239,11 @@ sub layer_count {
     return $count;
 }
 
+sub regions_count {
+    my $self = shift;
+    return scalar @{$self->regions};
+}
+
 sub duplicate {
     my $self = shift;
     
@@ -212,18 +255,18 @@ sub duplicate {
         
         # generate offsets for copies
         my $dist = scale $Slic3r::Config->duplicate_distance;
-        @{$self->copies->[0]} = ();
+        @{$self->objects->[0]->copies} = ();
         for my $x_copy (1..$Slic3r::Config->duplicate_grid->[X]) {
             for my $y_copy (1..$Slic3r::Config->duplicate_grid->[Y]) {
-                push @{$self->copies->[0]}, [
+                push @{$self->objects->[0]->copies}, [
                     ($object->size->[X] + $dist) * ($x_copy-1),
                     ($object->size->[Y] + $dist) * ($y_copy-1),
                 ];
             }
         }
     } elsif ($Slic3r::Config->duplicate > 1) {
-        foreach my $copies (@{$self->copies}) {
-            @$copies = map [0,0], 1..$Slic3r::Config->duplicate;
+        foreach my $object (@{$self->objects}) {
+            @{$object->copies} = map [0,0], 1..$Slic3r::Config->duplicate;
         }
         $self->arrange_objects;
     }
@@ -232,16 +275,14 @@ sub duplicate {
 sub arrange_objects {
     my $self = shift;
 
-    my $total_parts = scalar map @$_, @{$self->copies};
+    my $total_parts = scalar map @{$_->copies}, @{$self->objects};
     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 $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]};
-    }
+    @{$_->copies} = splice @positions, 0, scalar @{$_->copies} for @{$self->objects};
 }
 
 sub bounding_box {
@@ -250,7 +291,7 @@ sub bounding_box {
     my @points = ();
     foreach my $obj_idx (0 .. $#{$self->objects}) {
         my $object = $self->objects->[$obj_idx];
-        foreach my $copy (@{$self->copies->[$obj_idx]}) {
+        foreach my $copy (@{$self->objects->[$obj_idx]->copies}) {
             push @points,
                 [ $copy->[X], $copy->[Y] ],
                 [ $copy->[X] + $object->size->[X], $copy->[Y] ],
@@ -272,12 +313,12 @@ sub export_gcode {
     my $self = shift;
     my %params = @_;
     
+    $self->init_extruders;
     my $status_cb = $params{status_cb} || sub {};
     my $t0 = [gettimeofday];
     
     # skein the STL into layers
     # each layer has surfaces with holes
-    $status_cb->(5, "Processing input file");    
     $status_cb->(10, "Processing triangulated mesh");
     $_->slice(keep_meshes => $params{keep_meshes}) for @{$self->objects};
     
@@ -287,9 +328,12 @@ sub export_gcode {
     $status_cb->(20, "Generating perimeters");
     $_->make_perimeters for @{$self->objects};
     
-    # simplify slices, we only need the max resolution for perimeters
-    $_->simplify(scale &Slic3r::RESOLUTION)
-        for map @{$_->expolygon}, map @{$_->slices}, map @{$_->layers}, @{$self->objects};
+    # simplify slices (both layer and region slices),
+    # we only need the max resolution for perimeters
+    foreach my $layer (map @{$_->layers}, @{$self->objects}) {
+        $_->simplify(scale &Slic3r::RESOLUTION)
+            for @{$layer->slices}, (map $_->expolygon, map @{$_->slices}, @{$layer->regions});
+    }
     
     # this will clip $layer->surfaces to the infill boundaries 
     # and split them in top/bottom/internal surfaces;
@@ -298,12 +342,12 @@ sub export_gcode {
     
     # decide what surfaces are to be filled
     $status_cb->(35, "Preparing infill surfaces");
-    $_->prepare_fill_surfaces for map @{$_->layers}, @{$self->objects};
+    $_->prepare_fill_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects};
     
     # this will detect bridges and reverse bridges
     # and rearrange top/bottom/internal surfaces
     $status_cb->(45, "Detect bridges");
-    $_->process_bridges for map @{$_->layers}, @{$self->objects};
+    $_->process_bridges for map @{$_->regions}, map @{$_->layers}, @{$self->objects};
     
     # detect which fill surfaces are near external layers
     # they will be split in internal and internal-solid surfaces
@@ -311,7 +355,7 @@ sub export_gcode {
     $_->discover_horizontal_shells for @{$self->objects};
     
     # free memory
-    $_->surfaces(undef) for map @{$_->layers}, @{$self->objects};
+    $_->surfaces(undef) for map @{$_->regions}, map @{$_->layers}, @{$self->objects};
     
     # combine fill surfaces to honor the "infill every N layers" option
     $status_cb->(70, "Combining infill");
@@ -321,35 +365,45 @@ sub export_gcode {
     $status_cb->(80, "Infilling layers");
     {
         my $fill_maker = Slic3r::Fill->new('print' => $self);
-        
-        my @items = ();  # [obj_idx, layer_id]
-        foreach my $obj_idx (0 .. $#{$self->objects}) {
-            push @items, map [$obj_idx, $_], 0..$#{$self->objects->[$obj_idx]->layers};
-        }
         Slic3r::parallelize(
-            items => [@items],
+            items => sub {
+                my @items = ();  # [obj_idx, layer_id]
+                for my $obj_idx (0 .. $#{$self->objects}) {
+                    for my $region_id (0 .. ($self->regions_count-1)) {
+                        push @items, map [$obj_idx, $_, $region_id], 0..($self->objects->[$obj_idx]->layer_count-1);
+                    }
+                }
+                @items;
+            },
             thread_cb => sub {
                 my $q = shift;
                 $Slic3r::Geometry::Clipper::clipper = Math::Clipper->new;
                 my $fills = {};
                 while (defined (my $obj_layer = $q->dequeue)) {
-                    my ($obj_idx, $layer_id) = @$obj_layer;
+                    my ($obj_idx, $layer_id, $region_id) = @$obj_layer;
                     $fills->{$obj_idx} ||= {};
-                    $fills->{$obj_idx}{$layer_id} = [ $fill_maker->make_fill($self->objects->[$obj_idx]->layers->[$layer_id]) ];
+                    $fills->{$obj_idx}{$layer_id} ||= {};
+                    $fills->{$obj_idx}{$layer_id}{$region_id} = [
+                        $fill_maker->make_fill($self->objects->[$obj_idx]->layers->[$layer_id]->regions->[$region_id]),
+                    ];
                 }
                 return $fills;
             },
             collect_cb => sub {
                 my $fills = shift;
                 foreach my $obj_idx (keys %$fills) {
+                    my $object = $self->objects->[$obj_idx];
                     foreach my $layer_id (keys %{$fills->{$obj_idx}}) {
-                        $self->objects->[$obj_idx]->layers->[$layer_id]->fills($fills->{$obj_idx}{$layer_id});
+                        my $layer = $object->layers->[$layer_id];
+                        foreach my $region_id (keys %{$fills->{$obj_idx}{$layer_id}}) {
+                            $layer->regions->[$region_id]->fills($fills->{$obj_idx}{$layer_id}{$region_id});
+                        }
                     }
                 }
             },
             no_threads_cb => sub {
-                foreach my $layer (map @{$_->layers}, @{$self->objects}) {
-                    $layer->fills([ $fill_maker->make_fill($layer) ]);
+                foreach my $layerm (map @{$_->regions}, map @{$_->layers}, @{$self->objects}) {
+                    $layerm->fills([ $fill_maker->make_fill($layerm) ]);
                 }
             },
         );
@@ -358,11 +412,11 @@ sub export_gcode {
     # generate support material
     if ($Slic3r::Config->support_material) {
         $status_cb->(85, "Generating support material");
-        $_->generate_support_material(print => $self) for @{$self->objects};
+        $_->generate_support_material for @{$self->objects};
     }
     
     # free memory (note that support material needs fill_surfaces)
-    $_->fill_surfaces(undef) for map @{$_->layers}, @{$self->objects};
+    $_->fill_surfaces(undef) for map @{$_->regions}, map @{$_->layers}, @{$self->objects};
     
     # make skirt
     $status_cb->(88, "Generating skirt");
@@ -399,6 +453,10 @@ sub export_svg {
     my $self = shift;
     my %params = @_;
     
+    # this shouldn't be needed, but we're currently relying on ->make_surfaces() which
+    # calls ->perimeter_flow
+    $self->init_extruders;
+    
     $_->slice(keep_meshes => $params{keep_meshes}) for @{$self->objects};
     $self->arrange_objects;
     
@@ -435,10 +493,10 @@ EOF
             my $layer = $self->objects->[$obj_idx]->layers->[$layer_id] or next;
             
             # sort slices so that the outermost ones come first
-            my @slices = sort { $a->expolygon->contour->encloses_point($b->expolygon->contour->[0]) ? 0 : 1 } @{$layer->slices};
-            foreach my $copy (@{$self->copies->[$obj_idx]}) {
+            my @slices = sort { $a->contour->encloses_point($b->contour->[0]) ? 0 : 1 } @{$layer->slices};
+            foreach my $copy (@{$self->objects->[$obj_idx]->copies}) {
                 foreach my $slice (@slices) {
-                    my $expolygon = $slice->expolygon->clone;
+                    my $expolygon = $slice->clone;
                     $expolygon->translate(@$copy);
                     $print_polygon->($expolygon->contour, 'contour');
                     $print_polygon->($_, 'hole') for $expolygon->holes;
@@ -462,8 +520,9 @@ EOF
             foreach my $expolygon (@unsupported_slices) {
                 # look for the nearest point to this island among all
                 # supported points
-                my $support_point = nearest_point($expolygon->contour->[0], \@supported_points);
-                my $anchor_point = nearest_point($support_point, $expolygon->contour->[0]);
+                my $support_point = nearest_point($expolygon->contour->[0], \@supported_points)
+                    or next;
+                my $anchor_point = nearest_point($support_point, $expolygon->contour);
                 printf $fh qq{    <line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width: 2; stroke: white" />\n},
                     map @$_, $support_point, $anchor_point;
             }
@@ -488,11 +547,11 @@ sub make_skirt {
     foreach my $obj_idx (0 .. $#{$self->objects}) {
         my @layers = map $self->objects->[$obj_idx]->layer($_), 0..($skirt_height-1);
         my @layer_points = (
-            (map @$_, map @{$_->expolygon}, map @{$_->slices}, @layers),
-            (map @$_, map @{$_->thin_walls}, @layers),
+            (map @$_, map @$_, map @{$_->slices}, @layers),
+            (map @$_, map @{$_->thin_walls}, map @{$_->regions}, @layers),
             (map @{$_->unpack->polyline}, map @{$_->support_fills->paths}, grep $_->support_fills, @layers),
         );
-        push @points, map move_points($_, @layer_points), @{$self->copies->[$obj_idx]};
+        push @points, map move_points($_, @layer_points), @{$self->objects->[$obj_idx]->copies};
     }
     return if @points < 3;  # at least three points required for a convex hull
     
@@ -518,16 +577,16 @@ sub make_brim {
     return unless $Slic3r::Config->brim_width > 0;
     
     my $flow = $Slic3r::first_layer_flow || $Slic3r::flow;
-    my $grow_distance = scale $flow->width / 2;
+    my $grow_distance = $flow->scaled_width / 2;
     my @islands = (); # array of polygons
     foreach my $obj_idx (0 .. $#{$self->objects}) {
         my $layer0 = $self->objects->[$obj_idx]->layers->[0];
         my @object_islands = (
             (map $_->contour, @{$layer0->slices}),
-            (map { $_->isa('Slic3r::Polygon') ? $_ : $_->grow($grow_distance) } @{$layer0->thin_walls}),
+            (map { $_->isa('Slic3r::Polygon') ? $_ : $_->grow($grow_distance) } map @{$_->thin_walls}, @{$layer0->regions}),
             (map $_->unpack->polyline->grow($grow_distance), map @{$_->support_fills->paths}, grep $_->support_fills, $layer0),
         );
-        foreach my $copy (@{$self->copies->[$obj_idx]}) {
+        foreach my $copy (@{$self->objects->[$obj_idx]->copies}) {
             push @islands, map $_->clone->translate(@$copy), @object_islands;
         }
     }
@@ -538,7 +597,8 @@ sub make_brim {
         push @{$self->brim}, Slic3r::ExtrusionLoop->pack(
             polygon => Slic3r::Polygon->new($_),
             role    => EXTR_ROLE_SKIRT,
-        ) for @{Math::Clipper::offset(\@islands, $i * scale $flow->spacing, 100, JT_SQUARE)};
+        ) for @{Math::Clipper::offset(\@islands, $i * $flow->scaled_spacing, 100, JT_SQUARE)};
+        # TODO: we need the offset inwards/offset outwards logic to avoid overlapping extrusions
     }
 }
 
@@ -570,26 +630,28 @@ sub write_gcode {
     print  $fh "\n";
     
     # set up our extruder object
-    my $gcodegen = Slic3r::GCode->new;
+    my $gcodegen = Slic3r::GCode->new(
+        multiple_extruders => (@{$self->extruders} > 1),
+    );
     my $min_print_speed = 60 * $Slic3r::Config->min_print_speed;
     my $dec = $gcodegen->dec;
-    print $fh $gcodegen->set_tool(0);
+    print $fh $gcodegen->set_extruder($self->extruders->[0]);
     print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers;
     
     # 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;
     my $print_first_layer_temperature = sub {
-        for my $t (grep $Slic3r::extruders->[$_], 0 .. $#{$Slic3r::Config->first_layer_temperature}) {
-            printf $fh $gcodegen->set_temperature($Slic3r::extruders->[$t]->first_layer_temperature, 0, $t)
-                if $Slic3r::extruders->[$t]->first_layer_temperature;
+        for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->first_layer_temperature}) {
+            printf $fh $gcodegen->set_temperature($self->extruders->[$t]->first_layer_temperature, 0, $t)
+                if $self->extruders->[$t]->first_layer_temperature;
         }
     };
     $print_first_layer_temperature->();
     printf $fh "%s\n", $Slic3r::Config->replace_options($Slic3r::Config->start_gcode);
-    for my $t (grep $Slic3r::extruders->[$_], 0 .. $#{$Slic3r::Config->first_layer_temperature}) {
-        printf $fh $gcodegen->set_temperature($Slic3r::extruders->[$t]->first_layer_temperature, 1, $t)
-            if $Slic3r::extruders->[$t]->first_layer_temperature && $Slic3r::Config->start_gcode !~ /M109/i;
+    for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->first_layer_temperature}) {
+        printf $fh $gcodegen->set_temperature($self->extruders->[$t]->first_layer_temperature, 1, $t)
+            if $self->extruders->[$t]->first_layer_temperature && $Slic3r::Config->start_gcode !~ /M109/i;
     }
     print  $fh "G90 ; use absolute coordinates\n";
     print  $fh "G21 ; set units to millimeters\n";
@@ -619,9 +681,9 @@ sub write_gcode {
         my $gcode = "";
         
         if ($layer_id == 1) {
-            for my $t (grep $Slic3r::extruders->[$_], 0 .. $#{$Slic3r::Config->temperature}) {
-                $gcode .= $gcodegen->set_temperature($Slic3r::extruders->[$t]->temperature, 0, $t)
-                    if $Slic3r::extruders->[$t]->temperature && $Slic3r::extruders->[$t]->temperature != $Slic3r::extruders->[$t]->first_layer_temperature;
+            for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->temperature}) {
+                $gcode .= $gcodegen->set_temperature($self->extruders->[$t]->temperature, 0, $t)
+                    if $self->extruders->[$t]->temperature && $self->extruders->[$t]->temperature != $self->extruders->[$t]->first_layer_temperature;
             }
             $gcode .= $gcodegen->set_bed_temperature($Slic3r::Config->bed_temperature)
                 if $Slic3r::Config->first_layer_bed_temperature && $Slic3r::Config->bed_temperature != $Slic3r::Config->first_layer_bed_temperature;
@@ -645,7 +707,7 @@ sub write_gcode {
         
         # extrude brim
         if ($layer_id == 0 && !$brim_done) {
-            $gcode .= $gcodegen->set_tool($Slic3r::Config->support_material_extruder-1);
+            $gcode .= $gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]);
             $gcodegen->shift_x($shift[X]);
             $gcodegen->shift_y($shift[Y]);
             $gcode .= $gcodegen->extrude_loop($_, 'brim') for @{$self->brim};
@@ -663,25 +725,30 @@ sub write_gcode {
             $gcodegen->shift_x($shift[X] + unscale $copy->[X]);
             $gcodegen->shift_y($shift[Y] + unscale $copy->[Y]);
             
-            # extrude perimeters
-            $gcode .= $gcodegen->set_tool($Slic3r::Config->perimeter_extruder-1);
-            $gcode .= $gcodegen->extrude($_, 'perimeter') for @{ $layer->perimeters };
-            
-            # extrude fills
-            $gcode .= $gcodegen->set_tool($Slic3r::Config->infill_extruder-1);
-            $gcode .= $gcodegen->set_acceleration($Slic3r::Config->infill_acceleration);
-            for my $fill (@{ $layer->fills }) {
-                if ($fill->isa('Slic3r::ExtrusionPath::Collection')) {
-                    $gcode .= $gcodegen->extrude($_, 'fill') 
-                        for $fill->shortest_path($gcodegen->last_pos);
-                } else {
-                    $gcode .= $gcodegen->extrude($fill, 'fill') ;
+            foreach my $region_id (0 .. ($self->regions_count-1)) {
+                my $layerm = $layer->regions->[$region_id];
+                my $region = $self->regions->[$region_id];
+                
+                # extrude perimeters
+                $gcode .= $gcodegen->set_extruder($region->extruders->{perimeter});
+                $gcode .= $gcodegen->extrude($_, 'perimeter') for @{ $layerm->perimeters };
+                
+                # extrude fills
+                $gcode .= $gcodegen->set_extruder($region->extruders->{infill});
+                $gcode .= $gcodegen->set_acceleration($Slic3r::Config->infill_acceleration);
+                for my $fill (@{ $layerm->fills }) {
+                    if ($fill->isa('Slic3r::ExtrusionPath::Collection')) {
+                        $gcode .= $gcodegen->extrude($_, 'fill') 
+                            for $fill->shortest_path($gcodegen->last_pos);
+                    } else {
+                        $gcode .= $gcodegen->extrude($fill, 'fill') ;
+                    }
                 }
             }
-            
+                
             # extrude support material
             if ($layer->support_fills) {
-                $gcode .= $gcodegen->set_tool($Slic3r::Config->support_material_extruder-1);
+                $gcode .= $gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]);
                 $gcode .= $gcodegen->extrude_path($_, 'support material') 
                     for $layer->support_fills->shortest_path($gcodegen->last_pos);
             }
@@ -733,7 +800,7 @@ sub write_gcode {
         
         my $finished_objects = 0;
         for my $obj_idx (@obj_idx) {
-            for my $copy (@{ $self->copies->[$obj_idx] }) {
+            for my $copy (@{ $self->objects->[$obj_idx]->copies }) {
                 # move to the origin position for the copy we're going to print.
                 # this happens before Z goes down to layer 0 again, so that 
                 # no collision happens hopefully.
@@ -762,7 +829,7 @@ sub write_gcode {
         for my $layer_id (0..$self->layer_count-1) {
             my @object_copies = ();
             for my $obj_idx (grep $self->objects->[$_]->layers->[$layer_id], 0..$#{$self->objects}) {
-                push @object_copies, map [ $obj_idx, $_ ], @{ $self->copies->[$obj_idx] };
+                push @object_copies, map [ $obj_idx, $_ ], @{ $self->objects->[$obj_idx]->copies };
             }
             print $fh $extrude_layer->($layer_id, \@object_copies);
         }
@@ -786,7 +853,7 @@ sub write_gcode {
 
 sub total_extrusion_volume {
     my $self = shift;
-    return $self->total_extrusion_length * ($Slic3r::extruders->[0]->filament_diameter**2) * PI/4 / 1000;
+    return $self->total_extrusion_length * ($self->extruders->[0]->filament_diameter**2) * PI/4 / 1000;
 }
 
 # this method will return the supplied input file path after expanding its
diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm
index 696d72ca3..ccb9723ff 100644
--- a/lib/Slic3r/Print/Object.pm
+++ b/lib/Slic3r/Print/Object.pm
@@ -6,16 +6,12 @@ use Slic3r::Geometry qw(scale unscale deg2rad);
 use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex);
 use Slic3r::Surface ':types';
 
+has 'print'             => (is => 'ro', weak_ref => 1, required => 1);
 has 'input_file'        => (is => 'rw', required => 0);
-has 'mesh'              => (is => 'rw', required => 0);
+has 'meshes'            => (is => 'rw', default => sub { [] });  # by region_id
 has 'size'              => (is => 'rw', required => 1);
-
-has 'layers' => (
-    traits  => ['Array'],
-    is      => 'rw',
-    #isa     => 'ArrayRef[Slic3r::Layer]',
-    default => sub { [] },
-);
+has 'copies'            => (is => 'rw', default => sub {[ [0,0] ]});
+has 'layers'            => (is => 'rw', default => sub { [] });
 
 sub layer_count {
     my $self = shift;
@@ -27,11 +23,8 @@ sub layer {
     my ($layer_id) = @_;
     
     # extend our print by creating all necessary layers
-    
-    if ($self->layer_count < $layer_id + 1) {
-        for (my $i = $self->layer_count; $i <= $layer_id; $i++) {
-            push @{ $self->layers }, Slic3r::Layer->new(id => $i);
-        }
+    for (my $i = $self->layer_count; $i <= $layer_id; $i++) {
+        push @{ $self->layers }, Slic3r::Layer->new(id => $i, object => $self);
     }
     
     return $self->layers->[$layer_id];
@@ -42,22 +35,24 @@ sub slice {
     my %params = @_;
     
     # process facets
-    {
+    for my $region_id (0 .. $#{$self->meshes}) {
+        my $mesh = $self->meshes->[$region_id];  # ignore undef meshes
+        
         my $apply_lines = sub {
             my $lines = shift;
             foreach my $layer_id (keys %$lines) {
-                my $layer = $self->layer($layer_id);
-                push @{$layer->lines}, @{$lines->{$layer_id}};
+                my $layerm = $self->layer($layer_id)->region($region_id);
+                push @{$layerm->lines}, @{$lines->{$layer_id}};
             }
         };
         Slic3r::parallelize(
-            disable => ($#{$self->mesh->facets} < 500),  # don't parallelize when too few facets
-            items => [ 0..$#{$self->mesh->facets} ],
+            disable => ($#{$mesh->facets} < 500),  # don't parallelize when too few facets
+            items => [ 0..$#{$mesh->facets} ],
             thread_cb => sub {
                 my $q = shift;
                 my $result_lines = {};
                 while (defined (my $facet_id = $q->dequeue)) {
-                    my $lines = $self->mesh->slice_facet($self, $facet_id);
+                    my $lines = $mesh->slice_facet($self, $facet_id);
                     foreach my $layer_id (keys %$lines) {
                         $result_lines->{$layer_id} ||= [];
                         push @{ $result_lines->{$layer_id} }, @{ $lines->{$layer_id} };
@@ -69,8 +64,8 @@ sub slice {
                 $apply_lines->($_[0]);
             },
             no_threads_cb => sub {
-                for (0..$#{$self->mesh->facets}) {
-                    my $lines = $self->mesh->slice_facet($self, $_);
+                for (0..$#{$mesh->facets}) {
+                    my $lines = $mesh->slice_facet($self, $_);
                     $apply_lines->($lines);
                 }
             },
@@ -79,13 +74,16 @@ sub slice {
     die "Invalid input file\n" if !@{$self->layers};
     
     # free memory
-    $self->mesh(undef) unless $params{keep_meshes};
+    $self->meshes(undef) unless $params{keep_meshes};
     
     # remove last layer if empty
-    # (we might have created it because of the $max_layer = ... + 1 code below)
-    pop @{$self->layers} if !@{$self->layers->[-1]->lines};
+    # (we might have created it because of the $max_layer = ... + 1 code in TriangleMesh)
+    pop @{$self->layers} if !map @{$_->lines}, @{$self->layers->[-1]->regions};
     
     foreach my $layer (@{ $self->layers }) {
+        # make sure all layers contain layer region objects for all regions
+        $layer->region($_) for 0 .. ($self->print->regions_count-1);
+        
         Slic3r::debugf "Making surfaces for layer %d (slice z = %f):\n",
             $layer->id, unscale $layer->slice_z if $Slic3r::debug;
         
@@ -97,10 +95,17 @@ sub slice {
         # inside a closed polyline)
         
         # build surfaces from sparse lines
-        $layer->make_surfaces(Slic3r::TriangleMesh::make_loops($layer));
+        foreach my $layerm (@{$layer->regions}) {
+            my ($slicing_errors, $loops) = Slic3r::TriangleMesh::make_loops($layerm->lines);
+            $layer->slicing_errors(1) if $slicing_errors;
+            $layerm->make_surfaces($loops);
+            
+            # free memory
+            $layerm->lines(undef);
+        }
         
-        # free memory
-        $layer->lines(undef);
+        # merge all regions' slices to get islands
+        $layer->make_slices;
     }
     
     # detect slicing errors
@@ -118,35 +123,42 @@ sub slice {
         # neighbor layers
         Slic3r::debugf "Attempting to repair layer %d\n", $i;
         
-        my (@upper_surfaces, @lower_surfaces);
-        for (my $j = $i+1; $j <= $#{$self->layers}; $j++) {
-            if (!$self->layers->[$j]->slicing_errors) {
-                @upper_surfaces = @{$self->layers->[$j]->slices};
-                last;
+        foreach my $region_id (0 .. $#{$layer->regions}) {
+            my $layerm = $layer->region($region_id);
+            
+            my (@upper_surfaces, @lower_surfaces);
+            for (my $j = $i+1; $j <= $#{$self->layers}; $j++) {
+                if (!$self->layers->[$j]->slicing_errors) {
+                    @upper_surfaces = @{$self->layers->[$j]->region($region_id)->slices};
+                    last;
+                }
             }
-        }
-        for (my $j = $i-1; $j >= 0; $j--) {
-            if (!$self->layers->[$j]->slicing_errors) {
-                @lower_surfaces = @{$self->layers->[$j]->slices};
-                last;
+            for (my $j = $i-1; $j >= 0; $j--) {
+                if (!$self->layers->[$j]->slicing_errors) {
+                    @lower_surfaces = @{$self->layers->[$j]->region($region_id)->slices};
+                    last;
+                }
             }
+            
+            my $union = union_ex([
+                map $_->expolygon->contour, @upper_surfaces, @lower_surfaces,
+            ]);
+            my $diff = diff_ex(
+                [ map @$_, @$union ],
+                [ map $_->expolygon->holes, @upper_surfaces, @lower_surfaces, ],
+            );
+            
+            @{$layerm->slices} = map Slic3r::Surface->new
+                (expolygon => $_, surface_type => S_TYPE_INTERNAL),
+                @$diff;
         }
-        
-        my $union = union_ex([
-            map $_->expolygon->contour, @upper_surfaces, @lower_surfaces,
-        ]);
-        my $diff = diff_ex(
-            [ map @$_, @$union ],
-            [ map $_->expolygon->holes, @upper_surfaces, @lower_surfaces, ],
-        );
-        
-        @{$layer->slices} = map Slic3r::Surface->new
-            (expolygon => $_, surface_type => S_TYPE_INTERNAL),
-            @$diff;
+            
+        # update layer slices after repairing the single regions
+        $layer->make_slices;
     }
     
     # remove empty layers from bottom
-    while (@{$self->layers} && !@{$self->layers->[0]->slices} && !@{$self->layers->[0]->thin_walls}) {
+    while (@{$self->layers} && !@{$self->layers->[0]->slices} && !map @{$_->thin_walls}, @{$self->layers->[0]->regions}) {
         shift @{$self->layers};
         for (my $i = 0; $i <= $#{$self->layers}; $i++) {
             $self->layers->[$i]->id($i);
@@ -157,11 +169,6 @@ sub slice {
         if !@{$self->layers};
 }
 
-sub cleanup {
-    my $self = shift;
-    @{$self->layers} = ();
-}
-
 sub make_perimeters {
     my $self = shift;
     
@@ -170,53 +177,56 @@ sub make_perimeters {
     
     # this algorithm makes sure that almost one perimeter is overlapping
     if ($Slic3r::Config->extra_perimeters && $Slic3r::Config->perimeters > 0) {
-        for my $layer_id (0 .. $self->layer_count-2) {
-            my $layer = $self->layers->[$layer_id];
-            my $upper_layer = $self->layers->[$layer_id+1];
-            
-            my $overlap = $layer->perimeter_flow->spacing;  # one perimeter
-            
-            # compute polygons representing the thickness of the first external perimeter of
-            # the upper layer slices
-            my $upper = diff_ex(
-                [ map @$_, map $_->expolygon->offset_ex(+ 0.5 * scale $layer->perimeter_flow->spacing), @{$upper_layer->slices} ],
-                [ map @$_, map $_->expolygon->offset_ex(- scale($overlap) + (0.5 * scale $layer->perimeter_flow->spacing)), @{$upper_layer->slices} ],
-            );
-            next if !@$upper;
-            
-            # we need to limit our detection to the areas which would actually benefit from 
-            # more perimeters. so, let's compute the area we want to ignore
-            my $ignore = [];
-            {
-                my $diff = diff_ex(
-                    [ map @$_, map $_->expolygon->offset_ex(- ($Slic3r::Config->perimeters-0.5) * scale $layer->perimeter_flow->spacing), @{$layer->slices} ],
-                    [ map @{$_->expolygon}, @{$upper_layer->slices} ],
+        for my $region_id (0 .. ($self->print->regions_count-1)) {
+            for my $layer_id (0 .. $self->layer_count-2) {
+                my $layerm          = $self->layers->[$layer_id]->regions->[$region_id];
+                my $upper_layerm    = $self->layers->[$layer_id+1]->regions->[$region_id];
+                my $perimeter_flow  = $layerm->perimeter_flow;
+                
+                my $overlap = $perimeter_flow->spacing;  # one perimeter
+                
+                # compute polygons representing the thickness of the first external perimeter of
+                # the upper layer slices
+                my $upper = diff_ex(
+                    [ map @$_, map $_->expolygon->offset_ex(+ 0.5 * $perimeter_flow->scaled_spacing), @{$upper_layerm->slices} ],
+                    [ map @$_, map $_->expolygon->offset_ex(- scale($overlap) + (0.5 * $perimeter_flow->scaled_spacing)), @{$upper_layerm->slices} ],
                 );
-                $ignore = [ map @$_, map $_->offset_ex(scale $layer->perimeter_flow->spacing), @$diff ];
-            }
-            
-            foreach my $slice (@{$layer->slices}) {
-                my $hypothetical_perimeter_num = $Slic3r::Config->perimeters + 1;
-                CYCLE: while (1) {
-                    # compute polygons representing the thickness of the hypotetical new internal perimeter
-                    # of our slice
-                    my $hypothetical_perimeter;
-                    {
-                        my $outer = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-1.5) * scale $layer->perimeter_flow->spacing) ];
-                        last CYCLE if !@$outer;
-                        my $inner = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-0.5) * scale $layer->perimeter_flow->spacing) ];
-                        last CYCLE if !@$inner;
-                        $hypothetical_perimeter = diff_ex($outer, $inner);
+                next if !@$upper;
+                
+                # we need to limit our detection to the areas which would actually benefit from 
+                # more perimeters. so, let's compute the area we want to ignore
+                my $ignore = [];
+                {
+                    my $diff = diff_ex(
+                        [ map @$_, map $_->expolygon->offset_ex(- ($Slic3r::Config->perimeters-0.5) * $perimeter_flow->scaled_spacing), @{$layerm->slices} ],
+                        [ map @{$_->expolygon}, @{$upper_layerm->slices} ],
+                    );
+                    $ignore = [ map @$_, map $_->offset_ex($perimeter_flow->scaled_spacing), @$diff ];
+                }
+                
+                foreach my $slice (@{$layerm->slices}) {
+                    my $hypothetical_perimeter_num = $Slic3r::Config->perimeters + 1;
+                    CYCLE: while (1) {
+                        # compute polygons representing the thickness of the hypotetical new internal perimeter
+                        # of our slice
+                        my $hypothetical_perimeter;
+                        {
+                            my $outer = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-1.5) * $perimeter_flow->scaled_spacing) ];
+                            last CYCLE if !@$outer;
+                            my $inner = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-0.5) * $perimeter_flow->scaled_spacing) ];
+                            last CYCLE if !@$inner;
+                            $hypothetical_perimeter = diff_ex($outer, $inner);
+                        }
+                        last CYCLE if !@$hypothetical_perimeter;
+                        
+                        
+                        my $intersection = intersection_ex([ map @$_, @$upper ], [ map @$_, @$hypothetical_perimeter ]);
+                        $intersection = diff_ex([ map @$_, @$intersection ], $ignore) if @$ignore;
+                        last CYCLE if !@{ $intersection };
+                        Slic3r::debugf "  adding one more perimeter at layer %d\n", $layer_id;
+                        $slice->additional_inner_perimeters(($slice->additional_inner_perimeters || 0) + 1);
+                        $hypothetical_perimeter_num++;
                     }
-                    last CYCLE if !@$hypothetical_perimeter;
-                    
-                    
-                    my $intersection = intersection_ex([ map @$_, @$upper ], [ map @$_, @$hypothetical_perimeter ]);
-                    $intersection = diff_ex([ map @$_, @$intersection ], $ignore) if @$ignore;
-                    last CYCLE if !@{ $intersection };
-                    Slic3r::debugf "  adding one more perimeter at layer %d\n", $layer_id;
-                    $slice->additional_inner_perimeters(($slice->additional_inner_perimeters || 0) + 1);
-                    $hypothetical_perimeter_num++;
                 }
             }
         }
@@ -231,75 +241,80 @@ sub detect_surfaces_type {
     
     # prepare a reusable subroutine to make surface differences
     my $surface_difference = sub {
-        my ($subject_surfaces, $clip_surfaces, $result_type, $layer) = @_;
+        my ($subject_surfaces, $clip_surfaces, $result_type, $layerm) = @_;
         my $expolygons = diff_ex(
             [ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$subject_surfaces ],
             [ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$clip_surfaces ],
             1,
         );
-        return grep $_->contour->is_printable($layer->flow->width),
+        return grep $_->contour->is_printable($layerm->flow->width),
             map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type), 
             @$expolygons;
     };
     
-    for (my $i = 0; $i < $self->layer_count; $i++) {
-        my $layer = $self->layers->[$i];
-        my $upper_layer = $self->layers->[$i+1];
-        my $lower_layer = $i > 0 ? $self->layers->[$i-1] : undef;
-        
-        my (@bottom, @top, @internal) = ();
-        
-        # find top surfaces (difference between current surfaces
-        # of current layer and upper one)
-        if ($upper_layer) {
-            @top = $surface_difference->($layer->slices, $upper_layer->slices, S_TYPE_TOP, $layer);
-        } else {
-            # if no upper layer, all surfaces of this one are solid
-            @top = @{$layer->slices};
-            $_->surface_type(S_TYPE_TOP) for @top;
+    for my $region_id (0 .. ($self->print->regions_count-1)) {
+        for (my $i = 0; $i < $self->layer_count; $i++) {
+            my $layerm = $self->layers->[$i]->regions->[$region_id];
+            
+            # comparison happens against the *full* slices (considering all regions)
+            my $upper_layer = $self->layers->[$i+1];
+            my $lower_layer = $i > 0 ? $self->layers->[$i-1] : undef;
+            
+            my (@bottom, @top, @internal) = ();
+            
+            # find top surfaces (difference between current surfaces
+            # of current layer and upper one)
+            if ($upper_layer) {
+                @top = $surface_difference->($layerm->slices, $upper_layer->slices, S_TYPE_TOP, $layerm);
+            } else {
+                # if no upper layer, all surfaces of this one are solid
+                @top = @{$layerm->slices};
+                $_->surface_type(S_TYPE_TOP) for @top;
+            }
+            
+            # find bottom surfaces (difference between current surfaces
+            # of current layer and lower one)
+            if ($lower_layer) {
+                @bottom = $surface_difference->($layerm->slices, $lower_layer->slices, S_TYPE_BOTTOM, $layerm);
+            } else {
+                # if no lower layer, all surfaces of this one are solid
+                @bottom = @{$layerm->slices};
+                $_->surface_type(S_TYPE_BOTTOM) for @bottom;
+            }
+            
+            # now, if the object contained a thin membrane, we could have overlapping bottom
+            # and top surfaces; let's do an intersection to discover them and consider them
+            # as bottom surfaces (to allow for bridge detection)
+            if (@top && @bottom) {
+                my $overlapping = intersection_ex([ map $_->p, @top ], [ map $_->p, @bottom ]);
+                Slic3r::debugf "  layer %d contains %d membrane(s)\n", $layerm->id, scalar(@$overlapping);
+                @top = $surface_difference->([@top], $overlapping, S_TYPE_TOP, $layerm);
+            }
+            
+            # find internal surfaces (difference between top/bottom surfaces and others)
+            @internal = $surface_difference->($layerm->slices, [@top, @bottom], S_TYPE_INTERNAL, $layerm);
+            
+            # save surfaces to layer
+            @{$layerm->slices} = (@bottom, @top, @internal);
+            
+            Slic3r::debugf "  layer %d has %d bottom, %d top and %d internal surfaces\n",
+                $layerm->id, scalar(@bottom), scalar(@top), scalar(@internal);
         }
         
-        # find bottom surfaces (difference between current surfaces
-        # of current layer and lower one)
-        if ($lower_layer) {
-            @bottom = $surface_difference->($layer->slices, $lower_layer->slices, S_TYPE_BOTTOM, $layer);
-        } else {
-            # if no lower layer, all surfaces of this one are solid
-            @bottom = @{$layer->slices};
-            $_->surface_type(S_TYPE_BOTTOM) for @bottom;
-        }
-        
-        # now, if the object contained a thin membrane, we could have overlapping bottom
-        # and top surfaces; let's do an intersection to discover them and consider them
-        # as bottom surfaces (to allow for bridge detection)
-        if (@top && @bottom) {
-            my $overlapping = intersection_ex([ map $_->p, @top ], [ map $_->p, @bottom ]);
-            Slic3r::debugf "  layer %d contains %d membrane(s)\n", $layer->id, scalar(@$overlapping);
-            @top = $surface_difference->([@top], $overlapping, S_TYPE_TOP, $layer);
-        }
-        
-        # find internal surfaces (difference between top/bottom surfaces and others)
-        @internal = $surface_difference->($layer->slices, [@top, @bottom], S_TYPE_INTERNAL, $layer);
-        
-        # save surfaces to layer
-        @{$layer->slices} = (@bottom, @top, @internal);
-        
-        Slic3r::debugf "  layer %d has %d bottom, %d top and %d internal surfaces\n",
-            $layer->id, scalar(@bottom), scalar(@top), scalar(@internal);
-    }
-    
-    # clip surfaces to the fill boundaries
-    foreach my $layer (@{$self->layers}) {
-        my $fill_boundaries = [ map @$_, @{$layer->surfaces} ];
-        @{$layer->surfaces} = ();
-        foreach my $surface (@{$layer->slices}) {
-            my $intersection = intersection_ex(
-                [ $surface->p ],
-                $fill_boundaries,
-            );
-            push @{$layer->surfaces}, map Slic3r::Surface->new
-                (expolygon => $_, surface_type => $surface->surface_type),
-                @$intersection;
+        # clip surfaces to the fill boundaries
+        foreach my $layer (@{$self->layers}) {
+            my $layerm = $layer->regions->[$region_id];
+            my $fill_boundaries = [ map @$_, @{$layerm->surfaces} ];
+            @{$layerm->surfaces} = ();
+            foreach my $surface (@{$layerm->slices}) {
+                my $intersection = intersection_ex(
+                    [ $surface->p ],
+                    $fill_boundaries,
+                );
+                push @{$layerm->surfaces}, map Slic3r::Surface->new
+                    (expolygon => $_, surface_type => $surface->surface_type),
+                    @$intersection;
+            }
         }
     }
 }
@@ -309,84 +324,92 @@ sub discover_horizontal_shells {
     
     Slic3r::debugf "==> DISCOVERING HORIZONTAL SHELLS\n";
     
-    my $area_threshold = scale($Slic3r::flow->spacing) ** 2;
+    my $area_threshold = $Slic3r::flow->scaled_spacing ** 2;
     
-    for (my $i = 0; $i < $self->layer_count; $i++) {
-        my $layer = $self->layers->[$i];
-        foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM) {
-            # find surfaces of current type for current layer
-            # and offset them to take perimeters into account
-            my @surfaces = map $_->offset($Slic3r::Config->perimeters * scale $layer->perimeter_flow->width),
-                grep $_->surface_type == $type, @{$layer->fill_surfaces} or next;
-            my $surfaces_p = [ map $_->p, @surfaces ];
-            Slic3r::debugf "Layer %d has %d surfaces of type '%s'\n",
-                $i, scalar(@surfaces), ($type == S_TYPE_TOP ? 'top' : 'bottom');
+    for my $region_id (0 .. ($self->print->regions_count-1)) {
+        for (my $i = 0; $i < $self->layer_count; $i++) {
+            my $layerm = $self->layers->[$i]->regions->[$region_id];
             
-            for (my $n = $type == S_TYPE_TOP ? $i-1 : $i+1; 
-                    abs($n - $i) <= $Slic3r::Config->solid_layers-1; 
-                    $type == S_TYPE_TOP ? $n-- : $n++) {
+            if ($Slic3r::Config->solid_infill_every_layers && ($i % $Slic3r::Config->solid_infill_every_layers) == 0) {
+                $_->surface_type(S_TYPE_INTERNALSOLID)
+                    for grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces};
+            }
+            
+            foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM) {
+                # find surfaces of current type for current layer
+                # and offset them to take perimeters into account
+                my @surfaces = map $_->offset($Slic3r::Config->perimeters * $layerm->perimeter_flow->scaled_width),
+                    grep $_->surface_type == $type, @{$layerm->fill_surfaces} or next;
+                my $surfaces_p = [ map $_->p, @surfaces ];
+                Slic3r::debugf "Layer %d has %d surfaces of type '%s'\n",
+                    $i, scalar(@surfaces), ($type == S_TYPE_TOP ? 'top' : 'bottom');
                 
-                next if $n < 0 || $n >= $self->layer_count;
-                Slic3r::debugf "  looking for neighbors on layer %d...\n", $n;
-                
-                my @neighbor_surfaces = @{$self->layers->[$n]->surfaces};
-                my @neighbor_fill_surfaces = @{$self->layers->[$n]->fill_surfaces};
-                
-                # find intersection between neighbor and current layer's surfaces
-                # intersections have contours and holes
-                my $new_internal_solid = intersection_ex(
-                    $surfaces_p,
-                    [ map $_->p, grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @neighbor_surfaces ],
-                    undef, 1,
-                );
-                next if !@$new_internal_solid;
-                
-                # internal-solid are the union of the existing internal-solid surfaces
-                # and new ones
-                my $internal_solid = union_ex([
-                    ( map $_->p, grep $_->surface_type == S_TYPE_INTERNALSOLID, @neighbor_fill_surfaces ),
-                    ( map @$_, @$new_internal_solid ),
-                ]);
-                
-                # subtract intersections from layer surfaces to get resulting inner surfaces
-                my $internal = diff_ex(
-                    [ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @neighbor_fill_surfaces ],
-                    [ map @$_, @$internal_solid ],
-                    1,
-                );
-                Slic3r::debugf "    %d internal-solid and %d internal surfaces found\n",
-                    scalar(@$internal_solid), scalar(@$internal);
-                
-                # Note: due to floating point math we're going to get some very small
-                # polygons as $internal; they will be removed by removed_small_features()
-                
-                # assign resulting inner surfaces to layer
-                my $neighbor_fill_surfaces = $self->layers->[$n]->fill_surfaces;
-                @$neighbor_fill_surfaces = ();
-                push @$neighbor_fill_surfaces, Slic3r::Surface->new
-                    (expolygon => $_, surface_type => S_TYPE_INTERNAL)
-                    for @$internal;
-                
-                # assign new internal-solid surfaces to layer
-                push @$neighbor_fill_surfaces, Slic3r::Surface->new
-                    (expolygon => $_, surface_type => S_TYPE_INTERNALSOLID)
-                    for @$internal_solid;
-                
-                # assign top and bottom surfaces to layer
-                foreach my $s (Slic3r::Surface->group(grep { $_->surface_type == S_TYPE_TOP || $_->surface_type == S_TYPE_BOTTOM } @neighbor_fill_surfaces)) {
-                    my $solid_surfaces = diff_ex(
-                        [ map $_->p, @$s ],
-                        [ map @$_, @$internal_solid, @$internal ],
+                for (my $n = $type == S_TYPE_TOP ? $i-1 : $i+1; 
+                        abs($n - $i) <= $Slic3r::Config->solid_layers-1; 
+                        $type == S_TYPE_TOP ? $n-- : $n++) {
+                    
+                    next if $n < 0 || $n >= $self->layer_count;
+                    Slic3r::debugf "  looking for neighbors on layer %d...\n", $n;
+                    
+                    my @neighbor_surfaces       = @{$self->layers->[$n]->regions->[$region_id]->surfaces};
+                    my @neighbor_fill_surfaces  = @{$self->layers->[$n]->regions->[$region_id]->fill_surfaces};
+                    
+                    # find intersection between neighbor and current layer's surfaces
+                    # intersections have contours and holes
+                    my $new_internal_solid = intersection_ex(
+                        $surfaces_p,
+                        [ map $_->p, grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @neighbor_surfaces ],
+                        undef, 1,
+                    );
+                    next if !@$new_internal_solid;
+                    
+                    # internal-solid are the union of the existing internal-solid surfaces
+                    # and new ones
+                    my $internal_solid = union_ex([
+                        ( map $_->p, grep $_->surface_type == S_TYPE_INTERNALSOLID, @neighbor_fill_surfaces ),
+                        ( map @$_, @$new_internal_solid ),
+                    ]);
+                    
+                    # subtract intersections from layer surfaces to get resulting inner surfaces
+                    my $internal = diff_ex(
+                        [ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @neighbor_fill_surfaces ],
+                        [ map @$_, @$internal_solid ],
                         1,
                     );
+                    Slic3r::debugf "    %d internal-solid and %d internal surfaces found\n",
+                        scalar(@$internal_solid), scalar(@$internal);
+                    
+                    # Note: due to floating point math we're going to get some very small
+                    # polygons as $internal; they will be removed by removed_small_features()
+                    
+                    # assign resulting inner surfaces to layer
+                    my $neighbor_fill_surfaces = $self->layers->[$n]->regions->[$region_id]->fill_surfaces;
+                    @$neighbor_fill_surfaces = ();
                     push @$neighbor_fill_surfaces, Slic3r::Surface->new
-                        (expolygon => $_, surface_type => $s->[0]->surface_type, bridge_angle => $s->[0]->bridge_angle)
-                        for @$solid_surfaces;
+                        (expolygon => $_, surface_type => S_TYPE_INTERNAL)
+                        for @$internal;
+                    
+                    # assign new internal-solid surfaces to layer
+                    push @$neighbor_fill_surfaces, Slic3r::Surface->new
+                        (expolygon => $_, surface_type => S_TYPE_INTERNALSOLID)
+                        for @$internal_solid;
+                    
+                    # assign top and bottom surfaces to layer
+                    foreach my $s (Slic3r::Surface->group(grep { $_->surface_type == S_TYPE_TOP || $_->surface_type == S_TYPE_BOTTOM } @neighbor_fill_surfaces)) {
+                        my $solid_surfaces = diff_ex(
+                            [ map $_->p, @$s ],
+                            [ map @$_, @$internal_solid, @$internal ],
+                            1,
+                        );
+                        push @$neighbor_fill_surfaces, Slic3r::Surface->new
+                            (expolygon => $_, surface_type => $s->[0]->surface_type, bridge_angle => $s->[0]->bridge_angle)
+                            for @$solid_surfaces;
+                    }
                 }
             }
+            
+            @{$layerm->fill_surfaces} = grep $_->expolygon->area > $area_threshold, @{$layerm->fill_surfaces};
         }
-        
-        @{$layer->fill_surfaces} = grep $_->expolygon->area > $area_threshold, @{$layer->fill_surfaces};
     }
 }
 
@@ -395,99 +418,99 @@ sub combine_infill {
     my $self = shift;
     return unless $Slic3r::Config->infill_every_layers > 1 && $Slic3r::Config->fill_density > 0;
     
-    my $area_threshold = scale($Slic3r::flow->spacing) ** 2;
+    my $area_threshold = $Slic3r::flow->scaled_spacing ** 2;
     
-    # start from bottom, skip first layer
-    for (my $i = 1; $i < $self->layer_count; $i++) {
-        my $layer = $self->layer($i);
-        
-        # skip layer if no internal fill surfaces
-        next if !grep $_->surface_type == S_TYPE_INTERNAL, @{$layer->fill_surfaces};
-        
-        # for each possible depth, look for intersections with the lower layer
-        # we do this from the greater depth to the smaller
-        for (my $d = $Slic3r::Config->infill_every_layers - 1; $d >= 1; $d--) {
-            next if ($i - $d) < 0;
-            my $lower_layer = $self->layer($i - 1);
+    for my $region_id (0 .. ($self->print->regions_count-1)) {
+        # start from bottom, skip first layer
+        for (my $i = 1; $i < $self->layer_count; $i++) {
+            my $layerm = $self->layers->[$i]->regions->[$region_id];
             
-            # select surfaces of the lower layer having the depth we're looking for
-            my @lower_surfaces = grep $_->depth_layers == $d && $_->surface_type == S_TYPE_INTERNAL,
-                @{$lower_layer->fill_surfaces};
-            next if !@lower_surfaces;
+            # skip layer if no internal fill surfaces
+            next if !grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces};
             
-            # calculate intersection between our surfaces and theirs
-            my $intersection = intersection_ex(
-                [ map $_->p, grep $_->depth_layers <= $d, @lower_surfaces ],
-                [ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @{$layer->fill_surfaces} ],
-                undef, 1,
-            );
-            
-            # purge intersections, skip tiny regions
-            @$intersection = grep $_->area > $area_threshold, @$intersection;
-            next if !@$intersection;
-            
-            # new fill surfaces of the current layer are:
-            # - any non-internal surface
-            # - intersections found (with a $d + 1 depth)
-            # - any internal surface not belonging to the intersection (with its original depth)
-            {
-                my @new_surfaces = ();
-                push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$layer->fill_surfaces};
-                push @new_surfaces, map Slic3r::Surface->new
-                    (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $d + 1), @$intersection;
+            # for each possible depth, look for intersections with the lower layer
+            # we do this from the greater depth to the smaller
+            for (my $d = $Slic3r::Config->infill_every_layers - 1; $d >= 1; $d--) {
+                next if ($i - $d) < 0;
+                my $lower_layerm = $self->layer($i - 1)->regions->[$region_id];
                 
-                foreach my $depth (reverse $d..$Slic3r::Config->infill_every_layers) {
+                # select surfaces of the lower layer having the depth we're looking for
+                my @lower_surfaces = grep $_->depth_layers == $d && $_->surface_type == S_TYPE_INTERNAL,
+                    @{$lower_layerm->fill_surfaces};
+                next if !@lower_surfaces;
+                
+                # calculate intersection between our surfaces and theirs
+                my $intersection = intersection_ex(
+                    [ map $_->p, grep $_->depth_layers <= $d, @lower_surfaces ],
+                    [ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces} ],
+                    undef, 1,
+                );
+                
+                # purge intersections, skip tiny regions
+                @$intersection = grep $_->area > $area_threshold, @$intersection;
+                next if !@$intersection;
+                
+                # new fill surfaces of the current layer are:
+                # - any non-internal surface
+                # - intersections found (with a $d + 1 depth)
+                # - any internal surface not belonging to the intersection (with its original depth)
+                {
+                    my @new_surfaces = ();
+                    push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$layerm->fill_surfaces};
                     push @new_surfaces, map Slic3r::Surface->new
-                        (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth),
-                        
-                        # difference between our internal layers with depth == $depth
-                        # and the intersection found
-                        @{diff_ex(
-                            [
-                                map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth, 
-                                    @{$layer->fill_surfaces},
-                            ],
-                            [ map @$_, @$intersection ],
-                            1,
-                        )};
+                        (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $d + 1), @$intersection;
+                    
+                    foreach my $depth (reverse $d..$Slic3r::Config->infill_every_layers) {
+                        push @new_surfaces, map Slic3r::Surface->new
+                            (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth),
+                            
+                            # difference between our internal layers with depth == $depth
+                            # and the intersection found
+                            @{diff_ex(
+                                [
+                                    map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth, 
+                                        @{$layerm->fill_surfaces},
+                                ],
+                                [ map @$_, @$intersection ],
+                                1,
+                            )};
+                    }
+                    @{$layerm->fill_surfaces} = @new_surfaces;
                 }
-                @{$layer->fill_surfaces} = @new_surfaces;
-            }
-            
-            # now we remove the intersections from lower layer
-            {
-                my @new_surfaces = ();
-                push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$lower_layer->fill_surfaces};
-                foreach my $depth (1..$Slic3r::Config->infill_every_layers) {
-                    push @new_surfaces, map Slic3r::Surface->new
-                        (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth),
-                        
-                        # difference between internal layers with depth == $depth
-                        # and the intersection found
-                        @{diff_ex(
-                            [
-                                map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth, 
-                                    @{$lower_layer->fill_surfaces},
-                            ],
-                            [ map @$_, @$intersection ],
-                            1,
-                        )};
+                
+                # now we remove the intersections from lower layer
+                {
+                    my @new_surfaces = ();
+                    push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$lower_layerm->fill_surfaces};
+                    foreach my $depth (1..$Slic3r::Config->infill_every_layers) {
+                        push @new_surfaces, map Slic3r::Surface->new
+                            (expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth),
+                            
+                            # difference between internal layers with depth == $depth
+                            # and the intersection found
+                            @{diff_ex(
+                                [
+                                    map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth, 
+                                        @{$lower_layerm->fill_surfaces},
+                                ],
+                                [ map @$_, @$intersection ],
+                                1,
+                            )};
+                    }
+                    @{$lower_layerm->fill_surfaces} = @new_surfaces;
                 }
-                @{$lower_layer->fill_surfaces} = @new_surfaces;
             }
-            
-            
         }
     }
 }
 
 sub generate_support_material {
     my $self = shift;
-    my %params = @_;
     
+    my $flow                    = $self->print->support_material_flow;
     my $threshold_rad           = deg2rad($Slic3r::Config->support_material_threshold + 1);   # +1 makes the threshold inclusive
     my $overhang_width          = $threshold_rad == 0 ? undef : scale $Slic3r::Config->layer_height * ((cos $threshold_rad) / (sin $threshold_rad));
-    my $distance_from_object    = 1.5 * scale $Slic3r::support_material_flow->width;
+    my $distance_from_object    = 1.5 * $flow->scaled_width;
     
     # determine support regions in each layer (for upper layers)
     Slic3r::debugf "Detecting regions\n";
@@ -504,22 +527,22 @@ sub generate_support_material {
             
             @current_support_regions = @{diff_ex(
                 [ map @$_, @current_support_regions ],
-                [ map @{$_->expolygon}, @{$layer->slices} ],
+                [ map @$_, @{$layer->slices} ],
             )};
             
             $layers{$i} = diff_ex(
                 [ map @$_, @current_support_regions ],
-                [ map @$_, map $_->expolygon->offset_ex($distance_from_object),  @{$layer->slices} ],
+                [ map @$_, map $_->offset_ex($distance_from_object), @{$layer->slices} ],
             );
-            $_->simplify(scale $Slic3r::support_material_flow->spacing * 2) for @{$layers{$i}};
+            $_->simplify($flow->scaled_spacing * 2) for @{$layers{$i}};
             
             # step 2: get layer overhangs and put them into queue for adding support inside lower layers
             # we need an angle threshold for this
             my @overhangs = ();
             if ($lower_layer) {
                 @overhangs = map $_->offset_ex(2 * $overhang_width), @{diff_ex(
-                    [ map @$_, map $_->expolygon->offset_ex(-$overhang_width), @{$layer->slices} ],
-                    [ map @{$_->expolygon}, @{$lower_layer->slices} ],
+                    [ map @$_, map $_->offset_ex(-$overhang_width), @{$layer->slices} ],
+                    [ map @$_, @{$lower_layer->slices} ],
                     1,
                 )};
             }
@@ -533,10 +556,10 @@ sub generate_support_material {
     my $support_patterns = [];  # in case we want cross-hatching
     {
         # 0.5 makes sure the paths don't get clipped externally when applying them to layers
-        my @support_material_areas = map $_->offset_ex(- 0.5 * scale $Slic3r::support_material_flow->width),
+        my @support_material_areas = map $_->offset_ex(- 0.5 * $flow->scaled_width),
             @{union_ex([ map $_->contour, map @$_, values %layers ])};
         
-        my $fill = Slic3r::Fill->new(print => $params{print});
+        my $fill = Slic3r::Fill->new(print => $self->print);
         my $filler = $fill->filler($Slic3r::Config->support_material_pattern);
         $filler->angle($Slic3r::Config->support_material_angle);
         {
@@ -544,8 +567,8 @@ sub generate_support_material {
             foreach my $expolygon (@support_material_areas) {
                 my @paths = $filler->fill_surface(
                     Slic3r::Surface->new(expolygon => $expolygon),
-                    density         => $Slic3r::support_material_flow->spacing / $Slic3r::Config->support_material_spacing,
-                    flow_spacing    => $Slic3r::support_material_flow->spacing,
+                    density         => $flow->spacing / $Slic3r::Config->support_material_spacing,
+                    flow_spacing    => $flow->spacing,
                 );
                 my $params = shift @paths;
                 
@@ -578,6 +601,11 @@ sub generate_support_material {
             foreach my $expolygon (@$expolygons) {
                 push @paths,
                     map $_->pack,
+                    map {
+                        $_->flow_spacing($self->print->first_layer_support_material_flow->spacing)
+                            if $layer_id == 0;
+                        $_;
+                    }
                     map $_->clip_with_expolygon($expolygon),
                     map $_->clip_with_polygon($expolygon->bounding_box_polygon),
                     @{$support_patterns->[ $layer_id % @$support_patterns ]};
diff --git a/lib/Slic3r/Print/Region.pm b/lib/Slic3r/Print/Region.pm
new file mode 100644
index 000000000..253fde2ba
--- /dev/null
+++ b/lib/Slic3r/Print/Region.pm
@@ -0,0 +1,8 @@
+package Slic3r::Print::Region;
+use Moo;
+
+has 'extruders'         => (is => 'rw', default => sub { {} }); # by role
+has 'flows'             => (is => 'rw', default => sub { {} }); # by role
+has 'first_layer_flows' => (is => 'rw', default => sub { {} }); # by role
+
+1;
diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm
index 7f9d929b1..770a2a8c4 100644
--- a/lib/Slic3r/TriangleMesh.pm
+++ b/lib/Slic3r/TriangleMesh.pm
@@ -167,9 +167,8 @@ sub unpack_line {
 }
 
 sub make_loops {
-    my ($layer) = @_;
-    
-    my @lines = map unpack_line($_), @{$layer->lines};
+    my ($lines) = @_;
+    my @lines = map unpack_line($_), @$lines;
     
     # remove tangent edges
     {
@@ -258,6 +257,7 @@ sub make_loops {
         (0..$#lines);
     
     my (@polygons, @failed_loops, %visited_lines) = ();
+    my $slicing_errors = 0;
     CYCLE: for (my $i = 0; $i <= $#lines; $i++) {
         my $line = $lines[$i];
         next if $visited_lines{$line};
@@ -272,24 +272,24 @@ sub make_loops {
                 $next_line = $lines[$by_a_id{$line->[I_B_ID]}];
             } else {
                 Slic3r::debugf "  line has no next_facet_index or b_id\n";
-                $layer->slicing_errors(1);
+                $slicing_errors = 1;
                 push @failed_loops, [@points] if @points;
                 next CYCLE;
             }
             
             if (!$next_line || $visited_lines{$next_line}) {
                 Slic3r::debugf "  failed to close this loop\n";
-                $layer->slicing_errors(1);
+                $slicing_errors = 1;
                 push @failed_loops, [@points] if @points;
                 next CYCLE;
             } elsif (defined $next_line->[I_PREV_FACET_INDEX] && $next_line->[I_PREV_FACET_INDEX] != $line->[I_FACET_INDEX]) {
                 Slic3r::debugf "  wrong prev_facet_index\n";
-                $layer->slicing_errors(1);
+                $slicing_errors = 1;
                 push @failed_loops, [@points] if @points;
                 next CYCLE;
             } elsif (defined $next_line->[I_A_ID] && $next_line->[I_A_ID] != $line->[I_B_ID]) {
                 Slic3r::debugf "  wrong a_id\n";
-                $layer->slicing_errors(1);
+                $slicing_errors = 1;
                 push @failed_loops, [@points] if @points;
                 next CYCLE;
             }
@@ -313,7 +313,7 @@ sub make_loops {
             if $Slic3r::debug;
     }
     
-    return [@polygons];
+    return ($slicing_errors, [@polygons]);
 }
 
 sub rotate {
@@ -381,21 +381,12 @@ sub duplicate {
 
 sub extents {
     my $self = shift;
-    my @extents = (map [undef, undef], X,Y,Z);
-    foreach my $vertex (@{$self->vertices}) {
-        for (X,Y,Z) {
-            $extents[$_][MIN] = $vertex->[$_] if !defined $extents[$_][MIN] || $vertex->[$_] < $extents[$_][MIN];
-            $extents[$_][MAX] = $vertex->[$_] if !defined $extents[$_][MAX] || $vertex->[$_] > $extents[$_][MAX];
-        }
-    }
-    return @extents;
+    return Slic3r::Geometry::bounding_box_3D($self->vertices);
 }
 
 sub size {
     my $self = shift;
-    
-    my @extents = $self->extents;
-    return map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z);
+    return Slic3r::Geometry::size_3D($self->vertices);
 }
 
 sub slice_facet {
diff --git a/slic3r.pl b/slic3r.pl
index e5c63b99e..bcda50571 100755
--- a/slic3r.pl
+++ b/slic3r.pl
@@ -88,9 +88,9 @@ if (@ARGV) {  # slicing from command line
     
     while (my $input_file = shift @ARGV) {
         my $print = Slic3r::Print->new(config => $config);
-        $print->add_objects_from_file($input_file);
+        $print->add_model(Slic3r::Model->read_from_file($input_file));
         if ($opt{merge}) {
-            $print->add_objects_from_file($_) for splice @ARGV, 0;
+            $print->add_model(Slic3r::Model->read_from_file($_)) for splice @ARGV, 0;
         }
         $print->duplicate;
         $print->arrange_objects if @{$print->objects} > 1;
@@ -201,6 +201,8 @@ $j
     --first-layer-height Layer height for first layer (mm or %, default: $config->{first_layer_height})
     --infill-every-layers
                         Infill every N layers (default: $config->{infill_every_layers})
+    --solid-infill-every-layers
+                        Force a solid layer every N layers (default: $config->{solid_infill_every_layers})
   
   Print options:
     --perimeters        Number of perimeters/horizontal skins (range: 0+, default: $config->{perimeters})
diff --git a/t/fill.t b/t/fill.t
index dd8cdf938..27b9da41d 100644
--- a/t/fill.t
+++ b/t/fill.t
@@ -16,7 +16,9 @@ use Slic3r::Surface qw(:types);
 sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
 
 {
-    my $filler = Slic3r::Fill::Rectilinear->new(print => Slic3r::Print->new);
+    my $print = Slic3r::Print->new;
+    $print->init_extruders;
+    my $filler = Slic3r::Fill::Rectilinear->new(print => $print);
     my $surface_width = 250;
     my $distance = $filler->adjust_solid_spacing(
         width       => $surface_width,
diff --git a/utils/post-processing/decimate.pl b/utils/post-processing/decimate.pl
new file mode 100755
index 000000000..9e2938c5f
--- /dev/null
+++ b/utils/post-processing/decimate.pl
@@ -0,0 +1,53 @@
+#!/usr/bin/perl -i~
+
+use strict;
+use warnings;
+
+my %lastpos = (X => 10000, Y => 10000, Z => 10000, E => 10000, F => 10000);
+my %pos = (X => 0, Y => 0, Z => 0, E => 0, F => 0);
+
+my $mindist = 0.33;
+
+my $mindistz = 0.005;
+
+my $mindistsq = $mindist * $mindist;
+
+sub dist {
+	my $sq = 0;
+	for (qw/X Y Z E/) {
+		$sq += ($pos{$_} - $lastpos{$_}) ** 2;
+	}
+	return $sq;
+}
+
+while (<>) {
+	if (m#\bG[01]\b#) {
+		while (m#([XYZEF])(\d+(\.\d+)?)#gi) {
+			$pos{uc $1} = $2;
+		}
+		if (
+				(
+					/X/ &&
+					/Y/ &&
+					(dist() >= $mindistsq)
+				) ||
+				(abs($pos{Z} - $lastpos{Z}) > $mindistz) ||
+				(!/X/ || !/Y/)
+			) {
+			print;
+			%lastpos = %pos;
+		}
+		elsif (($pos{F} - $lastpos{F}) != 0) {
+			printf "G1 F%s\n", $pos{F};
+			$lastpos{F} = $pos{F};
+		}
+	}
+	else {
+		if (m#\bG92\b#) {
+			while (m#([XYZEF])(\d+(\.\d+)?)#gi) {
+				$lastpos{uc $1} = $2;
+			}
+		}
+		print;
+	}
+}
diff --git a/utils/post-processing/z-every-line.pl b/utils/post-processing/z-every-line.pl
index 49d5f4502..aaf57e172 100755
--- a/utils/post-processing/z-every-line.pl
+++ b/utils/post-processing/z-every-line.pl
@@ -8,17 +8,17 @@ my $z = 0;
 # read stdin and any/all files passed as parameters one line at a time
 while (<>) {
 	# if we find a Z word, save it
-	$z = $1 if /Z(\d+(\.\d+)?)/;
+	$z = $1 if /Z\s*(\d+(\.\d+)?)/;
 
 	# if we don't have Z, but we do have X and Y
 	if (!/Z/ && /X/ && /Y/ && $z > 0) {
 		# chop off the end of the line (incl. comments), saving chopped section in $1
-		s/\s*([\r\n\;\(].*)//s;
+		s/\s*([\r\n\;\(].*)/" Z$z $1"/es;
 		# print start of line, insert our Z value then re-add the chopped end of line
-		print "$_ Z$z $1";
+		# print "$_ Z$z $1";
 	}
-	else {
+	#else {
 		# nothing interesting, print line as-is
-		print;
-	}
+	print or die $!;
+	#}
 }
diff --git a/utils/stl-to-amf.pl b/utils/stl-to-amf.pl
index 78e5989b4..e1adec7ce 100755
--- a/utils/stl-to-amf.pl
+++ b/utils/stl-to-amf.pl
@@ -35,19 +35,11 @@ my %opt = ();
         my $new_object = $new_model->add_object;
         for my $m (0 .. $#models) {
             my $model = $models[$m];
-            my $v_offset = @{$new_object->vertices};
-            push @{$new_object->vertices}, @{$model->objects->[0]->vertices};
-            my @new_facets = map {
-                my $f = [@$_];
-                $f->[$_] += $v_offset for -3..-1;
-                $f;
-            } @{ $model->objects->[0]->volumes->[0]->facets };
-            
-            my $material_id = scalar keys %{$new_model->materials};
-            $new_model->materials->{$material_id} = { Name => basename($ARGV[$m]) };
+            $new_model->set_material($m, { Name => basename($ARGV[$m]) });
             $new_object->add_volume(
-                material_id => $material_id,
-                facets      => [@new_facets],
+                material_id => $m,
+                facets      => $model->objects->[0]->volumes->[0]->facets,
+                vertices    => $model->objects->[0]->vertices,
             );
         }
     } else {