diff --git a/CMakeLists.txt b/CMakeLists.txt
index e8b2a6faa..60d67553b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -49,6 +49,22 @@ if(NOT DEFINED CMAKE_PREFIX_PATH)
 	endif()
 endif()
 
+# WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory.
+# We pick it from environment if it is not defined in another way
+if(WIN32)
+	if(NOT DEFINED WIN10SDK_PATH)
+		if(DEFINED ENV{WIN10SDK_PATH})
+			set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}")
+		endif()
+	endif()
+	if(DEFINED WIN10SDK_PATH AND NOT EXISTS "${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h")
+		message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}")
+		message("${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h was not found")
+		message("STL fixing by the Netfabb service will not be compiled")
+		unset(WIN10SDK_PATH)
+	endif()
+endif()
+
 add_subdirectory(xs)
 
 get_filename_component(PERL_BIN_PATH "${PERL_EXECUTABLE}" DIRECTORY)
diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm
index 52c482813..80130fefe 100644
--- a/lib/Slic3r/GUI.pm
+++ b/lib/Slic3r/GUI.pm
@@ -7,7 +7,6 @@ use File::Basename qw(basename);
 use FindBin;
 use List::Util qw(first);
 use Slic3r::GUI::2DBed;
-use Slic3r::GUI::BedShapeDialog;
 use Slic3r::GUI::Controller;
 use Slic3r::GUI::Controller::ManualControlDialog;
 use Slic3r::GUI::Controller::PrinterPanel;
diff --git a/lib/Slic3r/GUI/BedShapeDialog.pm b/lib/Slic3r/GUI/BedShapeDialog.pm
deleted file mode 100644
index 70c8e0256..000000000
--- a/lib/Slic3r/GUI/BedShapeDialog.pm
+++ /dev/null
@@ -1,316 +0,0 @@
-# The bed shape dialog.
-# The dialog opens from Print Settins tab -> Bed Shape: Set...
-
-package Slic3r::GUI::BedShapeDialog;
-use strict;
-use warnings;
-use utf8;
-
-use List::Util qw(min max);
-use Slic3r::Geometry qw(X Y unscale);
-use Wx qw(:dialog :id :misc :sizer :choicebook wxTAB_TRAVERSAL);
-use Wx::Event qw(EVT_CLOSE);
-use base 'Wx::Dialog';
-
-sub new {
-    my $class = shift;
-    my ($parent, $default) = @_;
-    my $self = $class->SUPER::new($parent, -1, "Bed Shape", wxDefaultPosition, [350,700], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
-    
-    $self->{panel} = my $panel = Slic3r::GUI::BedShapePanel->new($self, $default);
-    
-    my $main_sizer = Wx::BoxSizer->new(wxVERTICAL);
-    $main_sizer->Add($panel, 1, wxEXPAND);
-    $main_sizer->Add($self->CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND);
-    
-    $self->SetSizer($main_sizer);
-    $self->SetMinSize($self->GetSize);
-    $main_sizer->SetSizeHints($self);
-    
-    # needed to actually free memory
-    EVT_CLOSE($self, sub {
-        $self->EndModal(wxID_OK);
-        $self->Destroy;
-    });
-    
-    return $self;
-}
-
-sub GetValue {
-    my ($self) = @_;
-    return $self->{panel}->GetValue;
-}
-
-package Slic3r::GUI::BedShapePanel;
-
-use List::Util qw(min max sum first);
-use Scalar::Util qw(looks_like_number);
-use Slic3r::Geometry qw(PI X Y unscale scaled_epsilon);
-use Wx qw(:font :id :misc :sizer :choicebook :filedialog :pen :brush wxTAB_TRAVERSAL);
-use Wx::Event qw(EVT_CLOSE EVT_CHOICEBOOK_PAGE_CHANGED EVT_BUTTON);
-use base 'Wx::Panel';
-
-use constant SHAPE_RECTANGULAR  => 0;
-use constant SHAPE_CIRCULAR     => 1;
-use constant SHAPE_CUSTOM       => 2;
-
-sub new {
-    my $class = shift;
-    my ($parent, $default) = @_;
-    my $self = $class->SUPER::new($parent, -1);
-    
-    $self->on_change(undef);
-    
-    my $box = Wx::StaticBox->new($self, -1, "Shape");
-    my $sbsizer = Wx::StaticBoxSizer->new($box, wxVERTICAL);
-    
-    # shape options
-    $self->{shape_options_book} = Wx::Choicebook->new($self, -1, wxDefaultPosition, [300,-1], wxCHB_TOP);
-    $sbsizer->Add($self->{shape_options_book});
-    
-    $self->{optgroups} = [];
-    {
-        my $optgroup = $self->_init_shape_options_page('Rectangular');
-        $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
-            opt_id      => 'rect_size',
-            type        => 'point',
-            label       => 'Size',
-            tooltip     => 'Size in X and Y of the rectangular plate.',
-            default     => [200,200],
-        ));
-        $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
-            opt_id      => 'rect_origin',
-            type        => 'point',
-            label       => 'Origin',
-            tooltip     => 'Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.',
-            default     => [0,0],
-        ));
-    }
-    {
-        my $optgroup = $self->_init_shape_options_page('Circular');
-        $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
-            opt_id      => 'diameter',
-            type        => 'f',
-            label       => 'Diameter',
-            tooltip     => 'Diameter of the print bed. It is assumed that origin (0,0) is located in the center.',
-            sidetext    => 'mm',
-            default     => 200,
-        ));
-    }
-    {
-        my $optgroup = $self->_init_shape_options_page('Custom');
-        $optgroup->append_line(Slic3r::GUI::OptionsGroup::Line->new(
-            full_width  => 1,
-            widget      => sub {
-                my ($parent) = @_;
-                
-                my $btn = Wx::Button->new($parent, -1, "Load shape from STL...", wxDefaultPosition, wxDefaultSize);
-                EVT_BUTTON($self, $btn, sub { $self->_load_stl });
-                return $btn;
-            }
-        ));
-    }
-    
-    EVT_CHOICEBOOK_PAGE_CHANGED($self, -1, sub {
-        $self->_update_shape;
-    });
-    
-    # right pane with preview canvas
-    my $canvas = $self->{canvas} = Slic3r::GUI::2DBed->new($self);
-    
-    # main sizer
-    my $top_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
-    $top_sizer->Add($sbsizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
-    $top_sizer->Add($canvas, 1, wxEXPAND | wxALL, 10) if $canvas;
-    
-    $self->SetSizerAndFit($top_sizer);
-    
-    $self->_set_shape($default);
-    $self->_update_preview;
-    
-    return $self;
-}
-
-sub on_change {
-    my ($self, $cb) = @_;
-    $self->{on_change} = $cb // sub {};
-}
-
-# Called from the constructor.
-# Set the initial bed shape from a list of points.
-# Deduce the bed shape type (rect, circle, custom)
-# This routine shall be smart enough if the user messes up
-# with the list of points in the ini file directly.
-sub _set_shape {
-    my ($self, $points) = @_;
-    
-    # is this a rectangle?
-    if (@$points == 4) {
-        my $polygon = Slic3r::Polygon->new_scale(@$points);
-        my $lines = $polygon->lines;
-        if ($lines->[0]->parallel_to_line($lines->[2]) && $lines->[1]->parallel_to_line($lines->[3])) {
-            # okay, it's a rectangle
-            
-            # find origin
-            # the || 0 hack prevents "-0" which might confuse the user
-            my $x_min = min(map $_->[X], @$points) || 0;
-            my $x_max = max(map $_->[X], @$points) || 0;
-            my $y_min = min(map $_->[Y], @$points) || 0;
-            my $y_max = max(map $_->[Y], @$points) || 0;
-            my $origin = [-$x_min, -$y_min];
-            
-            $self->{shape_options_book}->SetSelection(SHAPE_RECTANGULAR);
-            my $optgroup = $self->{optgroups}[SHAPE_RECTANGULAR];
-            $optgroup->set_value('rect_size', [ $x_max-$x_min, $y_max-$y_min ]);
-            $optgroup->set_value('rect_origin', $origin);
-            $self->_update_shape;
-            return;
-        }
-    }
-    
-    # is this a circle?
-    {
-        # Analyze the array of points. Do they reside on a circle?
-        my $polygon = Slic3r::Polygon->new_scale(@$points);
-        my $center = $polygon->bounding_box->center;
-        my @vertex_distances = map $center->distance_to($_), @$polygon;
-        my $avg_dist = sum(@vertex_distances)/@vertex_distances;
-        if (!defined first { abs($_ - $avg_dist) > 10*scaled_epsilon } @vertex_distances) {
-            # all vertices are equidistant to center
-            $self->{shape_options_book}->SetSelection(SHAPE_CIRCULAR);
-            my $optgroup = $self->{optgroups}[SHAPE_CIRCULAR];
-            $optgroup->set_value('diameter', sprintf("%.0f", unscale($avg_dist*2)));
-            $self->_update_shape;
-            return;
-        }
-    }
-    
-    if (@$points < 3) {
-        # Invalid polygon. Revert to default bed dimensions.
-        $self->{shape_options_book}->SetSelection(SHAPE_RECTANGULAR);
-        my $optgroup = $self->{optgroups}[SHAPE_RECTANGULAR];
-        $optgroup->set_value('rect_size', [200, 200]);
-        $optgroup->set_value('rect_origin', [0, 0]);
-        $self->_update_shape;
-        return;
-    }
-
-    # This is a custom bed shape, use the polygon provided.
-    $self->{shape_options_book}->SetSelection(SHAPE_CUSTOM);
-    # Copy the polygon to the canvas, make a copy of the array.
-    $self->{canvas}->bed_shape([@$points]);
-    $self->_update_shape;
-}
-
-# Update the bed shape from the dialog fields.
-sub _update_shape {
-    my ($self) = @_;
-    
-    my $page_idx = $self->{shape_options_book}->GetSelection;
-    if ($page_idx == SHAPE_RECTANGULAR) {
-        my $rect_size = $self->{optgroups}[SHAPE_RECTANGULAR]->get_value('rect_size');
-        my $rect_origin = $self->{optgroups}[SHAPE_RECTANGULAR]->get_value('rect_origin');
-        my ($x, $y) = @$rect_size;
-        return if !looks_like_number($x) || !looks_like_number($y);  # empty strings or '-' or other things
-        return if !$x || !$y or $x == 0 or $y == 0;
-        my ($x0, $y0) = (0,0);
-        my ($x1, $y1) = ($x ,$y);
-        {
-            my ($dx, $dy) = @$rect_origin;
-            return if !looks_like_number($dx) || !looks_like_number($dy);  # empty strings or '-' or other things
-            $x0 -= $dx;
-            $x1 -= $dx;
-            $y0 -= $dy;
-            $y1 -= $dy;
-        }
-        $self->{canvas}->bed_shape([
-            [$x0,$y0],
-            [$x1,$y0],
-            [$x1,$y1],
-            [$x0,$y1],
-        ]);
-    } elsif ($page_idx == SHAPE_CIRCULAR) {
-        my $diameter = $self->{optgroups}[SHAPE_CIRCULAR]->get_value('diameter');
-        return if !$diameter or $diameter == 0;
-        my $r = $diameter/2;
-        my $twopi = 2*PI;
-        my $edges = 60;
-        my $polygon = Slic3r::Polygon->new_scale(
-            map [ $r * cos $_, $r * sin $_ ],
-                map { $twopi/$edges*$_ } 1..$edges
-        );
-        $self->{canvas}->bed_shape([
-            map [ unscale($_->x), unscale($_->y) ], @$polygon  #))
-        ]);
-    }
-    
-    $self->{on_change}->();
-    $self->_update_preview;
-}
-
-sub _update_preview {
-    my ($self) = @_;
-    $self->{canvas}->Refresh if $self->{canvas};
-    $self->Refresh;
-}
-
-# Called from the constructor.
-# Create a panel for a rectangular / circular / custom bed shape. 
-sub _init_shape_options_page {
-    my ($self, $title) = @_;
-    
-    my $panel = Wx::Panel->new($self->{shape_options_book});
-    my $optgroup;
-    push @{$self->{optgroups}}, $optgroup = Slic3r::GUI::OptionsGroup->new(
-        parent      => $panel,
-        title       => 'Settings',
-        label_width => 100,
-        on_change   => sub {
-            my ($opt_id) = @_;
-            #$self->{"_$opt_id"} = $optgroup->get_value($opt_id);
-            $self->_update_shape;
-        },
-    );
-    $panel->SetSizerAndFit($optgroup->sizer);
-    $self->{shape_options_book}->AddPage($panel, $title);
-    
-    return $optgroup;
-}
-
-# Loads an stl file, projects it to the XY plane and calculates a polygon.
-sub _load_stl {
-    my ($self) = @_;
-    
-    my $dialog = Wx::FileDialog->new($self, 'Choose a file to import bed shape from (STL/OBJ/AMF/PRUSA):', "", "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
-    if ($dialog->ShowModal != wxID_OK) {
-        $dialog->Destroy;
-        return;
-    }
-    my $input_file = $dialog->GetPaths;
-    $dialog->Destroy;
-    
-    my $model = Slic3r::Model->read_from_file($input_file);
-    my $mesh = $model->mesh;
-    my $expolygons = $mesh->horizontal_projection;
-
-    if (@$expolygons == 0) {
-        Slic3r::GUI::show_error($self, "The selected file contains no geometry.");
-        return;
-    }
-    if (@$expolygons > 1) {
-        Slic3r::GUI::show_error($self, "The selected file contains several disjoint areas. This is not supported.");
-        return;
-    }
-    
-    my $polygon = $expolygons->[0]->contour;
-    $self->{canvas}->bed_shape([ map [ unscale($_->x), unscale($_->y) ], @$polygon ]);
-    $self->_update_preview();
-}
-
-# Returns the resulting bed shape polygon. This value will be stored to the ini file.
-sub GetValue {
-    my ($self) = @_;
-    return $self->{canvas}->bed_shape;
-}
-
-1;
diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index 0185749a3..4e0c5fdcf 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -116,6 +116,8 @@ sub new {
 
         my $model_object = $self->{model}->objects->[$obj_idx];
         my $model_instance = $model_object->instances->[0];
+
+        $self->stop_background_process;
         
         my $variation = $scale / $model_instance->scaling_factor;
         #FIXME Scale the layer height profile?
@@ -127,7 +129,6 @@ sub new {
         $object->transform_thumbnail($self->{model}, $obj_idx);
     
         #update print and start background processing
-        $self->stop_background_process;
         $self->{print}->add_model_object($model_object, $obj_idx);
     
         $self->selection_changed(1);  # refresh info (size, volume etc.)
diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs
index 22ba91a93..ea7e46e79 100644
--- a/resources/shaders/gouraud.vs
+++ b/resources/shaders/gouraud.vs
@@ -22,8 +22,8 @@ struct PrintBoxDetection
 {
     vec3 min;
     vec3 max;
-    // xyz contains the offset, if w == 1.0 detection needs to be performed
-    vec4 volume_origin;
+    bool volume_detection;
+    mat4 volume_world_matrix;
 };
 
 uniform PrintBoxDetection print_box;
@@ -54,9 +54,9 @@ void main()
     intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
 
     // compute deltas for out of print volume detection (world coordinates)
-    if (print_box.volume_origin.w == 1.0)
+    if (print_box.volume_detection)
     {
-        vec3 v = gl_Vertex.xyz + print_box.volume_origin.xyz;
+        vec3 v = (print_box.volume_world_matrix * gl_Vertex).xyz;
         delta_box_min = v - print_box.min;
         delta_box_max = v - print_box.max;
     }
diff --git a/resources/shaders/variable_layer_height.vs b/resources/shaders/variable_layer_height.vs
index 2c918c0d4..9763859d0 100644
--- a/resources/shaders/variable_layer_height.vs
+++ b/resources/shaders/variable_layer_height.vs
@@ -14,6 +14,8 @@ const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
 
 #define INTENSITY_AMBIENT    0.3
 
+uniform mat4 volume_world_matrix;
+
 // x = tainted, y = specular;
 varying vec2 intensity;
 
@@ -38,9 +40,8 @@ void main()
     NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
     
     intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
-    
-    // Scaled to widths of the Z texture.
-    object_z = gl_Vertex.z;
 
+    // Scaled to widths of the Z texture.
+    object_z = (volume_world_matrix * gl_Vertex).z;
     gl_Position = ftransform();
 }
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
index 5dbc2a2f7..66c1cdd6a 100644
--- a/xs/CMakeLists.txt
+++ b/xs/CMakeLists.txt
@@ -27,6 +27,13 @@ if(WIN32)
     # BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking.
     add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB)
     # -D_ITERATOR_DEBUG_LEVEL)
+    if(WIN10SDK_PATH)
+        message("Building with Win10 Netfabb STL fixing service support")
+        add_definitions(-DHAS_WIN10SDK)
+        include_directories("${WIN10SDK_PATH}/Include")
+    else()
+        message("Building without Win10 Netfabb STL fixing service support")
+    endif()
 endif()
 
 add_definitions(-DwxUSE_UNICODE -D_UNICODE -DUNICODE -DWXINTL_NO_GETTEXT_MACRO)
@@ -240,6 +247,8 @@ add_library(libslic3r_gui STATIC
     ${LIBDIR}/slic3r/GUI/FirmwareDialog.hpp
     ${LIBDIR}/slic3r/Utils/Http.cpp
     ${LIBDIR}/slic3r/Utils/Http.hpp
+    ${LIBDIR}/slic3r/Utils/FixModelByWin10.cpp
+    ${LIBDIR}/slic3r/Utils/FixModelByWin10.hpp
     ${LIBDIR}/slic3r/Utils/OctoPrint.cpp
     ${LIBDIR}/slic3r/Utils/OctoPrint.hpp
     ${LIBDIR}/slic3r/Utils/Bonjour.cpp
@@ -344,8 +353,6 @@ add_library(semver STATIC
 )
 
 
-add_subdirectory(src/avrdude)
-
 # Generate the Slic3r Perl module (XS) typemap file.
 set(MyTypemap ${CMAKE_CURRENT_BINARY_DIR}/typemap)
 add_custom_command(
@@ -508,12 +515,12 @@ if (WIN32 AND ";${PerlEmbed_CCFLAGS};" MATCHES ";[-/]Od;")
     message("Old CMAKE_CXX_FLAGS_RELEASE: ${CMAKE_CXX_FLAGS_RELEASE}")
     message("Old CMAKE_CXX_FLAGS_RELWITHDEBINFO: ${CMAKE_CXX_FLAGS_RELEASE}")
     message("Old CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS_RELEASE}")
-    set(CMAKE_CXX_FLAGS_RELEASE "/MD /Od /Zi /EHsc /DNDEBUG")
-    set(CMAKE_C_FLAGS_RELEASE "/MD /Od /Zi /DNDEBUG")
-    set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /EHsc /DNDEBUG")
-    set(CMAKE_C_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /DNDEBUG")
-    set(CMAKE_CXX_FLAGS "/MD /Od /Zi /EHsc /DNDEBUG")
-    set(CMAKE_C_FLAGS "/MD /Od /Zi /DNDEBUG")
+    set(CMAKE_CXX_FLAGS_RELEASE "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32")
+    set(CMAKE_C_FLAGS_RELEASE "/MD /Od /Zi /DNDEBUG /DWIN32")
+    set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32")
+    set(CMAKE_C_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /DNDEBUG /DWIN32")
+    set(CMAKE_CXX_FLAGS "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32")
+    set(CMAKE_C_FLAGS "/MD /Od /Zi /DNDEBUG /DWIN32")
 endif()
 # The following line will add -fPIC on Linux to make the XS.so rellocable.
 add_definitions(${PerlEmbed_CCCDLFLAGS})
@@ -521,6 +528,8 @@ if (WIN32)
     target_link_libraries(XS ${PERL_LIBRARY})
 endif()
 
+add_subdirectory(src/avrdude)
+
 ## REQUIRED packages
 
 # Find and configure boost
diff --git a/xs/src/avrdude/arduino.c b/xs/src/avrdude/arduino.c
index 566f56abd..fc9f4571f 100644
--- a/xs/src/avrdude/arduino.c
+++ b/xs/src/avrdude/arduino.c
@@ -80,6 +80,49 @@ static int arduino_read_sig_bytes(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m)
   return 3;
 }
 
+static int prusa_init_external_flash(PROGRAMMER * pgm)
+{
+  // Note: send/receive as in _the firmare_ send & receives
+  const char entry_magic_send   [] = "start\n";
+  const char entry_magic_receive[] = "w25x20cl_enter\n";
+  const char entry_magic_cfm    [] = "w25x20cl_cfm\n";
+  const size_t buffer_len = 32;     // Should be large enough for the above messages
+
+  int res;
+  size_t recv_size;
+  char *buffer = alloca(buffer_len);
+
+  // 1. receive the "start" command
+  recv_size = sizeof(entry_magic_send) - 1;
+  res = serial_recv(&pgm->fd, buffer, recv_size);
+  if (res < 0) {
+    avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer did not boot up on time or serial communication failed\n", progname);
+    return -1;
+  } else if (strncmp(buffer, entry_magic_send, recv_size) != 0) {
+    avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer emitted incorrect start code\n", progname);
+    return -1;
+  }
+
+  // 2. Send the external flash programmer enter command
+  if (serial_send(&pgm->fd, entry_magic_receive, sizeof(entry_magic_receive) - 1) < 0) {
+    avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): Failed to send command to the printer\n",progname);
+    return -1;
+  }
+
+  // 3. Receive the entry confirmation command
+  recv_size = sizeof(entry_magic_cfm) - 1;
+  res = serial_recv(&pgm->fd, buffer, recv_size);
+  if (res < 0) {
+    avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer did not boot up on time or serial communication failed\n", progname);
+    return -1;
+  } else if (strncmp(buffer, entry_magic_cfm, recv_size) != 0) {
+    avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer emitted incorrect start code\n", progname);
+    return -1;
+  }
+
+  return 0;
+}
+
 static int arduino_open(PROGRAMMER * pgm, char * port)
 {
   union pinfo pinfo;
@@ -102,6 +145,12 @@ static int arduino_open(PROGRAMMER * pgm, char * port)
    */
   stk500_drain(pgm, 0);
 
+  // Initialization sequence for programming the external FLASH on the Prusa MK3
+  if (prusa_init_external_flash(pgm) < 0) {
+    avrdude_message(MSG_INFO, "%s: arduino_open(): Failed to initialize MK3 external flash programming mode\n", progname);
+    return -1;
+  }
+
   if (stk500_getsync(pgm) < 0)
     return -1;
 
diff --git a/xs/src/avrdude/avrdude-slic3r.cpp b/xs/src/avrdude/avrdude-slic3r.cpp
index a859200fb..030353413 100644
--- a/xs/src/avrdude/avrdude-slic3r.cpp
+++ b/xs/src/avrdude/avrdude-slic3r.cpp
@@ -1,5 +1,6 @@
 #include "avrdude-slic3r.hpp"
 
+#include <deque>
 #include <thread>
 
 extern "C" {
@@ -33,17 +34,22 @@ static void avrdude_progress_handler_closure(const char *task, unsigned progress
 struct AvrDude::priv
 {
 	std::string sys_config;
-	std::vector<std::string> args;
+	std::deque<std::vector<std::string>> args;
+	size_t current_args_set = 0;
+	RunFn run_fn;
 	MessageFn message_fn;
 	ProgressFn progress_fn;
 	CompleteFn complete_fn;
 
 	std::thread avrdude_thread;
 
+	priv(std::string &&sys_config) : sys_config(sys_config) {}
+
+	int run_one(const std::vector<std::string> &args);
 	int run();
 };
 
-int AvrDude::priv::run() {
+int AvrDude::priv::run_one(const std::vector<std::string> &args) {
 	std::vector<char*> c_args {{ const_cast<char*>(PACKAGE_NAME) }};
 	for (const auto &arg : args) {
 		c_args.push_back(const_cast<char*>(arg.data()));
@@ -68,10 +74,22 @@ int AvrDude::priv::run() {
 	return res;
 }
 
+int AvrDude::priv::run() {
+	for (; args.size() > 0; current_args_set++) {
+		int res = run_one(args.front());
+		args.pop_front();
+		if (res != 0) {
+			return res;
+		}
+	}
+
+	return 0;
+}
+
 
 // Public
 
-AvrDude::AvrDude() : p(new priv()) {}
+AvrDude::AvrDude(std::string sys_config) : p(new priv(std::move(sys_config))) {}
 
 AvrDude::AvrDude(AvrDude &&other) : p(std::move(other.p)) {}
 
@@ -82,15 +100,15 @@ AvrDude::~AvrDude()
 	}
 }
 
-AvrDude& AvrDude::sys_config(std::string sys_config)
+AvrDude& AvrDude::push_args(std::vector<std::string> args)
 {
-	if (p) { p->sys_config = std::move(sys_config); }
+	if (p) { p->args.push_back(std::move(args)); }
 	return *this;
 }
 
-AvrDude& AvrDude::args(std::vector<std::string> args)
+AvrDude& AvrDude::on_run(RunFn fn)
 {
-	if (p) { p->args = std::move(args); }
+	if (p) { p->run_fn = std::move(fn); }
 	return *this;
 }
 
@@ -123,11 +141,17 @@ AvrDude::Ptr AvrDude::run()
 
 	if (self->p) {
 		auto avrdude_thread = std::thread([self]() {
-				auto res = self->p->run();
-				if (self->p->complete_fn) {
-					self->p->complete_fn(res);
-				}
-			});
+			if (self->p->run_fn) {
+				self->p->run_fn();
+			}
+
+			auto res = self->p->run();
+
+			if (self->p->complete_fn) {
+				self->p->complete_fn(res, self->p->current_args_set);
+			}
+		});
+
 		self->p->avrdude_thread = std::move(avrdude_thread);
 	}
 
diff --git a/xs/src/avrdude/avrdude-slic3r.hpp b/xs/src/avrdude/avrdude-slic3r.hpp
index 8d881b094..273aa2378 100644
--- a/xs/src/avrdude/avrdude-slic3r.hpp
+++ b/xs/src/avrdude/avrdude-slic3r.hpp
@@ -12,22 +12,28 @@ class AvrDude
 {
 public:
 	typedef std::shared_ptr<AvrDude> Ptr;
+	typedef std::function<void()> RunFn;
 	typedef std::function<void(const char * /* msg */, unsigned /* size */)> MessageFn;
 	typedef std::function<void(const char * /* task */, unsigned /* progress */)> ProgressFn;
-	typedef std::function<void(int /* exit status */)> CompleteFn;
+	typedef std::function<void(int /* exit status */, size_t /* args_id */)> CompleteFn;
 
-	AvrDude();
+	// Main c-tor, sys_config is the location of avrdude's main configuration file
+	AvrDude(std::string sys_config);
 	AvrDude(AvrDude &&);
 	AvrDude(const AvrDude &) = delete;
 	AvrDude &operator=(AvrDude &&) = delete;
 	AvrDude &operator=(const AvrDude &) = delete;
 	~AvrDude();
 
-	// Set location of avrdude's main configuration file
-	AvrDude& sys_config(std::string sys_config);
+	// Push a set of avrdude cli arguments
+	// Each set makes one avrdude invocation - use this method multiple times to push
+	// more than one avrdude invocations.
+	AvrDude& push_args(std::vector<std::string> args);
 
-	// Set avrdude cli arguments
-	AvrDude& args(std::vector<std::string> args);
+	// Set a callback to be called just after run() before avrdude is ran
+	// This can be used to perform any needed setup tasks from the background thread.
+	// This has no effect when using run_sync().
+	AvrDude& on_run(RunFn fn);
 
 	// Set message output callback
 	AvrDude& on_message(MessageFn fn);
@@ -36,7 +42,10 @@ public:
 	// Progress is reported per each task (reading / writing) in percents.
 	AvrDude& on_progress(ProgressFn fn);
 
-	// Called when avrdude's main function finishes
+	// Called when the last avrdude invocation finishes with the exit status of zero,
+	// or earlier, if one of the invocations return a non-zero status.
+	// The second argument contains the sequential id of the last avrdude invocation argument set.
+	// This has no effect when using run_sync().
 	AvrDude& on_complete(CompleteFn fn);
 
 	int run_sync();
diff --git a/xs/src/avrdude/avrpart.c b/xs/src/avrdude/avrpart.c
index 621a85b98..b04851ac1 100644
--- a/xs/src/avrdude/avrpart.c
+++ b/xs/src/avrdude/avrpart.c
@@ -378,7 +378,7 @@ void avr_mem_display(const char * prefix, FILE * f, AVRMEM * m, int type,
   char * optr;
 
   if (m == NULL) {
-      fprintf(f,
+      avrdude_message(MSG_INFO,
               "%s                       Block Poll               Page                       Polled\n"
               "%sMemory Type Mode Delay Size  Indx Paged  Size   Size #Pages MinW  MaxW   ReadBack\n"
               "%s----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------\n",
@@ -386,13 +386,13 @@ void avr_mem_display(const char * prefix, FILE * f, AVRMEM * m, int type,
   }
   else {
     if (verbose > 2) {
-      fprintf(f,
+      avrdude_message(MSG_INFO,
               "%s                       Block Poll               Page                       Polled\n"
               "%sMemory Type Mode Delay Size  Indx Paged  Size   Size #Pages MinW  MaxW   ReadBack\n"
               "%s----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------\n",
               prefix, prefix, prefix);
     }
-    fprintf(f,
+    avrdude_message(MSG_INFO,
             "%s%-11s %4d %5d %5d %4d %-6s %6d %4d %6d %5d %5d 0x%02x 0x%02x\n",
             prefix, m->desc, m->mode, m->delay, m->blocksize, m->pollindex,
             m->paged ? "yes" : "no",
@@ -415,7 +415,7 @@ void avr_mem_display(const char * prefix, FILE * f, AVRMEM * m, int type,
               optr = avr_op_str(i);
             else
               optr = " ";
-          fprintf(f,
+          avrdude_message(MSG_INFO,
                   "%s    %-11s  %8d  %8s  %5d  %5d\n",
                   prefix, optr, j,
                   bittype(m->op[i]->bit[j].type),
@@ -620,7 +620,7 @@ void avr_display(FILE * f, AVRPART * p, const char * prefix, int verbose)
   LNODEID ln;
   AVRMEM * m;
 
-  fprintf(f,
+  avrdude_message(MSG_INFO,
           "%sAVR Part                      : %s\n"
           "%sChip Erase delay              : %d us\n"
           "%sPAGEL                         : P%02X\n"
diff --git a/xs/src/avrdude/fileio.c b/xs/src/avrdude/fileio.c
index f2d617823..aa57f5587 100644
--- a/xs/src/avrdude/fileio.c
+++ b/xs/src/avrdude/fileio.c
@@ -45,6 +45,8 @@
 
 #define MAX_LINE_LEN 256  /* max line length for ASCII format input files */
 
+#define MAX_MODE_LEN 32  // For fopen_and_seek()
+
 
 struct ihexrec {
   unsigned char    reclen;
@@ -96,10 +98,42 @@ static int fileio_num(struct fioparms * fio,
 		char * filename, FILE * f, AVRMEM * mem, int size,
 		FILEFMT fmt);
 
-static int fmt_autodetect(char * fname);
+static int fmt_autodetect(char * fname, size_t offset);
 
 
 
+static FILE *fopen_and_seek(const char *filename, const char *mode, size_t offset)
+{
+  FILE *file;
+  // On Windows we need to convert the filename to UTF-16
+#if defined(WIN32NATIVE)
+  static wchar_t fname_buffer[PATH_MAX];
+  static wchar_t mode_buffer[MAX_MODE_LEN];
+
+  if (MultiByteToWideChar(CP_UTF8, 0, filename, -1, fname_buffer, PATH_MAX) == 0) { return NULL; }
+  if (MultiByteToWideChar(CP_ACP, 0, mode, -1, mode_buffer, MAX_MODE_LEN) == 0) { return NULL; }
+
+  file = _wfopen(fname_buffer, mode_buffer);
+#else
+  file = fopen(filename, mode);
+#endif
+
+  if (file != NULL) {
+    // Some systems allow seeking past the end of file, so we need check for that first and disallow
+    if (fseek(file, 0, SEEK_END) != 0
+      || offset >= ftell(file)
+      || fseek(file, offset, SEEK_SET) != 0
+      ) {
+      fclose(file);
+      file = NULL;
+      errno = EINVAL;
+    }
+  }
+
+  return file;
+}
+
+
 char * fmtstr(FILEFMT format)
 {
   switch (format) {
@@ -1358,7 +1392,7 @@ int fileio_setparms(int op, struct fioparms * fp,
 
 
 
-static int fmt_autodetect(char * fname)
+static int fmt_autodetect(char * fname, size_t offset)
 {
   FILE * f;
   unsigned char buf[MAX_LINE_LEN];
@@ -1368,10 +1402,11 @@ static int fmt_autodetect(char * fname)
   int first = 1;
 
 #if defined(WIN32NATIVE)
-  f = fopen(fname, "r");
+  f = fopen_and_seek(fname, "r", offset);
 #else
-  f = fopen(fname, "rb");
+  f = fopen_and_seek(fname, "rb", offset);
 #endif
+
   if (f == NULL) {
     avrdude_message(MSG_INFO, "%s: error opening %s: %s\n",
             progname, fname, strerror(errno));
@@ -1445,7 +1480,7 @@ static int fmt_autodetect(char * fname)
 
 
 int fileio(int op, char * filename, FILEFMT format, 
-             struct avrpart * p, char * memtype, int size)
+             struct avrpart * p, char * memtype, int size, size_t offset)
 {
   int rc;
   FILE * f;
@@ -1477,15 +1512,17 @@ int fileio(int op, char * filename, FILEFMT format,
   using_stdio = 0;
 
   if (strcmp(filename, "-")==0) {
-    if (fio.op == FIO_READ) {
-      fname = "<stdin>";
-      f = stdin;
-    }
-    else {
-      fname = "<stdout>";
-      f = stdout;
-    }
-    using_stdio = 1;
+    return -1;
+    // Note: we don't want to read stdin or write to stdout as part of Slic3r
+    // if (fio.op == FIO_READ) {
+    //   fname = "<stdin>";
+    //   f = stdin;
+    // }
+    // else {
+    //   fname = "<stdout>";
+    //   f = stdout;
+    // }
+    // using_stdio = 1;
   }
   else {
     fname = filename;
@@ -1502,7 +1539,7 @@ int fileio(int op, char * filename, FILEFMT format,
       return -1;
     }
 
-    format_detect = fmt_autodetect(fname);
+    format_detect = fmt_autodetect(fname, offset);
     if (format_detect < 0) {
       avrdude_message(MSG_INFO, "%s: can't determine file format for %s, specify explicitly\n",
                       progname, fname);
@@ -1533,7 +1570,7 @@ int fileio(int op, char * filename, FILEFMT format,
 
   if (format != FMT_IMM) {
     if (!using_stdio) {
-      f = fopen(fname, fio.mode);
+      f = fopen_and_seek(fname, fio.mode, offset);
       if (f == NULL) {
         avrdude_message(MSG_INFO, "%s: can't open %s file %s: %s\n",
                 progname, fio.iodesc, fname, strerror(errno));
diff --git a/xs/src/avrdude/libavrdude.h b/xs/src/avrdude/libavrdude.h
index e8197f9c2..536f1a2f7 100644
--- a/xs/src/avrdude/libavrdude.h
+++ b/xs/src/avrdude/libavrdude.h
@@ -737,7 +737,7 @@ extern bool cancel_flag;
 #define RETURN_IF_CANCEL() \
   do { \
     if (cancel_flag) { \
-      avrdude_message(MSG_INFO, "%s(): Cancelled, exiting...\n", __func__); \
+      avrdude_message(MSG_INFO, "avrdude: %s(): Cancelled, exiting...\n", __func__); \
       return -99; \
     } \
   } while (0)
@@ -821,7 +821,7 @@ extern "C" {
 char * fmtstr(FILEFMT format);
 
 int fileio(int op, char * filename, FILEFMT format,
-           struct avrpart * p, char * memtype, int size);
+           struct avrpart * p, char * memtype, int size, size_t offset);
 
 #ifdef __cplusplus
 }
@@ -870,6 +870,7 @@ enum updateflags {
 typedef struct update_t {
   char * memtype;
   int    op;
+  size_t offset;
   char * filename;
   int    format;
 } UPDATE;
@@ -881,7 +882,7 @@ extern "C" {
 extern UPDATE * parse_op(char * s);
 extern UPDATE * dup_update(UPDATE * upd);
 extern UPDATE * new_update(int op, char * memtype, int filefmt,
-			   char * filename);
+			   char * filename, size_t offset);
 extern void free_update(UPDATE * upd);
 extern int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd,
 		 enum updateflags flags);
diff --git a/xs/src/avrdude/main.c b/xs/src/avrdude/main.c
index 0550ceff1..d4c34fe44 100644
--- a/xs/src/avrdude/main.c
+++ b/xs/src/avrdude/main.c
@@ -194,7 +194,7 @@ static void usage(void)
  "  -F                         Override invalid signature check.\n"
  "  -e                         Perform a chip erase.\n"
  "  -O                         Perform RC oscillator calibration (see AVR053). \n"
- "  -U <memtype>:r|w|v:<filename>[:format]\n"
+ "  -U <memtype>:r|w|v:<offset>:<filename>[:format]\n"
  "                             Memory operation specification.\n"
  "                             Multiple -U options are allowed, each request\n"
  "                             is performed in the order specified.\n"
@@ -374,7 +374,7 @@ static void list_parts(FILE * f, const char *prefix, LISTID avrparts)
 
 static int cleanup_main(int status)
 {
-    if (pgm_setup && pgm->teardown) {
+    if (pgm_setup && pgm != NULL && pgm->teardown) {
         pgm->teardown(pgm);
     }
 
diff --git a/xs/src/avrdude/ser_posix.c b/xs/src/avrdude/ser_posix.c
index 91b18e945..cb0fc0385 100644
--- a/xs/src/avrdude/ser_posix.c
+++ b/xs/src/avrdude/ser_posix.c
@@ -376,6 +376,10 @@ static int ser_recv(union filedescriptor *fd, unsigned char * buf, size_t buflen
     FD_SET(fd->ifd, &rfds);
 
     nfds = select(fd->ifd + 1, &rfds, NULL, NULL, &to2);
+    // FIXME: The timeout has different behaviour on Linux vs other Unices
+    // On Linux, the timeout is modified by subtracting the time spent,
+    // on OS X (for example), it is not modified.
+    // POSIX recommends re-initializing it before selecting.
     if (nfds == 0) {
       avrdude_message(MSG_NOTICE2, "%s: ser_recv(): programmer is not responding\n",
                         progname);
diff --git a/xs/src/avrdude/stk500.c b/xs/src/avrdude/stk500.c
index 5d2d3c1df..63deb228f 100644
--- a/xs/src/avrdude/stk500.c
+++ b/xs/src/avrdude/stk500.c
@@ -716,11 +716,14 @@ static int stk500_loadaddr(PROGRAMMER * pgm, AVRMEM * mem, unsigned int addr)
   }
 
   buf[0] = Cmnd_STK_LOAD_ADDRESS;
-  buf[1] = addr & 0xff;
-  buf[2] = (addr >> 8) & 0xff;
-  buf[3] = Sync_CRC_EOP;
-
-  stk500_send(pgm, buf, 4);
+  // Workaround for the infamous ';' bug in the Prusa3D usb to serial converter.
+  // Send the binary data by nibbles to avoid transmitting the ';' character.
+  buf[1] = addr & 0x0f;
+  buf[2] = addr & 0xf0;
+  buf[3] = (addr >> 8) & 0x0f;
+  buf[4] = (addr >> 8) & 0xf0;
+  buf[5] = Sync_CRC_EOP;
+  stk500_send(pgm, buf, 6);
 
   if (stk500_recv(pgm, buf, 1) < 0)
     return -1;
@@ -765,7 +768,9 @@ static int stk500_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
   int block_size;
   int tries;
   unsigned int n;
-  unsigned int i;
+  unsigned int i, j;
+  unsigned int prusa3d_semicolon_workaround_round = 0;
+  bool has_semicolon = false;
 
   if (strcmp(m->desc, "flash") == 0) {
     memtype = 'F';
@@ -806,44 +811,64 @@ static int stk500_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
     tries++;
     stk500_loadaddr(pgm, m, addr/a_div);
 
-    /* build command block and avoid multiple send commands as it leads to a crash
-        of the silabs usb serial driver on mac os x */
-    i = 0;
-    buf[i++] = Cmnd_STK_PROG_PAGE;
-    buf[i++] = (block_size >> 8) & 0xff;
-    buf[i++] = block_size & 0xff;
-    buf[i++] = memtype;
-    memcpy(&buf[i], &m->buf[addr], block_size);
-    i += block_size;
-    buf[i++] = Sync_CRC_EOP;
-    stk500_send( pgm, buf, i);
-
-    if (stk500_recv(pgm, buf, 1) < 0)
-      return -1;
-    if (buf[0] == Resp_STK_NOSYNC) {
-      if (tries > 33) {
-        avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): can't get into sync\n",
-                progname);
-        return -3;
+    for (i = 0; i < n_bytes; ++ i)
+      if (m->buf[addr + i] == ';') {
+        has_semicolon = true;
+        break;
+      }
+
+    for (prusa3d_semicolon_workaround_round = 0; prusa3d_semicolon_workaround_round < (has_semicolon ? 2 : 1); ++ prusa3d_semicolon_workaround_round) {
+      /* build command block and avoid multiple send commands as it leads to a crash
+          of the silabs usb serial driver on mac os x */
+      i = 0;
+      buf[i++] = Cmnd_STK_PROG_PAGE;
+      // Workaround for the infamous ';' bug in the Prusa3D usb to serial converter.
+      // Send the binary data by nibbles to avoid transmitting the ';' character.
+      buf[i++] = (block_size >> 8) & 0xf0;
+      buf[i++] = (block_size >> 8) & 0x0f;
+      buf[i++] = block_size & 0xf0;
+      buf[i++] = block_size & 0x0f;
+      buf[i++] = memtype;
+      if (has_semicolon) {
+        for (j = 0; j < block_size; ++i, ++ j) {
+          buf[i] = m->buf[addr + j];
+          if (buf[i] == ';')
+            buf[i] |= (prusa3d_semicolon_workaround_round ? 0xf0 : 0x0f);
+        }
+      } else {
+        memcpy(&buf[i], &m->buf[addr], block_size);
+        i += block_size;
+      }
+      buf[i++] = Sync_CRC_EOP;
+      stk500_send( pgm, buf, i);
+
+      if (stk500_recv(pgm, buf, 1) < 0)
+        return -1;
+      if (buf[0] == Resp_STK_NOSYNC) {
+        if (tries > 33) {
+          avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): can't get into sync\n",
+                  progname);
+          return -3;
+        }
+        if (stk500_getsync(pgm) < 0)
+          return -1;
+        goto retry;
+      }
+      else if (buf[0] != Resp_STK_INSYNC) {
+        avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): (a) protocol error, "
+                        "expect=0x%02x, resp=0x%02x\n",
+                        progname, Resp_STK_INSYNC, buf[0]);
+        return -4;
+      }
+      
+      if (stk500_recv(pgm, buf, 1) < 0)
+        return -1;
+      if (buf[0] != Resp_STK_OK) {
+        avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): (a) protocol error, "
+                        "expect=0x%02x, resp=0x%02x\n",
+                        progname, Resp_STK_INSYNC, buf[0]);
+        return -5;
       }
-      if (stk500_getsync(pgm) < 0)
-	return -1;
-      goto retry;
-    }
-    else if (buf[0] != Resp_STK_INSYNC) {
-      avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): (a) protocol error, "
-                      "expect=0x%02x, resp=0x%02x\n",
-                      progname, Resp_STK_INSYNC, buf[0]);
-      return -4;
-    }
-    
-    if (stk500_recv(pgm, buf, 1) < 0)
-      return -1;
-    if (buf[0] != Resp_STK_OK) {
-      avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): (a) protocol error, "
-                      "expect=0x%02x, resp=0x%02x\n",
-                      progname, Resp_STK_INSYNC, buf[0]);
-      return -5;
     }
   }
 
@@ -893,11 +918,15 @@ static int stk500_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
     tries++;
     stk500_loadaddr(pgm, m, addr/a_div);
     buf[0] = Cmnd_STK_READ_PAGE;
-    buf[1] = (block_size >> 8) & 0xff;
-    buf[2] = block_size & 0xff;
-    buf[3] = memtype;
-    buf[4] = Sync_CRC_EOP;
-    stk500_send(pgm, buf, 5);
+    // Workaround for the infamous ';' bug in the Prusa3D usb to serial converter.
+    // Send the binary data by nibbles to avoid transmitting the ';' character.
+    buf[1] = (block_size >> 8) & 0xf0;
+    buf[2] = (block_size >> 8) & 0x0f;
+    buf[3] = block_size & 0xf0;
+    buf[4] = block_size & 0x0f;
+    buf[5] = memtype;
+    buf[6] = Sync_CRC_EOP;
+    stk500_send(pgm, buf, 7);
 
     if (stk500_recv(pgm, buf, 1) < 0)
       return -1;
diff --git a/xs/src/avrdude/stk500v2.c b/xs/src/avrdude/stk500v2.c
index d3acb639c..4d62640c0 100644
--- a/xs/src/avrdude/stk500v2.c
+++ b/xs/src/avrdude/stk500v2.c
@@ -79,7 +79,7 @@
 #define SERIAL_TIMEOUT 2
 
 // Retry count
-#define RETRIES 5
+#define RETRIES 0
 
 #if 0
 #define DEBUG(...) avrdude_message(MSG_INFO, __VA_ARGS__)
@@ -745,7 +745,7 @@ static int stk500v2_recv(PROGRAMMER * pgm, unsigned char *msg, size_t maxsize) {
 
 
 
-static int stk500v2_getsync_internal(PROGRAMMER * pgm, int retries) {
+int stk500v2_getsync(PROGRAMMER * pgm) {
   int tries = 0;
   unsigned char buf[1], resp[32];
   int status;
@@ -804,7 +804,7 @@ retry:
                         progname, pgmname[PDATA(pgm)->pgmtype]);
       return 0;
     } else {
-      if (tries > retries) {
+      if (tries > RETRIES) {
         avrdude_message(MSG_INFO, "%s: stk500v2_getsync(): can't communicate with device: resp=0x%02x\n",
                         progname, resp[0]);
         return -6;
@@ -814,7 +814,7 @@ retry:
 
   // or if we got a timeout
   } else if (status == -1) {
-    if (tries > retries) {
+    if (tries > RETRIES) {
       avrdude_message(MSG_INFO, "%s: stk500v2_getsync(): timeout communicating with programmer\n",
               progname);
       return -1;
@@ -823,7 +823,7 @@ retry:
 
   // or any other error
   } else {
-    if (tries > retries) {
+    if (tries > RETRIES) {
       avrdude_message(MSG_INFO, "%s: stk500v2_getsync(): error communicating with programmer: (%d)\n",
               progname,status);
     } else
@@ -833,11 +833,6 @@ retry:
   return 0;
 }
 
-int stk500v2_getsync(PROGRAMMER * pgm) {
-  // This is to avoid applying RETRIES exponentially
-  return stk500v2_getsync_internal(pgm, RETRIES);
-}
-
 static int stk500v2_command(PROGRAMMER * pgm, unsigned char * buf,
                             size_t len, size_t maxlen) {
   int i;
@@ -947,7 +942,7 @@ retry:
   }
 
   // otherwise try to sync up again
-  status = stk500v2_getsync_internal(pgm, 1);
+  status = stk500v2_getsync(pgm);
   if (status != 0) {
     if (tries > RETRIES) {
       avrdude_message(MSG_INFO, "%s: stk500v2_command(): failed miserably to execute command 0x%02x\n",
diff --git a/xs/src/avrdude/update.c b/xs/src/avrdude/update.c
index a73461dfa..e9dd6e325 100644
--- a/xs/src/avrdude/update.c
+++ b/xs/src/avrdude/update.c
@@ -101,6 +101,24 @@ UPDATE * parse_op(char * s)
 
   p++;
 
+  // Extension: Parse file contents offset
+  size_t offset = 0;
+
+  for (; *p != ':'; p++) {
+    if (*p >= '0' && *p <= '9') {
+      offset *= 10;
+      offset += *p - 0x30;
+    } else {
+      avrdude_message(MSG_INFO, "%s: invalid update specification: offset is not a number\n", progname);
+      free(upd->memtype);
+      free(upd);
+      return NULL;
+    }
+  }
+
+  upd->offset = offset;
+  p++;
+
   /*
    * Now, parse the filename component.  Instead of looking for the
    * leftmost possible colon delimiter, we look for the rightmost one.
@@ -176,7 +194,7 @@ UPDATE * dup_update(UPDATE * upd)
   return u;
 }
 
-UPDATE * new_update(int op, char * memtype, int filefmt, char * filename)
+UPDATE * new_update(int op, char * memtype, int filefmt, char * filename, size_t offset)
 {
   UPDATE * u;
 
@@ -190,6 +208,7 @@ UPDATE * new_update(int op, char * memtype, int filefmt, char * filename)
   u->filename = strdup(filename);
   u->op = op;
   u->format = filefmt;
+  u->offset = offset;
 
   return u;
 }
@@ -250,7 +269,7 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f
                       progname,
                       strcmp(upd->filename, "-")==0 ? "<stdout>" : upd->filename);
     }
-    rc = fileio(FIO_WRITE, upd->filename, upd->format, p, upd->memtype, size);
+    rc = fileio(FIO_WRITE, upd->filename, upd->format, p, upd->memtype, size, 0);
     if (rc < 0) {
       avrdude_message(MSG_INFO, "%s: write to file '%s' failed\n",
               progname, upd->filename);
@@ -267,7 +286,7 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f
                       progname,
                       strcmp(upd->filename, "-")==0 ? "<stdin>" : upd->filename);
     }
-    rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1);
+    rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->offset);
     if (rc < 0) {
       avrdude_message(MSG_INFO, "%s: read from file '%s' failed\n",
               progname, upd->filename);
@@ -296,11 +315,11 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f
       report_progress(1,1,NULL);
     }
     else {
-      /*
-       * test mode, don't actually write to the chip, output the buffer
-       * to stdout in intel hex instead
-       */
-      rc = fileio(FIO_WRITE, "-", FMT_IHEX, p, upd->memtype, size);
+      // /*
+      //  * test mode, don't actually write to the chip, output the buffer
+      //  * to stdout in intel hex instead
+      //  */
+      // rc = fileio(FIO_WRITE, "-", FMT_IHEX, p, upd->memtype, size, 0);
     }
 
     if (rc < 0) {
@@ -332,7 +351,7 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f
             progname, mem->desc, upd->filename);
     }
 
-    rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1);
+    rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->offset);
     if (rc < 0) {
       avrdude_message(MSG_INFO, "%s: read from file '%s' failed\n",
               progname, upd->filename);
diff --git a/xs/src/libslic3r/BoundingBox.cpp b/xs/src/libslic3r/BoundingBox.cpp
index ceb968a50..4355cd61b 100644
--- a/xs/src/libslic3r/BoundingBox.cpp
+++ b/xs/src/libslic3r/BoundingBox.cpp
@@ -2,6 +2,8 @@
 #include <algorithm>
 #include <assert.h>
 
+#include <Eigen/Dense>
+
 namespace Slic3r {
 
 template BoundingBoxBase<Point>::BoundingBoxBase(const std::vector<Point> &points);
@@ -251,4 +253,41 @@ void BoundingBox::align_to_grid(const coord_t cell_size)
     }
 }
 
+BoundingBoxf3 BoundingBoxf3::transformed(const std::vector<float>& matrix) const
+{
+    Eigen::Matrix<float, 3, 8> vertices;
+
+    vertices(0, 0) = (float)min.x; vertices(1, 0) = (float)min.y; vertices(2, 0) = (float)min.z;
+    vertices(0, 1) = (float)max.x; vertices(1, 1) = (float)min.y; vertices(2, 1) = (float)min.z;
+    vertices(0, 2) = (float)max.x; vertices(1, 2) = (float)max.y; vertices(2, 2) = (float)min.z;
+    vertices(0, 3) = (float)min.x; vertices(1, 3) = (float)max.y; vertices(2, 3) = (float)min.z;
+    vertices(0, 4) = (float)min.x; vertices(1, 4) = (float)min.y; vertices(2, 4) = (float)max.z;
+    vertices(0, 5) = (float)max.x; vertices(1, 5) = (float)min.y; vertices(2, 5) = (float)max.z;
+    vertices(0, 6) = (float)max.x; vertices(1, 6) = (float)max.y; vertices(2, 6) = (float)max.z;
+    vertices(0, 7) = (float)min.x; vertices(1, 7) = (float)max.y; vertices(2, 7) = (float)max.z;
+
+    Eigen::Transform<float, 3, Eigen::Affine> m;
+    ::memcpy((void*)m.data(), (const void*)matrix.data(), 16 * sizeof(float));
+    Eigen::Matrix<float, 3, 8> transf_vertices = m * vertices.colwise().homogeneous();
+
+    float min_x = transf_vertices(0, 0);
+    float max_x = transf_vertices(0, 0);
+    float min_y = transf_vertices(1, 0);
+    float max_y = transf_vertices(1, 0);
+    float min_z = transf_vertices(2, 0);
+    float max_z = transf_vertices(2, 0);
+
+    for (int i = 1; i < 8; ++i)
+    {
+        min_x = std::min(min_x, transf_vertices(0, i));
+        max_x = std::max(max_x, transf_vertices(0, i));
+        min_y = std::min(min_y, transf_vertices(1, i));
+        max_y = std::max(max_y, transf_vertices(1, i));
+        min_z = std::min(min_z, transf_vertices(2, i));
+        max_z = std::max(max_z, transf_vertices(2, i));
+    }
+
+    return BoundingBoxf3(Pointf3((coordf_t)min_x, (coordf_t)min_y, (coordf_t)min_z), Pointf3((coordf_t)max_x, (coordf_t)max_y, (coordf_t)max_z));
+}
+
 }
diff --git a/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp
index 5de94aa9c..1f71536ee 100644
--- a/xs/src/libslic3r/BoundingBox.hpp
+++ b/xs/src/libslic3r/BoundingBox.hpp
@@ -148,6 +148,8 @@ public:
     BoundingBoxf3() : BoundingBox3Base<Pointf3>() {};
     BoundingBoxf3(const Pointf3 &pmin, const Pointf3 &pmax) : BoundingBox3Base<Pointf3>(pmin, pmax) {};
     BoundingBoxf3(const std::vector<Pointf3> &points) : BoundingBox3Base<Pointf3>(points) {};
+
+    BoundingBoxf3 transformed(const std::vector<float>& matrix) const;
 };
 
 template<typename VT>
diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp
index 0467962c3..2c32db1a6 100644
--- a/xs/src/libslic3r/Format/3mf.cpp
+++ b/xs/src/libslic3r/Format/3mf.cpp
@@ -1271,6 +1271,7 @@ namespace Slic3r {
         if ((std::abs(sx - sy) > 0.00001) || (std::abs(sx - sz) > 0.00001))
             return;
 
+#if 0 // use quaternions
         // rotations (extracted using quaternion)
         double inv_sx = 1.0 / sx;
         double inv_sy = 1.0 / sy;
@@ -1331,6 +1332,25 @@ namespace Slic3r {
             if (angle_z < 0.0)
                 angle_z += 2.0 * PI;
         }
+#else // use eigen library
+        double inv_sx = 1.0 / sx;
+        double inv_sy = 1.0 / sy;
+        double inv_sz = 1.0 / sz;
+
+        Eigen::Matrix3d m3x3;
+        m3x3 << (double)matrix(0, 0) * inv_sx, (double)matrix(0, 1) * inv_sy, (double)matrix(0, 2) * inv_sz,
+                (double)matrix(1, 0) * inv_sx, (double)matrix(1, 1) * inv_sy, (double)matrix(1, 2) * inv_sz,
+                (double)matrix(2, 0) * inv_sx, (double)matrix(2, 1) * inv_sy, (double)matrix(2, 2) * inv_sz;
+
+        Eigen::AngleAxisd rotation;
+        rotation.fromRotationMatrix(m3x3);
+
+        // invalid rotation axis, we currently handle only rotations around Z axis
+        if ((rotation.angle() != 0.0) && (rotation.axis() != Eigen::Vector3d::UnitZ()) && (rotation.axis() != -Eigen::Vector3d::UnitZ()))
+            return;
+
+        double angle_z = (rotation.axis() == Eigen::Vector3d::UnitZ()) ? rotation.angle() : -rotation.angle();
+#endif 
 
         instance.offset.x = offset_x;
         instance.offset.y = offset_y;
diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp
index 83b50ec9e..21d4b4d3b 100644
--- a/xs/src/libslic3r/Format/AMF.cpp
+++ b/xs/src/libslic3r/Format/AMF.cpp
@@ -13,6 +13,7 @@
 
 #include <boost/filesystem/operations.hpp>
 #include <boost/algorithm/string.hpp>
+#include <boost/nowide/fstream.hpp>
 #include <miniz/miniz_zip.h>
 
 #if 0
@@ -666,10 +667,21 @@ bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model)
 // If bundle is not a null pointer, updates it if the amf file/archive contains config data
 bool load_amf(const char *path, PresetBundle* bundle, Model *model)
 {
-    if (boost::iends_with(path, ".zip.amf"))
-        return load_amf_archive(path, bundle, model);
-    else if (boost::iends_with(path, ".amf") || boost::iends_with(path, ".amf.xml"))
+    if (boost::iends_with(path, ".amf.xml"))
+        // backward compatibility with older slic3r output
         return load_amf_file(path, bundle, model);
+    else if (boost::iends_with(path, ".amf"))
+    {
+        boost::nowide::ifstream file(path, boost::nowide::ifstream::binary);
+        if (!file.good())
+            return false;
+
+        std::string zip_mask(2, '\0');
+        file.read(const_cast<char*>(zip_mask.data()), 2);
+        file.close();
+
+        return (zip_mask == "PK") ? load_amf_archive(path, bundle, model) : load_amf_file(path, bundle, model);
+    }
     else
         return false;
 }
diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp
index 147353abd..110581dde 100644
--- a/xs/src/libslic3r/Model.cpp
+++ b/xs/src/libslic3r/Model.cpp
@@ -14,6 +14,8 @@
 #include <boost/nowide/iostream.hpp>
 #include <boost/algorithm/string/replace.hpp>
 
+#include <Eigen/Dense>
+
 namespace Slic3r {
 
     unsigned int Model::s_auto_extruder_id = 1;
@@ -603,10 +605,7 @@ void ModelObject::clear_instances()
 
 // Returns the bounding box of the transformed instances.
 // This bounding box is approximate and not snug.
-//========================================================================================================
 const BoundingBoxf3& ModelObject::bounding_box() const
-//const BoundingBoxf3& ModelObject::bounding_box()
-//========================================================================================================
 {
     if (! m_bounding_box_valid) {
         BoundingBoxf3 raw_bbox;
@@ -1048,32 +1047,16 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mes
 
 BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const
 {
-    // rotate around mesh origin
-    double c = cos(this->rotation);
-    double s = sin(this->rotation);
-    Pointf3 pts[4] = {
-        bbox.min,
-        bbox.max,
-        Pointf3(bbox.min.x, bbox.max.y, bbox.min.z),
-        Pointf3(bbox.max.x, bbox.min.y, bbox.max.z)
-    };
-    BoundingBoxf3 out;
-    for (int i = 0; i < 4; ++ i) {
-        Pointf3 &v = pts[i];
-        double xold = v.x;
-        double yold = v.y;
-        v.x = float(c * xold - s * yold);
-        v.y = float(s * xold + c * yold);
-        v.x *= this->scaling_factor;
-        v.y *= this->scaling_factor;
-        v.z *= this->scaling_factor;
-        if (! dont_translate) {
-            v.x += this->offset.x;
-            v.y += this->offset.y;
-        }
-        out.merge(v);
-    }
-    return out;
+    Eigen::Transform<float, 3, Eigen::Affine> matrix = Eigen::Transform<float, 3, Eigen::Affine>::Identity();
+    if (!dont_translate)
+        matrix.translate(Eigen::Vector3f((float)offset.x, (float)offset.y, 0.0f));
+
+    matrix.rotate(Eigen::AngleAxisf(rotation, Eigen::Vector3f::UnitZ()));
+    matrix.scale(scaling_factor);
+
+    std::vector<float> m(16, 0.0f);
+    ::memcpy((void*)m.data(), (const void*)matrix.data(), 16 * sizeof(float));
+    return bbox.transformed(m);
 }
 
 void ModelInstance::transform_polygon(Polygon* polygon) const
diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp
index b148ec29d..355a66ac8 100644
--- a/xs/src/libslic3r/Model.hpp
+++ b/xs/src/libslic3r/Model.hpp
@@ -103,10 +103,7 @@ public:
     // Returns the bounding box of the transformed instances.
     // This bounding box is approximate and not snug.
     // This bounding box is being cached.
-//========================================================================================================
     const BoundingBoxf3& bounding_box() const;
-//    const BoundingBoxf3& bounding_box();
-//========================================================================================================
     void invalidate_bounding_box() { m_bounding_box_valid = false; }
     // Returns a snug bounding box of the transformed instances.
     // This bounding box is not being cached.
@@ -148,10 +145,9 @@ private:
     // Parent object, owning this ModelObject.
     Model          *m_model;
     // Bounding box, cached.
-//========================================================================================================
+
     mutable BoundingBoxf3 m_bounding_box;
     mutable bool          m_bounding_box_valid;
-//========================================================================================================
 };
 
 // An object STL, or a modifier volume, over which a different set of parameters shall be applied.
diff --git a/xs/src/libslic3r/Polygon.cpp b/xs/src/libslic3r/Polygon.cpp
index 27f9a2ca1..b5fd7e64f 100644
--- a/xs/src/libslic3r/Polygon.cpp
+++ b/xs/src/libslic3r/Polygon.cpp
@@ -103,7 +103,7 @@ double Polygon::area() const
 
     double a = 0.;
     for (size_t i = 0, j = n - 1; i < n; ++i) {
-        a += double(points[j].x + points[i].x) * double(points[i].y - points[j].y);
+        a += ((double)points[j].x + (double)points[i].x) * ((double)points[i].y - (double)points[j].y);
         j = i;
     }
     return 0.5 * a;
diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
index 352738c7c..e404a9d51 100644
--- a/xs/src/slic3r/GUI/3DScene.cpp
+++ b/xs/src/slic3r/GUI/3DScene.cpp
@@ -2,7 +2,6 @@
 
 #include "3DScene.hpp"
 
-#include "../../libslic3r/libslic3r.h"
 #include "../../libslic3r/ExtrusionEntity.hpp"
 #include "../../libslic3r/ExtrusionEntityCollection.hpp"
 #include "../../libslic3r/Geometry.hpp"
@@ -28,8 +27,15 @@
 #include <wx/image.h>
 #include <wx/settings.h>
 
+#include <Eigen/Dense>
+
 #include "GUI.hpp"
 
+static const float UNIT_MATRIX[] = { 1.0f, 0.0f, 0.0f, 0.0f,
+                                     0.0f, 1.0f, 0.0f, 0.0f,
+                                     0.0f, 0.0f, 1.0f, 0.0f,
+                                     0.0f, 0.0f, 0.0f, 1.0f };
+
 namespace Slic3r {
 
 void GLIndexedVertexArray::load_mesh_flat_shading(const TriangleMesh &mesh)
@@ -198,6 +204,34 @@ const float GLVolume::HOVER_COLOR[4] = { 0.4f, 0.9f, 0.1f, 1.0f };
 const float GLVolume::OUTSIDE_COLOR[4] = { 0.0f, 0.38f, 0.8f, 1.0f };
 const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 0.19f, 0.58f, 1.0f, 1.0f };
 
+GLVolume::GLVolume(float r, float g, float b, float a)
+    : m_angle_z(0.0f)
+    , m_scale_factor(1.0f)
+    , m_dirty(true)
+    , composite_id(-1)
+    , select_group_id(-1)
+    , drag_group_id(-1)
+    , extruder_id(0)
+    , selected(false)
+    , is_active(true)
+    , zoom_to_volumes(true)
+    , outside_printer_detection_enabled(true)
+    , is_outside(false)
+    , hover(false)
+    , is_modifier(false)
+    , is_wipe_tower(false)
+    , tverts_range(0, size_t(-1))
+    , qverts_range(0, size_t(-1))
+{
+    m_world_mat = std::vector<float>(UNIT_MATRIX, std::end(UNIT_MATRIX));
+
+    color[0] = r;
+    color[1] = g;
+    color[2] = b;
+    color[3] = a;
+    set_render_color(r, g, b, a);
+}
+
 void GLVolume::set_render_color(float r, float g, float b, float a)
 {
     render_color[0] = r;
@@ -218,12 +252,7 @@ void GLVolume::set_render_color(const float* rgba, unsigned int size)
 void GLVolume::set_render_color()
 {
     if (selected)
-    {
-        if (is_outside)
-            set_render_color(SELECTED_OUTSIDE_COLOR, 4);
-        else
-            set_render_color(SELECTED_COLOR, 4);
-    }
+        set_render_color(is_outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4);
     else if (hover)
         set_render_color(HOVER_COLOR, 4);
     else if (is_outside)
@@ -232,6 +261,52 @@ void GLVolume::set_render_color()
         set_render_color(color, 4);
 }
 
+const Pointf3& GLVolume::get_origin() const
+{
+    return m_origin;
+}
+
+void GLVolume::set_origin(const Pointf3& origin)
+{
+    m_origin = origin;
+    m_dirty = true;
+}
+
+void GLVolume::set_angle_z(float angle_z)
+{
+    m_angle_z = angle_z;
+    m_dirty = true;
+}
+
+void GLVolume::set_scale_factor(float scale_factor)
+{
+    m_scale_factor = scale_factor;
+    m_dirty = true;
+}
+
+const std::vector<float>& GLVolume::world_matrix() const
+{
+    if (m_dirty)
+    {
+        Eigen::Transform<float, 3, Eigen::Affine> m = Eigen::Transform<float, 3, Eigen::Affine>::Identity();
+        m.translate(Eigen::Vector3f((float)m_origin.x, (float)m_origin.y, (float)m_origin.z));
+        m.rotate(Eigen::AngleAxisf(m_angle_z, Eigen::Vector3f::UnitZ()));
+        m.scale(m_scale_factor);
+        ::memcpy((void*)m_world_mat.data(), (const void*)m.data(), 16 * sizeof(float));
+        m_dirty = false;
+    }
+
+    return m_world_mat;
+}
+
+BoundingBoxf3 GLVolume::transformed_bounding_box() const
+{
+    if (m_dirty)
+        m_transformed_bounding_box = bounding_box.transformed(world_matrix());
+
+    return m_transformed_bounding_box;
+}
+
 void GLVolume::set_range(double min_z, double max_z)
 {
     this->qverts_range.first  = 0;
@@ -272,14 +347,16 @@ void GLVolume::render() const
     if (!is_active)
         return;
 
-    glCullFace(GL_BACK);
-    glPushMatrix();
-    glTranslated(this->origin.x, this->origin.y, this->origin.z);
+    ::glCullFace(GL_BACK);
+    ::glPushMatrix();
+    ::glTranslated(m_origin.x, m_origin.y, m_origin.z);
+    ::glRotatef(m_angle_z * 180.0f / PI, 0.0f, 0.0f, 1.0f);
+    ::glScalef(m_scale_factor, m_scale_factor, m_scale_factor);
     if (this->indexed_vertex_array.indexed())
         this->indexed_vertex_array.render(this->tverts_range, this->qverts_range);
     else
         this->indexed_vertex_array.render();
-    glPopMatrix();
+    ::glPopMatrix();
 }
 
 void GLVolume::render_using_layer_height() const
@@ -297,6 +374,7 @@ void GLVolume::render_using_layer_height() const
     GLint z_texture_row_to_normalized_id = (layer_height_texture_data.shader_id > 0) ? glGetUniformLocation(layer_height_texture_data.shader_id, "z_texture_row_to_normalized") : -1;
     GLint z_cursor_id = (layer_height_texture_data.shader_id > 0) ? glGetUniformLocation(layer_height_texture_data.shader_id, "z_cursor") : -1;
     GLint z_cursor_band_width_id = (layer_height_texture_data.shader_id > 0) ? glGetUniformLocation(layer_height_texture_data.shader_id, "z_cursor_band_width") : -1;
+    GLint world_matrix_id = (layer_height_texture_data.shader_id > 0) ? glGetUniformLocation(layer_height_texture_data.shader_id, "volume_world_matrix") : -1;
 
     if (z_to_texture_row_id  >= 0)
         glUniform1f(z_to_texture_row_id, (GLfloat)layer_height_texture_z_to_row_id());
@@ -310,14 +388,19 @@ void GLVolume::render_using_layer_height() const
     if (z_cursor_band_width_id >= 0)
         glUniform1f(z_cursor_band_width_id, (GLfloat)layer_height_texture_data.edit_band_width);
 
-    unsigned int w = layer_height_texture_width();
-    unsigned int h = layer_height_texture_height();
+    if (world_matrix_id >= 0)
+        ::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().data());
+
+    GLsizei w = (GLsizei)layer_height_texture_width();
+    GLsizei h = (GLsizei)layer_height_texture_height();
+    GLsizei half_w = w / 2;
+    GLsizei half_h = h / 2;
 
     glBindTexture(GL_TEXTURE_2D, layer_height_texture_data.texture_id);
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
-    glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, w / 2, h / 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+    glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
     glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, layer_height_texture_data_ptr_level0());
-    glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, w / 2, h / 2, GL_RGBA, GL_UNSIGNED_BYTE, layer_height_texture_data_ptr_level1());
+    glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, layer_height_texture_data_ptr_level1());
 
     render();
 
@@ -327,6 +410,128 @@ void GLVolume::render_using_layer_height() const
         glUseProgram(current_program_id);
 }
 
+void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) const
+{
+    if (!is_active)
+        return;
+
+    if (!indexed_vertex_array.vertices_and_normals_interleaved_VBO_id)
+        return;
+
+    if (layer_height_texture_data.can_use())
+    {
+        ::glDisableClientState(GL_VERTEX_ARRAY);
+        ::glDisableClientState(GL_NORMAL_ARRAY);
+        render_using_layer_height();
+        ::glEnableClientState(GL_VERTEX_ARRAY);
+        ::glEnableClientState(GL_NORMAL_ARRAY);
+        return;
+    }
+
+    GLsizei n_triangles = GLsizei(std::min(indexed_vertex_array.triangle_indices_size, tverts_range.second - tverts_range.first));
+    GLsizei n_quads = GLsizei(std::min(indexed_vertex_array.quad_indices_size, qverts_range.second - qverts_range.first));
+    if (n_triangles + n_quads == 0)
+    {
+        ::glDisableClientState(GL_VERTEX_ARRAY);
+        ::glDisableClientState(GL_NORMAL_ARRAY);
+
+        if (color_id >= 0)
+        {
+            float color[4];
+            ::memcpy((void*)color, (const void*)render_color, 4 * sizeof(float));
+            ::glUniform4fv(color_id, 1, (const GLfloat*)color);
+        }
+        else
+            ::glColor4f(render_color[0], render_color[1], render_color[2], render_color[3]);
+
+        if (detection_id != -1)
+            ::glUniform1i(detection_id, outside_printer_detection_enabled ? 1 : 0);
+
+        if (worldmatrix_id != -1)
+            ::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().data());
+
+        render();
+
+        ::glEnableClientState(GL_VERTEX_ARRAY);
+        ::glEnableClientState(GL_NORMAL_ARRAY);
+
+        return;
+    }
+
+    if (color_id >= 0)
+        ::glUniform4fv(color_id, 1, (const GLfloat*)render_color);
+    else
+        ::glColor4f(render_color[0], render_color[1], render_color[2], render_color[3]);
+
+    if (detection_id != -1)
+        ::glUniform1i(detection_id, outside_printer_detection_enabled ? 1 : 0);
+
+    if (worldmatrix_id != -1)
+        ::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().data());
+
+    ::glBindBuffer(GL_ARRAY_BUFFER, indexed_vertex_array.vertices_and_normals_interleaved_VBO_id);
+    ::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)));
+    ::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr);
+
+    ::glPushMatrix();
+    ::glTranslated(m_origin.x, m_origin.y, m_origin.z);
+    ::glRotatef(m_angle_z * 180.0f / PI, 0.0f, 0.0f, 1.0f);
+    ::glScalef(m_scale_factor, m_scale_factor, m_scale_factor);
+
+    if (n_triangles > 0)
+    {
+        ::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexed_vertex_array.triangle_indices_VBO_id);
+        ::glDrawElements(GL_TRIANGLES, n_triangles, GL_UNSIGNED_INT, (const void*)(tverts_range.first * 4));
+    }
+    if (n_quads > 0)
+    {
+        ::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexed_vertex_array.quad_indices_VBO_id);
+        ::glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, (const void*)(qverts_range.first * 4));
+    }
+
+    ::glPopMatrix();
+}
+
+void GLVolume::render_legacy() const
+{
+    assert(!indexed_vertex_array.vertices_and_normals_interleaved_VBO_id);
+    if (!is_active)
+        return;
+
+    GLsizei n_triangles = GLsizei(std::min(indexed_vertex_array.triangle_indices_size, tverts_range.second - tverts_range.first));
+    GLsizei n_quads = GLsizei(std::min(indexed_vertex_array.quad_indices_size, qverts_range.second - qverts_range.first));
+    if (n_triangles + n_quads == 0)
+    {
+        ::glDisableClientState(GL_VERTEX_ARRAY);
+        ::glDisableClientState(GL_NORMAL_ARRAY);
+
+        ::glColor4f(render_color[0], render_color[1], render_color[2], render_color[3]);
+        render();
+
+        ::glEnableClientState(GL_VERTEX_ARRAY);
+        ::glEnableClientState(GL_NORMAL_ARRAY);
+
+        return;
+    }
+
+    ::glColor4f(render_color[0], render_color[1], render_color[2], render_color[3]);
+    ::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), indexed_vertex_array.vertices_and_normals_interleaved.data() + 3);
+    ::glNormalPointer(GL_FLOAT, 6 * sizeof(float), indexed_vertex_array.vertices_and_normals_interleaved.data());
+
+    ::glPushMatrix();
+    ::glTranslated(m_origin.x, m_origin.y, m_origin.z);
+    ::glRotatef(m_angle_z * 180.0f / PI, 0.0f, 0.0f, 1.0f);
+    ::glScalef(m_scale_factor, m_scale_factor, m_scale_factor);
+
+    if (n_triangles > 0)
+        ::glDrawElements(GL_TRIANGLES, n_triangles, GL_UNSIGNED_INT, indexed_vertex_array.triangle_indices.data() + tverts_range.first);
+
+    if (n_quads > 0)
+        ::glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, indexed_vertex_array.quad_indices.data() + qverts_range.first);
+
+    ::glPopMatrix();
+}
+
 double GLVolume::layer_height_texture_z_to_row_id() const
 {
     return (this->layer_height_texture.get() == nullptr) ? 0.0 : double(this->layer_height_texture->cells - 1) / (double(this->layer_height_texture->width) * this->layer_height_texture_data.print_object->model_object()->bounding_box().max.z);
@@ -399,7 +604,6 @@ std::vector<int> GLVolumeCollection::load_object(
         for (int instance_idx : instance_idxs) {
             const ModelInstance *instance = model_object->instances[instance_idx];
             TriangleMesh mesh = model_volume->mesh;
-            instance->transform_mesh(&mesh);
             volumes_idx.push_back(int(this->volumes.size()));
             float color[4];
             memcpy(color, colors[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3);
@@ -433,13 +637,15 @@ std::vector<int> GLVolumeCollection::load_object(
                     v.extruder_id = extruder_id;
             }
             v.is_modifier = model_volume->modifier;
+            v.set_origin(Pointf3(instance->offset.x, instance->offset.y, 0.0));
+            v.set_angle_z(instance->rotation);
+            v.set_scale_factor(instance->scaling_factor);
         }
     }
     
     return volumes_idx; 
 }
 
-
 int GLVolumeCollection::load_wipe_tower_preview(
     int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs)
 {
@@ -460,7 +666,8 @@ int GLVolumeCollection::load_wipe_tower_preview(
     else
         v.indexed_vertex_array.load_mesh_flat_shading(mesh);
 
-    v.origin = Pointf3(pos_x, pos_y, 0.);
+    v.set_origin(Pointf3(pos_x, pos_y, 0.));
+
     // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
     v.bounding_box = v.indexed_vertex_array.bounding_box();
     v.indexed_vertex_array.finalize_geometry(use_VBOs);
@@ -485,102 +692,23 @@ void GLVolumeCollection::render_VBOs() const
     GLint color_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "uniform_color") : -1;
     GLint print_box_min_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "print_box.min") : -1;
     GLint print_box_max_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "print_box.max") : -1;
-    GLint print_box_origin_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "print_box.volume_origin") : -1;
+    GLint print_box_detection_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "print_box.volume_detection") : -1;
+    GLint print_box_worldmatrix_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "print_box.volume_world_matrix") : -1;
 
-    for (GLVolume *volume : this->volumes) {
-        if (!volume->is_active)
-            continue;
+    if (print_box_min_id != -1)
+        ::glUniform3fv(print_box_min_id, 1, (const GLfloat*)print_box_min);
 
-        if (!volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id)
-            continue;
+    if (print_box_max_id != -1)
+        ::glUniform3fv(print_box_max_id, 1, (const GLfloat*)print_box_max);
 
+    for (GLVolume *volume : this->volumes)
+    {
         if (volume->layer_height_texture_data.can_use())
-        {
-            ::glDisableClientState(GL_VERTEX_ARRAY);
-            ::glDisableClientState(GL_NORMAL_ARRAY);
             volume->generate_layer_height_texture(volume->layer_height_texture_data.print_object, false);
-            volume->render_using_layer_height();
-            ::glEnableClientState(GL_VERTEX_ARRAY);
-            ::glEnableClientState(GL_NORMAL_ARRAY);
-            continue;
-        }
-
-        volume->set_render_color();
-
-        GLsizei n_triangles = GLsizei(std::min(volume->indexed_vertex_array.triangle_indices_size, volume->tverts_range.second - volume->tverts_range.first));
-        GLsizei n_quads     = GLsizei(std::min(volume->indexed_vertex_array.quad_indices_size,     volume->qverts_range.second - volume->qverts_range.first));
-        if (n_triangles + n_quads == 0)
-        {
-            ::glDisableClientState(GL_VERTEX_ARRAY);
-            ::glDisableClientState(GL_NORMAL_ARRAY);
-
-            if (color_id >= 0)
-            {
-                float color[4];
-                ::memcpy((void*)color, (const void*)volume->render_color, 4 * sizeof(float));
-                ::glUniform4fv(color_id, 1, (const GLfloat*)color);
-            }
-            else
-                ::glColor4f(volume->render_color[0], volume->render_color[1], volume->render_color[2], volume->render_color[3]);
-
-            if (print_box_min_id != -1)
-                ::glUniform3fv(print_box_min_id, 1, (const GLfloat*)print_box_min);
-
-            if (print_box_max_id != -1)
-                ::glUniform3fv(print_box_max_id, 1, (const GLfloat*)print_box_max);
-
-            if (print_box_origin_id != -1)
-            {
-                float origin[4] = { (float)volume->origin.x, (float)volume->origin.y, (float)volume->origin.z, volume->outside_printer_detection_enabled ? 1.0f : 0.0f };
-                ::glUniform4fv(print_box_origin_id, 1, (const GLfloat*)origin);
-            }
-
-            volume->render();
-
-            ::glEnableClientState(GL_VERTEX_ARRAY);
-            ::glEnableClientState(GL_NORMAL_ARRAY);
-
-            continue;
-        }
-
-        if (color_id >= 0)
-            ::glUniform4fv(color_id, 1, (const GLfloat*)volume->render_color);
         else
-            ::glColor4f(volume->render_color[0], volume->render_color[1], volume->render_color[2], volume->render_color[3]);
+            volume->set_render_color();
 
-        if (print_box_min_id != -1)
-            ::glUniform3fv(print_box_min_id, 1, (const GLfloat*)print_box_min);
-
-        if (print_box_max_id != -1)
-            ::glUniform3fv(print_box_max_id, 1, (const GLfloat*)print_box_max);
-
-        if (print_box_origin_id != -1)
-        {
-            float origin[4] = { (float)volume->origin.x, (float)volume->origin.y, (float)volume->origin.z, volume->outside_printer_detection_enabled ? 1.0f : 0.0f };
-            ::glUniform4fv(print_box_origin_id, 1, (const GLfloat*)origin);
-        }
-
-        ::glBindBuffer(GL_ARRAY_BUFFER, volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id);
-        ::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)));
-        ::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr);
-
-        bool has_offset = (volume->origin.x != 0) || (volume->origin.y != 0) || (volume->origin.z != 0);
-        if (has_offset) {
-            ::glPushMatrix();
-            ::glTranslated(volume->origin.x, volume->origin.y, volume->origin.z);
-        }
-
-        if (n_triangles > 0) {
-            ::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.triangle_indices_VBO_id);
-            ::glDrawElements(GL_TRIANGLES, n_triangles, GL_UNSIGNED_INT, (const void*)(volume->tverts_range.first * 4));
-        }
-        if (n_quads > 0) {
-            ::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.quad_indices_VBO_id);
-            ::glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, (const void*)(volume->qverts_range.first * 4));
-        }
-
-        if (has_offset)
-            ::glPopMatrix();
+        volume->render_VBOs(color_id, print_box_detection_id, print_box_worldmatrix_id);
     }
 
     ::glBindBuffer(GL_ARRAY_BUFFER, 0);
@@ -601,43 +729,10 @@ void GLVolumeCollection::render_legacy() const
     glEnableClientState(GL_VERTEX_ARRAY);
     glEnableClientState(GL_NORMAL_ARRAY);
  
-    for (GLVolume *volume : this->volumes) {
-        assert(! volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id);
-        if (!volume->is_active)
-            continue;
-
+    for (GLVolume *volume : this->volumes)
+    {
         volume->set_render_color();
-
-        GLsizei n_triangles = GLsizei(std::min(volume->indexed_vertex_array.triangle_indices_size, volume->tverts_range.second - volume->tverts_range.first));
-        GLsizei n_quads     = GLsizei(std::min(volume->indexed_vertex_array.quad_indices_size,     volume->qverts_range.second - volume->qverts_range.first));
-        if (n_triangles + n_quads == 0)
-        {
-            ::glDisableClientState(GL_VERTEX_ARRAY);
-            ::glDisableClientState(GL_NORMAL_ARRAY);
-
-            ::glColor4f(volume->render_color[0], volume->render_color[1], volume->render_color[2], volume->render_color[3]);
-            volume->render();
-
-            ::glEnableClientState(GL_VERTEX_ARRAY);
-            ::glEnableClientState(GL_NORMAL_ARRAY);
-
-            continue;
-        }
-
-        glColor4f(volume->render_color[0], volume->render_color[1], volume->render_color[2], volume->render_color[3]);
-        glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), volume->indexed_vertex_array.vertices_and_normals_interleaved.data() + 3);
-        glNormalPointer(GL_FLOAT, 6 * sizeof(float), volume->indexed_vertex_array.vertices_and_normals_interleaved.data());
-        bool has_offset = volume->origin.x != 0 || volume->origin.y != 0 || volume->origin.z != 0;
-        if (has_offset) {
-            glPushMatrix();
-            glTranslated(volume->origin.x, volume->origin.y, volume->origin.z);
-        }
-        if (n_triangles > 0)
-            glDrawElements(GL_TRIANGLES, n_triangles, GL_UNSIGNED_INT, volume->indexed_vertex_array.triangle_indices.data() + volume->tverts_range.first);
-        if (n_quads > 0)
-            glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, volume->indexed_vertex_array.quad_indices.data() + volume->qverts_range.first);
-        if (has_offset)
-            glPopMatrix();
+        volume->render_legacy();
     }
 
     glDisableClientState(GL_VERTEX_ARRAY);
diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp
index e7d43aee2..6cdd295c3 100644
--- a/xs/src/slic3r/GUI/3DScene.hpp
+++ b/xs/src/slic3r/GUI/3DScene.hpp
@@ -240,7 +240,7 @@ class GLVolume {
             edit_band_width = 0.0f;
         }
 
-        bool can_use() { return (texture_id > 0) && (shader_id > 0) && (print_object != nullptr); }
+        bool can_use() const { return (texture_id > 0) && (shader_id > 0) && (print_object != nullptr); }
     };
 
 public:
@@ -249,44 +249,27 @@ public:
     static const float OUTSIDE_COLOR[4];
     static const float SELECTED_OUTSIDE_COLOR[4];
 
-    GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f) :
-        composite_id(-1),
-        select_group_id(-1),
-        drag_group_id(-1),
-        extruder_id(0),
-        selected(false),
-        is_active(true),
-        zoom_to_volumes(true),
-        outside_printer_detection_enabled(true),
-        is_outside(false),
-        hover(false),
-        is_modifier(false),
-        is_wipe_tower(false),
-        tverts_range(0, size_t(-1)),
-        qverts_range(0, size_t(-1))
-    {
-        color[0] = r;
-        color[1] = g;
-        color[2] = b;
-        color[3] = a;
-        set_render_color(r, g, b, a);
-    }
+    GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f);
     GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {}
 
-    std::vector<int> load_object(
-        const ModelObject        *model_object, 
-        const std::vector<int>   &instance_idxs,
-        const std::string        &color_by,
-        const std::string        &select_by,
-        const std::string        &drag_by);
+private:
+    // Offset of the volume to be rendered.
+    Pointf3               m_origin;
+    // Rotation around Z axis of the volume to be rendered.
+    float                 m_angle_z;
+    // Scale factor of the volume to be rendered.
+    float                 m_scale_factor;
+    // World matrix of the volume to be rendered.
+    std::vector<float>    m_world_mat;
+    // Bounding box of this volume, in unscaled coordinates.
+    mutable BoundingBoxf3 m_transformed_bounding_box;
+    // Whether or not is needed to recalculate the world matrix.
+    mutable bool          m_dirty;
 
-    int load_wipe_tower_preview(
-        int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs);
+public:
 
     // Bounding box of this volume, in unscaled coordinates.
     BoundingBoxf3       bounding_box;
-    // Offset of the volume to be rendered.
-    Pointf3             origin;
     // Color of the triangles / quads held by this volume.
     float               color[4];
     // Color used to render this volume.
@@ -333,10 +316,17 @@ public:
     // Sets render color in dependence of current state
     void set_render_color();
 
+    const Pointf3& get_origin() const;
+    void set_origin(const Pointf3& origin);
+    void set_angle_z(float angle_z);
+    void set_scale_factor(float scale_factor);
+
     int                 object_idx() const { return this->composite_id / 1000000; }
     int                 volume_idx() const { return (this->composite_id / 1000) % 1000; }
     int                 instance_idx() const { return this->composite_id % 1000; }
-    BoundingBoxf3       transformed_bounding_box() const { BoundingBoxf3 bb = this->bounding_box; bb.translate(this->origin); return bb; }
+
+    const std::vector<float>& world_matrix() const;
+    BoundingBoxf3       transformed_bounding_box() const;
 
     bool                empty() const { return this->indexed_vertex_array.empty(); }
     bool                indexed() const { return this->indexed_vertex_array.indexed(); }
@@ -344,6 +334,9 @@ public:
     void                set_range(coordf_t low, coordf_t high);
     void                render() const;
     void                render_using_layer_height() const;
+    void                render_VBOs(int color_id, int detection_id, int worldmatrix_id) const;
+    void                render_legacy() const;
+
     void                finalize_geometry(bool use_VBOs) { this->indexed_vertex_array.finalize_geometry(use_VBOs); }
     void                release_geometry() { this->indexed_vertex_array.release_geometry(); }
 
diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp
index aed0c3534..ce06da853 100644
--- a/xs/src/slic3r/GUI/ConfigWizard.cpp
+++ b/xs/src/slic3r/GUI/ConfigWizard.cpp
@@ -113,6 +113,11 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, cons
 	sizer->Add(all_none_sizer, 0, wxEXPAND);
 
 	SetSizer(sizer);
+
+	if (cboxes.size() > 0) {
+		cboxes[0]->SetValue(true);
+		on_checkbox(cboxes[0], true);
+	}
 }
 
 void PrinterPicker::select_all(bool select)
@@ -598,10 +603,10 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt)
 
 static const std::unordered_map<std::string, std::pair<std::string, std::string>> legacy_preset_map {{
 	{ "Original Prusa i3 MK2.ini",                           std::make_pair("MK2S", "0.4") },
-	{ "Original Prusa i3 MK2 MM Single Mode.ini",            std::make_pair("MK2S", "0.4") },
-	{ "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") },
-	{ "Original Prusa i3 MK2 MultiMaterial.ini",             std::make_pair("MK2S", "0.4") },
-	{ "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini",  std::make_pair("MK2S", "0.6") },
+	{ "Original Prusa i3 MK2 MM Single Mode.ini",            std::make_pair("MK2SMM", "0.4") },
+	{ "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") },
+	{ "Original Prusa i3 MK2 MultiMaterial.ini",             std::make_pair("MK2SMM", "0.4") },
+	{ "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini",  std::make_pair("MK2SMM", "0.6") },
 	{ "Original Prusa i3 MK2 0.25 nozzle.ini",               std::make_pair("MK2S", "0.25") },
 	{ "Original Prusa i3 MK2 0.6 nozzle.ini",                std::make_pair("MK2S", "0.6") },
 	{ "Original Prusa i3 MK3.ini",                           std::make_pair("MK3",  "0.4") },
@@ -809,8 +814,8 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) :
 	topsizer->AddSpacer(INDEX_MARGIN);
 	topsizer->Add(p->hscroll, 1, wxEXPAND);
 
-	p->btn_prev = new wxButton(this, wxID_BACKWARD);
-	p->btn_next = new wxButton(this, wxID_FORWARD);
+	p->btn_prev = new wxButton(this, wxID_NONE, _(L("< &Back")));
+	p->btn_next = new wxButton(this, wxID_NONE, _(L("&Next >")));
 	p->btn_finish = new wxButton(this, wxID_APPLY, _(L("&Finish")));
 	p->btn_cancel = new wxButton(this, wxID_CANCEL);
 	p->btnsizer->AddStretchSpacer();
diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp
index 8ea9d2d6e..d74743055 100644
--- a/xs/src/slic3r/GUI/FirmwareDialog.cpp
+++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp
@@ -4,12 +4,14 @@
 #include <algorithm>
 #include <boost/format.hpp>
 #include <boost/filesystem/path.hpp>
+#include <boost/filesystem/fstream.hpp>
 #include <boost/log/trivial.hpp>
 
 #include <wx/app.h>
 #include <wx/event.h>
 #include <wx/sizer.h>
 #include <wx/settings.h>
+#include <wx/timer.h>
 #include <wx/panel.h>
 #include <wx/button.h>
 #include <wx/filepicker.h>
@@ -36,7 +38,7 @@ namespace Slic3r {
 enum AvrdudeEvent
 {
 	AE_MESSAGE,
-	AE_PRORGESS,
+	AE_PROGRESS,
 	AE_EXIT,
 };
 
@@ -62,7 +64,6 @@ struct FirmwareDialog::priv
 	std::vector<Utils::SerialPortInfo> ports;
 	wxFilePickerCtrl *hex_picker;
 	wxStaticText *txt_status;
-	wxStaticText *txt_progress;
 	wxGauge *progressbar;
 	wxCollapsiblePane *spoiler;
 	wxTextCtrl *txt_stdout;
@@ -72,6 +73,8 @@ struct FirmwareDialog::priv
 	wxString btn_flash_label_ready;
 	wxString btn_flash_label_flashing;
 
+	wxTimer timer_pulse;
+
 	// This is a shared pointer holding the background AvrDude task
 	// also serves as a status indication (it is set _iff_ the background task is running, otherwise it is reset).
 	AvrDude::Ptr avrdude;
@@ -83,13 +86,16 @@ struct FirmwareDialog::priv
 		q(q),
 		btn_flash_label_ready(_(L("Flash!"))),
 		btn_flash_label_flashing(_(L("Cancel"))),
+		timer_pulse(q),
 		avrdude_config((fs::path(::Slic3r::resources_dir()) / "avrdude" / "avrdude.conf").string()),
 		progress_tasks_done(0),
 		cancelled(false)
 	{}
 
 	void find_serial_ports();
-	void flashing_status(bool flashing, AvrDudeComplete complete = AC_NONE);
+	void flashing_start(bool flashing_l10n);
+	void flashing_done(AvrDudeComplete complete);
+	size_t hex_lang_offset(const wxString &path);
 	void perform_upload();
 	void cancel();
 	void on_avrdude(const wxCommandEvent &evt);
@@ -116,42 +122,76 @@ void FirmwareDialog::priv::find_serial_ports()
 	}
 }
 
-void FirmwareDialog::priv::flashing_status(bool value, AvrDudeComplete complete)
+void FirmwareDialog::priv::flashing_start(bool flashing_l10n)
 {
-	if (value) {
-		txt_stdout->Clear();
-		txt_status->SetLabel(_(L("Flashing in progress. Please do not disconnect the printer!")));
-		txt_status->SetForegroundColour(GUI::get_label_clr_modified());
-		port_picker->Disable();
-		btn_rescan->Disable();
-		hex_picker->Disable();
-		btn_close->Disable();
-		btn_flash->SetLabel(btn_flash_label_flashing);
-		progressbar->SetRange(200);   // See progress callback below
-		progressbar->SetValue(0);
-		progress_tasks_done = 0;
-		cancelled = false;
-	} else {
-		auto text_color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
-		port_picker->Enable();
-		btn_rescan->Enable();
-		hex_picker->Enable();
-		btn_close->Enable();
-		btn_flash->SetLabel(btn_flash_label_ready);
-		txt_status->SetForegroundColour(text_color);
-		progressbar->SetValue(200);
+	txt_stdout->Clear();
+	txt_status->SetLabel(_(L("Flashing in progress. Please do not disconnect the printer!")));
+	txt_status->SetForegroundColour(GUI::get_label_clr_modified());
+	port_picker->Disable();
+	btn_rescan->Disable();
+	hex_picker->Disable();
+	btn_close->Disable();
+	btn_flash->SetLabel(btn_flash_label_flashing);
+	progressbar->SetRange(flashing_l10n ? 500 : 200);   // See progress callback below
+	progressbar->SetValue(0);
+	progress_tasks_done = 0;
+	cancelled = false;
+	timer_pulse.Start(50);
+}
 
-		switch (complete) {
-		case AC_SUCCESS: txt_status->SetLabel(_(L("Flashing succeeded!"))); break;
-		case AC_FAILURE: txt_status->SetLabel(_(L("Flashing failed. Please see the avrdude log below."))); break;
-		case AC_CANCEL: txt_status->SetLabel(_(L("Flashing cancelled."))); break;
+void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete)
+{
+	auto text_color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
+	port_picker->Enable();
+	btn_rescan->Enable();
+	hex_picker->Enable();
+	btn_close->Enable();
+	btn_flash->SetLabel(btn_flash_label_ready);
+	txt_status->SetForegroundColour(text_color);
+	timer_pulse.Stop();
+	progressbar->SetValue(progressbar->GetRange());
+
+	switch (complete) {
+	case AC_SUCCESS: txt_status->SetLabel(_(L("Flashing succeeded!"))); break;
+	case AC_FAILURE: txt_status->SetLabel(_(L("Flashing failed. Please see the avrdude log below."))); break;
+	case AC_CANCEL: txt_status->SetLabel(_(L("Flashing cancelled."))); break;
+	}
+}
+
+size_t FirmwareDialog::priv::hex_lang_offset(const wxString &path)
+{
+	fs::ifstream file(fs::path(path.wx_str()));
+	if (! file.good()) {
+		return 0;
+	}
+
+	static const char *hex_terminator = ":00000001FF\r";
+	size_t res = 0;
+	std::string line;
+	while (getline(file, line, '\n').good()) {
+		// Account for LF vs CRLF
+		if (!line.empty() && line.back() != '\r') {
+			line.push_back('\r');
+		}
+
+		if (line == hex_terminator) {
+			if (res == 0) {
+				// This is the first terminator seen, save the position
+				res = file.tellg();
+			} else {
+				// We've found another terminator, return the offset just after the first one
+				// which is the start of the second 'section'.
+				return res;
+			}
 		}
 	}
+
+	return 0;
 }
 
 void FirmwareDialog::priv::perform_upload()
 {
-	auto filename  = hex_picker->GetPath();
+	auto filename = hex_picker->GetPath();
 	std::string port = port_picker->GetValue().ToStdString();
 	int  selection = port_picker->GetSelection();
 	if (selection != -1) {
@@ -161,16 +201,32 @@ void FirmwareDialog::priv::perform_upload()
 	}
 	if (filename.IsEmpty() || port.empty()) { return; }
 
-	flashing_status(true);
+	const bool extra_verbose = false;   // For debugging
+	const auto lang_offset = hex_lang_offset(filename);
+	const auto filename_utf8 = filename.utf8_str();
 
+	flashing_start(lang_offset > 0);
+
+	// It is ok here to use the q-pointer to the FirmwareDialog
+	// because the dialog ensures it doesn't exit before the background thread is done.
+	auto q = this->q;
+
+	// Init the avrdude object
+	AvrDude avrdude(avrdude_config);
+
+	// Build argument list(s)
 	std::vector<std::string> args {{
-		"-v",
+		extra_verbose ? "-vvvvv" : "-v",
 		"-p", "atmega2560",
+		// Using the "Wiring" mode to program Rambo or Einsy, using the STK500v2 protocol (not the STK500).
+		// The Prusa's avrdude is patched to never send semicolons inside the data packets, as the USB to serial chip
+		// is flashed with a buggy firmware.
 		"-c", "wiring",
 		"-P", port,
-		"-b", "115200",   // XXX: is this ok to hardcode?
+		"-b", "115200",   // TODO: Allow other rates? Ditto below.
 		"-D",
-		"-U", (boost::format("flash:w:%1%:i") % filename.ToStdString()).str()
+		// XXX: Safe mode?
+		"-U", (boost::format("flash:w:0:%1%:i") % filename_utf8.data()).str(),
 	}};
 
 	BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: "
@@ -178,26 +234,51 @@ void FirmwareDialog::priv::perform_upload()
 			return a + ' ' + b;
 		});
 
-	// It is ok here to use the q-pointer to the FirmwareDialog
-	// because the dialog ensures it doesn't exit before the background thread is done.
-	auto q = this->q;
+	avrdude.push_args(std::move(args));
+	
+	if (lang_offset > 0) {
+		// The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy)
+		// This is done via another avrdude invocation, here we build arg list for that:
+		std::vector<std::string> args_l10n {{
+			extra_verbose ? "-vvvvv" : "-v",
+			"-p", "atmega2560",
+			// Using the "Arduino" mode to program Einsy's external flash with languages, using the STK500 protocol (not the STK500v2).
+			// The Prusa's avrdude is patched again to never send semicolons inside the data packets.
+			"-c", "arduino",
+			"-P", port,
+			"-b", "115200",
+			"-D",
+			"-u", // disable safe mode
+			"-U", (boost::format("flash:w:%1%:%2%:i") % lang_offset % filename_utf8.data()).str(),
+		}};
+
+		BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: "
+			<< std::accumulate(std::next(args_l10n.begin()), args_l10n.end(), args_l10n[0], [](std::string a, const std::string &b) {
+				return a + ' ' + b;
+			});
+
+		avrdude.push_args(std::move(args_l10n));
+	}
+	
+	this->avrdude = avrdude
+		.on_message(std::move([q, extra_verbose](const char *msg, unsigned /* size */) {
+			if (extra_verbose) {
+				BOOST_LOG_TRIVIAL(debug) << "avrdude: " << msg;
+			}
 
-	avrdude = AvrDude()
-		.sys_config(avrdude_config)
-		.args(args)
-		.on_message(std::move([q](const char *msg, unsigned /* size */) {
 			auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId());
+			auto wxmsg = wxString::FromUTF8(msg);
 			evt->SetExtraLong(AE_MESSAGE);
-			evt->SetString(msg);
+			evt->SetString(std::move(wxmsg));
 			wxQueueEvent(q, evt);
 		}))
 		.on_progress(std::move([q](const char * /* task */, unsigned progress) {
 			auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId());
-			evt->SetExtraLong(AE_PRORGESS);
+			evt->SetExtraLong(AE_PROGRESS);
 			evt->SetInt(progress);
 			wxQueueEvent(q, evt);
 		}))
-		.on_complete(std::move([q](int status) {
+		.on_complete(std::move([q](int status, size_t /* args_id */) {
 			auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId());
 			evt->SetExtraLong(AE_EXIT);
 			evt->SetInt(status);
@@ -224,19 +305,19 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt)
 		txt_stdout->AppendText(evt.GetString());
 		break;
 
-	case AE_PRORGESS:
+	case AE_PROGRESS:
 		// We try to track overall progress here.
-		// When uploading the firmware, avrdude first reads a littlebit of status data,
-		// then performs write, then reading (verification).
-		// We Pulse() during the first read and combine progress of the latter two tasks.
+		// Avrdude performs 3 tasks per one memory operation ("-U" arg),
+		// first of which is reading of status data (very short).
+		// We use the timer_pulse during the very first task to indicate intialization
+		// and then display overall progress during the latter tasks.
 
-		if (progress_tasks_done == 0) {
-			progressbar->Pulse();
-		} else {
+		if (progress_tasks_done > 0) {
 			progressbar->SetValue(progress_tasks_done - 100 + evt.GetInt());
 		}
 
 		if (evt.GetInt() == 100) {
+			timer_pulse.Stop();
 			progress_tasks_done += 100;
 		}
 
@@ -246,7 +327,7 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt)
 		BOOST_LOG_TRIVIAL(info) << "avrdude exit code: " << evt.GetInt();
 
 		complete_kind = cancelled ? AC_CANCEL : (evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE);
-		flashing_status(false, complete_kind);
+		flashing_done(complete_kind);
 
 		// Make sure the background thread is collected and the AvrDude object reset
 		if (avrdude) { avrdude->join(); }
@@ -374,6 +455,8 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
 		}
 	});
 
+	Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->p->progressbar->Pulse(); });
+
 	Bind(EVT_AVRDUDE, [this](wxCommandEvent &evt) { this->p->on_avrdude(evt); });
 
 	Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt) {
diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
index b9a6ad218..497e613e9 100644
--- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
@@ -1,5 +1,6 @@
 #include "GLCanvas3D.hpp"
 
+#include "../../libslic3r/libslic3r.h"
 #include "../../slic3r/GUI/3DScene.hpp"
 #include "../../slic3r/GUI/GLShader.hpp"
 #include "../../slic3r/GUI/GUI.hpp"
@@ -41,6 +42,11 @@ static const float VIEW_REAR[2] = { 180.0f, 90.0f };
 static const float VARIABLE_LAYER_THICKNESS_BAR_WIDTH = 70.0f;
 static const float VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT = 22.0f;
 
+static const float UNIT_MATRIX[] = { 1.0f, 0.0f, 0.0f, 0.0f,
+                                     0.0f, 1.0f, 0.0f, 0.0f,
+                                     0.0f, 0.0f, 1.0f, 0.0f,
+                                     0.0f, 0.0f, 0.0f, 1.0f };
+
 namespace Slic3r {
 namespace GUI {
 
@@ -719,6 +725,12 @@ void GLCanvas3D::Shader::set_uniform(const std::string& name, float value) const
         m_shader->set_uniform(name.c_str(), value);
 }
 
+void GLCanvas3D::Shader::set_uniform(const std::string& name, const float* matrix) const
+{
+    if (m_shader != nullptr)
+        m_shader->set_uniform(name.c_str(), matrix);
+}
+
 const GLShader* GLCanvas3D::Shader::get_shader() const
 {
     return m_shader;
@@ -952,6 +964,8 @@ void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas
     m_shader.set_uniform("z_texture_row_to_normalized", 1.0f / (float)volume.layer_height_texture_height());
     m_shader.set_uniform("z_cursor", max_z * get_cursor_z_relative(canvas));
     m_shader.set_uniform("z_cursor_band_width", band_width);
+    // The shader requires the original model coordinates when rendering to the texture, so we pass it the unit matrix
+    m_shader.set_uniform("volume_world_matrix", UNIT_MATRIX);
 
     GLsizei w = (GLsizei)volume.layer_height_texture_width();
     GLsizei h = (GLsizei)volume.layer_height_texture_height();
@@ -1042,7 +1056,9 @@ const Pointf3 GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MA
 GLCanvas3D::Mouse::Drag::Drag()
     : start_position_2D(Invalid_2D_Point)
     , start_position_3D(Invalid_3D_Point)
-    , volume_idx(-1)
+    , move_with_ctrl(false)
+    , move_volume_idx(-1)
+    , gizmo_volume_idx(-1)
 {
 }
 
@@ -2765,6 +2781,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         {
             update_gizmos_data();
             m_gizmos.start_dragging();
+            m_mouse.drag.gizmo_volume_idx = _get_first_selected_volume_id();
             m_dirty = true;
         }
         else
@@ -2812,7 +2829,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                     if (volume_bbox.contains(pos3d))
                     {
                         // The dragging operation is initiated.
-                        m_mouse.drag.volume_idx = volume_idx;
+                        m_mouse.drag.move_with_ctrl = evt.ControlDown();
+                        m_mouse.drag.move_volume_idx = volume_idx;
                         m_mouse.drag.start_position_3D = pos3d;
                         // Remember the shift to to the object center.The object center will later be used
                         // to limit the object placement close to the bed.
@@ -2828,7 +2846,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             }
         }
     }
-    else if (evt.Dragging() && evt.LeftIsDown() && !gizmos_overlay_contains_mouse && (m_layers_editing.state == LayersEditing::Unknown) && (m_mouse.drag.volume_idx != -1))
+    else if (evt.Dragging() && evt.LeftIsDown() && !gizmos_overlay_contains_mouse && (m_layers_editing.state == LayersEditing::Unknown) && (m_mouse.drag.move_volume_idx != -1))
     {
         m_mouse.dragging = true;
 
@@ -2851,24 +2869,30 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         // Calculate the translation vector.
         Vectorf3 vector = m_mouse.drag.start_position_3D.vector_to(cur_pos);
         // Get the volume being dragged.
-        GLVolume* volume = m_volumes.volumes[m_mouse.drag.volume_idx];
+        GLVolume* volume = m_volumes.volumes[m_mouse.drag.move_volume_idx];
         // Get all volumes belonging to the same group, if any.
         std::vector<GLVolume*> volumes;
-        if (volume->drag_group_id == -1)
+        int group_id = m_mouse.drag.move_with_ctrl ? volume->select_group_id : volume->drag_group_id;
+        if (group_id == -1)
             volumes.push_back(volume);
         else
         {
             for (GLVolume* v : m_volumes.volumes)
             {
-                if ((v != nullptr) && (v->drag_group_id == volume->drag_group_id))
-                    volumes.push_back(v);
+                if (v != nullptr)
+                {
+                    if ((m_mouse.drag.move_with_ctrl && (v->select_group_id == group_id)) || (v->drag_group_id == group_id))
+                        volumes.push_back(v);
+                }
             }
         }
 
         // Apply new temporary volume origin and ignore Z.
         for (GLVolume* v : volumes)
         {
-            v->origin.translate(vector.x, vector.y, 0.0);
+            Pointf3 origin = v->get_origin();
+            origin.translate(vector.x, vector.y, 0.0);
+            v->set_origin(origin);
         }
 
         m_mouse.drag.start_position_3D = cur_pos;
@@ -2882,16 +2906,43 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         const Pointf3& cur_pos = _mouse_to_bed_3d(pos);
         m_gizmos.update(Pointf(cur_pos.x, cur_pos.y));
 
+        std::vector<GLVolume*> volumes;
+        if (m_mouse.drag.gizmo_volume_idx != -1)
+        {
+            GLVolume* volume = m_volumes.volumes[m_mouse.drag.gizmo_volume_idx];
+            // Get all volumes belonging to the same group, if any.
+            if (volume->select_group_id == -1)
+                volumes.push_back(volume);
+            else
+            {
+                for (GLVolume* v : m_volumes.volumes)
+                {
+                    if ((v != nullptr) && (v->select_group_id == volume->select_group_id))
+                        volumes.push_back(v);
+                }
+            }
+        }
+
         switch (m_gizmos.get_current_type())
         {
         case Gizmos::Scale:
         {
-            m_on_gizmo_scale_uniformly_callback.call((double)m_gizmos.get_scale());
+            // Apply new temporary scale factor
+            float scale_factor = m_gizmos.get_scale();
+            for (GLVolume* v : volumes)
+            {
+                v->set_scale_factor(scale_factor);
+            }
             break;
         }
         case Gizmos::Rotate:
         {
-            m_on_gizmo_rotate_callback.call((double)m_gizmos.get_angle_z());
+            // Apply new temporary angle_z
+            float angle_z = m_gizmos.get_angle_z();
+            for (GLVolume* v : volumes)
+            {
+                v->set_angle_z(angle_z);
+            }
             break;
         }
         default:
@@ -2954,19 +3005,19 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             if (layer_editing_object_idx != -1)
                 m_on_model_update_callback.call();
         }
-        else if ((m_mouse.drag.volume_idx != -1) && m_mouse.dragging)
+        else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging)
         {
             // get all volumes belonging to the same group, if any
             std::vector<int> volume_idxs;
-            int vol_id = m_mouse.drag.volume_idx;
-            int group_id = m_volumes.volumes[vol_id]->drag_group_id;
+            int vol_id = m_mouse.drag.move_volume_idx;
+            int group_id = m_mouse.drag.move_with_ctrl ? m_volumes.volumes[vol_id]->select_group_id : m_volumes.volumes[vol_id]->drag_group_id;
             if (group_id == -1)
                 volume_idxs.push_back(vol_id);
             else
             {
                 for (int i = 0; i < (int)m_volumes.volumes.size(); ++i)
                 {
-                    if (m_volumes.volumes[i]->drag_group_id == group_id)
+                    if ((m_mouse.drag.move_with_ctrl && (m_volumes.volumes[i]->select_group_id == group_id)) || (m_volumes.volumes[i]->drag_group_id == group_id))
                         volume_idxs.push_back(i);
                 }
             }
@@ -2984,10 +3035,26 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         }
         else if (evt.LeftUp() && m_gizmos.is_dragging())
         {
+            switch (m_gizmos.get_current_type())
+            {
+            case Gizmos::Scale:
+            {
+                m_on_gizmo_scale_uniformly_callback.call((double)m_gizmos.get_scale());
+                break;
+            }
+            case Gizmos::Rotate:
+            {
+                m_on_gizmo_rotate_callback.call((double)m_gizmos.get_angle_z());
+                break;
+            }
+            default:
+                break;
+            }
             m_gizmos.stop_dragging();
         }
 
-        m_mouse.drag.volume_idx = -1;
+        m_mouse.drag.move_volume_idx = -1;
+        m_mouse.drag.gizmo_volume_idx = -1;
         m_mouse.set_start_position_3D_as_invalid();
         m_mouse.set_start_position_2D_as_invalid();
         m_mouse.dragging = false;
@@ -3706,6 +3773,35 @@ int GLCanvas3D::_get_first_selected_object_id() const
     return -1;
 }
 
+int GLCanvas3D::_get_first_selected_volume_id() const
+{
+    if (m_print != nullptr)
+    {
+        int objects_count = (int)m_print->objects.size();
+
+        for (const GLVolume* vol : m_volumes.volumes)
+        {
+            if ((vol != nullptr) && vol->selected)
+            {
+                int object_id = vol->select_group_id / 1000000;
+                // Objects with object_id >= 1000 have a specific meaning, for example the wipe tower proxy.
+                if (object_id < 10000)
+                {
+                    int volume_id = 0;
+                    for (int i = 0; i < object_id; ++i)
+                    {
+                        const PrintObject* obj = m_print->objects[i];
+                        const ModelObject* model = obj->model_object();
+                        volume_id += model->instances.size();
+                    }
+                    return volume_id;
+                }
+            }
+        }
+    }
+    return -1;
+}
+
 static inline int hex_digit_to_int(const char c)
 {
     return
@@ -4311,13 +4407,14 @@ void GLCanvas3D::_on_move(const std::vector<int>& volume_idxs)
         {
             // Move a regular object.
             ModelObject* model_object = m_model->objects[obj_idx];
-            model_object->instances[instance_idx]->offset.translate(volume->origin.x, volume->origin.y);
+            const Pointf3& origin = volume->get_origin();
+            model_object->instances[instance_idx]->offset = Pointf(origin.x, origin.y);
             model_object->invalidate_bounding_box();
             object_moved = true;
         }
         else if (obj_idx == 1000)
             // Move a wipe tower proxy.
-            wipe_tower_origin = volume->origin;
+            wipe_tower_origin = volume->get_origin();
     }
 
     if (object_moved)
diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp
index ba4d4d106..77d3c5c54 100644
--- a/xs/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp
@@ -225,6 +225,7 @@ public:
         void stop_using() const;
 
         void set_uniform(const std::string& name, float value) const;
+        void set_uniform(const std::string& name, const float* matrix) const;
 
         const GLShader* get_shader() const;
 
@@ -302,7 +303,10 @@ public:
             Point start_position_2D;
             Pointf3 start_position_3D;
             Vectorf3 volume_center_offset;
-            int volume_idx;
+
+            bool move_with_ctrl;
+            int move_volume_idx;
+            int gizmo_volume_idx;
 
         public:
             Drag();
@@ -617,6 +621,7 @@ private:
     void _stop_timer();
 
     int _get_first_selected_object_id() const;
+    int _get_first_selected_volume_id() const;
 
     // generates gcode extrusion paths geometry
     void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
diff --git a/xs/src/slic3r/GUI/GLShader.cpp b/xs/src/slic3r/GUI/GLShader.cpp
index 903f6c347..e2995f7c3 100644
--- a/xs/src/slic3r/GUI/GLShader.cpp
+++ b/xs/src/slic3r/GUI/GLShader.cpp
@@ -214,6 +214,17 @@ bool GLShader::set_uniform(const char *name, float value) const
     return false;
 }
 
+bool GLShader::set_uniform(const char* name, const float* matrix) const
+{
+    int id = get_uniform_location(name);
+    if (id >= 0)
+    {
+        ::glUniformMatrix4fv(id, 1, GL_FALSE, (const GLfloat*)matrix);
+        return true;
+    }
+    return false;
+}
+
 /*
 # Set shader vector
 sub SetVector
diff --git a/xs/src/slic3r/GUI/GLShader.hpp b/xs/src/slic3r/GUI/GLShader.hpp
index 032640d8d..803b2f154 100644
--- a/xs/src/slic3r/GUI/GLShader.hpp
+++ b/xs/src/slic3r/GUI/GLShader.hpp
@@ -25,6 +25,7 @@ public:
     int  get_uniform_location(const char *name) const;
 
     bool set_uniform(const char *name, float value) const;
+    bool set_uniform(const char* name, const float* matrix) const;
 
     void enable() const;
     void disable() const;
diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp
index 974c554b6..e2f3925fc 100644
--- a/xs/src/slic3r/GUI/GUI.cpp
+++ b/xs/src/slic3r/GUI/GUI.cpp
@@ -423,7 +423,7 @@ bool check_unsaved_changes()
 
 bool config_wizard_startup(bool app_config_exists)
 {
-	if (! app_config_exists || g_PresetBundle->has_defauls_only()) {
+	if (! app_config_exists || g_PresetBundle->printers.size() <= 1) {
 		config_wizard(ConfigWizard::RR_DATA_EMPTY);
 		return true;
 	} else if (g_AppConfig->legacy_datadir()) {
diff --git a/xs/src/slic3r/Utils/FixModelByWin10.cpp b/xs/src/slic3r/Utils/FixModelByWin10.cpp
new file mode 100644
index 000000000..556035a5b
--- /dev/null
+++ b/xs/src/slic3r/Utils/FixModelByWin10.cpp
@@ -0,0 +1,402 @@
+#ifdef HAS_WIN10SDK
+
+#ifndef NOMINMAX
+# define NOMINMAX
+#endif
+
+#include "FixModelByWin10.hpp"
+
+#include <atomic>
+#include <chrono>
+#include <cstdint>
+#include <condition_variable>
+#include <exception>
+#include <string>
+#include <thread>
+
+#include <boost/filesystem.hpp>
+#include <boost/nowide/convert.hpp>
+#include <boost/nowide/cstdio.hpp>
+
+#include <roapi.h>
+// for ComPtr
+#include <wrl/client.h>
+// from C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/
+#include <winrt/robuffer.h>
+#include <winrt/windows.storage.provider.h>
+#include <winrt/windows.graphics.printing3d.h>
+
+#include "libslic3r/Model.hpp"
+#include "libslic3r/Print.hpp"
+#include "libslic3r/Format/3mf.hpp"
+#include "../GUI/GUI.hpp"
+#include "../GUI/PresetBundle.hpp"
+
+#include <wx/msgdlg.h>
+#include <wx/progdlg.h>
+
+extern "C"{
+	// from rapi.h
+	typedef HRESULT (__stdcall* FunctionRoInitialize)(int);
+	typedef HRESULT (__stdcall* FunctionRoUninitialize)();
+	typedef HRESULT	(__stdcall* FunctionRoActivateInstance)(HSTRING activatableClassId, IInspectable **instance);
+	typedef HRESULT (__stdcall* FunctionRoGetActivationFactory)(HSTRING activatableClassId, REFIID iid, void **factory);
+	// from winstring.h
+	typedef HRESULT	(__stdcall* FunctionWindowsCreateString)(LPCWSTR sourceString, UINT32  length, HSTRING *string);
+	typedef HRESULT	(__stdcall* FunctionWindowsDelteString)(HSTRING string);
+}
+
+namespace Slic3r {
+
+HMODULE							s_hRuntimeObjectLibrary  = nullptr;
+FunctionRoInitialize			s_RoInitialize			 = nullptr;
+FunctionRoUninitialize			s_RoUninitialize		 = nullptr;
+FunctionRoActivateInstance		s_RoActivateInstance     = nullptr;
+FunctionRoGetActivationFactory	s_RoGetActivationFactory = nullptr;
+FunctionWindowsCreateString		s_WindowsCreateString    = nullptr;
+FunctionWindowsDelteString		s_WindowsDeleteString    = nullptr;
+
+bool winrt_load_runtime_object_library()
+{
+	if (s_hRuntimeObjectLibrary == nullptr)
+		s_hRuntimeObjectLibrary = LoadLibrary(L"ComBase.dll");
+	if (s_hRuntimeObjectLibrary != nullptr) {
+		s_RoInitialize			 = (FunctionRoInitialize)			GetProcAddress(s_hRuntimeObjectLibrary, "RoInitialize");
+		s_RoUninitialize		 = (FunctionRoUninitialize)			GetProcAddress(s_hRuntimeObjectLibrary, "RoUninitialize");
+		s_RoActivateInstance	 = (FunctionRoActivateInstance)		GetProcAddress(s_hRuntimeObjectLibrary, "RoActivateInstance");
+		s_RoGetActivationFactory = (FunctionRoGetActivationFactory)	GetProcAddress(s_hRuntimeObjectLibrary, "RoGetActivationFactory");
+		s_WindowsCreateString	 = (FunctionWindowsCreateString)	GetProcAddress(s_hRuntimeObjectLibrary, "WindowsCreateString");
+		s_WindowsDeleteString	 = (FunctionWindowsDelteString)		GetProcAddress(s_hRuntimeObjectLibrary, "WindowsDeleteString");
+	}
+	return s_RoInitialize && s_RoUninitialize && s_RoActivateInstance && s_WindowsCreateString && s_WindowsDeleteString;
+}
+
+static HRESULT winrt_activate_instance(const std::wstring &class_name, IInspectable **pinst)
+{
+	HSTRING hClassName;
+	HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName);
+	if (S_OK != hr) 
+		return hr;
+	hr = (*s_RoActivateInstance)(hClassName, pinst);
+	(*s_WindowsDeleteString)(hClassName);
+	return hr;
+}
+
+template<typename TYPE>
+static HRESULT winrt_activate_instance(const std::wstring &class_name, TYPE **pinst)
+{
+	IInspectable *pinspectable = nullptr;
+	HRESULT hr = winrt_activate_instance(class_name, &pinspectable);
+	if (S_OK != hr)
+		return hr;
+	hr = pinspectable->QueryInterface(__uuidof(TYPE), (void**)pinst);
+	pinspectable->Release();
+	return hr;
+}
+
+static HRESULT winrt_get_activation_factory(const std::wstring &class_name, REFIID iid, void **pinst)
+{
+	HSTRING hClassName;
+	HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName);
+	if (S_OK != hr)
+		return hr;
+	hr = (*s_RoGetActivationFactory)(hClassName, iid, pinst);
+	(*s_WindowsDeleteString)(hClassName);
+	return hr;
+}
+
+template<typename TYPE>
+static HRESULT winrt_get_activation_factory(const std::wstring &class_name, TYPE **pinst)
+{
+	return winrt_get_activation_factory(class_name, __uuidof(TYPE), reinterpret_cast<void**>(pinst));
+}
+
+// To be called often to test whether to cancel the operation.
+typedef std::function<void ()> ThrowOnCancelFn;
+
+template<typename T>
+static AsyncStatus winrt_async_await(const Microsoft::WRL::ComPtr<T> &asyncAction, ThrowOnCancelFn throw_on_cancel, int blocking_tick_ms = 100)
+{
+	Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
+	asyncAction.As(&asyncInfo);
+	AsyncStatus status;
+	// Ugly blocking loop until the RepairAsync call finishes.
+//FIXME replace with a callback.
+// https://social.msdn.microsoft.com/Forums/en-US/a5038fb4-b7b7-4504-969d-c102faa389fb/trying-to-block-an-async-operation-and-wait-for-a-particular-time?forum=vclanguage
+	for (;;) {
+		asyncInfo->get_Status(&status);
+		if (status != AsyncStatus::Started)
+			return status;
+		throw_on_cancel();
+		::Sleep(blocking_tick_ms);
+	}
+}
+
+static HRESULT winrt_open_file_stream(
+	const std::wstring									 &path,
+	ABI::Windows::Storage::FileAccessMode				  mode,
+	ABI::Windows::Storage::Streams::IRandomAccessStream **fileStream,
+	ThrowOnCancelFn										  throw_on_cancel)
+{
+	// Get the file factory.
+	Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFileStatics> fileFactory;
+	HRESULT hr = winrt_get_activation_factory(L"Windows.Storage.StorageFile", fileFactory.GetAddressOf());
+	if (FAILED(hr)) return hr;
+
+	// Open the file asynchronously.
+	HSTRING hstr_path;
+	hr = (*s_WindowsCreateString)(path.c_str(), path.size(), &hstr_path);
+	if (FAILED(hr)) return hr;
+	Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::StorageFile*>> fileOpenAsync;
+	hr = fileFactory->GetFileFromPathAsync(hstr_path, fileOpenAsync.GetAddressOf());
+	if (FAILED(hr)) return hr;
+	(*s_WindowsDeleteString)(hstr_path);
+
+	// Wait until the file gets open, get the actual file.
+	AsyncStatus status = winrt_async_await(fileOpenAsync, throw_on_cancel);
+	Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFile> storageFile;
+	if (status == AsyncStatus::Completed) {
+		hr = fileOpenAsync->GetResults(storageFile.GetAddressOf());
+	} else {
+		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
+		hr = fileOpenAsync.As(&asyncInfo);
+		if (FAILED(hr)) return hr;
+		HRESULT err;
+		hr = asyncInfo->get_ErrorCode(&err);
+		return FAILED(hr) ? hr : err;
+	}
+
+	Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> fileStreamAsync;
+	hr = storageFile->OpenAsync(mode, fileStreamAsync.GetAddressOf());
+	if (FAILED(hr)) return hr;
+
+	status = winrt_async_await(fileStreamAsync, throw_on_cancel);
+	if (status == AsyncStatus::Completed) {
+		hr = fileStreamAsync->GetResults(fileStream);
+	} else {
+		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
+		hr = fileStreamAsync.As(&asyncInfo);
+		if (FAILED(hr)) return hr;
+		HRESULT err;
+		hr = asyncInfo->get_ErrorCode(&err);
+		if (!FAILED(hr))
+			hr = err;
+	}
+	return hr;
+}
+
+bool is_windows10()
+{
+	HKEY hKey;
+	LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey);
+	if (lRes == ERROR_SUCCESS) {
+		WCHAR szBuffer[512];
+		DWORD dwBufferSize = sizeof(szBuffer);
+		lRes = RegQueryValueExW(hKey, L"ProductName", 0, nullptr, (LPBYTE)szBuffer, &dwBufferSize);
+		if (lRes == ERROR_SUCCESS)
+			return wcsncmp(szBuffer, L"Windows 10", 10) == 0;
+		RegCloseKey(hKey);
+	}
+	return false;
+}
+
+// Progress function, to be called regularly to update the progress.
+typedef std::function<void (const char * /* message */, unsigned /* progress */)> ProgressFn;
+
+void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path_dst, ProgressFn on_progress, ThrowOnCancelFn throw_on_cancel)
+{
+	if (! is_windows10())
+		throw std::runtime_error("fix_model_by_win10_sdk called on non Windows 10 system");
+
+	if (! winrt_load_runtime_object_library())
+		throw std::runtime_error("Failed to initialize the WinRT library.");
+
+	HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED);
+	{
+		on_progress(L("Exporting the source model"), 20);
+
+		Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream>       fileStream;
+		hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel);
+
+		Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3D3MFPackage> printing3d3mfpackage;
+		hr = winrt_activate_instance(L"Windows.Graphics.Printing3D.Printing3D3MFPackage", printing3d3mfpackage.GetAddressOf());
+
+		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Graphics::Printing3D::Printing3DModel*>> modelAsync;
+		hr = printing3d3mfpackage->LoadModelFromPackageAsync(fileStream.Get(), modelAsync.GetAddressOf());
+
+		AsyncStatus status = winrt_async_await(modelAsync, throw_on_cancel);
+		Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3DModel>	  model;
+		if (status == AsyncStatus::Completed)
+			hr = modelAsync->GetResults(model.GetAddressOf());
+		else
+			throw std::runtime_error(L("Failed loading the input model."));
+
+		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::Collections::IVector<ABI::Windows::Graphics::Printing3D::Printing3DMesh*>> meshes;
+		hr = model->get_Meshes(meshes.GetAddressOf());
+		unsigned num_meshes = 0;
+		hr = meshes->get_Size(&num_meshes);
+		
+		on_progress(L("Repairing the model by the Netfabb service"), 40);
+		
+		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction>					  repairAsync;
+		hr = model->RepairAsync(repairAsync.GetAddressOf());
+		status = winrt_async_await(repairAsync, throw_on_cancel);
+		if (status != AsyncStatus::Completed)
+			throw std::runtime_error(L("Mesh repair failed."));
+		repairAsync->GetResults();
+
+		on_progress(L("Loading the repaired model"), 60);
+
+		// Verify the number of meshes returned after the repair action.
+		meshes.Reset();
+		hr = model->get_Meshes(meshes.GetAddressOf());
+		hr = meshes->get_Size(&num_meshes);
+
+		// Save model to this class' Printing3D3MFPackage.
+		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction>					  saveToPackageAsync;
+		hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf());
+		status = winrt_async_await(saveToPackageAsync, throw_on_cancel);
+		if (status != AsyncStatus::Completed)
+			throw std::runtime_error(L("Saving mesh into the 3MF container failed."));
+		hr = saveToPackageAsync->GetResults();
+
+		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> generatorStreamAsync;
+		hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf());
+		status = winrt_async_await(generatorStreamAsync, throw_on_cancel);
+		if (status != AsyncStatus::Completed)
+			throw std::runtime_error(L("Saving mesh into the 3MF container failed."));
+		Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream> generatorStream;
+		hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf());
+
+		// Go to the beginning of the stream.
+		generatorStream->Seek(0);
+		Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IInputStream> inputStream;
+		hr = generatorStream.As(&inputStream);
+
+		// Get the buffer factory.
+		Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory;
+		hr = winrt_get_activation_factory(L"Windows.Storage.Streams.Buffer", bufferFactory.GetAddressOf());
+
+		// Open the destination file.
+		FILE *fout = boost::nowide::fopen(path_dst.c_str(), "wb");
+
+		Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
+		byte														   *buffer_ptr;
+		bufferFactory->Create(65536 * 2048, buffer.GetAddressOf());
+		{
+			Microsoft::WRL::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
+			buffer.As(&bufferByteAccess);
+			hr = bufferByteAccess->Buffer(&buffer_ptr);
+		}
+		uint32_t length;
+		hr = buffer->get_Length(&length);
+
+		Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer*, UINT32>> asyncRead;
+		for (;;) {
+			hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf());
+			status = winrt_async_await(asyncRead, throw_on_cancel);
+			if (status != AsyncStatus::Completed)
+				throw std::runtime_error(L("Saving mesh into the 3MF container failed."));
+			hr = buffer->get_Length(&length);
+			if (length == 0)
+				break;
+			fwrite(buffer_ptr, length, 1, fout);
+		}
+		fclose(fout);
+		// Here all the COM objects will be released through the ComPtr destructors.
+	}
+	(*s_RoUninitialize)();
+}
+
+class RepairCanceledException : public std::exception {
+public:
+   const char* what() const throw() { return "Model repair has been canceled"; }
+};
+
+void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result)
+{
+	std::mutex 						mutex;
+	std::condition_variable			condition;
+	std::unique_lock<std::mutex>	lock(mutex);
+	struct Progress {
+		std::string 				message;
+		int 						percent  = 0;
+		bool						updated = false;
+	} progress;
+	std::atomic<bool>				canceled = false;
+	std::atomic<bool>				finished = false;
+
+	// Open a progress dialog.
+	wxProgressDialog progress_dialog(
+		_(L("Model fixing")),
+		_(L("Exporting model...")),
+		100, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
+	// Executing the calculation in a background thread, so that the COM context could be created with its own threading model.
+	// (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context).
+	bool success  = false;
+	auto on_progress = [&mutex, &condition, &progress](const char *msg, unsigned prcnt) {
+        std::lock_guard<std::mutex> lk(mutex);
+		progress.message = msg;
+		progress.percent = prcnt;
+		progress.updated = true;
+	    condition.notify_all();
+	};
+	auto worker_thread = boost::thread([&model_object, &print, &result, on_progress, &success, &canceled, &finished]() {
+		try {
+			on_progress(L("Exporting the source model"), 0);
+			boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
+			path_src += ".3mf";
+			Model model;
+			model.add_object(model_object);
+			if (! Slic3r::store_3mf(path_src.string().c_str(), &model, const_cast<Print*>(&print), false)) {
+				boost::filesystem::remove(path_src);
+				throw std::runtime_error(L("Export of a temporary 3mf file failed"));
+			}
+			model.clear_objects(); 
+			model.clear_materials();
+			boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
+			path_dst += ".3mf";
+			fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress, 
+				[&canceled]() { if (canceled) throw RepairCanceledException(); });
+			boost::filesystem::remove(path_src);
+			PresetBundle bundle;
+			on_progress(L("Loading the repaired model"), 80);
+		    bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &bundle, &result);
+			boost::filesystem::remove(path_dst);
+			if (! loaded)
+ 				throw std::runtime_error(L("Import of the repaired 3mf file failed"));
+			success  = true;
+			finished = true;
+			on_progress(L("Model repair finished"), 100);
+		} catch (RepairCanceledException &ex) {
+			canceled = true;
+			finished = true;
+			on_progress(L("Model repair canceled"), 100);
+		} catch (std::exception &ex) {
+			success = false;
+			finished = true;
+			on_progress(ex.what(), 100);
+		}
+	});
+    while (! finished) {
+		condition.wait_for(lock, std::chrono::milliseconds(500), [&progress]{ return progress.updated; });
+		if (! progress_dialog.Update(progress.percent, _(progress.message)))
+			canceled = true;
+		progress.updated = false;
+    }
+
+	if (canceled) {
+		// Nothing to show.
+	} else if (success) {
+		wxMessageDialog dlg(nullptr, _(L("Model repaired successfully")), _(L("Model Repair by the Netfabb service")), wxICON_INFORMATION | wxOK_DEFAULT);
+		dlg.ShowModal();
+	} else {
+		wxMessageDialog dlg(nullptr, _(L("Model repair failed: \n")) + _(progress.message), _(L("Model Repair by the Netfabb service")), wxICON_ERROR | wxOK_DEFAULT);
+		dlg.ShowModal();
+	}
+	worker_thread.join();
+}
+
+} // namespace Slic3r
+
+#endif /* HAS_WIN10SDK */
diff --git a/xs/src/slic3r/Utils/FixModelByWin10.hpp b/xs/src/slic3r/Utils/FixModelByWin10.hpp
new file mode 100644
index 000000000..c148a6970
--- /dev/null
+++ b/xs/src/slic3r/Utils/FixModelByWin10.hpp
@@ -0,0 +1,26 @@
+#ifndef slic3r_GUI_Utils_FixModelByWin10_hpp_
+#define slic3r_GUI_Utils_FixModelByWin10_hpp_
+
+#include <string>
+
+namespace Slic3r {
+
+class Model;
+class ModelObject;
+class Print;
+
+#ifdef HAS_WIN10SDK
+
+extern bool is_windows10();
+extern void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result);
+
+#else /* HAS_WIN10SDK */
+
+inline bool is_windows10() { return false; }
+inline void fix_model_by_win10_sdk_gui(const ModelObject &, const Print &, Model &) {}
+
+#endif /* HAS_WIN10SDK */
+
+} // namespace Slic3r
+
+#endif /* slic3r_GUI_Utils_FixModelByWin10_hpp_ */
diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp
index f34fc4c19..8159a75e2 100644
--- a/xs/src/slic3r/Utils/PresetUpdater.cpp
+++ b/xs/src/slic3r/Utils/PresetUpdater.cpp
@@ -259,7 +259,7 @@ void PresetUpdater::priv::sync_config(const std::set<VendorProfile> vendors) con
 		}
 		const auto recommended = recommended_it->config_version;
 
-		BOOST_LOG_TRIVIAL(debug) << boost::format("New index for vendor: %1%: current version: %2%, recommended version: %3%")
+		BOOST_LOG_TRIVIAL(debug) << boost::format("Got index for vendor: %1%: current version: %2%, recommended version: %3%")
 			% vendor.name
 			% vendor.config_version.to_string()
 			% recommended.to_string();
@@ -352,20 +352,25 @@ Updates PresetUpdater::priv::get_config_updates() const
 				continue;
 			}
 
-			auto path_in_cache = cache_path / (idx.vendor() + ".ini");
-			if (! fs::exists(path_in_cache)) {
-				BOOST_LOG_TRIVIAL(warning) << "Index indicates update, but new bundle not found in cache: " << path_in_cache.string();
-				continue;
+			auto path_src = cache_path / (idx.vendor() + ".ini");
+			if (! fs::exists(path_src)) {
+				auto path_in_rsrc = rsrc_path / (idx.vendor() + ".ini");
+				if (! fs::exists(path_in_rsrc)) {
+					BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update, but bundle found in neither cache nor resources")
+						% idx.vendor();;
+					continue;
+				} else {
+					path_src = std::move(path_in_rsrc);
+				}
 			}
 
-			const auto cached_vp = VendorProfile::from_ini(path_in_cache, false);
-			if (cached_vp.config_version == recommended->config_version) {
-				updates.updates.emplace_back(std::move(path_in_cache), std::move(bundle_path), *recommended);
+			const auto new_vp = VendorProfile::from_ini(path_src, false);
+			if (new_vp.config_version == recommended->config_version) {
+				updates.updates.emplace_back(std::move(path_src), std::move(bundle_path), *recommended);
 			} else {
-				BOOST_LOG_TRIVIAL(warning) << boost::format("Vendor: %1%: Index indicates update (%2%) but cached bundle has a different version: %3%")
+				BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update (%2%) but the new bundle was found neither in cache nor resources")
 					% idx.vendor()
-					% recommended->config_version.to_string()
-					% cached_vp.config_version.to_string();
+					% recommended->config_version.to_string();
 			}
 		}
 	}
diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp
index 50fffc545..af0612f19 100644
--- a/xs/xsp/GUI.xsp
+++ b/xs/xsp/GUI.xsp
@@ -4,6 +4,7 @@
 #include <xsinit.h>
 #include "slic3r/GUI/GUI.hpp"
 #include "slic3r/Utils/ASCIIFolding.hpp"
+#include "slic3r/Utils/FixModelByWin10.hpp"
 #include "slic3r/Utils/Serial.hpp"
 %}
 
@@ -28,6 +29,9 @@ bool debugged()
 void break_to_debugger()
     %code{% Slic3r::GUI::break_to_debugger(); %};
 
+bool is_windows10()
+    %code{% RETVAL=Slic3r::is_windows10(); %};
+
 void set_wxapp(SV *ui)
     %code%{ Slic3r::GUI::set_wxapp((wxApp*)wxPli_sv_2_object(aTHX_ ui, "Wx::App")); %};
 
@@ -94,3 +98,6 @@ int get_export_option(SV *ui)
     
 void desktop_open_datadir_folder()
     %code%{ Slic3r::GUI::desktop_open_datadir_folder(); %};
+
+void fix_model_by_win10_sdk_gui(ModelObject *model_object_src, Print *print, Model *model_dst)
+    %code%{ Slic3r::fix_model_by_win10_sdk_gui(*model_object_src, *print, *model_dst); %};
diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp
index f9c1f9f0f..bddae54a6 100644
--- a/xs/xsp/GUI_3DScene.xsp
+++ b/xs/xsp/GUI_3DScene.xsp
@@ -56,9 +56,13 @@
     int                 volume_idx() const;
     int                 instance_idx() const;
     Clone<Pointf3>      origin() const
-        %code%{ RETVAL = THIS->origin; %};
+        %code%{ RETVAL = THIS->get_origin(); %};
     void                translate(double x, double y, double z)
-        %code%{ THIS->origin.translate(x, y, z); %};
+        %code%{
+                Pointf3 o = THIS->get_origin();
+                o.translate(x, y, z);
+                THIS->set_origin(o);
+             %};
     Clone<BoundingBoxf3> bounding_box() const
         %code%{ RETVAL = THIS->bounding_box; %};
     Clone<BoundingBoxf3> transformed_bounding_box() const;
diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp
index e05112932..b53b5e82d 100644
--- a/xs/xsp/Print.xsp
+++ b/xs/xsp/Print.xsp
@@ -54,7 +54,6 @@ _constant()
 
     int region_volumes_count()
         %code%{ RETVAL = THIS->region_volumes.size(); %};
-            
     Ref<Print> print();
     Ref<ModelObject> model_object();
     Ref<StaticPrintConfig> config()