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 f2bf3baf8..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;
@@ -223,8 +222,8 @@ sub system_info {
     my $opengl_info_txt = '';
     if (defined($self->{mainframe}) && defined($self->{mainframe}->{plater}) &&
         defined($self->{mainframe}->{plater}->{canvas3D})) {
-        $opengl_info = $self->{mainframe}->{plater}->{canvas3D}->opengl_info(format => 'html');
-        $opengl_info_txt = $self->{mainframe}->{plater}->{canvas3D}->opengl_info;
+        $opengl_info = Slic3r::GUI::_3DScene::get_gl_info(1, 1);
+        $opengl_info_txt = Slic3r::GUI::_3DScene::get_gl_info(0, 1);
     }
     my $about = Slic3r::GUI::SystemInfo->new(
         parent      => undef, 
diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm
index 33c0e8d37..157e7229c 100644
--- a/lib/Slic3r/GUI/3DScene.pm
+++ b/lib/Slic3r/GUI/3DScene.pm
@@ -16,97 +16,102 @@ use strict;
 use warnings;
 
 use Wx qw(wxTheApp :timer :bitmap :icon :dialog);
-use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS EVT_CHAR EVT_TIMER);
+#==============================================================================================================================
+#use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS EVT_CHAR EVT_TIMER);
 # must load OpenGL *before* Wx::GLCanvas
 use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants);
 use base qw(Wx::GLCanvas Class::Accessor);
-use Math::Trig qw(asin tan);
-use List::Util qw(reduce min max first);
-use Slic3r::Geometry qw(X Y normalize scale unscale scaled_epsilon);
-use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl JT_ROUND);
+#==============================================================================================================================
+#use Math::Trig qw(asin tan);
+#use List::Util qw(reduce min max first);
+#use Slic3r::Geometry qw(X Y normalize scale unscale scaled_epsilon);
+#use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl JT_ROUND);
+#==============================================================================================================================
 use Wx::GLCanvas qw(:all);
-use Slic3r::Geometry qw(PI);
+#==============================================================================================================================
+#use Slic3r::Geometry qw(PI);
+#==============================================================================================================================
 
-# _dirty: boolean flag indicating, that the screen has to be redrawn on EVT_IDLE.
 # volumes: reference to vector of Slic3r::GUI::3DScene::Volume.
-# _camera_type: 'perspective' or 'ortho'
-__PACKAGE__->mk_accessors( qw(_quat _dirty init
-                              enable_picking
-                              enable_moving
-                              use_plain_shader
-                              on_viewport_changed
-                              on_hover
-                              on_select
-                              on_double_click
-                              on_right_click
-                              on_move
-                              on_model_update
-                              volumes
-                              _sphi _stheta
-                              cutting_plane_z
-                              cut_lines_vertices
-                              bed_shape
-                              bed_triangles
-                              bed_grid_lines
-                              bed_polygon
-                              background
-                              origin
-                              _mouse_pos
-                              _hover_volume_idx
-
-                              _drag_volume_idx
-                              _drag_start_pos
-                              _drag_volume_center_offset
-                              _drag_start_xy
-                              _dragged
-
-                              _layer_height_edited
-
-                              _camera_type
-                              _camera_target
-                              _camera_distance
-                              _zoom
-                              
-                              _legend_enabled
-                              _warning_enabled
-                              _apply_zoom_to_volumes_filter
-                              _mouse_dragging
-                                                            
-                              ) );
-                              
-use constant TRACKBALLSIZE  => 0.8;
-use constant TURNTABLE_MODE => 1;
-use constant GROUND_Z       => -0.02;
-# For mesh selection: Not selected - bright yellow.
-use constant DEFAULT_COLOR  => [1,1,0];
-# For mesh selection: Selected - bright green.
-use constant SELECTED_COLOR => [0,1,0,1];
-# For mesh selection: Mouse hovers over the object, but object not selected yet - dark green.
-use constant HOVER_COLOR    => [0.4,0.9,0,1];
-
-# phi / theta angles to orient the camera.
-use constant VIEW_DEFAULT    => [45.0,45.0];
-use constant VIEW_LEFT       => [90.0,90.0];
-use constant VIEW_RIGHT      => [-90.0,90.0];
-use constant VIEW_TOP        => [0.0,0.0];
-use constant VIEW_BOTTOM     => [0.0,180.0];
-use constant VIEW_FRONT      => [0.0,90.0];
-use constant VIEW_REAR       => [180.0,90.0];
-
-use constant MANIPULATION_IDLE          => 0;
-use constant MANIPULATION_DRAGGING      => 1;
-use constant MANIPULATION_LAYER_HEIGHT  => 2;
-
-use constant GIMBALL_LOCK_THETA_MAX => 180;
-
-use constant VARIABLE_LAYER_THICKNESS_BAR_WIDTH => 70;
-use constant VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT => 22;
-
-# make OpenGL::Array thread-safe
-{
-    no warnings 'redefine';
-    *OpenGL::Array::CLONE_SKIP = sub { 1 };
-}
+#==============================================================================================================================
+#__PACKAGE__->mk_accessors( qw(_quat _dirty init
+#                              enable_picking
+#                              enable_moving
+#                              use_plain_shader
+#                              on_viewport_changed
+#                              on_hover
+#                              on_select
+#                              on_double_click
+#                              on_right_click
+#                              on_move
+#                              on_model_update
+#                              volumes
+#                              _sphi _stheta
+#                              cutting_plane_z
+#                              cut_lines_vertices
+#                              bed_shape
+#                              bed_triangles
+#                              bed_grid_lines
+#                              bed_polygon
+#                              background
+#                              origin
+#                              _mouse_pos
+#                              _hover_volume_idx
+#
+#                              _drag_volume_idx
+#                              _drag_start_pos
+#                              _drag_volume_center_offset
+#                              _drag_start_xy
+#                              _dragged
+#
+#                              _layer_height_edited
+#
+#                              _camera_type
+#                              _camera_target
+#                              _camera_distance
+#                              _zoom
+#                              
+#                              _legend_enabled
+#                              _warning_enabled
+#                              _apply_zoom_to_volumes_filter
+#                              _mouse_dragging
+#                                                            
+#                              ) );
+#
+#use constant TRACKBALLSIZE  => 0.8;
+#use constant TURNTABLE_MODE => 1;
+#use constant GROUND_Z       => -0.02;
+## For mesh selection: Not selected - bright yellow.
+#use constant DEFAULT_COLOR  => [1,1,0];
+## For mesh selection: Selected - bright green.
+#use constant SELECTED_COLOR => [0,1,0,1];
+## For mesh selection: Mouse hovers over the object, but object not selected yet - dark green.
+#use constant HOVER_COLOR    => [0.4,0.9,0,1];
+#
+## phi / theta angles to orient the camera.
+#use constant VIEW_DEFAULT    => [45.0,45.0];
+#use constant VIEW_LEFT       => [90.0,90.0];
+#use constant VIEW_RIGHT      => [-90.0,90.0];
+#use constant VIEW_TOP        => [0.0,0.0];
+#use constant VIEW_BOTTOM     => [0.0,180.0];
+#use constant VIEW_FRONT      => [0.0,90.0];
+#use constant VIEW_REAR       => [180.0,90.0];
+#
+#use constant MANIPULATION_IDLE          => 0;
+#use constant MANIPULATION_DRAGGING      => 1;
+#use constant MANIPULATION_LAYER_HEIGHT  => 2;
+#
+#use constant GIMBALL_LOCK_THETA_MAX => 180;
+#
+#use constant VARIABLE_LAYER_THICKNESS_BAR_WIDTH => 70;
+#use constant VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT => 22;
+#
+## make OpenGL::Array thread-safe
+#{
+#    no warnings 'redefine';
+#    *OpenGL::Array::CLONE_SKIP = sub { 1 };
+#}
+#==============================================================================================================================
 
 sub new {
     my ($class, $parent) = @_;
@@ -130,2070 +135,2097 @@ sub new {
     # we request a depth buffer explicitely because it looks like it's not created by 
     # default on Linux, causing transparency issues
     my $self = $class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "", $attrib);
-    if (Wx::wxVERSION >= 3.000003) {
-        # Wx 3.0.3 contains an ugly hack to support some advanced OpenGL attributes through the attribute list.
-        # The attribute list is transferred between the wxGLCanvas and wxGLContext constructors using a single static array s_wglContextAttribs.
-        # Immediatelly force creation of the OpenGL context to consume the static variable s_wglContextAttribs.
-        $self->GetContext();
-    }
+#==============================================================================================================================
+#    if (Wx::wxVERSION >= 3.000003) {
+#        # Wx 3.0.3 contains an ugly hack to support some advanced OpenGL attributes through the attribute list.
+#        # The attribute list is transferred between the wxGLCanvas and wxGLContext constructors using a single static array s_wglContextAttribs.
+#        # Immediatelly force creation of the OpenGL context to consume the static variable s_wglContextAttribs.
+#        $self->GetContext();
+#    }
+#==============================================================================================================================
 
-    $self->{can_multisample} = $can_multisample;
-    $self->background(1);
-    $self->_quat((0, 0, 0, 1));
-    $self->_stheta(45);
-    $self->_sphi(45);
-    $self->_zoom(1);
-    $self->_legend_enabled(0);
-    $self->_warning_enabled(0);
-    $self->use_plain_shader(0);
-    $self->_apply_zoom_to_volumes_filter(0);
-    $self->_mouse_dragging(0);
-
-    # Collection of GLVolume objects
-    $self->volumes(Slic3r::GUI::_3DScene::GLVolume::Collection->new);
-
-    # 3D point in model space
-    $self->_camera_type('ortho');
-#    $self->_camera_type('perspective');
-    $self->_camera_target(Slic3r::Pointf3->new(0,0,0));
-    $self->_camera_distance(0.);
-
-    $self->layer_editing_enabled(0);
-    $self->{layer_height_edit_band_width} = 2.;
-    $self->{layer_height_edit_strength} = 0.005;
-    $self->{layer_height_edit_last_object_id} = -1;
-    $self->{layer_height_edit_last_z} = 0.;
-    $self->{layer_height_edit_last_action} = 0;
-
-    $self->reset_objects;
-    
-    EVT_PAINT($self, sub {
-        my $dc = Wx::PaintDC->new($self);
-        $self->Render($dc);
-    });
-    EVT_SIZE($self, sub { $self->_dirty(1) });
-    EVT_IDLE($self, sub {
-        return unless $self->_dirty;
-        return if !$self->IsShownOnScreen;
-        $self->Resize( $self->GetSizeWH );
-        $self->Refresh;
-    });
-    EVT_MOUSEWHEEL($self, \&mouse_wheel_event);
-    EVT_MOUSE_EVENTS($self, \&mouse_event);
-#    EVT_KEY_DOWN($self, sub {
-    EVT_CHAR($self, sub {
-        my ($s, $event) = @_;
-        if ($event->HasModifiers) {
-            $event->Skip;
-        } else {
-            my $key = $event->GetKeyCode;
-            if ($key == ord('0')) {
-                $self->select_view('iso');
-            } elsif ($key == ord('1')) {
-                $self->select_view('top');
-            } elsif ($key == ord('2')) {
-                $self->select_view('bottom');
-            } elsif ($key == ord('3')) {
-                $self->select_view('front');
-            } elsif ($key == ord('4')) {
-                $self->select_view('rear');
-            } elsif ($key == ord('5')) {
-                $self->select_view('left');
-            } elsif ($key == ord('6')) {
-                $self->select_view('right');
-            } elsif ($key == ord('z')) {
-                $self->zoom_to_volumes;
-            } elsif ($key == ord('b')) {
-                $self->zoom_to_bed;
-            } else {
-                $event->Skip;
-            }
-        }
-    });
-    
-    $self->{layer_height_edit_timer_id} = &Wx::NewId();
-    $self->{layer_height_edit_timer} = Wx::Timer->new($self, $self->{layer_height_edit_timer_id});
-    EVT_TIMER($self, $self->{layer_height_edit_timer_id}, sub {
-        my ($self, $event) = @_;
-        return if $self->_layer_height_edited != 1;
-        return if $self->{layer_height_edit_last_object_id} == -1;
-        $self->_variable_layer_thickness_action(undef);
-    });
+#==============================================================================================================================
+    Slic3r::GUI::_3DScene::add_canvas($self);
+    Slic3r::GUI::_3DScene::allow_multisample($self, $can_multisample);
+#    my $context = $self->GetContext;
+#    $self->SetCurrent($context);
+#    Slic3r::GUI::_3DScene::add_canvas($self, $context);
+#    
+#    $self->{can_multisample} = $can_multisample;
+#    $self->background(1);
+#    $self->_quat((0, 0, 0, 1));
+#    $self->_stheta(45);
+#    $self->_sphi(45);
+#    $self->_zoom(1);
+#    $self->_legend_enabled(0);
+#    $self->_warning_enabled(0);
+#    $self->use_plain_shader(0);
+#    $self->_apply_zoom_to_volumes_filter(0);
+#    $self->_mouse_dragging(0);
+#
+#    # Collection of GLVolume objects
+#    $self->volumes(Slic3r::GUI::_3DScene::GLVolume::Collection->new);
+#
+#    # 3D point in model space
+#    $self->_camera_type('ortho');
+##    $self->_camera_type('perspective');
+#    $self->_camera_target(Slic3r::Pointf3->new(0,0,0));
+#    $self->_camera_distance(0.);
+#    $self->layer_editing_enabled(0);
+#    $self->{layer_height_edit_band_width} = 2.;
+#    $self->{layer_height_edit_strength} = 0.005;
+#    $self->{layer_height_edit_last_object_id} = -1;
+#    $self->{layer_height_edit_last_z} = 0.;
+#    $self->{layer_height_edit_last_action} = 0;
+#
+#    $self->reset_objects;
+#    
+#    EVT_PAINT($self, sub {
+#        my $dc = Wx::PaintDC->new($self);
+#        $self->Render($dc);
+#    });
+#    EVT_SIZE($self, sub { $self->_dirty(1) });
+#    EVT_IDLE($self, sub {
+#        return unless $self->_dirty;
+#        return if !$self->IsShownOnScreen;
+#        $self->Resize( $self->GetSizeWH );
+#        $self->Refresh;
+#    });
+#    EVT_MOUSEWHEEL($self, \&mouse_wheel_event);
+#    EVT_MOUSE_EVENTS($self, \&mouse_event);
+##    EVT_KEY_DOWN($self, sub {
+#    EVT_CHAR($self, sub {
+#        my ($s, $event) = @_;
+#        if ($event->HasModifiers) {
+#            $event->Skip;
+#        } else {
+#            my $key = $event->GetKeyCode;
+#            if ($key == ord('0')) {
+#                $self->select_view('iso');
+#            } elsif ($key == ord('1')) {
+#                $self->select_view('top');
+#            } elsif ($key == ord('2')) {
+#                $self->select_view('bottom');
+#            } elsif ($key == ord('3')) {
+#                $self->select_view('front');
+#            } elsif ($key == ord('4')) {
+#                $self->select_view('rear');
+#            } elsif ($key == ord('5')) {
+#                $self->select_view('left');
+#            } elsif ($key == ord('6')) {
+#                $self->select_view('right');
+#            } elsif ($key == ord('z')) {
+#                $self->zoom_to_volumes;
+#            } elsif ($key == ord('b')) {
+#                $self->zoom_to_bed;
+#            } else {
+#                $event->Skip;
+#            }
+#        }
+#    });
+#    
+#    $self->{layer_height_edit_timer_id} = &Wx::NewId();
+#    $self->{layer_height_edit_timer} = Wx::Timer->new($self, $self->{layer_height_edit_timer_id});
+#    EVT_TIMER($self, $self->{layer_height_edit_timer_id}, sub {
+#        my ($self, $event) = @_;
+#        return if $self->_layer_height_edited != 1;
+#        $self->_variable_layer_thickness_action(undef);
+#    });
+#==============================================================================================================================
     
     return $self;
 }
 
-sub set_legend_enabled {
-    my ($self, $value) = @_;
-    $self->_legend_enabled($value);
-}
-
-sub set_warning_enabled {
-    my ($self, $value) = @_;
-    $self->_warning_enabled($value);
-}
+#==============================================================================================================================
+#sub set_legend_enabled {
+#    my ($self, $value) = @_;
+#    $self->_legend_enabled($value);
+#}
+#
+#sub set_warning_enabled {
+#    my ($self, $value) = @_;
+#    $self->_warning_enabled($value);
+#}
+#==============================================================================================================================
 
 sub Destroy {
     my ($self) = @_;
-    $self->{layer_height_edit_timer}->Stop;
-    $self->DestroyGL;
+#==============================================================================================================================
+    Slic3r::GUI::_3DScene::remove_canvas($self);    
+#    $self->{layer_height_edit_timer}->Stop;
+#    $self->DestroyGL;
+#==============================================================================================================================
     return $self->SUPER::Destroy;
 }
 
-sub layer_editing_enabled {
-    my ($self, $value) = @_;
-    if (@_ == 2) {
-        $self->{layer_editing_enabled} = $value;
-        if ($value) {
-            if (! $self->{layer_editing_initialized}) {
-                # Enabling the layer editing for the first time. This triggers compilation of the necessary OpenGL shaders.
-                # If compilation fails, a message box is shown with the error codes.
-                $self->SetCurrent($self->GetContext);
-                my $shader = new Slic3r::GUI::_3DScene::GLShader;
-                my $error_message;
-                if (! $shader->load($self->_fragment_shader_variable_layer_height, $self->_vertex_shader_variable_layer_height)) {
-                    # Compilation or linking of the shaders failed.
-                    $error_message = "Cannot compile an OpenGL Shader, therefore the Variable Layer Editing will be disabled.\n\n" 
-                        . $shader->last_error;
-                    $shader = undef;
-                } else {
-                    $self->{layer_height_edit_shader} = $shader;
-                    ($self->{layer_preview_z_texture_id}) = glGenTextures_p(1);
-                    glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id});
-                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
-                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
-                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
-                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
-                    glBindTexture(GL_TEXTURE_2D, 0);
-                }
-                if (defined($error_message)) {
-                    # Don't enable the layer editing tool.
-                    $self->{layer_editing_enabled} = 0;
-                    # 2 means failed
-                    $self->{layer_editing_initialized} = 2;
-                    # Show the error message.
-                    Wx::MessageBox($error_message, "Slic3r Error", wxOK | wxICON_EXCLAMATION, $self);
-                } else {
-                    $self->{layer_editing_initialized} = 1;
-                }
-            } elsif ($self->{layer_editing_initialized} == 2) {
-                # Initilization failed before. Don't try to initialize and disable layer editing.
-                $self->{layer_editing_enabled} = 0;
-            }
-        }
-    }
-    return $self->{layer_editing_enabled};
-}
-
-sub layer_editing_allowed {
-    my ($self) = @_;
-    # Allow layer editing if either the shaders were not initialized yet and we don't know
-    # whether it will be possible to initialize them, 
-    # or if the initialization was done already and it failed.
-    return ! (defined($self->{layer_editing_initialized}) && $self->{layer_editing_initialized} == 2);
-}
-
-sub _first_selected_object_id_for_variable_layer_height_editing {
-    my ($self) = @_;
-    for my $i (0..$#{$self->volumes}) {
-        if ($self->volumes->[$i]->selected) {
-            my $object_id = int($self->volumes->[$i]->select_group_id / 1000000);
-            # Objects with object_id >= 1000 have a specific meaning, for example the wipe tower proxy.
-            return ($object_id >= $self->{print}->object_count) ? -1 : $object_id
-                if $object_id < 10000;
-        }
-    }
-    return -1;
-}
-
-# Returns an array with (left, top, right, bottom) of the variable layer thickness bar on the screen.
-sub _variable_layer_thickness_bar_rect_screen {
-    my ($self) = @_;
-    my ($cw, $ch) = $self->GetSizeWH;
-    return ($cw - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, 0, $cw, $ch - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT);
-}
-
-sub _variable_layer_thickness_bar_rect_viewport {
-    my ($self) = @_;
-    my ($cw, $ch) = $self->GetSizeWH;
-    return ((0.5*$cw-VARIABLE_LAYER_THICKNESS_BAR_WIDTH)/$self->_zoom, (-0.5*$ch+VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT)/$self->_zoom, $cw/(2*$self->_zoom), $ch/(2*$self->_zoom));
-}
-
-# Returns an array with (left, top, right, bottom) of the variable layer thickness bar on the screen.
-sub _variable_layer_thickness_reset_rect_screen {
-    my ($self) = @_;
-    my ($cw, $ch) = $self->GetSizeWH;
-    return ($cw - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, $ch - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT, $cw, $ch);
-}
-
-sub _variable_layer_thickness_reset_rect_viewport {
-    my ($self) = @_;
-    my ($cw, $ch) = $self->GetSizeWH;
-    return ((0.5*$cw-VARIABLE_LAYER_THICKNESS_BAR_WIDTH)/$self->_zoom, -$ch/(2*$self->_zoom), $cw/(2*$self->_zoom), (-0.5*$ch+VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT)/$self->_zoom);
-}
-
-sub _variable_layer_thickness_bar_rect_mouse_inside {
-   my ($self, $mouse_evt) = @_;
-   my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen;
-   return $mouse_evt->GetX >= $bar_left && $mouse_evt->GetX <= $bar_right && $mouse_evt->GetY >= $bar_top && $mouse_evt->GetY <= $bar_bottom;
-}
-
-sub _variable_layer_thickness_reset_rect_mouse_inside {
-   my ($self, $mouse_evt) = @_;
-   my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_reset_rect_screen;
-   return $mouse_evt->GetX >= $bar_left && $mouse_evt->GetX <= $bar_right && $mouse_evt->GetY >= $bar_top && $mouse_evt->GetY <= $bar_bottom;
-}
-
-sub _variable_layer_thickness_bar_mouse_cursor_z_relative {
-   my ($self) = @_;
-   my $mouse_pos = $self->ScreenToClientPoint(Wx::GetMousePosition());
-   my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen;
-   return ($mouse_pos->x >= $bar_left && $mouse_pos->x <= $bar_right && $mouse_pos->y >= $bar_top && $mouse_pos->y <= $bar_bottom) ?
-        # Inside the bar.
-        ($bar_bottom - $mouse_pos->y - 1.) / ($bar_bottom - $bar_top - 1) :
-        # Outside the bar.
-        -1000.;
-}
-
-sub _variable_layer_thickness_action {
-    my ($self, $mouse_event, $do_modification) = @_;
-    # A volume is selected. Test, whether hovering over a layer thickness bar.
-    return if $self->{layer_height_edit_last_object_id} == -1;
-    if (defined($mouse_event)) {
-        my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen;
-        $self->{layer_height_edit_last_z} = unscale($self->{print}->get_object($self->{layer_height_edit_last_object_id})->size->z)
-            * ($bar_bottom - $mouse_event->GetY - 1.) / ($bar_bottom - $bar_top);
-        $self->{layer_height_edit_last_action} = $mouse_event->ShiftDown ? ($mouse_event->RightIsDown ? 3 : 2) : ($mouse_event->RightIsDown ? 0 : 1);
-    }
-    # Mark the volume as modified, so Print will pick its layer height profile? Where to mark it?
-    # Start a timer to refresh the print? schedule_background_process() ?
-    # The PrintObject::adjust_layer_height_profile() call adjusts the profile of its associated ModelObject, it does not modify the profile of the PrintObject itself.
-    $self->{print}->get_object($self->{layer_height_edit_last_object_id})->adjust_layer_height_profile(
-        $self->{layer_height_edit_last_z},
-        $self->{layer_height_edit_strength},
-        $self->{layer_height_edit_band_width}, 
-        $self->{layer_height_edit_last_action});
-    $self->volumes->[$self->{layer_height_edit_last_object_id}]->generate_layer_height_texture(
-        $self->{print}->get_object($self->{layer_height_edit_last_object_id}), 1);
-    $self->Refresh;
-    # Automatic action on mouse down with the same coordinate.
-    $self->{layer_height_edit_timer}->Start(100, wxTIMER_CONTINUOUS);
-}
-
-sub mouse_event {
-    my ($self, $e) = @_;
-    
-    my $pos = Slic3r::Pointf->new($e->GetPositionXY);
-    my $object_idx_selected = $self->{layer_height_edit_last_object_id} = ($self->layer_editing_enabled && $self->{print}) ? $self->_first_selected_object_id_for_variable_layer_height_editing : -1;
-
-    $self->_mouse_dragging($e->Dragging);
-    
-    if ($e->Entering && (&Wx::wxMSW || $^O eq 'linux')) {
-        # wxMSW needs focus in order to catch mouse wheel events
-        $self->SetFocus;
-        $self->_drag_start_xy(undef);        
-    } elsif ($e->LeftDClick) {
-        if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) {
-        } elsif ($self->on_double_click) {
-            $self->on_double_click->();
-        }
-    } elsif ($e->LeftDown || $e->RightDown) {
-        # If user pressed left or right button we first check whether this happened
-        # on a volume or not.
-        my $volume_idx = $self->_hover_volume_idx // -1;
-        $self->_layer_height_edited(0);
-        if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) {
-            # A volume is selected and the mouse is hovering over a layer thickness bar.
-            # Start editing the layer height.
-            $self->_layer_height_edited(1);
-            $self->_variable_layer_thickness_action($e);
-        } elsif ($object_idx_selected != -1 && $self->_variable_layer_thickness_reset_rect_mouse_inside($e)) {
-            $self->{print}->get_object($object_idx_selected)->reset_layer_height_profile;
-            # Index 2 means no editing, just wait for mouse up event.
-            $self->_layer_height_edited(2);
-            $self->Refresh;
-            $self->Update;
-        } else {
-            # The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate $pos->x,y,
-            # an converts the screen space coordinate to unscaled object space.
-            my $pos3d = ($volume_idx == -1) ? undef : $self->mouse_to_3d(@$pos);
-
-            # Select volume in this 3D canvas.
-            # Don't deselect a volume if layer editing is enabled. We want the object to stay selected
-            # during the scene manipulation.
-            if ($self->enable_picking && ($volume_idx != -1 || ! $self->layer_editing_enabled)) {
-                $self->deselect_volumes;
-                $self->select_volume($volume_idx);
-                
-                if ($volume_idx != -1) {
-                    my $group_id = $self->volumes->[$volume_idx]->select_group_id;
-                    my @volumes;
-                    if ($group_id != -1) {
-                        $self->select_volume($_)
-                            for grep $self->volumes->[$_]->select_group_id == $group_id,
-                            0..$#{$self->volumes};
-                    }
-                }
-                
-                $self->Refresh;
-                $self->Update;
-            }
-            
-            # propagate event through callback
-            $self->on_select->($volume_idx)
-                if $self->on_select;
-            
-            if ($volume_idx != -1) {
-                if ($e->LeftDown && $self->enable_moving) {
-                    # Only accept the initial position, if it is inside the volume bounding box.
-                    my $volume_bbox = $self->volumes->[$volume_idx]->transformed_bounding_box;
-                    $volume_bbox->offset(1.);
-                    if ($volume_bbox->contains_point($pos3d)) {
-                        # The dragging operation is initiated.
-                        $self->_drag_volume_idx($volume_idx);
-                        $self->_drag_start_pos($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.
-                        $self->_drag_volume_center_offset($pos3d->vector_to($volume_bbox->center));
-                    }
-                } elsif ($e->RightDown) {
-                    # if right clicking on volume, propagate event through callback
-                    $self->on_right_click->($e->GetPosition)
-                        if $self->on_right_click;
-                }
-            }
-        }
-    } elsif ($e->Dragging && $e->LeftIsDown && ! $self->_layer_height_edited && defined($self->_drag_volume_idx)) {
-        # Get new position at the same Z of the initial click point.
-        my $cur_pos = Slic3r::Linef3->new(
-                $self->mouse_to_3d($e->GetX, $e->GetY, 0),
-                $self->mouse_to_3d($e->GetX, $e->GetY, 1))
-            ->intersect_plane($self->_drag_start_pos->z);
-        # Clip the new position, so the object center remains close to the bed.
-        {
-            $cur_pos->translate(@{$self->_drag_volume_center_offset});
-            my $cur_pos2 = Slic3r::Point->new(scale($cur_pos->x), scale($cur_pos->y));
-            if (! $self->bed_polygon->contains_point($cur_pos2)) {
-                my $ip = $self->bed_polygon->point_projection($cur_pos2);
-                $cur_pos->set_x(unscale($ip->x));
-                $cur_pos->set_y(unscale($ip->y));
-            }
-            $cur_pos->translate(@{$self->_drag_volume_center_offset->negative});
-        }
-        # Calculate the translation vector.
-        my $vector = $self->_drag_start_pos->vector_to($cur_pos);
-        # Get the volume being dragged.
-        my $volume = $self->volumes->[$self->_drag_volume_idx];
-        # Get all volumes belonging to the same group, if any.
-        my @volumes = ($volume->drag_group_id == -1) ?
-            ($volume) :
-            grep $_->drag_group_id == $volume->drag_group_id, @{$self->volumes};
-        # Apply new temporary volume origin and ignore Z.
-        $_->translate($vector->x, $vector->y, 0) for @volumes;
-        $self->_drag_start_pos($cur_pos);
-        $self->_dragged(1);
-        $self->Refresh;
-        $self->Update;
-    } elsif ($e->Dragging) {
-        if ($self->_layer_height_edited && $object_idx_selected != -1) {
-            $self->_variable_layer_thickness_action($e) if ($self->_layer_height_edited == 1);
-        } elsif ($e->LeftIsDown) {
-            # if dragging over blank area with left button, rotate
-            if (defined $self->_drag_start_pos) {
-                my $orig = $self->_drag_start_pos;
-                if (TURNTABLE_MODE) {
-                    # Turntable mode is enabled by default.
-                    $self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE);
-                    $self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE);        #-
-                    $self->_stheta(GIMBALL_LOCK_THETA_MAX) if $self->_stheta > GIMBALL_LOCK_THETA_MAX;
-                    $self->_stheta(0) if $self->_stheta < 0;
-                } else {
-                    my $size = $self->GetClientSize;
-                    my @quat = trackball(
-                        $orig->x / ($size->width / 2) - 1,
-                        1 - $orig->y / ($size->height / 2),       #/
-                        $pos->x / ($size->width / 2) - 1,
-                        1 - $pos->y / ($size->height / 2),        #/
-                    );
-                    $self->_quat(mulquats($self->_quat, \@quat));
-                }
-                $self->on_viewport_changed->() if $self->on_viewport_changed;
-                $self->Refresh;
-                $self->Update;
-            }
-            $self->_drag_start_pos($pos);
-        } elsif ($e->MiddleIsDown || $e->RightIsDown) {
-            # If dragging over blank area with right button, pan.
-            if (defined $self->_drag_start_xy) {
-                # get point in model space at Z = 0
-                my $cur_pos = $self->mouse_to_3d($e->GetX, $e->GetY, 0);
-                my $orig    = $self->mouse_to_3d($self->_drag_start_xy->x, $self->_drag_start_xy->y, 0);
-                $self->_camera_target->translate(@{$orig->vector_to($cur_pos)->negative});
-                $self->on_viewport_changed->() if $self->on_viewport_changed;
-                $self->Refresh;
-                $self->Update;
-            }
-            $self->_drag_start_xy($pos);
-        }
-    } elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) {
-        if ($self->_layer_height_edited) {
-            $self->_layer_height_edited(undef);
-            $self->{layer_height_edit_timer}->Stop;
-            $self->on_model_update->()
-                if ($object_idx_selected != -1 && $self->on_model_update);
-        } elsif ($self->on_move && defined($self->_drag_volume_idx) && $self->_dragged) {
-            # get all volumes belonging to the same group, if any
-            my @volume_idxs;
-            my $group_id = $self->volumes->[$self->_drag_volume_idx]->drag_group_id;
-            if ($group_id == -1) {
-                @volume_idxs = ($self->_drag_volume_idx);
-            } else {
-                @volume_idxs = grep $self->volumes->[$_]->drag_group_id == $group_id,
-                    0..$#{$self->volumes};
-            }
-            $self->on_move->(@volume_idxs);
-        }
-        $self->_drag_volume_idx(undef);
-        $self->_drag_start_pos(undef);
-        $self->_drag_start_xy(undef);
-        $self->_dragged(undef);
-    } elsif ($e->Moving) {
-        $self->_mouse_pos($pos);
-        # Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor
-        # hovers over.
-        if ($self->enable_picking) {
-            $self->Update;
-            $self->Refresh;
-        }
-    } else {
-        $e->Skip();
-    }
-}
-
-sub mouse_wheel_event {
-    my ($self, $e) = @_;
-
-    if ($e->MiddleIsDown) {
-        # Ignore the wheel events if the middle button is pressed.
-        return;
-    }
-    
-    if ($self->layer_editing_enabled && $self->{print}) {
-        my $object_idx_selected = $self->_first_selected_object_id_for_variable_layer_height_editing;
-        if ($object_idx_selected != -1) {
-            # A volume is selected. Test, whether hovering over a layer thickness bar.
-            if ($self->_variable_layer_thickness_bar_rect_mouse_inside($e)) {
-                # Adjust the width of the selection.
-                $self->{layer_height_edit_band_width} = max(min($self->{layer_height_edit_band_width} * (1 + 0.1 * $e->GetWheelRotation() / $e->GetWheelDelta()), 10.), 1.5);
-                $self->Refresh;
-                return;
-            }
-        }
-    }
-
-    # Calculate the zoom delta and apply it to the current zoom factor
-    my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta();
-    $zoom = max(min($zoom, 4), -4);
-    $zoom /= 10;
-    $zoom = $self->_zoom / (1-$zoom);
-    # Don't allow to zoom too far outside the scene.
-    my $zoom_min = $self->get_zoom_to_bounding_box_factor($self->max_bounding_box);
-    $zoom_min *= 0.4 if defined $zoom_min;
-    $zoom = $zoom_min if defined $zoom_min && $zoom < $zoom_min;
-    $self->_zoom($zoom);
-    
-#    # In order to zoom around the mouse point we need to translate
-#    # the camera target
-#    my $size = Slic3r::Pointf->new($self->GetSizeWH);
-#    my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #-
-#    $self->_camera_target->translate(
-#        # ($pos - $size/2) represents the vector from the viewport center
-#        # to the mouse point. By multiplying it by $zoom we get the new,
-#        # transformed, length of such vector.
-#        # Since we want that point to stay fixed, we move our camera target
-#        # in the opposite direction by the delta of the length of such vector
-#        # ($zoom - 1). We then scale everything by 1/$self->_zoom since 
-#        # $self->_camera_target is expressed in terms of model units.
-#        -($pos->x - $size->x/2) * ($zoom) / $self->_zoom,
-#        -($pos->y - $size->y/2) * ($zoom) / $self->_zoom,
-#        0,
-#    ) if 0;
-
-    $self->on_viewport_changed->() if $self->on_viewport_changed;
-    $self->Resize($self->GetSizeWH) if $self->IsShownOnScreen;
-    $self->Refresh;
-}
-
-# Reset selection.
-sub reset_objects {
-    my ($self) = @_;
-    if ($self->GetContext) {
-        $self->SetCurrent($self->GetContext);
-        $self->volumes->release_geometry;
-    }
-    $self->volumes->erase;
-    $self->_dirty(1);
-}
-
-# Setup camera to view all objects.
-sub set_viewport_from_scene {
-    my ($self, $scene) = @_;
-    
-    $self->_sphi($scene->_sphi);
-    $self->_stheta($scene->_stheta);
-    $self->_camera_target($scene->_camera_target);
-    $self->_zoom($scene->_zoom);
-    $self->_quat($scene->_quat);
-    $self->_dirty(1);
-}
-
-# Set the camera to a default orientation,
-# zoom to volumes.
-sub select_view {
-    my ($self, $direction) = @_;
-    my $dirvec;
-    if (ref($direction)) {
-        $dirvec = $direction;
-    } else {
-        if ($direction eq 'iso') {
-            $dirvec = VIEW_DEFAULT;
-        } elsif ($direction eq 'left') {
-            $dirvec = VIEW_LEFT;
-        } elsif ($direction eq 'right') {
-            $dirvec = VIEW_RIGHT;
-        } elsif ($direction eq 'top') {
-            $dirvec = VIEW_TOP;
-        } elsif ($direction eq 'bottom') {
-            $dirvec = VIEW_BOTTOM;
-        } elsif ($direction eq 'front') {
-            $dirvec = VIEW_FRONT;
-        } elsif ($direction eq 'rear') {
-            $dirvec = VIEW_REAR;
-        }
-    }
-    my $bb = $self->volumes_bounding_box;
-    if (! $bb->empty) {
-        $self->_sphi($dirvec->[0]);
-        $self->_stheta($dirvec->[1]);
-        # Avoid gimball lock.
-        $self->_stheta(GIMBALL_LOCK_THETA_MAX) if $self->_stheta > GIMBALL_LOCK_THETA_MAX;
-        $self->_stheta(0) if $self->_stheta < 0;
-        $self->on_viewport_changed->() if $self->on_viewport_changed;
-        $self->Refresh;
-    }
-}
-
-sub get_zoom_to_bounding_box_factor {
-    my ($self, $bb) = @_;    
-    my $max_bb_size = max(@{ $bb->size });
-    return undef if ($max_bb_size == 0);
-        
-    # project the bbox vertices on a plane perpendicular to the camera forward axis
-    # then calculates the vertices coordinate on this plane along the camera xy axes
-    
-    # we need the view matrix, we let opengl calculate it (same as done in render sub)
-    glMatrixMode(GL_MODELVIEW);
-    glLoadIdentity();
-
-    if (!TURNTABLE_MODE) {
-        # Shift the perspective camera.
-        my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance);
-        glTranslatef(@$camera_pos);
-    }
-    
-    if (TURNTABLE_MODE) {
-        # Turntable mode is enabled by default.
-        glRotatef(-$self->_stheta, 1, 0, 0); # pitch
-        glRotatef($self->_sphi, 0, 0, 1);    # yaw
-    } else {
-        # Shift the perspective camera.
-        my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance);
-        glTranslatef(@$camera_pos);
-        my @rotmat = quat_to_rotmatrix($self->quat);
-        glMultMatrixd_p(@rotmat[0..15]);
-    }    
-    glTranslatef(@{ $self->_camera_target->negative });
-    
-    # get the view matrix back from opengl
-    my @matrix = glGetFloatv_p(GL_MODELVIEW_MATRIX);
-
-    # camera axes
-    my $right = Slic3r::Pointf3->new($matrix[0], $matrix[4], $matrix[8]);
-    my $up = Slic3r::Pointf3->new($matrix[1], $matrix[5], $matrix[9]);
-    my $forward = Slic3r::Pointf3->new($matrix[2], $matrix[6], $matrix[10]);
-    
-    my $bb_min = $bb->min_point();
-    my $bb_max = $bb->max_point();
-    my $bb_center = $bb->center();
-    
-    # bbox vertices in world space
-    my @vertices = ();    
-    push(@vertices, $bb_min);
-    push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_min->y(), $bb_min->z()));
-    push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_max->y(), $bb_min->z()));
-    push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_max->y(), $bb_min->z()));
-    push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_min->y(), $bb_max->z()));
-    push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_min->y(), $bb_max->z()));
-    push(@vertices, $bb_max);
-    push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_max->y(), $bb_max->z()));
-    
-    my $max_x = 0.0;
-    my $max_y = 0.0;
-
-    # margin factor to give some empty space around the bbox
-    my $margin_factor = 1.25;
-    
-    foreach my $v (@vertices) {
-        # project vertex on the plane perpendicular to camera forward axis
-        my $pos = Slic3r::Pointf3->new($v->x() - $bb_center->x(), $v->y() - $bb_center->y(), $v->z() - $bb_center->z());
-        my $proj_on_normal = $pos->x() * $forward->x() + $pos->y() * $forward->y() + $pos->z() * $forward->z();
-        my $proj_on_plane = Slic3r::Pointf3->new($pos->x() - $proj_on_normal * $forward->x(), $pos->y() - $proj_on_normal * $forward->y(), $pos->z() - $proj_on_normal * $forward->z());
-        
-        # calculates vertex coordinate along camera xy axes
-        my $x_on_plane = $proj_on_plane->x() * $right->x() + $proj_on_plane->y() * $right->y() + $proj_on_plane->z() * $right->z();
-        my $y_on_plane = $proj_on_plane->x() * $up->x() + $proj_on_plane->y() * $up->y() + $proj_on_plane->z() * $up->z();
-    
-        $max_x = max($max_x, $margin_factor * 2 * abs($x_on_plane));
-        $max_y = max($max_y, $margin_factor * 2 * abs($y_on_plane));
-    }
-    
-    return undef if (($max_x == 0) || ($max_y == 0));
-    
-    my ($cw, $ch) = $self->GetSizeWH;
-    my $min_ratio = min($cw / $max_x, $ch / $max_y);
-
-    return $min_ratio;
-}
-
-sub zoom_to_bounding_box {
-    my ($self, $bb) = @_;
-    # Calculate the zoom factor needed to adjust viewport to bounding box.
-    my $zoom = $self->get_zoom_to_bounding_box_factor($bb);
-    if (defined $zoom) {
-        $self->_zoom($zoom);
-        # center view around bounding box center
-        $self->_camera_target($bb->center);
-        $self->on_viewport_changed->() if $self->on_viewport_changed;
-        $self->Resize($self->GetSizeWH) if $self->IsShownOnScreen;
-        $self->Refresh;
-    }
-}
-
-sub zoom_to_bed {
-    my ($self) = @_;
-    
-    if ($self->bed_shape) {
-        $self->zoom_to_bounding_box($self->bed_bounding_box);
-    }
-}
-
-sub zoom_to_volume {
-    my ($self, $volume_idx) = @_;
-    
-    my $volume = $self->volumes->[$volume_idx];
-    my $bb = $volume->transformed_bounding_box;
-    $self->zoom_to_bounding_box($bb);
-}
-
-sub zoom_to_volumes {
-    my ($self) = @_;
-    $self->_apply_zoom_to_volumes_filter(1);
-    $self->zoom_to_bounding_box($self->volumes_bounding_box);
-    $self->_apply_zoom_to_volumes_filter(0);
-}
-
-sub volumes_bounding_box {
-    my ($self) = @_;
-    
-    my $bb = Slic3r::Geometry::BoundingBoxf3->new;
-    foreach my $v (@{$self->volumes}) {
-        $bb->merge($v->transformed_bounding_box) if (! $self->_apply_zoom_to_volumes_filter || $v->zoom_to_volumes);
-    }
-    return $bb;
-}
-
-sub bed_bounding_box {
-    my ($self) = @_;
-    
-    my $bb = Slic3r::Geometry::BoundingBoxf3->new;
-    if ($self->bed_shape) {
-        $bb->merge_point(Slic3r::Pointf3->new(@$_, 0)) for @{$self->bed_shape};
-    }
-    return $bb;
-}
-
-sub max_bounding_box {
-    my ($self) = @_;
-    
-    my $bb = $self->bed_bounding_box;
-    $bb->merge($self->volumes_bounding_box);
-    return $bb;
-}
-
-# Used by ObjectCutDialog and ObjectPartsPanel to generate a rectangular ground plane
-# to support the scene objects.
-sub set_auto_bed_shape {
-    my ($self, $bed_shape) = @_;
-    
-    # draw a default square bed around object center
-    my $max_size = max(@{ $self->volumes_bounding_box->size });
-    my $center = $self->volumes_bounding_box->center;
-    $self->set_bed_shape([
-        [ $center->x - $max_size, $center->y - $max_size ],  #--
-        [ $center->x + $max_size, $center->y - $max_size ],  #--
-        [ $center->x + $max_size, $center->y + $max_size ],  #++
-        [ $center->x - $max_size, $center->y + $max_size ],  #++
-    ]);
-    # Set the origin for painting of the coordinate system axes.
-    $self->origin(Slic3r::Pointf->new(@$center[X,Y]));
-}
-
-# Set the bed shape to a single closed 2D polygon (array of two element arrays),
-# triangulate the bed and store the triangles into $self->bed_triangles,
-# fills the $self->bed_grid_lines and sets $self->origin.
-# Sets $self->bed_polygon to limit the object placement.
-sub set_bed_shape {
-    my ($self, $bed_shape) = @_;
-    
-    $self->bed_shape($bed_shape);
-    
-    # triangulate bed
-    my $expolygon = Slic3r::ExPolygon->new([ map [map scale($_), @$_], @$bed_shape ]);
-    my $bed_bb = $expolygon->bounding_box;
-    
-    {
-        my @points = ();
-        foreach my $triangle (@{ $expolygon->triangulate }) {
-            push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$triangle;
-        }
-        $self->bed_triangles(OpenGL::Array->new_list(GL_FLOAT, @points));
-    }
-    
-    {
-        my @polylines = ();
-        for (my $x = $bed_bb->x_min; $x <= $bed_bb->x_max; $x += scale 10) {
-            push @polylines, Slic3r::Polyline->new([$x,$bed_bb->y_min], [$x,$bed_bb->y_max]);
-        }
-        for (my $y = $bed_bb->y_min; $y <= $bed_bb->y_max; $y += scale 10) {
-            push @polylines, Slic3r::Polyline->new([$bed_bb->x_min,$y], [$bed_bb->x_max,$y]);
-        }
-        # clip with a slightly grown expolygon because our lines lay on the contours and
-        # may get erroneously clipped
-        my @lines = map Slic3r::Line->new(@$_[0,-1]),
-            @{intersection_pl(\@polylines, [ @{$expolygon->offset(+scaled_epsilon)} ])};
-        
-        # append bed contours
-        push @lines, map @{$_->lines}, @$expolygon;
-        
-        my @points = ();
-        foreach my $line (@lines) {
-            push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$line;  #))
-        }
-        $self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points));
-    }
-    
-    # Set the origin for painting of the coordinate system axes.
-    $self->origin(Slic3r::Pointf->new(0,0));
-
-    $self->bed_polygon(offset_ex([$expolygon->contour], $bed_bb->radius * 1.7, JT_ROUND, scale(0.5))->[0]->contour->clone);
-}
-
-sub deselect_volumes {
-    my ($self) = @_;
-    $_->set_selected(0) for @{$self->volumes};
-}
-
-sub select_volume {
-    my ($self, $volume_idx) = @_;
-
-    return if ($volume_idx >= scalar(@{$self->volumes}));
-
-    $self->volumes->[$volume_idx]->set_selected(1)
-        if $volume_idx != -1;
-}
-
-sub SetCuttingPlane {
-    my ($self, $z, $expolygons) = @_;
-    
-    $self->cutting_plane_z($z);
-    
-    # grow slices in order to display them better
-    $expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1);
-    
-    my @verts = ();
-    foreach my $line (map @{$_->lines}, map @$_, @$expolygons) {
-        push @verts, (
-            unscale($line->a->x), unscale($line->a->y), $z,  #))
-            unscale($line->b->x), unscale($line->b->y), $z,  #))
-        );
-    }
-    $self->cut_lines_vertices(OpenGL::Array->new_list(GL_FLOAT, @verts));
-}
-
-# Given an axis and angle, compute quaternion.
-sub axis_to_quat {
-    my ($ax, $phi) = @_;
-    
-    my $lena = sqrt(reduce { $a + $b } (map { $_ * $_ } @$ax));
-    my @q = map { $_ * (1 / $lena) } @$ax;
-    @q = map { $_ * sin($phi / 2.0) } @q;
-    $q[$#q + 1] = cos($phi / 2.0);
-    return @q;
-}
-
-# Project a point on the virtual trackball. 
-# If it is inside the sphere, map it to the sphere, if it outside map it
-# to a hyperbola.
-sub project_to_sphere {
-    my ($r, $x, $y) = @_;
-    
-    my $d = sqrt($x * $x + $y * $y);
-    if ($d < $r * 0.70710678118654752440) {     # Inside sphere
-        return sqrt($r * $r - $d * $d);
-    } else {                                    # On hyperbola
-        my $t = $r / 1.41421356237309504880;
-        return $t * $t / $d;
-    }
-}
-
-sub cross {
-    my ($v1, $v2) = @_;
-  
-    return (@$v1[1] * @$v2[2] - @$v1[2] * @$v2[1],
-            @$v1[2] * @$v2[0] - @$v1[0] * @$v2[2],
-            @$v1[0] * @$v2[1] - @$v1[1] * @$v2[0]);
-}
-
-# Simulate a track-ball. Project the points onto the virtual trackball, 
-# then figure out the axis of rotation, which is the cross product of 
-# P1 P2 and O P1 (O is the center of the ball, 0,0,0) Note: This is a 
-# deformed trackball-- is a trackball in the center, but is deformed 
-# into a hyperbolic sheet of rotation away from the center. 
-# It is assumed that the arguments to this routine are in the range 
-# (-1.0 ... 1.0).
-sub trackball {
-    my ($p1x, $p1y, $p2x, $p2y) = @_;
-    
-    if ($p1x == $p2x && $p1y == $p2y) {
-        # zero rotation
-        return (0.0, 0.0, 0.0, 1.0);
-    }
-    
-    # First, figure out z-coordinates for projection of P1 and P2 to
-    # deformed sphere
-    my @p1 = ($p1x, $p1y, project_to_sphere(TRACKBALLSIZE, $p1x, $p1y));
-    my @p2 = ($p2x, $p2y, project_to_sphere(TRACKBALLSIZE, $p2x, $p2y));
-    
-    # axis of rotation (cross product of P1 and P2)
-    my @a = cross(\@p2, \@p1);
-
-    # Figure out how much to rotate around that axis.
-    my @d = map { $_ * $_ } (map { $p1[$_] - $p2[$_] } 0 .. $#p1);
-    my $t = sqrt(reduce { $a + $b } @d) / (2.0 * TRACKBALLSIZE);
-    
-    # Avoid problems with out-of-control values...
-    $t = 1.0 if ($t > 1.0);
-    $t = -1.0 if ($t < -1.0);
-    my $phi = 2.0 * asin($t);
-
-    return axis_to_quat(\@a, $phi);
-}
-
-# Build a rotation matrix, given a quaternion rotation.
-sub quat_to_rotmatrix {
-    my ($q) = @_;
-  
-    my @m = ();
-  
-    $m[0] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[2] * @$q[2]);
-    $m[1] = 2.0 * (@$q[0] * @$q[1] - @$q[2] * @$q[3]);
-    $m[2] = 2.0 * (@$q[2] * @$q[0] + @$q[1] * @$q[3]);
-    $m[3] = 0.0;
-
-    $m[4] = 2.0 * (@$q[0] * @$q[1] + @$q[2] * @$q[3]);
-    $m[5] = 1.0 - 2.0 * (@$q[2] * @$q[2] + @$q[0] * @$q[0]);
-    $m[6] = 2.0 * (@$q[1] * @$q[2] - @$q[0] * @$q[3]);
-    $m[7] = 0.0;
-
-    $m[8] = 2.0 * (@$q[2] * @$q[0] - @$q[1] * @$q[3]);
-    $m[9] = 2.0 * (@$q[1] * @$q[2] + @$q[0] * @$q[3]);
-    $m[10] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[0] * @$q[0]);
-    $m[11] = 0.0;
-
-    $m[12] = 0.0;
-    $m[13] = 0.0;
-    $m[14] = 0.0;
-    $m[15] = 1.0;
-  
-    return @m;
-}
-
-sub mulquats {
-    my ($q1, $rq) = @_;
-  
-    return (@$q1[3] * @$rq[0] + @$q1[0] * @$rq[3] + @$q1[1] * @$rq[2] - @$q1[2] * @$rq[1],
-            @$q1[3] * @$rq[1] + @$q1[1] * @$rq[3] + @$q1[2] * @$rq[0] - @$q1[0] * @$rq[2],
-            @$q1[3] * @$rq[2] + @$q1[2] * @$rq[3] + @$q1[0] * @$rq[1] - @$q1[1] * @$rq[0],
-            @$q1[3] * @$rq[3] - @$q1[0] * @$rq[0] - @$q1[1] * @$rq[1] - @$q1[2] * @$rq[2])
-}
-
-# Convert the screen space coordinate to an object space coordinate.
-# If the Z screen space coordinate is not provided, a depth buffer value is substituted.
-sub mouse_to_3d {
-    my ($self, $x, $y, $z) = @_;
-
-    return unless $self->GetContext;
-    $self->SetCurrent($self->GetContext);
-
-    my @viewport    = glGetIntegerv_p(GL_VIEWPORT);             # 4 items
-    my @mview       = glGetDoublev_p(GL_MODELVIEW_MATRIX);      # 16 items
-    my @proj        = glGetDoublev_p(GL_PROJECTION_MATRIX);     # 16 items
-    
-    $y = $viewport[3] - $y;
-    $z //= glReadPixels_p($x, $y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT);
-    my @projected = gluUnProject_p($x, $y, $z, @mview, @proj, @viewport);
-    return Slic3r::Pointf3->new(@projected);
-}
-
-sub GetContext {
-    my ($self) = @_;
-    return $self->{context} ||= Wx::GLContext->new($self);
-}
- 
-sub SetCurrent {
-    my ($self, $context) = @_;
-    return $self->SUPER::SetCurrent($context);
-}
-
-sub UseVBOs {
-    my ($self) = @_;
-
-    if (! defined ($self->{use_VBOs})) {
-        my $use_legacy = wxTheApp->{app_config}->get('use_legacy_opengl');
-        if ($use_legacy eq '1') {
-            # Disable OpenGL 2.0 rendering.
-            $self->{use_VBOs} = 0;
-            # Don't enable the layer editing tool.
-            $self->{layer_editing_enabled} = 0;
-            # 2 means failed
-            $self->{layer_editing_initialized} = 2;
-            return 0;
-        }
-        # This is a special path for wxWidgets on GTK, where an OpenGL context is initialized
-        # first when an OpenGL widget is shown for the first time. How ugly.
-        return 0 if (! $self->init && $^O eq 'linux');
-        # Don't use VBOs if anything fails.
-        $self->{use_VBOs} = 0;
-        if ($self->GetContext) {
-            $self->SetCurrent($self->GetContext);
-            Slic3r::GUI::_3DScene::_glew_init;
-            my @gl_version = split(/\./, glGetString(GL_VERSION));
-            $self->{use_VBOs} = int($gl_version[0]) >= 2;
-            # print "UseVBOs $self OpenGL major: $gl_version[0], minor: $gl_version[1]. Use VBOs: ", $self->{use_VBOs}, "\n";
-        }
-    }
-    return $self->{use_VBOs};
-}
-
-sub Resize {
-    my ($self, $x, $y) = @_;
- 
-    return unless $self->GetContext;
-    $self->_dirty(0);
-    
-    $self->SetCurrent($self->GetContext);
-    glViewport(0, 0, $x, $y);
- 
-    $x /= $self->_zoom;
-    $y /= $self->_zoom;
-    
-    glMatrixMode(GL_PROJECTION);
-    glLoadIdentity();
-    if ($self->_camera_type eq 'ortho') {
-        #FIXME setting the size of the box 10x larger than necessary
-        # is only a workaround for an incorrectly set camera.
-        # This workaround harms Z-buffer accuracy!
-#        my $depth = 1.05 * $self->max_bounding_box->radius();
-       my $depth = 5.0 * max(@{ $self->max_bounding_box->size });
-        glOrtho(
-            -$x/2, $x/2, -$y/2, $y/2,
-            -$depth, $depth,
-        );
-    } else {
-        die "Invalid camera type: ", $self->_camera_type, "\n" if ($self->_camera_type ne 'perspective');
-        my $bbox_r = $self->max_bounding_box->radius();
-        my $fov = PI * 45. / 180.;
-        my $fov_tan = tan(0.5 * $fov);
-        my $cam_distance = 0.5 * $bbox_r / $fov_tan;
-        $self->_camera_distance($cam_distance);
-        my $nr = $cam_distance - $bbox_r * 1.1;
-        my $fr = $cam_distance + $bbox_r * 1.1;
-        $nr = 1 if ($nr < 1);
-        $fr = $nr + 1 if ($fr < $nr + 1);
-        my $h2 = $fov_tan * $nr;
-        my $w2 = $h2 * $x / $y;
-        glFrustum(-$w2, $w2, -$h2, $h2, $nr, $fr);        
-    }
-
-    glMatrixMode(GL_MODELVIEW);
-}
-
-sub InitGL {
-    my $self = shift;
- 
-    return if $self->init;
-    return unless $self->GetContext;
-    $self->init(1);
-
-    # This is a special path for wxWidgets on GTK, where an OpenGL context is initialized
-    # first when an OpenGL widget is shown for the first time. How ugly.
-    # In that case the volumes are wainting to be moved to Vertex Buffer Objects
-    # after the OpenGL context is being initialized.
-    $self->volumes->finalize_geometry(1) 
-        if ($^O eq 'linux' && $self->UseVBOs);
-
-    $self->zoom_to_bed;
-        
-    glClearColor(0, 0, 0, 1);
-    glColor3f(1, 0, 0);
-    glEnable(GL_DEPTH_TEST);
-    glClearDepth(1.0);
-    glDepthFunc(GL_LEQUAL);
-    glEnable(GL_CULL_FACE);
-    glEnable(GL_BLEND);
-    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-    
-    # Set antialiasing/multisampling
-    glDisable(GL_LINE_SMOOTH);
-    glDisable(GL_POLYGON_SMOOTH);
-
-    # See "GL_MULTISAMPLE and GL_ARRAY_BUFFER_ARB messages on failed launch"
-    # https://github.com/alexrj/Slic3r/issues/4085
-    eval {
-        # Disable the multi sampling by default, so the picking by color will work correctly.
-        glDisable(GL_MULTISAMPLE);
-    };
-    # Disable multi sampling if the eval failed.
-    $self->{can_multisample} = 0 if $@;
-    
-    # ambient lighting
-    glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.3, 0.3, 0.3, 1);
-    
-    glEnable(GL_LIGHTING);
-    glEnable(GL_LIGHT0);
-    glEnable(GL_LIGHT1);
-    
-    # light from camera
-    glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0);
-    glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.3, 0.3, 0.3, 1);
-    glLightfv_p(GL_LIGHT1, GL_DIFFUSE,  0.2, 0.2, 0.2, 1);
-    
-    # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
-    glShadeModel(GL_SMOOTH);
-    
-#    glMaterialfv_p(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 0.5, 0.3, 0.3, 1);
-#    glMaterialfv_p(GL_FRONT_AND_BACK, GL_SPECULAR, 1, 1, 1, 1);
-#    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50);
-#    glMaterialfv_p(GL_FRONT_AND_BACK, GL_EMISSION, 0.1, 0, 0, 0.9);
-    
-    # A handy trick -- have surface material mirror the color.
-    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
-    glEnable(GL_COLOR_MATERIAL);
-    glEnable(GL_MULTISAMPLE) if ($self->{can_multisample});
-
-    if ($self->UseVBOs) {
-        my $shader = new Slic3r::GUI::_3DScene::GLShader;
-        if (! $shader->load($self->_fragment_shader_Gouraud, $self->_vertex_shader_Gouraud)) {
-#        if (! $shader->load($self->_fragment_shader_Phong, $self->_vertex_shader_Phong)) {
-            print "Compilaton of path shader failed: \n" . $shader->last_error . "\n";
-            $shader = undef;
-        } else {
-            $self->{plain_shader} = $shader;
-        }
-    }
-}
-
-sub DestroyGL {
-    my $self = shift;
-    if ($self->GetContext) {
-        $self->SetCurrent($self->GetContext);
-        if ($self->{plain_shader}) {
-            $self->{plain_shader}->release;
-            delete $self->{plain_shader};
-        }
-        if ($self->{layer_height_edit_shader}) {
-            $self->{layer_height_edit_shader}->release;
-            delete $self->{layer_height_edit_shader};
-        }
-        $self->volumes->release_geometry;
-    }
-}
-
-sub Render {
-    my ($self, $dc) = @_;
-    
-    # prevent calling SetCurrent() when window is not shown yet
-    return unless $self->IsShownOnScreen;
-    return unless my $context = $self->GetContext;
-    $self->SetCurrent($context);
-    $self->InitGL;
-    
-    glClearColor(1, 1, 1, 1);
-    glClearDepth(1);
-    glDepthFunc(GL_LESS);
-    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-    
-    glMatrixMode(GL_MODELVIEW);
-    glLoadIdentity();
-
-    if (!TURNTABLE_MODE) {
-        # Shift the perspective camera.
-        my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance);
-        glTranslatef(@$camera_pos);
-    }
-    
-    if (TURNTABLE_MODE) {
-        # Turntable mode is enabled by default.
-        glRotatef(-$self->_stheta, 1, 0, 0); # pitch
-        glRotatef($self->_sphi, 0, 0, 1);    # yaw
-    } else {
-        my @rotmat = quat_to_rotmatrix($self->quat);
-        glMultMatrixd_p(@rotmat[0..15]);
-    }
-    glTranslatef(@{ $self->_camera_target->negative });
-    
-    # light from above
-    glLightfv_p(GL_LIGHT0, GL_POSITION, -0.5, -0.5, 1, 0);
-    glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.2, 0.2, 0.2, 1);
-    glLightfv_p(GL_LIGHT0, GL_DIFFUSE,  0.5, 0.5, 0.5, 1);
-
-    # Head light
-    glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0);
-    
-    if ($self->enable_picking && !$self->_mouse_dragging) {
-        if (my $pos = $self->_mouse_pos) {
-            # Render the object for picking.
-            # FIXME This cannot possibly work in a multi-sampled context as the color gets mangled by the anti-aliasing.
-            # Better to use software ray-casting on a bounding-box hierarchy.
-            glPushAttrib(GL_ENABLE_BIT);
-            glDisable(GL_MULTISAMPLE) if ($self->{can_multisample});
-            glDisable(GL_LIGHTING);
-            glDisable(GL_BLEND);
-            $self->draw_volumes(1);
-            glPopAttrib();
-            glFlush();
-            my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ];
-            my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256;
-            $self->_hover_volume_idx(undef);
-            $_->set_hover(0) for @{$self->volumes};
-            if ($volume_idx <= $#{$self->volumes}) {
-                $self->_hover_volume_idx($volume_idx);
-                
-                $self->volumes->[$volume_idx]->set_hover(1);
-                my $group_id = $self->volumes->[$volume_idx]->select_group_id;
-                if ($group_id != -1) {
-                    $_->set_hover(1) for grep { $_->select_group_id == $group_id } @{$self->volumes};
-                }
-                
-                $self->on_hover->($volume_idx) if $self->on_hover;
-            }
-            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-        }
-    }
-    
-    # draw fixed background
-    if ($self->background) {
-        glDisable(GL_LIGHTING);
-        glPushMatrix();
-        glLoadIdentity();
-        
-        glMatrixMode(GL_PROJECTION);
-        glPushMatrix();
-        glLoadIdentity();
-        
-        # Draws a bluish bottom to top gradient over the complete screen.
-        glDisable(GL_DEPTH_TEST);
-        glBegin(GL_QUADS);
-        glColor3f(0.0,0.0,0.0);
-        glVertex3f(-1.0,-1.0, 1.0);
-        glVertex3f( 1.0,-1.0, 1.0);
-        glColor3f(10/255,98/255,144/255);
-        glVertex3f( 1.0, 1.0, 1.0);
-        glVertex3f(-1.0, 1.0, 1.0);
-        glEnd();
-        glPopMatrix();
-        glEnable(GL_DEPTH_TEST);
-        
-        glMatrixMode(GL_MODELVIEW);
-        glPopMatrix();
-        glEnable(GL_LIGHTING);
-    }
-    
-    # draw ground and axes
-    glDisable(GL_LIGHTING);
-    
-    # draw ground
-    my $ground_z = GROUND_Z;
-    if ($self->bed_triangles) {
-        glDisable(GL_DEPTH_TEST);
-        
-        glEnable(GL_BLEND);
-        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-        
-        glEnableClientState(GL_VERTEX_ARRAY);
-        glColor4f(0.8, 0.6, 0.5, 0.4);
-        glNormal3d(0,0,1);
-        glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_triangles->ptr());
-        glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
-        glDisableClientState(GL_VERTEX_ARRAY);
-        
-        # we need depth test for grid, otherwise it would disappear when looking
-        # the object from below
-        glEnable(GL_DEPTH_TEST);
-    
-        # draw grid
-        glLineWidth(3);
-        glColor4f(0.2, 0.2, 0.2, 0.4);
-        glEnableClientState(GL_VERTEX_ARRAY);
-        glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_grid_lines->ptr());
-        glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
-        glDisableClientState(GL_VERTEX_ARRAY);
-        
-        glDisable(GL_BLEND);
-    }
-    
-    my $volumes_bb = $self->volumes_bounding_box;
-    
-    {
-        # draw axes
-        # disable depth testing so that axes are not covered by ground
-        glDisable(GL_DEPTH_TEST);
-        my $origin = $self->origin;
-        my $axis_len = $self->use_plain_shader ? 0.3 * max(@{ $self->bed_bounding_box->size }) : 2 * max(@{ $volumes_bb->size });
-        glLineWidth(2);
-        glBegin(GL_LINES);
-        # draw line for x axis
-        glColor3f(1, 0, 0);
-        glVertex3f(@$origin, $ground_z);
-        glVertex3f($origin->x + $axis_len, $origin->y, $ground_z);  #,,
-        # draw line for y axis
-        glColor3f(0, 1, 0);
-        glVertex3f(@$origin, $ground_z);
-        glVertex3f($origin->x, $origin->y + $axis_len, $ground_z);  #++
-        glEnd();
-        # draw line for Z axis
-        # (re-enable depth test so that axis is correctly shown when objects are behind it)
-        glEnable(GL_DEPTH_TEST);
-        glBegin(GL_LINES);
-        glColor3f(0, 0, 1);
-        glVertex3f(@$origin, $ground_z);
-        glVertex3f(@$origin, $ground_z+$axis_len);
-        glEnd();
-    }
-    
-    glEnable(GL_LIGHTING);
-        
-    # draw objects
-    if (! $self->use_plain_shader) {
-        $self->draw_volumes;
-    } elsif ($self->UseVBOs) {
-        if ($self->enable_picking) {
-            $self->mark_volumes_for_layer_height;
-            $self->volumes->set_print_box($self->bed_bounding_box->x_min, $self->bed_bounding_box->y_min, 0.0, $self->bed_bounding_box->x_max, $self->bed_bounding_box->y_max, $self->{config}->get('max_print_height'));
-            $self->volumes->check_outside_state($self->{config});
-            # do not cull backfaces to show broken geometry, if any
-            glDisable(GL_CULL_FACE);
-        }
-        $self->{plain_shader}->enable if $self->{plain_shader};
-        $self->volumes->render_VBOs;
-        $self->{plain_shader}->disable;
-        glEnable(GL_CULL_FACE) if ($self->enable_picking);
-    } else {
-        # do not cull backfaces to show broken geometry, if any
-        glDisable(GL_CULL_FACE) if ($self->enable_picking);
-        $self->volumes->render_legacy;
-        glEnable(GL_CULL_FACE) if ($self->enable_picking);
-    }
-
-    if (defined $self->cutting_plane_z) {
-        # draw cutting plane
-        my $plane_z = $self->cutting_plane_z;
-        my $bb = $volumes_bb;
-        glDisable(GL_CULL_FACE);
-        glDisable(GL_LIGHTING);
-        glEnable(GL_BLEND);
-        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-        glBegin(GL_QUADS);
-        glColor4f(0.8, 0.8, 0.8, 0.5);
-        glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z);
-        glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z);
-        glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z);
-        glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z);
-        glEnd();
-        glEnable(GL_CULL_FACE);
-        glDisable(GL_BLEND);
-        
-        # draw cutting contours
-        glEnableClientState(GL_VERTEX_ARRAY);
-        glLineWidth(2);
-        glColor3f(0, 0, 0);
-        glVertexPointer_c(3, GL_FLOAT, 0, $self->cut_lines_vertices->ptr());
-        glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3);
-        glVertexPointer_c(3, GL_FLOAT, 0, 0);
-        glDisableClientState(GL_VERTEX_ARRAY);
-    }
-
-    # draw warning message
-    $self->draw_warning;
-    
-    # draw gcode preview legend
-    $self->draw_legend;
-    
-    $self->draw_active_object_annotations;
-    
-    $self->SwapBuffers();
-}
-
-sub draw_volumes {
-    # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking.
-    my ($self, $fakecolor) = @_;
-    
-    # do not cull backfaces to show broken geometry, if any
-    glDisable(GL_CULL_FACE);
-    
-    glEnable(GL_BLEND);
-    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-    
-    glEnableClientState(GL_VERTEX_ARRAY);
-    glEnableClientState(GL_NORMAL_ARRAY);
-    
-    foreach my $volume_idx (0..$#{$self->volumes}) {
-        my $volume = $self->volumes->[$volume_idx];
-
-        if ($fakecolor) {
-            # Object picking mode. Render the object with a color encoding the object index.
-            my $r = ($volume_idx & 0x000000FF) >>  0;
-            my $g = ($volume_idx & 0x0000FF00) >>  8;
-            my $b = ($volume_idx & 0x00FF0000) >> 16;
-            glColor4f($r/255.0, $g/255.0, $b/255.0, 1);
-        } elsif ($volume->selected) {
-            glColor4f(@{ &SELECTED_COLOR });
-        } elsif ($volume->hover) {
-            glColor4f(@{ &HOVER_COLOR });
-        } else {
-            glColor4f(@{ $volume->color });
-        }
-
-        $volume->render;
-    }
-    glDisableClientState(GL_NORMAL_ARRAY);
-    glDisableClientState(GL_VERTEX_ARRAY);
-    
-    glDisable(GL_BLEND);
-    glEnable(GL_CULL_FACE);    
-}
-
-sub mark_volumes_for_layer_height {
-    my ($self) = @_;
-    
-    foreach my $volume_idx (0..$#{$self->volumes}) {
-        my $volume = $self->volumes->[$volume_idx];
-        my $object_id = int($volume->select_group_id / 1000000);
-        if ($self->layer_editing_enabled && $volume->selected && $self->{layer_height_edit_shader} && 
-            $volume->has_layer_height_texture && $object_id < $self->{print}->object_count) {
-            $volume->set_layer_height_texture_data($self->{layer_preview_z_texture_id}, $self->{layer_height_edit_shader}->shader_program_id,
-                $self->{print}->get_object($object_id), $self->_variable_layer_thickness_bar_mouse_cursor_z_relative, $self->{layer_height_edit_band_width});
-        } else {
-            $volume->reset_layer_height_texture_data();
-        }
-    }
-}
-
-sub _load_image_set_texture {
-    my ($self, $file_name) = @_;
-    # Load a PNG with an alpha channel.
-    my $img = Wx::Image->new;
-    $img->LoadFile(Slic3r::var($file_name), wxBITMAP_TYPE_PNG);
-    # Get RGB & alpha raw data from wxImage, interleave them into a Perl array.
-    my @rgb = unpack 'C*', $img->GetData();
-    my @alpha = $img->HasAlpha ? unpack 'C*', $img->GetAlpha() : (255) x (int(@rgb) / 3);
-    my $n_pixels = int(@alpha);
-    my @data = (0)x($n_pixels * 4);
-    for (my $i = 0; $i < $n_pixels; $i += 1) {
-        $data[$i*4  ] = $rgb[$i*3];
-        $data[$i*4+1] = $rgb[$i*3+1];
-        $data[$i*4+2] = $rgb[$i*3+2];
-        $data[$i*4+3] = $alpha[$i];
-    }
-    # Initialize a raw bitmap data.
-    my $params = {
-        loaded => 1,
-        valid  => $n_pixels > 0,
-        width  => $img->GetWidth, 
-        height => $img->GetHeight,
-        data   => OpenGL::Array->new_list(GL_UNSIGNED_BYTE, @data),
-        texture_id => glGenTextures_p(1)
-    };
-    # Create and initialize a texture with the raw data.
-    glBindTexture(GL_TEXTURE_2D, $params->{texture_id});
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
-    glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $params->{width}, $params->{height}, 0, GL_RGBA, GL_UNSIGNED_BYTE, $params->{data}->ptr);
-    glBindTexture(GL_TEXTURE_2D, 0);
-    return $params;
-}
-
-sub _variable_layer_thickness_load_overlay_image {
-    my ($self) = @_;
-    $self->{layer_preview_annotation} = $self->_load_image_set_texture('variable_layer_height_tooltip.png')
-        if (! $self->{layer_preview_annotation}->{loaded});
-    return $self->{layer_preview_annotation}->{valid};
-}
-
-sub _variable_layer_thickness_load_reset_image {
-    my ($self) = @_;
-    $self->{layer_preview_reset_image} = $self->_load_image_set_texture('variable_layer_height_reset.png')
-        if (! $self->{layer_preview_reset_image}->{loaded});
-    return $self->{layer_preview_reset_image}->{valid};
-}
-
-# Paint the tooltip.
-sub _render_image {
-    my ($self, $image, $l, $r, $b, $t) = @_;
-    $self->_render_texture($image->{texture_id}, $l, $r, $b, $t);
-}
-
-sub _render_texture {
-    my ($self, $tex_id, $l, $r, $b, $t) = @_;
-    
-    glColor4f(1.,1.,1.,1.);
-    glDisable(GL_LIGHTING);
-    glEnable(GL_BLEND);
-    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-    glEnable(GL_TEXTURE_2D);
-    glBindTexture(GL_TEXTURE_2D, $tex_id);
-    glBegin(GL_QUADS);
-    glTexCoord2d(0.,1.); glVertex3f($l, $b, 0);
-    glTexCoord2d(1.,1.); glVertex3f($r, $b, 0);
-    glTexCoord2d(1.,0.); glVertex3f($r, $t, 0);
-    glTexCoord2d(0.,0.); glVertex3f($l, $t, 0);
-    glEnd();
-    glBindTexture(GL_TEXTURE_2D, 0);
-    glDisable(GL_TEXTURE_2D);
-    glDisable(GL_BLEND);
-    glEnable(GL_LIGHTING);
-}
-
-sub draw_active_object_annotations {
-    # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking.
-    my ($self) = @_;
-
-    return if (! $self->{layer_height_edit_shader} || ! $self->layer_editing_enabled);
-
-    # Find the selected volume, over which the layer editing is active.
-    my $volume;
-    foreach my $volume_idx (0..$#{$self->volumes}) {
-        my $v = $self->volumes->[$volume_idx];
-        if ($v->selected && $v->has_layer_height_texture) {
-            $volume = $v;
-            last;
-        }
-    }
-    return if (! $volume);
-    
-    # If the active object was not allocated at the Print, go away. This should only be a momentary case between an object addition / deletion
-    # and an update by Platter::async_apply_config.
-    my $object_idx = int($volume->select_group_id / 1000000);
-    return if $object_idx >= $self->{print}->object_count;
-
-    # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth), 
-    # where x, y is the window size divided by $self->_zoom.
-    my ($bar_left, $bar_bottom, $bar_right, $bar_top) = $self->_variable_layer_thickness_bar_rect_viewport;
-    my ($reset_left, $reset_bottom, $reset_right, $reset_top) = $self->_variable_layer_thickness_reset_rect_viewport;
-    my $z_cursor_relative = $self->_variable_layer_thickness_bar_mouse_cursor_z_relative;
-
-    my $print_object = $self->{print}->get_object($object_idx);
-    my $z_max = $print_object->model_object->bounding_box->z_max;
-    
-    $self->{layer_height_edit_shader}->enable;
-    $self->{layer_height_edit_shader}->set_uniform('z_to_texture_row',            $volume->layer_height_texture_z_to_row_id);
-    $self->{layer_height_edit_shader}->set_uniform('z_texture_row_to_normalized', 1. / $volume->layer_height_texture_height);
-    $self->{layer_height_edit_shader}->set_uniform('z_cursor',                    $z_max * $z_cursor_relative);
-    $self->{layer_height_edit_shader}->set_uniform('z_cursor_band_width',         $self->{layer_height_edit_band_width});
-    glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id});
-    glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $volume->layer_height_texture_width, $volume->layer_height_texture_height, 
-        0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
-    glTexImage2D_c(GL_TEXTURE_2D, 1, GL_RGBA8, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2,
-        0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
-    glTexSubImage2D_c(GL_TEXTURE_2D, 0, 0, 0, $volume->layer_height_texture_width, $volume->layer_height_texture_height,
-        GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level0);
-    glTexSubImage2D_c(GL_TEXTURE_2D, 1, 0, 0, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2,
-        GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level1);
-    
-    # Render the color bar.
-    glDisable(GL_DEPTH_TEST);
-    # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth), 
-    # where x, y is the window size divided by $self->_zoom.
-    glPushMatrix();
-    glLoadIdentity();
-    # Paint the overlay.
-    glBegin(GL_QUADS);
-    glVertex3f($bar_left,  $bar_bottom, 0);
-    glVertex3f($bar_right, $bar_bottom, 0);
-    glVertex3f($bar_right, $bar_top, $z_max);
-    glVertex3f($bar_left,  $bar_top, $z_max);
-    glEnd();
-    glBindTexture(GL_TEXTURE_2D, 0);
-    $self->{layer_height_edit_shader}->disable;
-
-    # Paint the tooltip.
-    if ($self->_variable_layer_thickness_load_overlay_image) {
-        my $gap = 10/$self->_zoom;
-        my ($l, $r, $b, $t) = ($bar_left - $self->{layer_preview_annotation}->{width}/$self->_zoom - $gap, $bar_left - $gap, $reset_bottom + $self->{layer_preview_annotation}->{height}/$self->_zoom + $gap, $reset_bottom + $gap);
-        $self->_render_image($self->{layer_preview_annotation}, $l, $r, $t, $b);
-    }
-
-    # Paint the reset button.
-    if ($self->_variable_layer_thickness_load_reset_image) {
-        $self->_render_image($self->{layer_preview_reset_image}, $reset_left, $reset_right, $reset_bottom, $reset_top);
-    }
-
-    # Paint the graph.
-    #FIXME show some kind of legend.
-    my $max_z = unscale($print_object->size->z);
-    my $profile = $print_object->model_object->layer_height_profile;
-    my $layer_height = $print_object->config->get('layer_height');
-    my $layer_height_max  = 10000000000.;
-    {
-        # Get a maximum layer height value.
-        #FIXME This is a duplicate code of Slicing.cpp.
-        my $nozzle_diameters  = $print_object->print->config->get('nozzle_diameter');
-        my $layer_heights_min = $print_object->print->config->get('min_layer_height');
-        my $layer_heights_max = $print_object->print->config->get('max_layer_height');
-        for (my $i = 0; $i < scalar(@{$nozzle_diameters}); $i += 1) {
-            my $lh_min = ($layer_heights_min->[$i] == 0.) ? 0.07 : max(0.01, $layer_heights_min->[$i]);
-            my $lh_max = ($layer_heights_max->[$i] == 0.) ? (0.75 * $nozzle_diameters->[$i]) : $layer_heights_max->[$i];
-            $layer_height_max = min($layer_height_max, max($lh_min, $lh_max));
-        }
-    }
-    # Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region.
-    $layer_height_max *= 1.12;
-    # Baseline
-    glColor3f(0., 0., 0.);
-    glBegin(GL_LINE_STRIP);
-    glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / $layer_height_max,  $bar_bottom);
-    glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / $layer_height_max,  $bar_top);
-    glEnd();
-    # Curve
-    glColor3f(0., 0., 1.);
-    glBegin(GL_LINE_STRIP);
-    for (my $i = 0; $i < int(@{$profile}); $i += 2) {
-        my $z = $profile->[$i];
-        my $h = $profile->[$i+1];
-        glVertex3f($bar_left + $h * ($bar_right - $bar_left) / $layer_height_max,  $bar_bottom + $z * ($bar_top - $bar_bottom) / $max_z, $z);
-    }
-    glEnd();
-    # Revert the matrices.
-    glPopMatrix();
-    glEnable(GL_DEPTH_TEST);
-}
-
-sub draw_legend {
-    my ($self) = @_;
- 
-    if (!$self->_legend_enabled) {
-        return;
-    }
-
-    # If the legend texture has not been loaded into the GPU, do it now.
-    my $tex_id = Slic3r::GUI::_3DScene::finalize_legend_texture;
-    if ($tex_id > 0)
-    {
-        my $tex_w = Slic3r::GUI::_3DScene::get_legend_texture_width;
-        my $tex_h = Slic3r::GUI::_3DScene::get_legend_texture_height;
-        if (($tex_w > 0) && ($tex_h > 0))
-        {
-            glDisable(GL_DEPTH_TEST);
-            glPushMatrix();
-            glLoadIdentity();
-
-            my ($cw, $ch) = $self->GetSizeWH;
-
-            my $l = (-0.5 * $cw) / $self->_zoom;
-            my $t = (0.5 * $ch) / $self->_zoom;
-            my $r = $l + $tex_w / $self->_zoom;
-            my $b = $t - $tex_h / $self->_zoom;
-            $self->_render_texture($tex_id, $l, $r, $b, $t);
-
-            glPopMatrix();
-            glEnable(GL_DEPTH_TEST);
-        }
-    }
-}
-
-sub draw_warning {
-    my ($self) = @_;
- 
-    if (!$self->_warning_enabled) {
-        return;
-    }
-
-    # If the warning texture has not been loaded into the GPU, do it now.
-    my $tex_id = Slic3r::GUI::_3DScene::finalize_warning_texture;
-    if ($tex_id > 0)
-    {
-        my $tex_w = Slic3r::GUI::_3DScene::get_warning_texture_width;
-        my $tex_h = Slic3r::GUI::_3DScene::get_warning_texture_height;
-        if (($tex_w > 0) && ($tex_h > 0))
-        {
-            glDisable(GL_DEPTH_TEST);
-            glPushMatrix();
-            glLoadIdentity();
-        
-            my ($cw, $ch) = $self->GetSizeWH;
-                
-            my $l = (-0.5 * $tex_w) / $self->_zoom;
-            my $t = (-0.5 * $ch + $tex_h) / $self->_zoom;
-            my $r = $l + $tex_w / $self->_zoom;
-            my $b = $t - $tex_h / $self->_zoom;
-            $self->_render_texture($tex_id, $l, $r, $b, $t);
-
-            glPopMatrix();
-            glEnable(GL_DEPTH_TEST);
-        }
-    }
-}
-
-sub update_volumes_colors_by_extruder {
-    my ($self, $config) = @_;    
-    $self->volumes->update_colors_by_extruder($config);
-}
-
-sub opengl_info
-{
-    my ($self, %params) = @_;
-    my %tag = Slic3r::tags($params{format});
-
-    my $gl_version       = glGetString(GL_VERSION);
-    my $gl_vendor        = glGetString(GL_VENDOR);
-    my $gl_renderer      = glGetString(GL_RENDERER);
-    my $glsl_version     = glGetString(GL_SHADING_LANGUAGE_VERSION);
-
-    my $out = '';
-    $out .= "$tag{h2start}OpenGL installation$tag{h2end}$tag{eol}";
-    $out .= "  $tag{bstart}Using POGL$tag{bend} v$OpenGL::BUILD_VERSION$tag{eol}";
-    $out .= "  $tag{bstart}GL version:   $tag{bend}${gl_version}$tag{eol}";
-    $out .= "  $tag{bstart}vendor:       $tag{bend}${gl_vendor}$tag{eol}";
-    $out .= "  $tag{bstart}renderer:     $tag{bend}${gl_renderer}$tag{eol}";
-    $out .= "  $tag{bstart}GLSL version: $tag{bend}${glsl_version}$tag{eol}";
-
-    # Check for other OpenGL extensions
-    $out .= "$tag{h2start}Installed extensions (* implemented in the module):$tag{h2end}$tag{eol}";
-    my $extensions = glGetString(GL_EXTENSIONS);
-    my @extensions = split(' ',$extensions);
-    foreach my $ext (sort @extensions) {
-        my $stat = glpCheckExtension($ext);
-        $out .= sprintf("%s ${ext}$tag{eol}", $stat?' ':'*');
-        $out .= sprintf("    ${stat}$tag{eol}") if ($stat && $stat !~ m|^$ext |);
-    }
-
-    return $out;
-}
-
-sub _report_opengl_state
-{
-    my ($self, $comment) = @_;
-    my $err = glGetError();
-    return 0 if ($err == 0);
- 
-    # gluErrorString() hangs. Don't use it.
-#    my $errorstr = gluErrorString();
-    my $errorstr = '';
-    if ($err == 0x0500) {
-        $errorstr = 'GL_INVALID_ENUM';
-    } elsif ($err == GL_INVALID_VALUE) {
-        $errorstr = 'GL_INVALID_VALUE';
-    } elsif ($err == GL_INVALID_OPERATION) {
-        $errorstr = 'GL_INVALID_OPERATION';
-    } elsif ($err == GL_STACK_OVERFLOW) {
-        $errorstr = 'GL_STACK_OVERFLOW';
-    } elsif ($err == GL_OUT_OF_MEMORY) {
-        $errorstr = 'GL_OUT_OF_MEMORY';
-    } else {        
-        $errorstr = 'unknown';
-    }
-    if (defined($comment)) {
-        printf("OpenGL error at %s, nr %d (0x%x): %s\n", $comment, $err, $err, $errorstr);
-    } else {
-        printf("OpenGL error nr %d (0x%x): %s\n", $err, $err, $errorstr);
-    }
-}
-
-sub _vertex_shader_Gouraud {
-    return <<'VERTEX';
-#version 110
-
-#define INTENSITY_CORRECTION 0.6
-
-// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
-const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
-#define LIGHT_TOP_DIFFUSE    (0.8 * INTENSITY_CORRECTION)
-#define LIGHT_TOP_SPECULAR   (0.125 * INTENSITY_CORRECTION)
-#define LIGHT_TOP_SHININESS  20.0
-
-// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
-const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
-#define LIGHT_FRONT_DIFFUSE  (0.3 * INTENSITY_CORRECTION)
-//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
-//#define LIGHT_FRONT_SHININESS 5.0
-
-#define INTENSITY_AMBIENT    0.3
-
-const vec3 ZERO = vec3(0.0, 0.0, 0.0);
-
-struct PrintBoxDetection
-{
-    vec3 min;
-    vec3 max;
-    // xyz contains the offset, if w == 1.0 detection needs to be performed
-    vec4 volume_origin;
-};
-
-uniform PrintBoxDetection print_box;
-
-// x = tainted, y = specular;
-varying vec2 intensity;
-
-varying vec3 delta_box_min;
-varying vec3 delta_box_max;
-
-void main()
-{
-    // First transform the normal into camera space and normalize the result.
-    vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
-    
-    // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
-    // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
-    float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
-
-    intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
-    intensity.y = 0.0;
-
-    if (NdotL > 0.0)
-        intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
-
-    // Perform the same lighting calculation for the 2nd light source (no specular applied).
-    NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
-    intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
-
-    // compute deltas for out of print volume detection (world coordinates)
-    if (print_box.volume_origin.w == 1.0)
-    {
-        vec3 v = gl_Vertex.xyz + print_box.volume_origin.xyz;
-        delta_box_min = v - print_box.min;
-        delta_box_max = v - print_box.max;
-    }
-    else
-    {
-        delta_box_min = ZERO;
-        delta_box_max = ZERO;
-    }    
-
-    gl_Position = ftransform();
-} 
-
-VERTEX
-}
-
-sub _fragment_shader_Gouraud {
-    return <<'FRAGMENT';
-#version 110
-
-const vec3 ZERO = vec3(0.0, 0.0, 0.0);
-
-// x = tainted, y = specular;
-varying vec2 intensity;
-
-varying vec3 delta_box_min;
-varying vec3 delta_box_max;
-
-uniform vec4 uniform_color;
-
-void main()
-{
-    // if the fragment is outside the print volume -> use darker color
-    vec3 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(uniform_color.rgb, ZERO, 0.3333) : uniform_color.rgb;
-    gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + color * intensity.x, uniform_color.a);
-}
-
-FRAGMENT
-}
-
-sub _vertex_shader_Phong {
-    return <<'VERTEX';
-#version 110
-
-varying vec3 normal;
-varying vec3 eye;
-void main(void)  
-{
-   eye    = normalize(vec3(gl_ModelViewMatrix * gl_Vertex));
-   normal = normalize(gl_NormalMatrix * gl_Normal);
-   gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
-}
-VERTEX
-}
-
-sub _fragment_shader_Phong {
-    return <<'FRAGMENT';
-#version 110
-
-#define INTENSITY_CORRECTION 0.7
-
-#define LIGHT_TOP_DIR        -0.6/1.31, 0.6/1.31, 1./1.31
-#define LIGHT_TOP_DIFFUSE    (0.8 * INTENSITY_CORRECTION)
-#define LIGHT_TOP_SPECULAR   (0.5 * INTENSITY_CORRECTION)
-//#define LIGHT_TOP_SHININESS  50.
-#define LIGHT_TOP_SHININESS  10.
-
-#define LIGHT_FRONT_DIR      1./1.43, 0.2/1.43, 1./1.43
-#define LIGHT_FRONT_DIFFUSE  (0.3 * INTENSITY_CORRECTION)
-#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
-#define LIGHT_FRONT_SHININESS 50.
-
-#define INTENSITY_AMBIENT    0.0
-
-varying vec3 normal;
-varying vec3 eye;
-uniform vec4 uniform_color;
-void main() {
- 
-    float intensity_specular = 0.;
-    float intensity_tainted  = 0.;
-    float intensity = max(dot(normal,vec3(LIGHT_TOP_DIR)), 0.0);
-    // if the vertex is lit compute the specular color
-    if (intensity > 0.0) {
-        intensity_tainted = LIGHT_TOP_DIFFUSE * intensity;
-        // compute the half vector
-        vec3 h = normalize(vec3(LIGHT_TOP_DIR) + eye);  
-        // compute the specular term into spec
-        intensity_specular = LIGHT_TOP_SPECULAR * pow(max(dot(h, normal), 0.0), LIGHT_TOP_SHININESS);
-    }
-    intensity = max(dot(normal,vec3(LIGHT_FRONT_DIR)), 0.0);
-    // if the vertex is lit compute the specular color
-    if (intensity > 0.0) {
-        intensity_tainted += LIGHT_FRONT_DIFFUSE * intensity;
-        // compute the half vector
-//        vec3 h = normalize(vec3(LIGHT_FRONT_DIR) + eye);
-        // compute the specular term into spec
-//        intensity_specular += LIGHT_FRONT_SPECULAR * pow(max(dot(h,normal), 0.0), LIGHT_FRONT_SHININESS);
-    }
-    
-    gl_FragColor = max(
-        vec4(intensity_specular, intensity_specular, intensity_specular, 0.) + uniform_color * intensity_tainted, 
-        INTENSITY_AMBIENT * uniform_color);
-    gl_FragColor.a = uniform_color.a;
-}
-FRAGMENT
-}
-
-sub _vertex_shader_variable_layer_height {
-    return <<'VERTEX';
-#version 110
-
-#define INTENSITY_CORRECTION 0.6
-
-const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
-#define LIGHT_TOP_DIFFUSE    (0.8 * INTENSITY_CORRECTION)
-#define LIGHT_TOP_SPECULAR   (0.125 * INTENSITY_CORRECTION)
-#define LIGHT_TOP_SHININESS  20.0
-
-const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
-#define LIGHT_FRONT_DIFFUSE  (0.3 * INTENSITY_CORRECTION)
-//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
-//#define LIGHT_FRONT_SHININESS 5.0
-
-#define INTENSITY_AMBIENT    0.3
-
-// x = tainted, y = specular;
-varying vec2 intensity;
-
-varying float object_z;
-
-void main()
-{
-    // First transform the normal into camera space and normalize the result.
-    vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
-    
-    // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
-    // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
-    float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
-
-    intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
-    intensity.y = 0.0;
-
-    if (NdotL > 0.0)
-        intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
-
-    // Perform the same lighting calculation for the 2nd light source (no specular)
-    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;
-
-    gl_Position = ftransform();
-} 
-
-VERTEX
-}
-
-sub _fragment_shader_variable_layer_height {
-    return <<'FRAGMENT';
-#version 110
-
-#define M_PI 3.1415926535897932384626433832795
-
-// 2D texture (1D texture split by the rows) of color along the object Z axis.
-uniform sampler2D z_texture;
-// Scaling from the Z texture rows coordinate to the normalized texture row coordinate.
-uniform float z_to_texture_row;
-uniform float z_texture_row_to_normalized;
-uniform float z_cursor;
-uniform float z_cursor_band_width;
-
-// x = tainted, y = specular;
-varying vec2 intensity;
-
-varying float object_z;
-
-void main()
-{
-    float object_z_row = z_to_texture_row * object_z;
-    // Index of the row in the texture.
-    float z_texture_row = floor(object_z_row);
-    // Normalized coordinate from 0. to 1.
-    float z_texture_col = object_z_row - z_texture_row;
-    float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25;
-    // Calculate level of detail from the object Z coordinate.
-    // This makes the slowly sloping surfaces to be show with high detail (with stripes),
-    // and the vertical surfaces to be shown with low detail (no stripes)
-    float z_in_cells    = object_z_row * 190.;
-    // Gradient of Z projected on the screen.
-    float dx_vtc        = dFdx(z_in_cells);
-    float dy_vtc        = dFdy(z_in_cells);
-    float lod           = clamp(0.5 * log2(max(dx_vtc*dx_vtc, dy_vtc*dy_vtc)), 0., 1.);
-    // Sample the Z texture. Texture coordinates are normalized to <0, 1>.
-    vec4 color       =
-        mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5    )), -10000.),
-            texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)),  10000.), lod);
-            
-    // Mix the final color.
-    gl_FragColor = 
-        vec4(intensity.y, intensity.y, intensity.y, 1.0) +  intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend);
-}
-
-FRAGMENT
-}
+#==============================================================================================================================
+#sub layer_editing_enabled {
+#    my ($self, $value) = @_;
+#    if (@_ == 2) {
+#        $self->{layer_editing_enabled} = $value;
+#        if ($value) {
+#            if (! $self->{layer_editing_initialized}) {
+#                # Enabling the layer editing for the first time. This triggers compilation of the necessary OpenGL shaders.
+#                # If compilation fails, a message box is shown with the error codes.
+#                $self->SetCurrent($self->GetContext);
+#                my $shader = new Slic3r::GUI::_3DScene::GLShader;
+#                my $error_message;
+#                if (! $shader->load_from_text($self->_fragment_shader_variable_layer_height, $self->_vertex_shader_variable_layer_height)) {
+#                    # Compilation or linking of the shaders failed.
+#                    $error_message = "Cannot compile an OpenGL Shader, therefore the Variable Layer Editing will be disabled.\n\n" 
+#                        . $shader->last_error;
+#                    $shader = undef;
+#                } else {
+#                    $self->{layer_height_edit_shader} = $shader;
+#                    ($self->{layer_preview_z_texture_id}) = glGenTextures_p(1);
+#                    glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id});
+#                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+#                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+#                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+#                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+#                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
+#                    glBindTexture(GL_TEXTURE_2D, 0);
+#                }
+#                if (defined($error_message)) {
+#                    # Don't enable the layer editing tool.
+#                    $self->{layer_editing_enabled} = 0;
+#                    # 2 means failed
+#                    $self->{layer_editing_initialized} = 2;
+#                    # Show the error message.
+#                    Wx::MessageBox($error_message, "Slic3r Error", wxOK | wxICON_EXCLAMATION, $self);
+#                } else {
+#                    $self->{layer_editing_initialized} = 1;
+#                }
+#            } elsif ($self->{layer_editing_initialized} == 2) {
+#                # Initilization failed before. Don't try to initialize and disable layer editing.
+#                $self->{layer_editing_enabled} = 0;
+#            }
+#        }
+#    }
+#    return $self->{layer_editing_enabled};
+#}
+#
+#sub layer_editing_allowed {
+#    my ($self) = @_;
+#    # Allow layer editing if either the shaders were not initialized yet and we don't know
+#    # whether it will be possible to initialize them, 
+#    # or if the initialization was done already and it failed.
+#    return ! (defined($self->{layer_editing_initialized}) && $self->{layer_editing_initialized} == 2);
+#}
+#
+#sub _first_selected_object_id_for_variable_layer_height_editing {
+#    my ($self) = @_;
+#    for my $i (0..$#{$self->volumes}) {
+#        if ($self->volumes->[$i]->selected) {
+#            my $object_id = int($self->volumes->[$i]->select_group_id / 1000000);
+#            # Objects with object_id >= 1000 have a specific meaning, for example the wipe tower proxy.
+#            return ($object_id >= $self->{print}->object_count) ? -1 : $object_id
+#                if $object_id < 10000;
+#        }
+#    }
+#    return -1;
+#}
+#
+## Returns an array with (left, top, right, bottom) of the variable layer thickness bar on the screen.
+#sub _variable_layer_thickness_bar_rect_screen {
+#    my ($self) = @_;
+#    my ($cw, $ch) = $self->GetSizeWH;
+#    return ($cw - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, 0, $cw, $ch - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT);
+#}
+#
+#sub _variable_layer_thickness_bar_rect_viewport {
+#    my ($self) = @_;
+#    my ($cw, $ch) = $self->GetSizeWH;
+#    return ((0.5*$cw-VARIABLE_LAYER_THICKNESS_BAR_WIDTH)/$self->_zoom, (-0.5*$ch+VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT)/$self->_zoom, $cw/(2*$self->_zoom), $ch/(2*$self->_zoom));
+#}
+#
+## Returns an array with (left, top, right, bottom) of the variable layer thickness bar on the screen.
+#sub _variable_layer_thickness_reset_rect_screen {
+#    my ($self) = @_;
+#    my ($cw, $ch) = $self->GetSizeWH;
+#    return ($cw - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, $ch - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT, $cw, $ch);
+#}
+#
+#sub _variable_layer_thickness_reset_rect_viewport {
+#    my ($self) = @_;
+#    my ($cw, $ch) = $self->GetSizeWH;
+#    return ((0.5*$cw-VARIABLE_LAYER_THICKNESS_BAR_WIDTH)/$self->_zoom, -$ch/(2*$self->_zoom), $cw/(2*$self->_zoom), (-0.5*$ch+VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT)/$self->_zoom);
+#}
+#
+#sub _variable_layer_thickness_bar_rect_mouse_inside {
+#   my ($self, $mouse_evt) = @_;
+#   my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen;
+#   return $mouse_evt->GetX >= $bar_left && $mouse_evt->GetX <= $bar_right && $mouse_evt->GetY >= $bar_top && $mouse_evt->GetY <= $bar_bottom;
+#}
+#
+#sub _variable_layer_thickness_reset_rect_mouse_inside {
+#   my ($self, $mouse_evt) = @_;
+#   my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_reset_rect_screen;
+#   return $mouse_evt->GetX >= $bar_left && $mouse_evt->GetX <= $bar_right && $mouse_evt->GetY >= $bar_top && $mouse_evt->GetY <= $bar_bottom;
+#}
+#
+#sub _variable_layer_thickness_bar_mouse_cursor_z_relative {
+#   my ($self) = @_;
+#   my $mouse_pos = $self->ScreenToClientPoint(Wx::GetMousePosition());
+#   my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen;
+#   return ($mouse_pos->x >= $bar_left && $mouse_pos->x <= $bar_right && $mouse_pos->y >= $bar_top && $mouse_pos->y <= $bar_bottom) ?
+#        # Inside the bar.
+#        ($bar_bottom - $mouse_pos->y - 1.) / ($bar_bottom - $bar_top - 1) :
+#        # Outside the bar.
+#        -1000.;
+#}
+#
+#sub _variable_layer_thickness_action {
+#    my ($self, $mouse_event, $do_modification) = @_;
+#    # A volume is selected. Test, whether hovering over a layer thickness bar.
+#    return if $self->{layer_height_edit_last_object_id} == -1;
+#    if (defined($mouse_event)) {
+#        my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen;
+#        $self->{layer_height_edit_last_z} = unscale($self->{print}->get_object($self->{layer_height_edit_last_object_id})->size->z)
+#            * ($bar_bottom - $mouse_event->GetY - 1.) / ($bar_bottom - $bar_top);
+#        $self->{layer_height_edit_last_action} = $mouse_event->ShiftDown ? ($mouse_event->RightIsDown ? 3 : 2) : ($mouse_event->RightIsDown ? 0 : 1);
+#    }
+#    # Mark the volume as modified, so Print will pick its layer height profile? Where to mark it?
+#    # Start a timer to refresh the print? schedule_background_process() ?
+#    # The PrintObject::adjust_layer_height_profile() call adjusts the profile of its associated ModelObject, it does not modify the profile of the PrintObject itself.
+#    $self->{print}->get_object($self->{layer_height_edit_last_object_id})->adjust_layer_height_profile(
+#        $self->{layer_height_edit_last_z},
+#        $self->{layer_height_edit_strength},
+#        $self->{layer_height_edit_band_width}, 
+#        $self->{layer_height_edit_last_action});
+#             
+#    $self->volumes->[$self->{layer_height_edit_last_object_id}]->generate_layer_height_texture(
+#        $self->{print}->get_object($self->{layer_height_edit_last_object_id}), 1);
+#    $self->Refresh;
+#    # Automatic action on mouse down with the same coordinate.
+#    $self->{layer_height_edit_timer}->Start(100, wxTIMER_CONTINUOUS);
+#}
+#
+#sub mouse_event {
+#    my ($self, $e) = @_;
+#    
+#    my $pos = Slic3r::Pointf->new($e->GetPositionXY);
+#    my $object_idx_selected = $self->{layer_height_edit_last_object_id} = ($self->layer_editing_enabled && $self->{print}) ? $self->_first_selected_object_id_for_variable_layer_height_editing : -1;
+#
+#    $self->_mouse_dragging($e->Dragging);
+#    
+#    if ($e->Entering && (&Wx::wxMSW || $^O eq 'linux')) {
+#        # wxMSW needs focus in order to catch mouse wheel events
+#        $self->SetFocus;
+#        $self->_drag_start_xy(undef);        
+#    } elsif ($e->LeftDClick) {
+#        if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) {
+#        } elsif ($self->on_double_click) {
+#            $self->on_double_click->();
+#        }
+#    } elsif ($e->LeftDown || $e->RightDown) {
+#        # If user pressed left or right button we first check whether this happened
+#        # on a volume or not.
+#        my $volume_idx = $self->_hover_volume_idx // -1;
+#        $self->_layer_height_edited(0);
+#        if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) {
+#            # A volume is selected and the mouse is hovering over a layer thickness bar.
+#            # Start editing the layer height.
+#            $self->_layer_height_edited(1);
+#            $self->_variable_layer_thickness_action($e);
+#        } elsif ($object_idx_selected != -1 && $self->_variable_layer_thickness_reset_rect_mouse_inside($e)) {
+#            $self->{print}->get_object($object_idx_selected)->reset_layer_height_profile;
+#            # Index 2 means no editing, just wait for mouse up event.
+#            $self->_layer_height_edited(2);
+#            $self->Refresh;
+#            $self->Update;
+#        } else {
+#            # The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate $pos->x,y,
+#            # an converts the screen space coordinate to unscaled object space.
+#            my $pos3d = ($volume_idx == -1) ? undef : $self->mouse_to_3d(@$pos);
+#
+#            # Select volume in this 3D canvas.
+#            # Don't deselect a volume if layer editing is enabled. We want the object to stay selected
+#            # during the scene manipulation.
+#
+#            if ($self->enable_picking && ($volume_idx != -1 || ! $self->layer_editing_enabled)) {
+#                $self->deselect_volumes;
+#                $self->select_volume($volume_idx);
+#                
+#                if ($volume_idx != -1) {
+#                    my $group_id = $self->volumes->[$volume_idx]->select_group_id;
+#                    my @volumes;
+#                    if ($group_id != -1) {
+#                        $self->select_volume($_)
+#                            for grep $self->volumes->[$_]->select_group_id == $group_id,
+#                            0..$#{$self->volumes};
+#                    }
+#                }
+#                
+#                $self->Refresh;
+#                $self->Update;
+#            }
+#            
+#            # propagate event through callback
+#            $self->on_select->($volume_idx)
+#                if $self->on_select;
+#            
+#            if ($volume_idx != -1) {
+#                if ($e->LeftDown && $self->enable_moving) {
+#                    # Only accept the initial position, if it is inside the volume bounding box.
+#                    my $volume_bbox = $self->volumes->[$volume_idx]->transformed_bounding_box;
+#                    $volume_bbox->offset(1.);
+#                    if ($volume_bbox->contains_point($pos3d)) {
+#                        # The dragging operation is initiated.
+#                        $self->_drag_volume_idx($volume_idx);
+#                        $self->_drag_start_pos($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.
+#                        $self->_drag_volume_center_offset($pos3d->vector_to($volume_bbox->center));
+#                    }
+#                } elsif ($e->RightDown) {
+#                    # if right clicking on volume, propagate event through callback
+#                    $self->on_right_click->($e->GetPosition)
+#                        if $self->on_right_click;
+#                }
+#            }
+#        }
+#    } elsif ($e->Dragging && $e->LeftIsDown && ! $self->_layer_height_edited && defined($self->_drag_volume_idx)) {
+#        # Get new position at the same Z of the initial click point.
+#        my $cur_pos = Slic3r::Linef3->new(
+#                $self->mouse_to_3d($e->GetX, $e->GetY, 0),
+#                $self->mouse_to_3d($e->GetX, $e->GetY, 1))
+#            ->intersect_plane($self->_drag_start_pos->z);
+# 
+#        # Clip the new position, so the object center remains close to the bed.
+#        {
+#            $cur_pos->translate(@{$self->_drag_volume_center_offset});
+#            my $cur_pos2 = Slic3r::Point->new(scale($cur_pos->x), scale($cur_pos->y));
+#            if (! $self->bed_polygon->contains_point($cur_pos2)) {
+#                my $ip = $self->bed_polygon->point_projection($cur_pos2);
+#                $cur_pos->set_x(unscale($ip->x));
+#                $cur_pos->set_y(unscale($ip->y));
+#            }
+#            $cur_pos->translate(@{$self->_drag_volume_center_offset->negative});
+#        }
+#        # Calculate the translation vector.
+#        my $vector = $self->_drag_start_pos->vector_to($cur_pos);
+#        # Get the volume being dragged.
+#        my $volume = $self->volumes->[$self->_drag_volume_idx];
+#        # Get all volumes belonging to the same group, if any.
+#        my @volumes = ($volume->drag_group_id == -1) ?
+#            ($volume) :
+#            grep $_->drag_group_id == $volume->drag_group_id, @{$self->volumes};
+#        # Apply new temporary volume origin and ignore Z.
+#        $_->translate($vector->x, $vector->y, 0) for @volumes;
+#        $self->_drag_start_pos($cur_pos);
+#        $self->_dragged(1);
+#        $self->Refresh;
+#        $self->Update;
+#    } elsif ($e->Dragging) {
+#        if ($self->_layer_height_edited && $object_idx_selected != -1) {
+#            $self->_variable_layer_thickness_action($e) if ($self->_layer_height_edited == 1);
+#        } elsif ($e->LeftIsDown) {
+#            # if dragging over blank area with left button, rotate
+#            if (defined $self->_drag_start_pos) {
+#                my $orig = $self->_drag_start_pos;
+#                if (TURNTABLE_MODE) {
+#                    # Turntable mode is enabled by default.
+#                    $self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE);
+#                    $self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE);        #-
+#                    $self->_stheta(GIMBALL_LOCK_THETA_MAX) if $self->_stheta > GIMBALL_LOCK_THETA_MAX;
+#                    $self->_stheta(0) if $self->_stheta < 0;
+#                } else {
+#                    my $size = $self->GetClientSize;
+#                    my @quat = trackball(
+#                        $orig->x / ($size->width / 2) - 1,
+#                        1 - $orig->y / ($size->height / 2),       #/
+#                        $pos->x / ($size->width / 2) - 1,
+#                        1 - $pos->y / ($size->height / 2),        #/
+#                    );
+#                    $self->_quat(mulquats($self->_quat, \@quat));
+#                }
+#                $self->on_viewport_changed->() if $self->on_viewport_changed;
+#                $self->Refresh;
+#                $self->Update;
+#            }
+#            $self->_drag_start_pos($pos);
+#        } elsif ($e->MiddleIsDown || $e->RightIsDown) {
+#            # If dragging over blank area with right button, pan.
+#            if (defined $self->_drag_start_xy) {
+#                # get point in model space at Z = 0
+#                my $cur_pos = $self->mouse_to_3d($e->GetX, $e->GetY, 0);
+#                my $orig    = $self->mouse_to_3d($self->_drag_start_xy->x, $self->_drag_start_xy->y, 0);
+#                $self->_camera_target->translate(@{$orig->vector_to($cur_pos)->negative});
+#                $self->on_viewport_changed->() if $self->on_viewport_changed;
+#                $self->Refresh;
+#                $self->Update;
+#            }
+#            $self->_drag_start_xy($pos);
+#        }
+#    } elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) {
+#        if ($self->_layer_height_edited) {
+#            $self->_layer_height_edited(undef);
+#            $self->{layer_height_edit_timer}->Stop;
+#            $self->on_model_update->()
+#                if ($object_idx_selected != -1 && $self->on_model_update);
+#        } elsif ($self->on_move && defined($self->_drag_volume_idx) && $self->_dragged) {
+#            # get all volumes belonging to the same group, if any
+#            my @volume_idxs;
+#            my $group_id = $self->volumes->[$self->_drag_volume_idx]->drag_group_id;
+#            if ($group_id == -1) {
+#                @volume_idxs = ($self->_drag_volume_idx);
+#            } else {
+#                @volume_idxs = grep $self->volumes->[$_]->drag_group_id == $group_id,
+#                    0..$#{$self->volumes};
+#            }
+#            $self->on_move->(@volume_idxs);
+#        }
+#        $self->_drag_volume_idx(undef);
+#        $self->_drag_start_pos(undef);
+#        $self->_drag_start_xy(undef);
+#        $self->_dragged(undef);
+#    } elsif ($e->Moving) {
+#        $self->_mouse_pos($pos);
+#        # Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor
+#        # hovers over.
+#        if ($self->enable_picking) {
+#            $self->Update;
+#            $self->Refresh;
+#        }
+#    } else {
+#        $e->Skip();
+#    }
+#}
+#
+#sub mouse_wheel_event {
+#    my ($self, $e) = @_;
+#
+#    if ($e->MiddleIsDown) {
+#        # Ignore the wheel events if the middle button is pressed.
+#        return;
+#    }
+#    if ($self->layer_editing_enabled && $self->{print}) {
+#        my $object_idx_selected = $self->_first_selected_object_id_for_variable_layer_height_editing;
+#        if ($object_idx_selected != -1) {
+#            # A volume is selected. Test, whether hovering over a layer thickness bar.
+#            if ($self->_variable_layer_thickness_bar_rect_mouse_inside($e)) {
+#                # Adjust the width of the selection.
+#                $self->{layer_height_edit_band_width} = max(min($self->{layer_height_edit_band_width} * (1 + 0.1 * $e->GetWheelRotation() / $e->GetWheelDelta()), 10.), 1.5);
+#                $self->Refresh;
+#                return;
+#            }
+#        }
+#    }
+#
+#    # Calculate the zoom delta and apply it to the current zoom factor
+#    my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta();
+#    $zoom = max(min($zoom, 4), -4);
+#    $zoom /= 10;
+#    $zoom = $self->_zoom / (1-$zoom);
+#    # Don't allow to zoom too far outside the scene.
+#    my $zoom_min = $self->get_zoom_to_bounding_box_factor($self->max_bounding_box);
+#    $zoom_min *= 0.4 if defined $zoom_min;
+#    $zoom = $zoom_min if defined $zoom_min && $zoom < $zoom_min;
+#    $self->_zoom($zoom);
+#    
+##    # In order to zoom around the mouse point we need to translate
+##    # the camera target
+##    my $size = Slic3r::Pointf->new($self->GetSizeWH);
+##    my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #-
+##    $self->_camera_target->translate(
+##        # ($pos - $size/2) represents the vector from the viewport center
+##        # to the mouse point. By multiplying it by $zoom we get the new,
+##        # transformed, length of such vector.
+##        # Since we want that point to stay fixed, we move our camera target
+##        # in the opposite direction by the delta of the length of such vector
+##        # ($zoom - 1). We then scale everything by 1/$self->_zoom since 
+##        # $self->_camera_target is expressed in terms of model units.
+##        -($pos->x - $size->x/2) * ($zoom) / $self->_zoom,
+##        -($pos->y - $size->y/2) * ($zoom) / $self->_zoom,
+##        0,
+##    ) if 0;
+#
+#    $self->on_viewport_changed->() if $self->on_viewport_changed;
+#    $self->Resize($self->GetSizeWH) if $self->IsShownOnScreen;
+#    $self->Refresh;
+#}
+#
+## Reset selection.
+#sub reset_objects {
+#    my ($self) = @_;
+#    if ($self->GetContext) {
+#        $self->SetCurrent($self->GetContext);
+#        $self->volumes->release_geometry;
+#    }
+#    $self->volumes->erase;
+#    $self->_dirty(1);
+#}
+#
+## Setup camera to view all objects.
+#sub set_viewport_from_scene {
+#    my ($self, $scene) = @_;
+#    
+#    $self->_sphi($scene->_sphi);
+#    $self->_stheta($scene->_stheta);
+#    $self->_camera_target($scene->_camera_target);
+#    $self->_zoom($scene->_zoom);
+#    $self->_quat($scene->_quat);
+#    $self->_dirty(1);
+#}
+#
+## Set the camera to a default orientation,
+## zoom to volumes.
+#sub select_view {
+#    my ($self, $direction) = @_;
+#
+#    my $dirvec;
+#    if (ref($direction)) {
+#        $dirvec = $direction;
+#    } else {
+#        if ($direction eq 'iso') {
+#            $dirvec = VIEW_DEFAULT;
+#        } elsif ($direction eq 'left') {
+#            $dirvec = VIEW_LEFT;
+#        } elsif ($direction eq 'right') {
+#            $dirvec = VIEW_RIGHT;
+#        } elsif ($direction eq 'top') {
+#            $dirvec = VIEW_TOP;
+#        } elsif ($direction eq 'bottom') {
+#            $dirvec = VIEW_BOTTOM;
+#        } elsif ($direction eq 'front') {
+#            $dirvec = VIEW_FRONT;
+#        } elsif ($direction eq 'rear') {
+#            $dirvec = VIEW_REAR;
+#        }
+#    }
+#    my $bb = $self->volumes_bounding_box;
+#    if (! $bb->empty) {
+#        $self->_sphi($dirvec->[0]);
+#        $self->_stheta($dirvec->[1]);
+#        # Avoid gimball lock.
+#        $self->_stheta(GIMBALL_LOCK_THETA_MAX) if $self->_stheta > GIMBALL_LOCK_THETA_MAX;
+#        $self->_stheta(0) if $self->_stheta < 0;
+#        $self->on_viewport_changed->() if $self->on_viewport_changed;
+#        $self->Refresh;
+#    }
+#}
+#
+#sub get_zoom_to_bounding_box_factor {
+#    my ($self, $bb) = @_;    
+#    my $max_bb_size = max(@{ $bb->size });
+#    return undef if ($max_bb_size == 0);
+#        
+#    # project the bbox vertices on a plane perpendicular to the camera forward axis
+#    # then calculates the vertices coordinate on this plane along the camera xy axes
+#    
+#    # we need the view matrix, we let opengl calculate it (same as done in render sub)
+#    glMatrixMode(GL_MODELVIEW);
+#    glLoadIdentity();
+#
+#    if (!TURNTABLE_MODE) {
+#        # Shift the perspective camera.
+#        my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance);
+#        glTranslatef(@$camera_pos);
+#    }
+#    
+#    if (TURNTABLE_MODE) {
+#        # Turntable mode is enabled by default.
+#        glRotatef(-$self->_stheta, 1, 0, 0); # pitch
+#        glRotatef($self->_sphi, 0, 0, 1);    # yaw
+#    } else {
+#        # Shift the perspective camera.
+#        my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance);
+#        glTranslatef(@$camera_pos);
+#        my @rotmat = quat_to_rotmatrix($self->quat);
+#        glMultMatrixd_p(@rotmat[0..15]);
+#    }    
+#    glTranslatef(@{ $self->_camera_target->negative });
+#    
+#    # get the view matrix back from opengl
+#    my @matrix = glGetFloatv_p(GL_MODELVIEW_MATRIX);
+#
+#    # camera axes
+#    my $right = Slic3r::Pointf3->new($matrix[0], $matrix[4], $matrix[8]);
+#    my $up = Slic3r::Pointf3->new($matrix[1], $matrix[5], $matrix[9]);
+#    my $forward = Slic3r::Pointf3->new($matrix[2], $matrix[6], $matrix[10]);
+#    
+#    my $bb_min = $bb->min_point();
+#    my $bb_max = $bb->max_point();
+#    my $bb_center = $bb->center();
+#    
+#    # bbox vertices in world space
+#    my @vertices = ();    
+#    push(@vertices, $bb_min);
+#    push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_min->y(), $bb_min->z()));
+#    push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_max->y(), $bb_min->z()));
+#    push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_max->y(), $bb_min->z()));
+#    push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_min->y(), $bb_max->z()));
+#    push(@vertices, Slic3r::Pointf3->new($bb_max->x(), $bb_min->y(), $bb_max->z()));
+#    push(@vertices, $bb_max);
+#    push(@vertices, Slic3r::Pointf3->new($bb_min->x(), $bb_max->y(), $bb_max->z()));
+#    
+#    my $max_x = 0.0;
+#    my $max_y = 0.0;
+#
+#    # margin factor to give some empty space around the bbox
+#    my $margin_factor = 1.25;
+#    
+#    foreach my $v (@vertices) {
+#        # project vertex on the plane perpendicular to camera forward axis
+#        my $pos = Slic3r::Pointf3->new($v->x() - $bb_center->x(), $v->y() - $bb_center->y(), $v->z() - $bb_center->z());
+#        my $proj_on_normal = $pos->x() * $forward->x() + $pos->y() * $forward->y() + $pos->z() * $forward->z();
+#        my $proj_on_plane = Slic3r::Pointf3->new($pos->x() - $proj_on_normal * $forward->x(), $pos->y() - $proj_on_normal * $forward->y(), $pos->z() - $proj_on_normal * $forward->z());
+#        
+#        # calculates vertex coordinate along camera xy axes
+#        my $x_on_plane = $proj_on_plane->x() * $right->x() + $proj_on_plane->y() * $right->y() + $proj_on_plane->z() * $right->z();
+#        my $y_on_plane = $proj_on_plane->x() * $up->x() + $proj_on_plane->y() * $up->y() + $proj_on_plane->z() * $up->z();
+#    
+#        $max_x = max($max_x, $margin_factor * 2 * abs($x_on_plane));
+#        $max_y = max($max_y, $margin_factor * 2 * abs($y_on_plane));
+#    }
+#    
+#    return undef if (($max_x == 0) || ($max_y == 0));
+#    
+#    my ($cw, $ch) = $self->GetSizeWH;
+#    my $min_ratio = min($cw / $max_x, $ch / $max_y);
+#
+#    return $min_ratio;
+#}
+#
+#sub zoom_to_bounding_box {
+#    my ($self, $bb) = @_;
+#    # Calculate the zoom factor needed to adjust viewport to bounding box.
+#    my $zoom = $self->get_zoom_to_bounding_box_factor($bb);
+#    if (defined $zoom) {
+#        $self->_zoom($zoom);
+#        # center view around bounding box center
+#        $self->_camera_target($bb->center);
+#        $self->on_viewport_changed->() if $self->on_viewport_changed;
+#        $self->Resize($self->GetSizeWH) if $self->IsShownOnScreen;
+#        $self->Refresh;
+#    }
+#}
+#
+#sub zoom_to_bed {
+#    my ($self) = @_;
+#    
+#    if ($self->bed_shape) {
+#        $self->zoom_to_bounding_box($self->bed_bounding_box);
+#    }
+#}
+#
+#sub zoom_to_volume {
+#    my ($self, $volume_idx) = @_;
+#    
+#    my $volume = $self->volumes->[$volume_idx];
+#    my $bb = $volume->transformed_bounding_box;
+#    $self->zoom_to_bounding_box($bb);
+#}
+#
+#sub zoom_to_volumes {
+#    my ($self) = @_;
+#
+#    $self->_apply_zoom_to_volumes_filter(1);
+#    $self->zoom_to_bounding_box($self->volumes_bounding_box);
+#    $self->_apply_zoom_to_volumes_filter(0);
+#}
+#
+#sub volumes_bounding_box {
+#    my ($self) = @_;
+#    
+#    my $bb = Slic3r::Geometry::BoundingBoxf3->new;
+#    foreach my $v (@{$self->volumes}) {
+#        $bb->merge($v->transformed_bounding_box) if (! $self->_apply_zoom_to_volumes_filter || $v->zoom_to_volumes);
+#    }
+#    return $bb;
+#}
+#
+#sub bed_bounding_box {
+#    my ($self) = @_;
+#        
+#    my $bb = Slic3r::Geometry::BoundingBoxf3->new;
+#    if ($self->bed_shape) {
+#        $bb->merge_point(Slic3r::Pointf3->new(@$_, 0)) for @{$self->bed_shape};
+#    }
+#    return $bb;
+#}
+#
+#sub max_bounding_box {
+#    my ($self) = @_;
+#        
+#    my $bb = $self->bed_bounding_box;
+#    $bb->merge($self->volumes_bounding_box);
+#    return $bb;
+#}
+#
+## Used by ObjectCutDialog and ObjectPartsPanel to generate a rectangular ground plane
+## to support the scene objects.
+#sub set_auto_bed_shape {
+#    my ($self, $bed_shape) = @_;
+#    
+#    # draw a default square bed around object center
+#    my $max_size = max(@{ $self->volumes_bounding_box->size });
+#    my $center = $self->volumes_bounding_box->center;
+#    $self->set_bed_shape([
+#        [ $center->x - $max_size, $center->y - $max_size ],  #--
+#        [ $center->x + $max_size, $center->y - $max_size ],  #--
+#        [ $center->x + $max_size, $center->y + $max_size ],  #++
+#        [ $center->x - $max_size, $center->y + $max_size ],  #++
+#    ]);
+#    # Set the origin for painting of the coordinate system axes.
+#    $self->origin(Slic3r::Pointf->new(@$center[X,Y]));
+#}
+#
+## Set the bed shape to a single closed 2D polygon (array of two element arrays),
+## triangulate the bed and store the triangles into $self->bed_triangles,
+## fills the $self->bed_grid_lines and sets $self->origin.
+## Sets $self->bed_polygon to limit the object placement.
+#sub set_bed_shape {
+#    my ($self, $bed_shape) = @_;
+#    
+#    $self->bed_shape($bed_shape);
+#    
+#    # triangulate bed
+#    my $expolygon = Slic3r::ExPolygon->new([ map [map scale($_), @$_], @$bed_shape ]);
+#    my $bed_bb = $expolygon->bounding_box;
+#    
+#    {
+#        my @points = ();
+#        foreach my $triangle (@{ $expolygon->triangulate }) {
+#            push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$triangle;
+#        }
+#        $self->bed_triangles(OpenGL::Array->new_list(GL_FLOAT, @points));
+#    }
+#    
+#    {
+#        my @polylines = ();
+#        for (my $x = $bed_bb->x_min; $x <= $bed_bb->x_max; $x += scale 10) {
+#            push @polylines, Slic3r::Polyline->new([$x,$bed_bb->y_min], [$x,$bed_bb->y_max]);
+#        }
+#        for (my $y = $bed_bb->y_min; $y <= $bed_bb->y_max; $y += scale 10) {
+#            push @polylines, Slic3r::Polyline->new([$bed_bb->x_min,$y], [$bed_bb->x_max,$y]);
+#        }
+#        # clip with a slightly grown expolygon because our lines lay on the contours and
+#        # may get erroneously clipped
+#        my @lines = map Slic3r::Line->new(@$_[0,-1]),
+#            @{intersection_pl(\@polylines, [ @{$expolygon->offset(+scaled_epsilon)} ])};
+#        
+#        # append bed contours
+#        push @lines, map @{$_->lines}, @$expolygon;
+#        
+#        my @points = ();
+#        foreach my $line (@lines) {
+#            push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$line;  #))
+#        }
+#        $self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points));
+#    }
+#    
+#    # Set the origin for painting of the coordinate system axes.
+#    $self->origin(Slic3r::Pointf->new(0,0));
+#
+#    $self->bed_polygon(offset_ex([$expolygon->contour], $bed_bb->radius * 1.7, JT_ROUND, scale(0.5))->[0]->contour->clone);
+#}
+#
+#sub deselect_volumes {
+#    my ($self) = @_;
+#    $_->set_selected(0) for @{$self->volumes};
+#}
+#
+#sub select_volume {
+#    my ($self, $volume_idx) = @_;
+#
+#    return if ($volume_idx >= scalar(@{$self->volumes}));
+#
+#    $self->volumes->[$volume_idx]->set_selected(1)
+#        if $volume_idx != -1;
+#}
+#
+#sub SetCuttingPlane {
+#    my ($self, $z, $expolygons) = @_;
+#    
+#    $self->cutting_plane_z($z);
+#    
+#    # grow slices in order to display them better
+#    $expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1);
+#    
+#    my @verts = ();
+#    foreach my $line (map @{$_->lines}, map @$_, @$expolygons) {
+#        push @verts, (
+#            unscale($line->a->x), unscale($line->a->y), $z,  #))
+#            unscale($line->b->x), unscale($line->b->y), $z,  #))
+#        );
+#    }
+#    $self->cut_lines_vertices(OpenGL::Array->new_list(GL_FLOAT, @verts));
+#}
+#
+## Given an axis and angle, compute quaternion.
+#sub axis_to_quat {
+#    my ($ax, $phi) = @_;
+#    
+#    my $lena = sqrt(reduce { $a + $b } (map { $_ * $_ } @$ax));
+#    my @q = map { $_ * (1 / $lena) } @$ax;
+#    @q = map { $_ * sin($phi / 2.0) } @q;
+#    $q[$#q + 1] = cos($phi / 2.0);
+#    return @q;
+#}
+#
+## Project a point on the virtual trackball. 
+## If it is inside the sphere, map it to the sphere, if it outside map it
+## to a hyperbola.
+#sub project_to_sphere {
+#    my ($r, $x, $y) = @_;
+#    
+#    my $d = sqrt($x * $x + $y * $y);
+#    if ($d < $r * 0.70710678118654752440) {     # Inside sphere
+#        return sqrt($r * $r - $d * $d);
+#    } else {                                    # On hyperbola
+#        my $t = $r / 1.41421356237309504880;
+#        return $t * $t / $d;
+#    }
+#}
+#
+#sub cross {
+#    my ($v1, $v2) = @_;
+#  
+#    return (@$v1[1] * @$v2[2] - @$v1[2] * @$v2[1],
+#            @$v1[2] * @$v2[0] - @$v1[0] * @$v2[2],
+#            @$v1[0] * @$v2[1] - @$v1[1] * @$v2[0]);
+#}
+#
+## Simulate a track-ball. Project the points onto the virtual trackball, 
+## then figure out the axis of rotation, which is the cross product of 
+## P1 P2 and O P1 (O is the center of the ball, 0,0,0) Note: This is a 
+## deformed trackball-- is a trackball in the center, but is deformed 
+## into a hyperbolic sheet of rotation away from the center. 
+## It is assumed that the arguments to this routine are in the range 
+## (-1.0 ... 1.0).
+#sub trackball {
+#    my ($p1x, $p1y, $p2x, $p2y) = @_;
+#    
+#    if ($p1x == $p2x && $p1y == $p2y) {
+#        # zero rotation
+#        return (0.0, 0.0, 0.0, 1.0);
+#    }
+#    
+#    # First, figure out z-coordinates for projection of P1 and P2 to
+#    # deformed sphere
+#    my @p1 = ($p1x, $p1y, project_to_sphere(TRACKBALLSIZE, $p1x, $p1y));
+#    my @p2 = ($p2x, $p2y, project_to_sphere(TRACKBALLSIZE, $p2x, $p2y));
+#    
+#    # axis of rotation (cross product of P1 and P2)
+#    my @a = cross(\@p2, \@p1);
+#
+#    # Figure out how much to rotate around that axis.
+#    my @d = map { $_ * $_ } (map { $p1[$_] - $p2[$_] } 0 .. $#p1);
+#    my $t = sqrt(reduce { $a + $b } @d) / (2.0 * TRACKBALLSIZE);
+#    
+#    # Avoid problems with out-of-control values...
+#    $t = 1.0 if ($t > 1.0);
+#    $t = -1.0 if ($t < -1.0);
+#    my $phi = 2.0 * asin($t);
+#
+#    return axis_to_quat(\@a, $phi);
+#}
+#
+## Build a rotation matrix, given a quaternion rotation.
+#sub quat_to_rotmatrix {
+#    my ($q) = @_;
+#  
+#    my @m = ();
+#  
+#    $m[0] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[2] * @$q[2]);
+#    $m[1] = 2.0 * (@$q[0] * @$q[1] - @$q[2] * @$q[3]);
+#    $m[2] = 2.0 * (@$q[2] * @$q[0] + @$q[1] * @$q[3]);
+#    $m[3] = 0.0;
+#
+#    $m[4] = 2.0 * (@$q[0] * @$q[1] + @$q[2] * @$q[3]);
+#    $m[5] = 1.0 - 2.0 * (@$q[2] * @$q[2] + @$q[0] * @$q[0]);
+#    $m[6] = 2.0 * (@$q[1] * @$q[2] - @$q[0] * @$q[3]);
+#    $m[7] = 0.0;
+#
+#    $m[8] = 2.0 * (@$q[2] * @$q[0] - @$q[1] * @$q[3]);
+#    $m[9] = 2.0 * (@$q[1] * @$q[2] + @$q[0] * @$q[3]);
+#    $m[10] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[0] * @$q[0]);
+#    $m[11] = 0.0;
+#
+#    $m[12] = 0.0;
+#    $m[13] = 0.0;
+#    $m[14] = 0.0;
+#    $m[15] = 1.0;
+#  
+#    return @m;
+#}
+#
+#sub mulquats {
+#    my ($q1, $rq) = @_;
+#  
+#    return (@$q1[3] * @$rq[0] + @$q1[0] * @$rq[3] + @$q1[1] * @$rq[2] - @$q1[2] * @$rq[1],
+#            @$q1[3] * @$rq[1] + @$q1[1] * @$rq[3] + @$q1[2] * @$rq[0] - @$q1[0] * @$rq[2],
+#            @$q1[3] * @$rq[2] + @$q1[2] * @$rq[3] + @$q1[0] * @$rq[1] - @$q1[1] * @$rq[0],
+#            @$q1[3] * @$rq[3] - @$q1[0] * @$rq[0] - @$q1[1] * @$rq[1] - @$q1[2] * @$rq[2])
+#}
+#
+## Convert the screen space coordinate to an object space coordinate.
+## If the Z screen space coordinate is not provided, a depth buffer value is substituted.
+#sub mouse_to_3d {
+#    my ($self, $x, $y, $z) = @_;
+#
+#    return unless $self->GetContext;
+#    $self->SetCurrent($self->GetContext);
+#
+#    my @viewport    = glGetIntegerv_p(GL_VIEWPORT);             # 4 items
+#    my @mview       = glGetDoublev_p(GL_MODELVIEW_MATRIX);      # 16 items
+#    my @proj        = glGetDoublev_p(GL_PROJECTION_MATRIX);     # 16 items
+#    
+#    $y = $viewport[3] - $y;
+#    $z //= glReadPixels_p($x, $y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT);
+#    my @projected = gluUnProject_p($x, $y, $z, @mview, @proj, @viewport);
+#    return Slic3r::Pointf3->new(@projected);
+#}
+#
+#sub GetContext {
+#    my ($self) = @_;
+#    return $self->{context} ||= Wx::GLContext->new($self);
+#}
+# 
+#sub SetCurrent {
+#    my ($self, $context) = @_;
+#    return $self->SUPER::SetCurrent($context);
+#}
+#
+#sub UseVBOs {
+#    my ($self) = @_;
+#        
+#    if (! defined ($self->{use_VBOs})) {
+#        my $use_legacy = wxTheApp->{app_config}->get('use_legacy_opengl');
+#        if ($use_legacy eq '1') {
+#            # Disable OpenGL 2.0 rendering.
+#            $self->{use_VBOs} = 0;
+#            # Don't enable the layer editing tool.
+#            $self->{layer_editing_enabled} = 0;
+#            # 2 means failed
+#            $self->{layer_editing_initialized} = 2;
+#            return 0;
+#        }
+#        # This is a special path for wxWidgets on GTK, where an OpenGL context is initialized
+#        # first when an OpenGL widget is shown for the first time. How ugly.
+#        return 0 if (! $self->init && $^O eq 'linux');
+#        # Don't use VBOs if anything fails.
+#        $self->{use_VBOs} = 0;
+#        if ($self->GetContext) {
+#            $self->SetCurrent($self->GetContext);
+#            Slic3r::GUI::_3DScene::_glew_init;
+#            my @gl_version = split(/\./, glGetString(GL_VERSION));
+#            $self->{use_VBOs} = int($gl_version[0]) >= 2;
+#            # print "UseVBOs $self OpenGL major: $gl_version[0], minor: $gl_version[1]. Use VBOs: ", $self->{use_VBOs}, "\n";
+#        }
+#    }
+#    return $self->{use_VBOs};
+#}
+#
+#sub Resize {
+#    my ($self, $x, $y) = @_;
+#
+#    return unless $self->GetContext;
+#    $self->_dirty(0);
+#    
+#    $self->SetCurrent($self->GetContext);
+#    glViewport(0, 0, $x, $y);
+# 
+#    $x /= $self->_zoom;
+#    $y /= $self->_zoom;
+#    
+#    glMatrixMode(GL_PROJECTION);
+#    glLoadIdentity();
+#    if ($self->_camera_type eq 'ortho') {
+#        #FIXME setting the size of the box 10x larger than necessary
+#        # is only a workaround for an incorrectly set camera.
+#        # This workaround harms Z-buffer accuracy!
+##        my $depth = 1.05 * $self->max_bounding_box->radius();
+#       my $depth = 5.0 * max(@{ $self->max_bounding_box->size });
+#        glOrtho(
+#            -$x/2, $x/2, -$y/2, $y/2,
+#            -$depth, $depth,
+#        );
+#    } else {
+#        die "Invalid camera type: ", $self->_camera_type, "\n" if ($self->_camera_type ne 'perspective');
+#        my $bbox_r = $self->max_bounding_box->radius();
+#        my $fov = PI * 45. / 180.;
+#        my $fov_tan = tan(0.5 * $fov);
+#        my $cam_distance = 0.5 * $bbox_r / $fov_tan;
+#        $self->_camera_distance($cam_distance);
+#        my $nr = $cam_distance - $bbox_r * 1.1;
+#        my $fr = $cam_distance + $bbox_r * 1.1;
+#        $nr = 1 if ($nr < 1);
+#        $fr = $nr + 1 if ($fr < $nr + 1);
+#        my $h2 = $fov_tan * $nr;
+#        my $w2 = $h2 * $x / $y;
+#        glFrustum(-$w2, $w2, -$h2, $h2, $nr, $fr);        
+#    }
+#    glMatrixMode(GL_MODELVIEW);
+#}
+#
+#sub InitGL {
+#    my $self = shift;
+# 
+#    return if $self->init;
+#    return unless $self->GetContext;
+#    $self->init(1);
+#    
+##    # This is a special path for wxWidgets on GTK, where an OpenGL context is initialized
+##    # first when an OpenGL widget is shown for the first time. How ugly.
+##    # In that case the volumes are wainting to be moved to Vertex Buffer Objects
+##    # after the OpenGL context is being initialized.
+##    $self->volumes->finalize_geometry(1) 
+##        if ($^O eq 'linux' && $self->UseVBOs);
+# 
+#    $self->zoom_to_bed;
+#    
+#    glClearColor(0, 0, 0, 1);
+#    glColor3f(1, 0, 0);
+#    glEnable(GL_DEPTH_TEST);
+#    glClearDepth(1.0);
+#    glDepthFunc(GL_LEQUAL);
+#    glEnable(GL_CULL_FACE);
+#    glEnable(GL_BLEND);
+#    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+#    
+#    # Set antialiasing/multisampling
+#    glDisable(GL_LINE_SMOOTH);
+#    glDisable(GL_POLYGON_SMOOTH);
+#
+#    # See "GL_MULTISAMPLE and GL_ARRAY_BUFFER_ARB messages on failed launch"
+#    # https://github.com/alexrj/Slic3r/issues/4085
+#    eval {
+#        # Disable the multi sampling by default, so the picking by color will work correctly.
+#        glDisable(GL_MULTISAMPLE);
+#    };
+#    # Disable multi sampling if the eval failed.
+#    $self->{can_multisample} = 0 if $@;
+#    
+#    # ambient lighting
+#    glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.3, 0.3, 0.3, 1);
+#    
+#    glEnable(GL_LIGHTING);
+#    glEnable(GL_LIGHT0);
+#    glEnable(GL_LIGHT1);
+#    
+#    # light from camera
+#    glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0);
+#    glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.3, 0.3, 0.3, 1);
+#    glLightfv_p(GL_LIGHT1, GL_DIFFUSE,  0.2, 0.2, 0.2, 1);
+#    
+#    # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
+#    glShadeModel(GL_SMOOTH);
+#    
+##    glMaterialfv_p(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 0.5, 0.3, 0.3, 1);
+##    glMaterialfv_p(GL_FRONT_AND_BACK, GL_SPECULAR, 1, 1, 1, 1);
+##    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50);
+##    glMaterialfv_p(GL_FRONT_AND_BACK, GL_EMISSION, 0.1, 0, 0, 0.9);
+#    
+#    # A handy trick -- have surface material mirror the color.
+#    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
+#    glEnable(GL_COLOR_MATERIAL);
+#    glEnable(GL_MULTISAMPLE) if ($self->{can_multisample});
+#
+#    if ($self->UseVBOs) {
+#        my $shader = new Slic3r::GUI::_3DScene::GLShader;
+##        if (! $shader->load($self->_fragment_shader_Phong, $self->_vertex_shader_Phong)) {
+#            print "Compilaton of path shader failed: \n" . $shader->last_error . "\n";
+#            $shader = undef;
+#        } else {
+#            $self->{plain_shader} = $shader;
+#        }
+#    }
+#}
+#
+#sub DestroyGL {
+#    my $self = shift;
+#    if ($self->GetContext) {
+#        $self->SetCurrent($self->GetContext);
+#        if ($self->{plain_shader}) {
+#            $self->{plain_shader}->release;
+#            delete $self->{plain_shader};
+#        }
+#        if ($self->{layer_height_edit_shader}) {
+#            $self->{layer_height_edit_shader}->release;
+#            delete $self->{layer_height_edit_shader};
+#        }
+#        $self->volumes->release_geometry;
+#    }
+#}
+#
+#sub Render {
+#    my ($self, $dc) = @_;
+#    
+#    # prevent calling SetCurrent() when window is not shown yet
+#    return unless $self->IsShownOnScreen;
+#    return unless my $context = $self->GetContext;
+#    $self->SetCurrent($context);
+#    $self->InitGL;
+#        
+#    glClearColor(1, 1, 1, 1);
+#    glClearDepth(1);
+#    glDepthFunc(GL_LESS);
+#    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+#    
+#    glMatrixMode(GL_MODELVIEW);
+#    glLoadIdentity();
+#
+#    if (!TURNTABLE_MODE) {
+#        # Shift the perspective camera.
+#        my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance);
+#        glTranslatef(@$camera_pos);
+#    }
+#    
+#    if (TURNTABLE_MODE) {
+#        # Turntable mode is enabled by default.
+#        glRotatef(-$self->_stheta, 1, 0, 0); # pitch
+#        glRotatef($self->_sphi, 0, 0, 1);    # yaw
+#    } else {
+#        my @rotmat = quat_to_rotmatrix($self->quat);
+#        glMultMatrixd_p(@rotmat[0..15]);
+#    }
+#    
+#    glTranslatef(@{ $self->_camera_target->negative });
+#    
+#    # light from above
+#    glLightfv_p(GL_LIGHT0, GL_POSITION, -0.5, -0.5, 1, 0);
+#    glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.2, 0.2, 0.2, 1);
+#    glLightfv_p(GL_LIGHT0, GL_DIFFUSE,  0.5, 0.5, 0.5, 1);
+#
+#    # Head light
+#    glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0);
+#    
+#    if ($self->enable_picking && !$self->_mouse_dragging) {
+#        if (my $pos = $self->_mouse_pos) {
+#            # Render the object for picking.
+#            # FIXME This cannot possibly work in a multi-sampled context as the color gets mangled by the anti-aliasing.
+#            # Better to use software ray-casting on a bounding-box hierarchy.
+#            glPushAttrib(GL_ENABLE_BIT);
+#            glDisable(GL_MULTISAMPLE) if ($self->{can_multisample});
+#            glDisable(GL_LIGHTING);
+#            glDisable(GL_BLEND);
+#            $self->draw_volumes(1);
+#            glPopAttrib();
+#            glFlush();
+#            my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ];
+#            my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256;
+#            $self->_hover_volume_idx(undef);
+#            $_->set_hover(0) for @{$self->volumes};
+#            if ($volume_idx <= $#{$self->volumes}) {
+#                $self->_hover_volume_idx($volume_idx);
+#                
+#                $self->volumes->[$volume_idx]->set_hover(1);
+#                my $group_id = $self->volumes->[$volume_idx]->select_group_id;
+#                if ($group_id != -1) {
+#                    $_->set_hover(1) for grep { $_->select_group_id == $group_id } @{$self->volumes};
+#                }
+#                
+#                $self->on_hover->($volume_idx) if $self->on_hover;
+#            }
+#            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+#        }
+#    }
+#    
+#    # draw fixed background
+#    if ($self->background) {
+#        glDisable(GL_LIGHTING);
+#        glPushMatrix();
+#        glLoadIdentity();
+#        
+#        glMatrixMode(GL_PROJECTION);
+#        glPushMatrix();
+#        glLoadIdentity();
+#        
+#        # Draws a bluish bottom to top gradient over the complete screen.
+#        glDisable(GL_DEPTH_TEST);
+#        glBegin(GL_QUADS);
+#        glColor3f(0.0,0.0,0.0);
+#        glVertex3f(-1.0,-1.0, 1.0);
+#        glVertex3f( 1.0,-1.0, 1.0);
+#        glColor3f(10/255,98/255,144/255);
+#        glVertex3f( 1.0, 1.0, 1.0);
+#        glVertex3f(-1.0, 1.0, 1.0);
+#        glEnd();
+#        glPopMatrix();
+#        glEnable(GL_DEPTH_TEST);
+#        
+#        glMatrixMode(GL_MODELVIEW);
+#        glPopMatrix();
+#        glEnable(GL_LIGHTING);
+#    }
+#    
+#    # draw ground and axes
+#    glDisable(GL_LIGHTING);
+#    
+#    # draw ground
+#    my $ground_z = GROUND_Z;
+#    
+#    if ($self->bed_triangles) {
+#        glDisable(GL_DEPTH_TEST);
+#        
+#        glEnable(GL_BLEND);
+#        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+#        
+#        glEnableClientState(GL_VERTEX_ARRAY);
+#        glColor4f(0.8, 0.6, 0.5, 0.4);
+#        glNormal3d(0,0,1);
+#        glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_triangles->ptr());
+#        glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
+#        glDisableClientState(GL_VERTEX_ARRAY);
+#        
+#        # we need depth test for grid, otherwise it would disappear when looking
+#        # the object from below
+#        glEnable(GL_DEPTH_TEST);
+#    
+#        # draw grid
+#        glLineWidth(3);
+#        glColor4f(0.2, 0.2, 0.2, 0.4);
+#        glEnableClientState(GL_VERTEX_ARRAY);
+#        glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_grid_lines->ptr());
+#        glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
+#        glDisableClientState(GL_VERTEX_ARRAY);
+#        
+#        glDisable(GL_BLEND);
+#    }
+#    
+#    my $volumes_bb = $self->volumes_bounding_box;
+#    
+#    {
+#       # draw axes
+#        # disable depth testing so that axes are not covered by ground
+#        glDisable(GL_DEPTH_TEST);
+#        my $origin = $self->origin;
+#        my $axis_len = $self->use_plain_shader ? 0.3 * max(@{ $self->bed_bounding_box->size }) : 2 * max(@{ $volumes_bb->size });
+#        glLineWidth(2);
+#        glBegin(GL_LINES);
+#        # draw line for x axis
+#        glColor3f(1, 0, 0);
+#        glVertex3f(@$origin, $ground_z);
+#        glVertex3f($origin->x + $axis_len, $origin->y, $ground_z);  #,,
+#        # draw line for y axis
+#        glColor3f(0, 1, 0);
+#        glVertex3f(@$origin, $ground_z);
+#        glVertex3f($origin->x, $origin->y + $axis_len, $ground_z);  #++
+#        glEnd();
+#        # draw line for Z axis
+#        # (re-enable depth test so that axis is correctly shown when objects are behind it)
+#        glEnable(GL_DEPTH_TEST);
+#        glBegin(GL_LINES);
+#        glColor3f(0, 0, 1);
+#        glVertex3f(@$origin, $ground_z);
+#        glVertex3f(@$origin, $ground_z+$axis_len);
+#        glEnd();
+#    }
+#    
+#    glEnable(GL_LIGHTING);
+#    
+#    # draw objects
+#    if (! $self->use_plain_shader) {
+#        $self->draw_volumes;
+#    } elsif ($self->UseVBOs) {
+#        if ($self->enable_picking) {
+#            $self->mark_volumes_for_layer_height;
+#            $self->volumes->set_print_box($self->bed_bounding_box->x_min, $self->bed_bounding_box->y_min, 0.0, $self->bed_bounding_box->x_max, $self->bed_bounding_box->y_max, $self->{config}->get('max_print_height'));
+#            $self->volumes->check_outside_state($self->{config});
+#            # do not cull backfaces to show broken geometry, if any
+#            glDisable(GL_CULL_FACE);
+#        }
+#        $self->{plain_shader}->enable if $self->{plain_shader};
+#        $self->volumes->render_VBOs;
+#        $self->{plain_shader}->disable;
+#        glEnable(GL_CULL_FACE) if ($self->enable_picking);
+#    } else {
+#        # do not cull backfaces to show broken geometry, if any
+#        glDisable(GL_CULL_FACE) if ($self->enable_picking);
+#        $self->volumes->render_legacy;
+#        glEnable(GL_CULL_FACE) if ($self->enable_picking);
+#    }
+#
+#    if (defined $self->cutting_plane_z) {
+#        # draw cutting plane
+#        my $plane_z = $self->cutting_plane_z;
+#        my $bb = $volumes_bb;
+#        glDisable(GL_CULL_FACE);
+#        glDisable(GL_LIGHTING);
+#        glEnable(GL_BLEND);
+#        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+#        glBegin(GL_QUADS);
+#        glColor4f(0.8, 0.8, 0.8, 0.5);
+#        glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z);
+#        glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z);
+#        glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z);
+#        glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z);
+#        glEnd();
+#        glEnable(GL_CULL_FACE);
+#        glDisable(GL_BLEND);
+#        
+#        # draw cutting contours
+#        glEnableClientState(GL_VERTEX_ARRAY);
+#        glLineWidth(2);
+#        glColor3f(0, 0, 0);
+#        glVertexPointer_c(3, GL_FLOAT, 0, $self->cut_lines_vertices->ptr());
+#        glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3);
+#        glVertexPointer_c(3, GL_FLOAT, 0, 0);
+#        glDisableClientState(GL_VERTEX_ARRAY);
+#    }
+#
+#    # draw warning message
+#    $self->draw_warning;
+#    
+#    # draw gcode preview legend
+#    $self->draw_legend;
+#    
+#    $self->draw_active_object_annotations;
+#    
+#    $self->SwapBuffers();
+#}
+#
+#sub draw_volumes {
+#    # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking.
+#    my ($self, $fakecolor) = @_;
+#    
+#    # do not cull backfaces to show broken geometry, if any
+#    glDisable(GL_CULL_FACE);
+#    
+#    glEnable(GL_BLEND);
+#    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+#    
+#    glEnableClientState(GL_VERTEX_ARRAY);
+#    glEnableClientState(GL_NORMAL_ARRAY);
+#    
+#    foreach my $volume_idx (0..$#{$self->volumes}) {
+#        my $volume = $self->volumes->[$volume_idx];
+#
+#        if ($fakecolor) {
+#            # Object picking mode. Render the object with a color encoding the object index.
+#            my $r = ($volume_idx & 0x000000FF) >>  0;
+#            my $g = ($volume_idx & 0x0000FF00) >>  8;
+#            my $b = ($volume_idx & 0x00FF0000) >> 16;
+#            glColor4f($r/255.0, $g/255.0, $b/255.0, 1);
+#        } elsif ($volume->selected) {
+#            glColor4f(@{ &SELECTED_COLOR });
+#        } elsif ($volume->hover) {
+#            glColor4f(@{ &HOVER_COLOR });
+#        } else {
+#            glColor4f(@{ $volume->color });
+#        }
+#
+#        $volume->render;
+#    }
+#    glDisableClientState(GL_NORMAL_ARRAY);
+#    glDisableClientState(GL_VERTEX_ARRAY);
+#    
+#    glDisable(GL_BLEND);
+#    glEnable(GL_CULL_FACE);    
+#}
+#
+#sub mark_volumes_for_layer_height {
+#    my ($self) = @_;
+#    
+#    foreach my $volume_idx (0..$#{$self->volumes}) {
+#        my $volume = $self->volumes->[$volume_idx];
+#        my $object_id = int($volume->select_group_id / 1000000);
+#        if ($self->layer_editing_enabled && $volume->selected && $self->{layer_height_edit_shader} && 
+#            $volume->has_layer_height_texture && $object_id < $self->{print}->object_count) {
+#            $volume->set_layer_height_texture_data($self->{layer_preview_z_texture_id}, $self->{layer_height_edit_shader}->shader_program_id,
+#                $self->{print}->get_object($object_id), $self->_variable_layer_thickness_bar_mouse_cursor_z_relative, $self->{layer_height_edit_band_width});
+#        } else {
+#            $volume->reset_layer_height_texture_data();
+#        }
+#    }
+#}
+#
+#sub _load_image_set_texture {
+#    my ($self, $file_name) = @_;
+#    # Load a PNG with an alpha channel.
+#    my $img = Wx::Image->new;
+#    $img->LoadFile(Slic3r::var($file_name), wxBITMAP_TYPE_PNG);
+#    # Get RGB & alpha raw data from wxImage, interleave them into a Perl array.
+#    my @rgb = unpack 'C*', $img->GetData();
+#    my @alpha = $img->HasAlpha ? unpack 'C*', $img->GetAlpha() : (255) x (int(@rgb) / 3);
+#    my $n_pixels = int(@alpha);
+#    my @data = (0)x($n_pixels * 4);
+#    for (my $i = 0; $i < $n_pixels; $i += 1) {
+#        $data[$i*4  ] = $rgb[$i*3];
+#        $data[$i*4+1] = $rgb[$i*3+1];
+#        $data[$i*4+2] = $rgb[$i*3+2];
+#        $data[$i*4+3] = $alpha[$i];
+#    }
+#    # Initialize a raw bitmap data.
+#    my $params = {
+#        loaded => 1,
+#        valid  => $n_pixels > 0,
+#        width  => $img->GetWidth, 
+#        height => $img->GetHeight,
+#        data   => OpenGL::Array->new_list(GL_UNSIGNED_BYTE, @data),
+#        texture_id => glGenTextures_p(1)
+#    };
+#    # Create and initialize a texture with the raw data.
+#    glBindTexture(GL_TEXTURE_2D, $params->{texture_id});
+#    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+#    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+#    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
+#    glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $params->{width}, $params->{height}, 0, GL_RGBA, GL_UNSIGNED_BYTE, $params->{data}->ptr);
+#    glBindTexture(GL_TEXTURE_2D, 0);
+#    return $params;
+#}
+#
+#sub _variable_layer_thickness_load_overlay_image {
+#    my ($self) = @_;
+#    $self->{layer_preview_annotation} = $self->_load_image_set_texture('variable_layer_height_tooltip.png')
+#        if (! $self->{layer_preview_annotation}->{loaded});
+#    return $self->{layer_preview_annotation}->{valid};
+#}
+#
+#sub _variable_layer_thickness_load_reset_image {
+#    my ($self) = @_;
+#    $self->{layer_preview_reset_image} = $self->_load_image_set_texture('variable_layer_height_reset.png')
+#        if (! $self->{layer_preview_reset_image}->{loaded});
+#    return $self->{layer_preview_reset_image}->{valid};
+#}
+#
+## Paint the tooltip.
+#sub _render_image {
+#    my ($self, $image, $l, $r, $b, $t) = @_;
+#    $self->_render_texture($image->{texture_id}, $l, $r, $b, $t);
+#}
+#
+#sub _render_texture {
+#    my ($self, $tex_id, $l, $r, $b, $t) = @_;
+#    
+#    glColor4f(1.,1.,1.,1.);
+#    glDisable(GL_LIGHTING);
+#    glEnable(GL_BLEND);
+#    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+#    glEnable(GL_TEXTURE_2D);
+#    glBindTexture(GL_TEXTURE_2D, $tex_id);
+#    glBegin(GL_QUADS);
+#    glTexCoord2d(0.,1.); glVertex3f($l, $b, 0);
+#    glTexCoord2d(1.,1.); glVertex3f($r, $b, 0);
+#    glTexCoord2d(1.,0.); glVertex3f($r, $t, 0);
+#    glTexCoord2d(0.,0.); glVertex3f($l, $t, 0);
+#    glEnd();
+#    glBindTexture(GL_TEXTURE_2D, 0);
+#    glDisable(GL_TEXTURE_2D);
+#    glDisable(GL_BLEND);
+#    glEnable(GL_LIGHTING);
+#}
+#
+#sub draw_active_object_annotations {
+#    # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking.
+#    my ($self) = @_;
+#
+#    return if (! $self->{layer_height_edit_shader} || ! $self->layer_editing_enabled);
+#
+#    # Find the selected volume, over which the layer editing is active.
+#    my $volume;
+#    foreach my $volume_idx (0..$#{$self->volumes}) {
+#        my $v = $self->volumes->[$volume_idx];
+#        if ($v->selected && $v->has_layer_height_texture) {
+#            $volume = $v;
+#            last;
+#        }
+#    }
+#    return if (! $volume);
+#    
+#    # If the active object was not allocated at the Print, go away. This should only be a momentary case between an object addition / deletion
+#    # and an update by Platter::async_apply_config.
+#    my $object_idx = int($volume->select_group_id / 1000000);
+#    return if $object_idx >= $self->{print}->object_count;
+#
+#    # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth), 
+#    # where x, y is the window size divided by $self->_zoom.
+#    my ($bar_left, $bar_bottom, $bar_right, $bar_top) = $self->_variable_layer_thickness_bar_rect_viewport;
+#    my ($reset_left, $reset_bottom, $reset_right, $reset_top) = $self->_variable_layer_thickness_reset_rect_viewport;
+#    my $z_cursor_relative = $self->_variable_layer_thickness_bar_mouse_cursor_z_relative;
+#
+#    my $print_object = $self->{print}->get_object($object_idx);
+#    my $z_max = $print_object->model_object->bounding_box->z_max;
+#    
+#    $self->{layer_height_edit_shader}->enable;
+#    $self->{layer_height_edit_shader}->set_uniform('z_to_texture_row',            $volume->layer_height_texture_z_to_row_id);
+#    $self->{layer_height_edit_shader}->set_uniform('z_texture_row_to_normalized', 1. / $volume->layer_height_texture_height);
+#    $self->{layer_height_edit_shader}->set_uniform('z_cursor',                    $z_max * $z_cursor_relative);
+#    $self->{layer_height_edit_shader}->set_uniform('z_cursor_band_width',         $self->{layer_height_edit_band_width});
+#    glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id});
+#    glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $volume->layer_height_texture_width, $volume->layer_height_texture_height, 
+#        0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+#    glTexImage2D_c(GL_TEXTURE_2D, 1, GL_RGBA8, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2,
+#        0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+#    glTexSubImage2D_c(GL_TEXTURE_2D, 0, 0, 0, $volume->layer_height_texture_width, $volume->layer_height_texture_height,
+#        GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level0);
+#    glTexSubImage2D_c(GL_TEXTURE_2D, 1, 0, 0, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2,
+#        GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level1);
+#    
+#    # Render the color bar.
+#    glDisable(GL_DEPTH_TEST);
+#    # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth), 
+#    # where x, y is the window size divided by $self->_zoom.
+#    glPushMatrix();
+#    glLoadIdentity();
+#    # Paint the overlay.
+#    glBegin(GL_QUADS);
+#    glVertex3f($bar_left,  $bar_bottom, 0);
+#    glVertex3f($bar_right, $bar_bottom, 0);
+#    glVertex3f($bar_right, $bar_top, $z_max);
+#    glVertex3f($bar_left,  $bar_top, $z_max);
+#    glEnd();
+#    glBindTexture(GL_TEXTURE_2D, 0);
+#    $self->{layer_height_edit_shader}->disable;
+#
+#    # Paint the tooltip.
+#    if ($self->_variable_layer_thickness_load_overlay_image) 
+#        my $gap = 10/$self->_zoom;
+#        my ($l, $r, $b, $t) = ($bar_left - $self->{layer_preview_annotation}->{width}/$self->_zoom - $gap, $bar_left - $gap, $reset_bottom + $self->{layer_preview_annotation}->{height}/$self->_zoom + $gap, $reset_bottom + $gap);
+#        $self->_render_image($self->{layer_preview_annotation}, $l, $r, $t, $b);
+#    }
+#
+#    # Paint the reset button.
+#    if ($self->_variable_layer_thickness_load_reset_image) {
+#        $self->_render_image($self->{layer_preview_reset_image}, $reset_left, $reset_right, $reset_bottom, $reset_top);
+#    }
+#
+#    # Paint the graph.
+#    #FIXME show some kind of legend.
+#    my $max_z = unscale($print_object->size->z);
+#    my $profile = $print_object->model_object->layer_height_profile;
+#    my $layer_height = $print_object->config->get('layer_height');
+#    my $layer_height_max  = 10000000000.;
+#    {
+#        # Get a maximum layer height value.
+#        #FIXME This is a duplicate code of Slicing.cpp.
+#        my $nozzle_diameters  = $print_object->print->config->get('nozzle_diameter');
+#        my $layer_heights_min = $print_object->print->config->get('min_layer_height');
+#        my $layer_heights_max = $print_object->print->config->get('max_layer_height');
+#        for (my $i = 0; $i < scalar(@{$nozzle_diameters}); $i += 1) {
+#            my $lh_min = ($layer_heights_min->[$i] == 0.) ? 0.07 : max(0.01, $layer_heights_min->[$i]);
+#            my $lh_max = ($layer_heights_max->[$i] == 0.) ? (0.75 * $nozzle_diameters->[$i]) : $layer_heights_max->[$i];
+#            $layer_height_max = min($layer_height_max, max($lh_min, $lh_max));
+#        }
+#    }
+#    # Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region.
+#    $layer_height_max *= 1.12;
+#    # Baseline
+#    glColor3f(0., 0., 0.);
+#    glBegin(GL_LINE_STRIP);
+#    glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / $layer_height_max,  $bar_bottom);
+#    glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / $layer_height_max,  $bar_top);
+#    glEnd();
+#    # Curve
+#    glColor3f(0., 0., 1.);
+#    glBegin(GL_LINE_STRIP);
+#    for (my $i = 0; $i < int(@{$profile}); $i += 2) {
+#        my $z = $profile->[$i];
+#        my $h = $profile->[$i+1];
+#        glVertex3f($bar_left + $h * ($bar_right - $bar_left) / $layer_height_max,  $bar_bottom + $z * ($bar_top - $bar_bottom) / $max_z, $z);
+#    }
+#    glEnd();
+#    # Revert the matrices.
+#    glPopMatrix();
+#    glEnable(GL_DEPTH_TEST);
+#}
+#
+#sub draw_legend {
+#    my ($self) = @_;
+# 
+#    if (!$self->_legend_enabled) {
+#        return;
+#    }
+#
+#    # If the legend texture has not been loaded into the GPU, do it now.
+#    my $tex_id = Slic3r::GUI::_3DScene::finalize_legend_texture;
+#    if ($tex_id > 0)
+#    {
+#        my $tex_w = Slic3r::GUI::_3DScene::get_legend_texture_width;
+#        my $tex_h = Slic3r::GUI::_3DScene::get_legend_texture_height;
+#        if (($tex_w > 0) && ($tex_h > 0))
+#        {
+#            glDisable(GL_DEPTH_TEST);
+#            glPushMatrix();
+#            glLoadIdentity();
+#
+#            my ($cw, $ch) = $self->GetSizeWH;
+#
+#            my $l = (-0.5 * $cw) / $self->_zoom;
+#            my $t = (0.5 * $ch) / $self->_zoom;
+#            my $r = $l + $tex_w / $self->_zoom;
+#            my $b = $t - $tex_h / $self->_zoom;
+#            $self->_render_texture($tex_id, $l, $r, $b, $t);
+#
+#            glPopMatrix();
+#            glEnable(GL_DEPTH_TEST);
+#        }
+#    }
+#}
+#
+#sub draw_warning {
+#    my ($self) = @_;
+# 
+#    if (!$self->_warning_enabled) {
+#        return;
+#    }
+#
+#    # If the warning texture has not been loaded into the GPU, do it now.
+#    my $tex_id = Slic3r::GUI::_3DScene::finalize_warning_texture;
+#    if ($tex_id > 0)
+#    {
+#        my $tex_w = Slic3r::GUI::_3DScene::get_warning_texture_width;
+#        my $tex_h = Slic3r::GUI::_3DScene::get_warning_texture_height;
+#        if (($tex_w > 0) && ($tex_h > 0))
+#        {
+#            glDisable(GL_DEPTH_TEST);
+#            glPushMatrix();
+#            glLoadIdentity();
+#        
+#            my ($cw, $ch) = $self->GetSizeWH;
+#                
+#            my $l = (-0.5 * $tex_w) / $self->_zoom;
+#            my $t = (-0.5 * $ch + $tex_h) / $self->_zoom;
+#            my $r = $l + $tex_w / $self->_zoom;
+#            my $b = $t - $tex_h / $self->_zoom;
+#            $self->_render_texture($tex_id, $l, $r, $b, $t);
+#
+#            glPopMatrix();
+#            glEnable(GL_DEPTH_TEST);
+#        }
+#    }
+#}
+#
+#sub update_volumes_colors_by_extruder {
+#    my ($self, $config) = @_;    
+#    $self->volumes->update_colors_by_extruder($config);
+#}
+#
+#sub opengl_info
+#{
+#    my ($self, %params) = @_;
+#    my %tag = Slic3r::tags($params{format});
+#
+#    my $gl_version       = glGetString(GL_VERSION);
+#    my $gl_vendor        = glGetString(GL_VENDOR);
+#    my $gl_renderer      = glGetString(GL_RENDERER);
+#    my $glsl_version     = glGetString(GL_SHADING_LANGUAGE_VERSION);
+#
+#    my $out = '';
+#    $out .= "$tag{h2start}OpenGL installation$tag{h2end}$tag{eol}";
+#    $out .= "  $tag{bstart}Using POGL$tag{bend} v$OpenGL::BUILD_VERSION$tag{eol}";
+#    $out .= "  $tag{bstart}GL version:   $tag{bend}${gl_version}$tag{eol}";
+#    $out .= "  $tag{bstart}vendor:       $tag{bend}${gl_vendor}$tag{eol}";
+#    $out .= "  $tag{bstart}renderer:     $tag{bend}${gl_renderer}$tag{eol}";
+#    $out .= "  $tag{bstart}GLSL version: $tag{bend}${glsl_version}$tag{eol}";
+#
+#    # Check for other OpenGL extensions
+#    $out .= "$tag{h2start}Installed extensions (* implemented in the module):$tag{h2end}$tag{eol}";
+#    my $extensions = glGetString(GL_EXTENSIONS);
+#    my @extensions = split(' ',$extensions);
+#    foreach my $ext (sort @extensions) {
+#        my $stat = glpCheckExtension($ext);
+#        $out .= sprintf("%s ${ext}$tag{eol}", $stat?' ':'*');
+#        $out .= sprintf("    ${stat}$tag{eol}") if ($stat && $stat !~ m|^$ext |);
+#    }
+#
+#    return $out;
+#}
+#
+#sub _report_opengl_state
+#{
+#    my ($self, $comment) = @_;
+#    my $err = glGetError();
+#    return 0 if ($err == 0);
+# 
+#    # gluErrorString() hangs. Don't use it.
+##    my $errorstr = gluErrorString();
+#    my $errorstr = '';
+#    if ($err == 0x0500) {
+#        $errorstr = 'GL_INVALID_ENUM';
+#    } elsif ($err == GL_INVALID_VALUE) {
+#        $errorstr = 'GL_INVALID_VALUE';
+#    } elsif ($err == GL_INVALID_OPERATION) {
+#        $errorstr = 'GL_INVALID_OPERATION';
+#    } elsif ($err == GL_STACK_OVERFLOW) {
+#        $errorstr = 'GL_STACK_OVERFLOW';
+#    } elsif ($err == GL_OUT_OF_MEMORY) {
+#        $errorstr = 'GL_OUT_OF_MEMORY';
+#    } else {        
+#        $errorstr = 'unknown';
+#    }
+#    if (defined($comment)) {
+#        printf("OpenGL error at %s, nr %d (0x%x): %s\n", $comment, $err, $err, $errorstr);
+#    } else {
+#        printf("OpenGL error nr %d (0x%x): %s\n", $err, $err, $errorstr);
+#    }
+#}
+#
+#sub _vertex_shader_Gouraud {
+#    return <<'VERTEX';
+##version 110
+#
+##define INTENSITY_CORRECTION 0.6
+#
+#// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
+#const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+##define LIGHT_TOP_DIFFUSE    (0.8 * INTENSITY_CORRECTION)
+##define LIGHT_TOP_SPECULAR   (0.125 * INTENSITY_CORRECTION)
+##define LIGHT_TOP_SHININESS  20.0
+#
+#// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
+#const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
+##define LIGHT_FRONT_DIFFUSE  (0.3 * INTENSITY_CORRECTION)
+#//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
+#//#define LIGHT_FRONT_SHININESS 5.0
+#
+##define INTENSITY_AMBIENT    0.3
+#
+#const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+#
+#struct PrintBoxDetection
+#{
+#    vec3 min;
+#    vec3 max;
+#    // xyz contains the offset, if w == 1.0 detection needs to be performed
+#    vec4 volume_origin;
+#};
+#
+#uniform PrintBoxDetection print_box;
+#
+#// x = tainted, y = specular;
+#varying vec2 intensity;
+#
+#varying vec3 delta_box_min;
+#varying vec3 delta_box_max;
+#
+#void main()
+#{
+#    // First transform the normal into camera space and normalize the result.
+#    vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
+#    
+#    // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+#    // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
+#    float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+#
+#    intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+#    intensity.y = 0.0;
+#
+#    if (NdotL > 0.0)
+#        intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+#
+#    // Perform the same lighting calculation for the 2nd light source (no specular applied).
+#    NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+#    intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+#
+#    // compute deltas for out of print volume detection (world coordinates)
+#    if (print_box.volume_origin.w == 1.0)
+#    {
+#        vec3 v = gl_Vertex.xyz + print_box.volume_origin.xyz;
+#        delta_box_min = v - print_box.min;
+#        delta_box_max = v - print_box.max;
+#    }
+#    else
+#    {
+#        delta_box_min = ZERO;
+#        delta_box_max = ZERO;
+#    }    
+#
+#    gl_Position = ftransform();
+#} 
+#
+#VERTEX
+#}
+#
+#sub _fragment_shader_Gouraud {
+#    return <<'FRAGMENT';
+##version 110
+#
+#const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+#
+#// x = tainted, y = specular;
+#varying vec2 intensity;
+#
+#varying vec3 delta_box_min;
+#varying vec3 delta_box_max;
+#
+#uniform vec4 uniform_color;
+#
+#void main()
+#{
+#    // if the fragment is outside the print volume -> use darker color
+#    vec3 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(uniform_color.rgb, ZERO, 0.3333) : uniform_color.rgb;
+#    gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + color * intensity.x, uniform_color.a);
+#}
+#
+#FRAGMENT
+#}
+#
+#sub _vertex_shader_Phong {
+#    return <<'VERTEX';
+##version 110
+#
+#varying vec3 normal;
+#varying vec3 eye;
+#void main(void)  
+#{
+#   eye    = normalize(vec3(gl_ModelViewMatrix * gl_Vertex));
+#   normal = normalize(gl_NormalMatrix * gl_Normal);
+#   gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+#}
+#VERTEX
+#}
+#
+#sub _fragment_shader_Phong {
+#    return <<'FRAGMENT';
+##version 110
+#
+##define INTENSITY_CORRECTION 0.7
+#
+##define LIGHT_TOP_DIR        -0.6/1.31, 0.6/1.31, 1./1.31
+##define LIGHT_TOP_DIFFUSE    (0.8 * INTENSITY_CORRECTION)
+##define LIGHT_TOP_SPECULAR   (0.5 * INTENSITY_CORRECTION)
+#//#define LIGHT_TOP_SHININESS  50.
+##define LIGHT_TOP_SHININESS  10.
+#
+##define LIGHT_FRONT_DIR      1./1.43, 0.2/1.43, 1./1.43
+##define LIGHT_FRONT_DIFFUSE  (0.3 * INTENSITY_CORRECTION)
+##define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
+##define LIGHT_FRONT_SHININESS 50.
+#
+##define INTENSITY_AMBIENT    0.0
+#
+#varying vec3 normal;
+#varying vec3 eye;
+#uniform vec4 uniform_color;
+#void main() {
+# 
+#    float intensity_specular = 0.;
+#    float intensity_tainted  = 0.;
+#    float intensity = max(dot(normal,vec3(LIGHT_TOP_DIR)), 0.0);
+#    // if the vertex is lit compute the specular color
+#    if (intensity > 0.0) {
+#        intensity_tainted = LIGHT_TOP_DIFFUSE * intensity;
+#        // compute the half vector
+#        vec3 h = normalize(vec3(LIGHT_TOP_DIR) + eye);  
+#        // compute the specular term into spec
+#        intensity_specular = LIGHT_TOP_SPECULAR * pow(max(dot(h, normal), 0.0), LIGHT_TOP_SHININESS);
+#    }
+#    intensity = max(dot(normal,vec3(LIGHT_FRONT_DIR)), 0.0);
+#    // if the vertex is lit compute the specular color
+#    if (intensity > 0.0) {
+#        intensity_tainted += LIGHT_FRONT_DIFFUSE * intensity;
+#        // compute the half vector
+#//        vec3 h = normalize(vec3(LIGHT_FRONT_DIR) + eye);
+#        // compute the specular term into spec
+#//        intensity_specular += LIGHT_FRONT_SPECULAR * pow(max(dot(h,normal), 0.0), LIGHT_FRONT_SHININESS);
+#    }
+#    
+#    gl_FragColor = max(
+#        vec4(intensity_specular, intensity_specular, intensity_specular, 0.) + uniform_color * intensity_tainted, 
+#        INTENSITY_AMBIENT * uniform_color);
+#    gl_FragColor.a = uniform_color.a;
+#}
+#FRAGMENT
+#}
+#
+#sub _vertex_shader_variable_layer_height {
+#    return <<'VERTEX';
+##version 110
+#
+##define INTENSITY_CORRECTION 0.6
+#
+#const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+##define LIGHT_TOP_DIFFUSE    (0.8 * INTENSITY_CORRECTION)
+##define LIGHT_TOP_SPECULAR   (0.125 * INTENSITY_CORRECTION)
+##define LIGHT_TOP_SHININESS  20.0
+#
+#const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
+##define LIGHT_FRONT_DIFFUSE  (0.3 * INTENSITY_CORRECTION)
+#//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
+#//#define LIGHT_FRONT_SHININESS 5.0
+#
+##define INTENSITY_AMBIENT    0.3
+#
+#// x = tainted, y = specular;
+#varying vec2 intensity;
+#
+#varying float object_z;
+#
+#void main()
+#{
+#    // First transform the normal into camera space and normalize the result.
+#    vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
+#    
+#    // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+#    // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
+#    float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+#
+#    intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+#    intensity.y = 0.0;
+#
+#    if (NdotL > 0.0)
+#        intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+#
+#    // Perform the same lighting calculation for the 2nd light source (no specular)
+#    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;
+#
+#    gl_Position = ftransform();
+#} 
+#
+#VERTEX
+#}
+#
+#sub _fragment_shader_variable_layer_height {
+#    return <<'FRAGMENT';
+##version 110
+#
+##define M_PI 3.1415926535897932384626433832795
+#
+#// 2D texture (1D texture split by the rows) of color along the object Z axis.
+#uniform sampler2D z_texture;
+#// Scaling from the Z texture rows coordinate to the normalized texture row coordinate.
+#uniform float z_to_texture_row;
+#uniform float z_texture_row_to_normalized;
+#uniform float z_cursor;
+#uniform float z_cursor_band_width;
+#
+#// x = tainted, y = specular;
+#varying vec2 intensity;
+#
+#varying float object_z;
+#
+#void main()
+#{
+#    float object_z_row = z_to_texture_row * object_z;
+#    // Index of the row in the texture.
+#    float z_texture_row = floor(object_z_row);
+#    // Normalized coordinate from 0. to 1.
+#    float z_texture_col = object_z_row - z_texture_row;
+#    float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25;
+#    // Calculate level of detail from the object Z coordinate.
+#    // This makes the slowly sloping surfaces to be show with high detail (with stripes),
+#    // and the vertical surfaces to be shown with low detail (no stripes)
+#    float z_in_cells    = object_z_row * 190.;
+#    // Gradient of Z projected on the screen.
+#    float dx_vtc        = dFdx(z_in_cells);
+#    float dy_vtc        = dFdy(z_in_cells);
+#    float lod           = clamp(0.5 * log2(max(dx_vtc*dx_vtc, dy_vtc*dy_vtc)), 0., 1.);
+#    // Sample the Z texture. Texture coordinates are normalized to <0, 1>.
+#    vec4 color       =
+#        mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5    )), -10000.),
+#            texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)),  10000.), lod);
+#            
+#    // Mix the final color.
+#    gl_FragColor = 
+#        vec4(intensity.y, intensity.y, intensity.y, 1.0) +  intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend);
+#}
+#
+#FRAGMENT
+#}
+#===================================================================================================================================        
 
 # The 3D canvas to display objects and tool paths.
 package Slic3r::GUI::3DScene;
 use base qw(Slic3r::GUI::3DScene::Base);
 
-use OpenGL qw(:glconstants :gluconstants :glufunctions);
-use List::Util qw(first min max);
-use Slic3r::Geometry qw(scale unscale epsilon);
-use Slic3r::Print::State ':steps';
+#===================================================================================================================================        
+#use OpenGL qw(:glconstants :gluconstants :glufunctions);
+#use List::Util qw(first min max);
+#use Slic3r::Geometry qw(scale unscale epsilon);
+#use Slic3r::Print::State ':steps';
+#===================================================================================================================================        
 
-__PACKAGE__->mk_accessors(qw(
-    color_by
-    select_by
-    drag_by
-));
+#===================================================================================================================================        
+#__PACKAGE__->mk_accessors(qw(
+#    color_by
+#    select_by
+#    drag_by
+#));
+#===================================================================================================================================        
 
 sub new {
     my $class = shift;
     
     my $self = $class->SUPER::new(@_);
-    $self->color_by('volume');      # object | volume
-    $self->select_by('object');     # object | volume | instance
-    $self->drag_by('instance');     # object | instance
+#===================================================================================================================================        
+#    $self->color_by('volume');      # object | volume
+#    $self->select_by('object');     # object | volume | instance
+#    $self->drag_by('instance');     # object | instance
+#===================================================================================================================================        
     
     return $self;
 }
 
-sub load_object {
-    my ($self, $model, $print, $obj_idx, $instance_idxs) = @_;
-    
-    $self->SetCurrent($self->GetContext) if $self->UseVBOs;
-
-    my $model_object;
-    if ($model->isa('Slic3r::Model::Object')) {
-        $model_object = $model;
-        $model = $model_object->model;
-        $obj_idx = 0;
-    } else {
-        $model_object = $model->get_object($obj_idx);
-    }
-    
-    $instance_idxs ||= [0..$#{$model_object->instances}];
-    my $volume_indices = $self->volumes->load_object(
-        $model_object, $obj_idx, $instance_idxs, $self->color_by, $self->select_by, $self->drag_by,
-        $self->UseVBOs);
-    return @{$volume_indices};
-}
-
-# Create 3D thick extrusion lines for a skirt and brim.
-# Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes.
-sub load_print_toolpaths {
-    my ($self, $print, $colors) = @_;
-
-    $self->SetCurrent($self->GetContext) if $self->UseVBOs;
-    Slic3r::GUI::_3DScene::_load_print_toolpaths($print, $self->volumes, $colors, $self->UseVBOs)
-        if ($print->step_done(STEP_SKIRT) && $print->step_done(STEP_BRIM));
-}
-
-# Create 3D thick extrusion lines for object forming extrusions.
-# Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes,
-# one for perimeters, one for infill and one for supports.
-sub load_print_object_toolpaths {
-    my ($self, $object, $colors) = @_;
-
-    $self->SetCurrent($self->GetContext) if $self->UseVBOs;
-    Slic3r::GUI::_3DScene::_load_print_object_toolpaths($object, $self->volumes, $colors, $self->UseVBOs);
-}
-
-# Create 3D thick extrusion lines for wipe tower extrusions.
-sub load_wipe_tower_toolpaths {
-    my ($self, $print, $colors) = @_;
-
-    $self->SetCurrent($self->GetContext) if $self->UseVBOs;
-    Slic3r::GUI::_3DScene::_load_wipe_tower_toolpaths($print, $self->volumes, $colors, $self->UseVBOs)
-        if ($print->step_done(STEP_WIPE_TOWER));
-}
-
-sub load_gcode_preview {
-    my ($self, $print, $gcode_preview_data, $colors) = @_;
-
-    $self->SetCurrent($self->GetContext) if $self->UseVBOs;
-    Slic3r::GUI::_3DScene::load_gcode_preview($print, $gcode_preview_data, $self->volumes, $colors, $self->UseVBOs);
-}
-
-sub set_toolpaths_range {
-    my ($self, $min_z, $max_z) = @_;
-    $self->volumes->set_range($min_z, $max_z);
-}
-
-sub reset_legend_texture {
-    Slic3r::GUI::_3DScene::reset_legend_texture();
-}
-
-sub get_current_print_zs {
-    my ($self, $active_only) = @_;
-    return $self->volumes->get_current_print_zs($active_only);
-}
+#==============================================================================================================================
+#sub load_object {
+#    my ($self, $model, $print, $obj_idx, $instance_idxs) = @_;
+#    
+#    $self->SetCurrent($self->GetContext) if $useVBOs;
+#
+#    my $model_object;
+#    if ($model->isa('Slic3r::Model::Object')) {
+#        $model_object = $model;
+#        $model = $model_object->model;
+#        $obj_idx = 0;
+#    } else {
+#        $model_object = $model->get_object($obj_idx);
+#    }
+#    
+#    $instance_idxs ||= [0..$#{$model_object->instances}];
+#    my $volume_indices = $self->volumes->load_object(
+#        $model_object, $obj_idx, $instance_idxs, $self->color_by, $self->select_by, $self->drag_by,
+#        $self->UseVBOs);
+#    return @{$volume_indices};
+#}
+#
+## Create 3D thick extrusion lines for a skirt and brim.
+## Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes.
+#sub load_print_toolpaths {
+#    my ($self, $print, $colors) = @_;
+#
+#    $self->SetCurrent($self->GetContext) if $self->UseVBOs;
+#    Slic3r::GUI::_3DScene::_load_print_toolpaths($print, $self->volumes, $colors, $self->UseVBOs)
+#        if ($print->step_done(STEP_SKIRT) && $print->step_done(STEP_BRIM));
+#}
+#
+## Create 3D thick extrusion lines for object forming extrusions.
+## Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes,
+## one for perimeters, one for infill and one for supports.
+#sub load_print_object_toolpaths {
+#    my ($self, $object, $colors) = @_;
+#
+#    $self->SetCurrent($self->GetContext) if $self->UseVBOs;
+#    Slic3r::GUI::_3DScene::_load_print_object_toolpaths($object, $self->volumes, $colors, $self->UseVBOs);
+#}
+#
+## Create 3D thick extrusion lines for wipe tower extrusions.
+#sub load_wipe_tower_toolpaths {
+#    my ($self, $print, $colors) = @_;
+#       
+#    $self->SetCurrent($self->GetContext) if $self->UseVBOs;
+#    Slic3r::GUI::_3DScene::_load_wipe_tower_toolpaths($print, $self->volumes, $colors, $self->UseVBOs)
+#        if ($print->step_done(STEP_WIPE_TOWER));
+#}
+#
+#sub load_gcode_preview {
+#    my ($self, $print, $gcode_preview_data, $colors) = @_;
+#
+#    $self->SetCurrent($self->GetContext) if $self->UseVBOs;
+#    Slic3r::GUI::_3DScene::load_gcode_preview($print, $gcode_preview_data, $self->volumes, $colors, $self->UseVBOs);
+#}
+#
+#sub set_toolpaths_range {
+#    my ($self, $min_z, $max_z) = @_;
+#    $self->volumes->set_range($min_z, $max_z);
+#}
+#
+#sub reset_legend_texture {
+#    Slic3r::GUI::_3DScene::reset_legend_texture();
+#}
+#
+#sub get_current_print_zs {
+#    my ($self, $active_only) = @_;
+#    return $self->volumes->get_current_print_zs($active_only);
+#}
+#==============================================================================================================================
 
 1;
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/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm
index 2b93351f8..910b86dd8 100644
--- a/lib/Slic3r/GUI/MainFrame.pm
+++ b/lib/Slic3r/GUI/MainFrame.pm
@@ -28,9 +28,9 @@ our $PRESETS_CHANGED_EVENT = Wx::NewEventType;
 
 sub new {
     my ($class, %params) = @_;
-    
+        
     my $self = $class->SUPER::new(undef, -1, $Slic3r::FORK_NAME . ' - ' . $Slic3r::VERSION, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE);
-    Slic3r::GUI::set_main_frame($self);
+        Slic3r::GUI::set_main_frame($self);
     if ($^O eq 'MSWin32') {
         # Load the icon either from the exe, or from the ico file.
         my $iconfile = Slic3r::decode_path($FindBin::Bin) . '\slic3r.exe';
@@ -39,7 +39,7 @@ sub new {
     } else {
         $self->SetIcon(Wx::Icon->new(Slic3r::var("Slic3r_128px.png"), wxBITMAP_TYPE_PNG));        
     }
-    
+        
     # store input params
     # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
     $self->{no_controller} = $params{no_controller};
@@ -47,7 +47,7 @@ sub new {
     $self->{loaded} = 0;
     $self->{lang_ch_event} = $params{lang_ch_event};
     $self->{preferences_event} = $params{preferences_event};
-
+    
     # initialize tabpanel and menubar
     $self->_init_tabpanel;
     $self->_init_menubar;
@@ -63,7 +63,7 @@ sub new {
     $self->SetStatusBar($self->{statusbar});
     
     $self->{loaded} = 1;
-    
+        
     # initialize layout
     {
         my $sizer = Wx::BoxSizer->new(wxVERTICAL);
@@ -90,6 +90,8 @@ sub new {
         # Save the slic3r.ini. Usually the ini file is saved from "on idle" callback,
         # but in rare cases it may not have been called yet.
         wxTheApp->{app_config}->save;
+        $self->{plater}->{print} = undef if($self->{plater});
+        Slic3r::GUI::_3DScene::remove_all_canvases();
         # propagate event
         $event->Skip;
     });
diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index 911c07cbf..e08353f27 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -78,19 +78,19 @@ sub new {
     my $on_select_object = sub {
         my ($obj_idx) = @_;
         # Ignore the special objects (the wipe tower proxy and such).
-        $self->select_object((defined($obj_idx) && $obj_idx < 1000) ? $obj_idx : undef);
+        $self->select_object((defined($obj_idx) && $obj_idx >= 0 && $obj_idx < 1000) ? $obj_idx : undef);
     };
     my $on_double_click = sub {
         $self->object_settings_dialog if $self->selected_object;
     };
     my $on_right_click = sub {
-        my ($canvas, $click_pos) = @_;
-        
+        my ($canvas, $click_pos_x, $click_pos_y) = @_;
+
         my ($obj_idx, $object) = $self->selected_object;
         return if !defined $obj_idx;
         
         my $menu = $self->object_menu;
-        $canvas->PopupMenu($menu, $click_pos);
+        $canvas->PopupMenu($menu, $click_pos_x, $click_pos_y);
         $menu->Destroy;
     };
     my $on_instances_moved = sub {
@@ -105,32 +105,65 @@ sub new {
         $self->{btn_print}->Enable($enable);
         $self->{btn_send_gcode}->Enable($enable);
     };
+
+    # callback to react to gizmo scale
+    my $on_gizmo_scale_uniformly = sub {
+        my ($scale) = @_;
+
+        my ($obj_idx, $object) = $self->selected_object;
+        return if !defined $obj_idx;
+
+        my $model_object = $self->{model}->objects->[$obj_idx];
+        my $model_instance = $model_object->instances->[0];
+        
+        my $variation = $scale / $model_instance->scaling_factor;
+        #FIXME Scale the layer height profile?
+        foreach my $range (@{ $model_object->layer_height_ranges }) {
+            $range->[0] *= $variation;
+            $range->[1] *= $variation;
+        }
+        $_->set_scaling_factor($scale) for @{ $model_object->instances };
+        $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.)
+        $self->update;
+        $self->schedule_background_process;
+    };
     
     # Initialize 3D plater
     if ($Slic3r::GUI::have_OpenGL) {
         $self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{print}, $self->{config});
         $self->{preview_notebook}->AddPage($self->{canvas3D}, L('3D'));
-        $self->{canvas3D}->set_on_select_object($on_select_object);
-        $self->{canvas3D}->set_on_double_click($on_double_click);
-        $self->{canvas3D}->set_on_right_click(sub { $on_right_click->($self->{canvas3D}, @_); });
-        $self->{canvas3D}->set_on_arrange(sub { $self->arrange });
-        $self->{canvas3D}->set_on_rotate_object_left(sub { $self->rotate(-45, Z, 'relative') });
-        $self->{canvas3D}->set_on_rotate_object_right(sub { $self->rotate( 45, Z, 'relative') });
-        $self->{canvas3D}->set_on_scale_object_uniformly(sub { $self->changescale(undef) });
-        $self->{canvas3D}->set_on_increase_objects(sub { $self->increase() });
-        $self->{canvas3D}->set_on_decrease_objects(sub { $self->decrease() });
-        $self->{canvas3D}->set_on_remove_object(sub { $self->remove() });
-        $self->{canvas3D}->set_on_instances_moved($on_instances_moved);
-        $self->{canvas3D}->set_on_enable_action_buttons($enable_action_buttons);
-        $self->{canvas3D}->use_plain_shader(1);
-        $self->{canvas3D}->set_on_wipe_tower_moved(sub {
-            my ($new_pos_3f) = @_;
+        Slic3r::GUI::_3DScene::register_on_select_object_callback($self->{canvas3D}, $on_select_object);
+        Slic3r::GUI::_3DScene::register_on_double_click_callback($self->{canvas3D}, $on_double_click);
+        Slic3r::GUI::_3DScene::register_on_right_click_callback($self->{canvas3D}, sub { $on_right_click->($self->{canvas3D}, @_); });
+        Slic3r::GUI::_3DScene::register_on_arrange_callback($self->{canvas3D}, sub { $self->arrange });
+        Slic3r::GUI::_3DScene::register_on_rotate_object_left_callback($self->{canvas3D}, sub { $self->rotate(-45, Z, 'relative') });
+        Slic3r::GUI::_3DScene::register_on_rotate_object_right_callback($self->{canvas3D}, sub { $self->rotate( 45, Z, 'relative') });
+        Slic3r::GUI::_3DScene::register_on_scale_object_uniformly_callback($self->{canvas3D}, sub { $self->changescale(undef) });
+        Slic3r::GUI::_3DScene::register_on_increase_objects_callback($self->{canvas3D}, sub { $self->increase() });
+        Slic3r::GUI::_3DScene::register_on_decrease_objects_callback($self->{canvas3D}, sub { $self->decrease() });
+        Slic3r::GUI::_3DScene::register_on_remove_object_callback($self->{canvas3D}, sub { $self->remove() });
+        Slic3r::GUI::_3DScene::register_on_instance_moved_callback($self->{canvas3D}, $on_instances_moved);
+        Slic3r::GUI::_3DScene::register_on_enable_action_buttons_callback($self->{canvas3D}, $enable_action_buttons);
+        Slic3r::GUI::_3DScene::register_on_gizmo_scale_uniformly_callback($self->{canvas3D}, $on_gizmo_scale_uniformly);
+#        Slic3r::GUI::_3DScene::enable_gizmos($self->{canvas3D}, 1);
+        Slic3r::GUI::_3DScene::enable_shader($self->{canvas3D}, 1);
+        Slic3r::GUI::_3DScene::enable_force_zoom_to_bed($self->{canvas3D}, 1);
+
+        Slic3r::GUI::_3DScene::register_on_wipe_tower_moved_callback($self->{canvas3D}, sub {
+            my ($x, $y) = @_;
             my $cfg = Slic3r::Config->new;
-            $cfg->set('wipe_tower_x', $new_pos_3f->x);
-            $cfg->set('wipe_tower_y', $new_pos_3f->y);
+            $cfg->set('wipe_tower_x', $x);
+            $cfg->set('wipe_tower_y', $y);
             $self->GetFrame->{options_tabs}{print}->load_config($cfg);
         });
-        $self->{canvas3D}->set_on_model_update(sub {
+
+        Slic3r::GUI::_3DScene::register_on_model_update_callback($self->{canvas3D}, sub {
             if (wxTheApp->{app_config}->get("background_processing")) {
                 $self->schedule_background_process;
             } else {
@@ -138,9 +171,8 @@ sub new {
                 $self->{"print_info_box_show"}->(0);
             }
         });
-        $self->{canvas3D}->on_viewport_changed(sub {
-            $self->{preview3D}->canvas->set_viewport_from_scene($self->{canvas3D});
-        });
+
+        Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{canvas3D}, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{preview3D}->canvas, $self->{canvas3D}); });
     }
     
     # Initialize 2D preview canvas
@@ -154,9 +186,8 @@ sub new {
     # Initialize 3D toolpaths preview
     if ($Slic3r::GUI::have_OpenGL) {
         $self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print}, $self->{gcode_preview_data}, $self->{config});
-        $self->{preview3D}->canvas->on_viewport_changed(sub {
-            $self->{canvas3D}->set_viewport_from_scene($self->{preview3D}->canvas);
-        });
+        Slic3r::GUI::_3DScene::set_active($self->{preview3D}->canvas, 0);
+        Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{preview3D}->canvas, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{canvas3D}, $self->{preview3D}->canvas); });
         $self->{preview_notebook}->AddPage($self->{preview3D}, L('Preview'));
         $self->{preview3D_page_idx} = $self->{preview_notebook}->GetPageCount-1;
     }
@@ -171,13 +202,25 @@ sub new {
         my $preview = $self->{preview_notebook}->GetCurrentPage;
         if ($preview == $self->{preview3D})
         {
-            $self->{preview3D}->canvas->set_legend_enabled(1);
+            Slic3r::GUI::_3DScene::set_active($self->{preview3D}->canvas, 1);
+            Slic3r::GUI::_3DScene::set_active($self->{canvas3D}, 0);
+            Slic3r::GUI::_3DScene::enable_legend_texture($self->{preview3D}->canvas, 1);
             $self->{preview3D}->load_print(1);
         } else {
-            $self->{preview3D}->canvas->set_legend_enabled(0);
+            Slic3r::GUI::_3DScene::enable_legend_texture($self->{preview3D}->canvas, 0);
         }
 
-        $preview->OnActivate if $preview->can('OnActivate');        
+        if ($preview == $self->{canvas3D}) {
+            Slic3r::GUI::_3DScene::set_active($self->{canvas3D}, 1);
+            Slic3r::GUI::_3DScene::set_active($self->{preview3D}->canvas, 0);
+            if (Slic3r::GUI::_3DScene::is_reload_delayed($self->{canvas3D})) {
+                my $selections = $self->collect_selections;
+                Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections);
+                Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1);
+            }            
+        } else {
+            $preview->OnActivate if $preview->can('OnActivate');        
+        }
     });
     
     # toolbar for object manipulation
@@ -314,7 +357,7 @@ sub new {
         EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog });
         EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog });
         EVT_TOOL($self, TB_LAYER_EDITING, sub {
-            my $state = $self->{canvas3D}->layer_editing_enabled;
+            my $state = Slic3r::GUI::_3DScene::is_layers_editing_enabled($self->{canvas3D});
             $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, ! $state);
             $self->on_layer_editing_toggled(! $state);
         });
@@ -369,11 +412,11 @@ sub new {
     
     $self->{canvas}->update_bed_size;
     if ($self->{canvas3D}) {
-        $self->{canvas3D}->update_bed_size;
-        $self->{canvas3D}->zoom_to_bed;
+        Slic3r::GUI::_3DScene::set_bed_shape($self->{canvas3D}, $self->{config}->bed_shape);
+        Slic3r::GUI::_3DScene::zoom_to_bed($self->{canvas3D});
     }
     if ($self->{preview3D}) {
-        $self->{preview3D}->set_bed_shape($self->{config}->bed_shape);
+        Slic3r::GUI::_3DScene::set_bed_shape($self->{preview3D}->canvas, $self->{config}->bed_shape);
     }
     $self->update;
     
@@ -591,8 +634,8 @@ sub _on_select_preset {
 
 sub on_layer_editing_toggled {
     my ($self, $new_state) = @_;
-    $self->{canvas3D}->layer_editing_enabled($new_state);
-    if ($new_state && ! $self->{canvas3D}->layer_editing_enabled) {
+    Slic3r::GUI::_3DScene::enable_layers_editing($self->{canvas3D}, $new_state);
+    if ($new_state && ! Slic3r::GUI::_3DScene::is_layers_editing_enabled($self->{canvas3D})) {
         # Initialization of the OpenGL shaders failed. Disable the tool.
         if ($self->{htoolbar}) {
             $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0);
@@ -826,8 +869,7 @@ sub load_model_objects {
     $self->update;
     
     # zoom to objects
-    $self->{canvas3D}->zoom_to_volumes
-        if $self->{canvas3D};
+    Slic3r::GUI::_3DScene::zoom_to_volumes($self->{canvas3D}) if $self->{canvas3D};
     
     $self->{list}->Update;
     $self->{list}->Select($obj_idx[-1], 1);
@@ -1218,8 +1260,7 @@ sub async_apply_config {
     my $invalidated = $self->{print}->apply_config(wxTheApp->{preset_bundle}->full_config);
 
     # Just redraw the 3D canvas without reloading the scene.
-#    $self->{canvas3D}->Refresh if ($invalidated && $self->{canvas3D}->layer_editing_enabled);
-    $self->{canvas3D}->Refresh if ($self->{canvas3D}->layer_editing_enabled);
+    $self->{canvas3D}->Refresh if Slic3r::GUI::_3DScene::is_layers_editing_enabled($self->{canvas3D});
 
     # Hide the slicing results if the current slicing status is no more valid.    
     $self->{"print_info_box_show"}->(0) if $invalidated;
@@ -1621,6 +1662,34 @@ sub export_object_stl {
     $self->statusbar->SetStatusText(L("STL file exported to ").$output_file);
 }
 
+sub fix_through_netfabb {
+    my ($self) = @_;
+    my ($obj_idx, $object) = $self->selected_object;
+    return if !defined $obj_idx;
+    my $model_object = $self->{model}->objects->[$obj_idx];
+    my $model_fixed = Slic3r::Model->new;
+    Slic3r::GUI::fix_model_by_win10_sdk_gui($model_object, $self->{print}, $model_fixed);
+
+    my @new_obj_idx = $self->load_model_objects(@{$model_fixed->objects});
+    return if !@new_obj_idx;
+    
+    foreach my $new_obj_idx (@new_obj_idx) {
+        my $o = $self->{model}->objects->[$new_obj_idx];
+        $o->clear_instances;
+        $o->add_instance($_) for @{$model_object->instances};
+        #$o->invalidate_bounding_box;
+        
+        if ($o->volumes_count == $model_object->volumes_count) {
+            for my $i (0..($o->volumes_count-1)) {
+                $o->get_volume($i)->config->apply($model_object->get_volume($i)->config);
+            }
+        }
+        #FIXME restore volumes and their configs, layer_height_ranges, layer_height_profile, layer_height_profile_valid,
+    }
+    
+    $self->remove($obj_idx);
+}
+
 sub export_amf {
     my ($self) = @_;
     return if !@{$self->{objects}};
@@ -1725,7 +1794,9 @@ sub update {
     }
 
     $self->{canvas}->reload_scene if $self->{canvas};
-    $self->{canvas3D}->reload_scene if $self->{canvas3D};
+    my $selections = $self->collect_selections;
+    Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections);
+    Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 0);
     $self->{preview3D}->reset_gcode_preview_data if $self->{preview3D};
     $self->{preview3D}->reload_print if $self->{preview3D};
 }
@@ -1780,9 +1851,8 @@ sub on_config_change {
         $self->{config}->set($opt_key, $config->get($opt_key));
         if ($opt_key eq 'bed_shape') {
             $self->{canvas}->update_bed_size;
-            $self->{canvas3D}->update_bed_size if $self->{canvas3D};
-            $self->{preview3D}->set_bed_shape($self->{config}->bed_shape)
-                if $self->{preview3D};
+            Slic3r::GUI::_3DScene::set_bed_shape($self->{canvas3D}, $self->{config}->bed_shape) if $self->{canvas3D};
+            Slic3r::GUI::_3DScene::set_bed_shape($self->{preview3D}->canvas, $self->{config}->bed_shape) if $self->{preview3D};
             $update_scheduled = 1;
         } elsif ($opt_key =~ '^wipe_tower' || $opt_key eq 'single_extruder_multi_material') {
             $update_scheduled = 1;
@@ -1801,10 +1871,10 @@ sub on_config_change {
                     $self->{"btn_layer_editing"}->Disable;
                     $self->{"btn_layer_editing"}->SetValue(0);
                 }
-                $self->{canvas3D}->layer_editing_enabled(0);
+                Slic3r::GUI::_3DScene::enable_layers_editing($self->{canvas3D}, 0);
                 $self->{canvas3D}->Refresh;
                 $self->{canvas3D}->Update;
-            } elsif ($self->{canvas3D}->layer_editing_allowed) {
+            } elsif (Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D})) {
                 # Want to allow the layer editing, but do it only if the OpenGL supports it.
                 if ($self->{htoolbar}) {
                     $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 1);
@@ -1836,8 +1906,8 @@ sub list_item_deselected {
     if ($self->{list}->GetFirstSelected == -1) {
         $self->select_object(undef);
         $self->{canvas}->Refresh;
-        $self->{canvas3D}->deselect_volumes if $self->{canvas3D};
-        $self->{canvas3D}->Render if $self->{canvas3D};
+        Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas3D}) if $self->{canvas3D};
+        Slic3r::GUI::_3DScene::render($self->{canvas3D}) if $self->{canvas3D};
     }
     undef $self->{_lecursor};
 }
@@ -1849,11 +1919,23 @@ sub list_item_selected {
     my $obj_idx = $event->GetIndex;
     $self->select_object($obj_idx);
     $self->{canvas}->Refresh;
-    $self->{canvas3D}->update_volumes_selection if $self->{canvas3D};
-    $self->{canvas3D}->Render if $self->{canvas3D};
+    if ($self->{canvas3D}) {
+        my $selections = $self->collect_selections;
+        Slic3r::GUI::_3DScene::update_volumes_selection($self->{canvas3D}, \@$selections);
+        Slic3r::GUI::_3DScene::render($self->{canvas3D});
+    }
     undef $self->{_lecursor};
 }
 
+sub collect_selections {
+    my ($self) = @_;
+    my $selections = [];
+    foreach my $o (@{$self->{objects}}) {
+        push(@$selections, $o->selected);
+    }            
+    return $selections;
+}
+
 sub list_item_activated {
     my ($self, $event, $obj_idx) = @_;
     
@@ -1910,7 +1992,7 @@ sub object_cut_dialog {
 	    $self->remove($obj_idx);
 	    $self->load_model_objects(grep defined($_), @new_objects);
 	    $self->arrange;
-        $self->{canvas3D}->zoom_to_volumes if $self->{canvas3D};
+        Slic3r::GUI::_3DScene::zoom_to_volumes($self->{canvas3D}) if $self->{canvas3D};
 	}
 }
 
@@ -1946,7 +2028,9 @@ sub object_settings_dialog {
         $self->{print}->reload_object($obj_idx);
         $self->schedule_background_process;
         $self->{canvas}->reload_scene if $self->{canvas};
-        $self->{canvas3D}->reload_scene if $self->{canvas3D};
+        my $selections = $self->collect_selections;
+        Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections);
+        Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 0);
     } else {
         $self->resume_background_process;
     }
@@ -1959,7 +2043,7 @@ sub object_list_changed {
         
     # Enable/disable buttons depending on whether there are any objects on the platter.
     my $have_objects = @{$self->{objects}} ? 1 : 0;
-    my $variable_layer_height_allowed = $self->{config}->variable_layer_height && $self->{canvas3D}->layer_editing_allowed;
+    my $variable_layer_height_allowed = $self->{config}->variable_layer_height && Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D});
     if ($self->{htoolbar}) {
         # On OSX or Linux
         $self->{htoolbar}->EnableTool($_, $have_objects)
@@ -1974,7 +2058,7 @@ sub object_list_changed {
     }
 
     my $export_in_progress = $self->{export_gcode_output_file} || $self->{send_gcode_file};
-    my $model_fits = $self->{canvas3D} ? $self->{canvas3D}->volumes->check_outside_state($self->{config}) : 1;
+    my $model_fits = $self->{canvas3D} ? Slic3r::GUI::_3DScene::check_volumes_outside_state($self->{canvas3D}, $self->{config}) : 1;
     my $method = ($have_objects && ! $export_in_progress && $model_fits) ? 'Enable' : 'Disable';
     $self->{"btn_$_"}->$method
         for grep $self->{"btn_$_"}, qw(reslice export_gcode print send_gcode);
@@ -2179,6 +2263,11 @@ sub object_menu {
     $frame->_append_menu_item($menu, L("Export object as STL…"), L('Export this single object as STL file'), sub {
         $self->export_object_stl;
     }, undef, 'brick_go.png');
+    if (Slic3r::GUI::is_windows10) {
+        $frame->_append_menu_item($menu, L("Fix STL through Netfabb"), L('Fix the model by sending it to a Netfabb cloud service through Windows 10 API'), sub {
+            $self->fix_through_netfabb;
+        }, undef, 'brick_go.png');
+    }
     
     return $menu;
 }
@@ -2189,11 +2278,11 @@ sub select_view {
     my $idx_page = $self->{preview_notebook}->GetSelection;
     my $page = ($idx_page == &Wx::wxNOT_FOUND) ? L('3D') : $self->{preview_notebook}->GetPageText($idx_page);
     if ($page eq L('Preview')) {
-        $self->{preview3D}->canvas->select_view($direction);
-        $self->{canvas3D}->set_viewport_from_scene($self->{preview3D}->canvas);
+        Slic3r::GUI::_3DScene::select_view($self->{preview3D}->canvas, $direction);
+        Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{canvas3D}, $self->{preview3D}->canvas);
     } else {
-        $self->{canvas3D}->select_view($direction);
-        $self->{preview3D}->canvas->set_viewport_from_scene($self->{canvas3D});
+        Slic3r::GUI::_3DScene::select_view($self->{canvas3D}, $direction);
+        Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{preview3D}->canvas, $self->{canvas3D});
     }
 }
 
diff --git a/lib/Slic3r/GUI/Plater/2D.pm b/lib/Slic3r/GUI/Plater/2D.pm
index 7beba9299..ad8f54ddb 100644
--- a/lib/Slic3r/GUI/Plater/2D.pm
+++ b/lib/Slic3r/GUI/Plater/2D.pm
@@ -222,7 +222,7 @@ sub mouse_event {
                         ];
                         $self->{drag_object} = [ $obj_idx, $instance_idx ];
                     } elsif ($event->RightDown) {
-                        $self->{on_right_click}->($pos);
+                        $self->{on_right_click}->($pos->x, $pos->y);
                     }
                     
                     last OBJECTS;
diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm
index 6f77f805a..7f83e0f57 100644
--- a/lib/Slic3r/GUI/Plater/3D.pm
+++ b/lib/Slic3r/GUI/Plater/3D.pm
@@ -5,264 +5,279 @@ use utf8;
 
 use List::Util qw();
 use Wx qw(:misc :pen :brush :sizer :font :cursor :keycode wxTAB_TRAVERSAL);
-use Wx::Event qw(EVT_KEY_DOWN EVT_CHAR);
+#==============================================================================================================================
+#use Wx::Event qw(EVT_KEY_DOWN EVT_CHAR);
+#==============================================================================================================================
 use base qw(Slic3r::GUI::3DScene Class::Accessor);
 
-use Wx::Locale gettext => 'L';
-
-__PACKAGE__->mk_accessors(qw(
-    on_arrange on_rotate_object_left on_rotate_object_right on_scale_object_uniformly
-    on_remove_object on_increase_objects on_decrease_objects on_enable_action_buttons));
+#==============================================================================================================================
+#use Wx::Locale gettext => 'L';
+#
+#__PACKAGE__->mk_accessors(qw(
+#    on_arrange on_rotate_object_left on_rotate_object_right on_scale_object_uniformly
+#    on_remove_object on_increase_objects on_decrease_objects on_enable_action_buttons));
+#==============================================================================================================================
 
 sub new {
     my $class = shift;
     my ($parent, $objects, $model, $print, $config) = @_;
     
     my $self = $class->SUPER::new($parent);
-    $self->enable_picking(1);
-    $self->enable_moving(1);
-    $self->select_by('object');
-    $self->drag_by('instance');
-    
-    $self->{objects}            = $objects;
-    $self->{model}              = $model;
-    $self->{print}              = $print;
-    $self->{config}             = $config;
-    $self->{on_select_object}   = sub {};
-    $self->{on_instances_moved} = sub {};
-    $self->{on_wipe_tower_moved} = sub {};
-
-    $self->{objects_volumes_idxs} = [];
-        
-    $self->on_select(sub {
-        my ($volume_idx) = @_;
-        $self->{on_select_object}->(($volume_idx == -1) ? undef : $self->volumes->[$volume_idx]->object_idx)
-            if ($self->{on_select_object});
-    });
-    $self->on_move(sub {
-        my @volume_idxs = @_;
-        
-        my %done = ();  # prevent moving instances twice
-        my $object_moved;
-        my $wipe_tower_moved;
-        foreach my $volume_idx (@volume_idxs) {
-            my $volume = $self->volumes->[$volume_idx];
-            my $obj_idx = $volume->object_idx;
-            my $instance_idx = $volume->instance_idx;
-            next if $done{"${obj_idx}_${instance_idx}"};
-            $done{"${obj_idx}_${instance_idx}"} = 1;
-            if ($obj_idx < 1000) {
-                # Move a regular object.
-                my $model_object = $self->{model}->get_object($obj_idx);
-                $model_object
-                    ->instances->[$instance_idx]
-                    ->offset
-                    ->translate($volume->origin->x, $volume->origin->y); #))
-                $model_object->invalidate_bounding_box;
-                $object_moved = 1;
-            } elsif ($obj_idx == 1000) {
-                # Move a wipe tower proxy.
-                $wipe_tower_moved = $volume->origin;
-            }
-        }
-        
-        $self->{on_instances_moved}->()
-            if $object_moved && $self->{on_instances_moved};
-        $self->{on_wipe_tower_moved}->($wipe_tower_moved)
-            if $wipe_tower_moved && $self->{on_wipe_tower_moved};
-    });
-
-    EVT_KEY_DOWN($self, sub {
-        my ($s, $event) = @_;
-        if ($event->HasModifiers) {
-            $event->Skip;
-        } else {
-            my $key = $event->GetKeyCode;
-            if ($key == WXK_DELETE) {
-                $self->on_remove_object->() if $self->on_remove_object;
-            } else {
-                $event->Skip;
-            }
-        }
-    });
-
-    EVT_CHAR($self, sub {
-        my ($s, $event) = @_;
-        if ($event->HasModifiers) {
-            $event->Skip;
-        } else {
-            my $key = $event->GetKeyCode;
-            if ($key == ord('a')) {
-                $self->on_arrange->() if $self->on_arrange;
-            } elsif ($key == ord('l')) {
-                $self->on_rotate_object_left->() if $self->on_rotate_object_left;
-            } elsif ($key == ord('r')) {
-                $self->on_rotate_object_right->() if $self->on_rotate_object_right;
-            } elsif ($key == ord('s')) {
-                $self->on_scale_object_uniformly->() if $self->on_scale_object_uniformly;
-            } elsif ($key == ord('+')) {
-                $self->on_increase_objects->() if $self->on_increase_objects;
-            } elsif ($key == ord('-')) {
-                $self->on_decrease_objects->() if $self->on_decrease_objects;
-            } else {
-                $event->Skip;
-            }
-        }
-    });
+#==============================================================================================================================
+    Slic3r::GUI::_3DScene::enable_picking($self, 1);
+    Slic3r::GUI::_3DScene::enable_moving($self, 1);
+    Slic3r::GUI::_3DScene::set_select_by($self, 'object');
+    Slic3r::GUI::_3DScene::set_drag_by($self, 'instance');
+    Slic3r::GUI::_3DScene::set_model($self, $model);
+    Slic3r::GUI::_3DScene::set_print($self, $print);
+    Slic3r::GUI::_3DScene::set_config($self, $config);
+#    $self->enable_picking(1);
+#    $self->enable_moving(1);
+#    $self->select_by('object');
+#    $self->drag_by('instance');
+#    
+#    $self->{objects}            = $objects;
+#    $self->{model}              = $model;
+#    $self->{print}              = $print;
+#    $self->{config}             = $config;
+#    $self->{on_select_object}   = sub {};
+#    $self->{on_instances_moved} = sub {};
+#    $self->{on_wipe_tower_moved} = sub {};
+#
+#    $self->{objects_volumes_idxs} = [];
+#
+#    $self->on_select(sub {
+#        my ($volume_idx) = @_;
+#        $self->{on_select_object}->(($volume_idx == -1) ? undef : $self->volumes->[$volume_idx]->object_idx)
+#            if ($self->{on_select_object});
+#    });
+#
+#    $self->on_move(sub {
+#        my @volume_idxs = @_;
+#        my %done = ();  # prevent moving instances twice
+#        my $object_moved;
+#        my $wipe_tower_moved;
+#        foreach my $volume_idx (@volume_idxs) {
+#            my $volume = $self->volumes->[$volume_idx];
+#            my $obj_idx = $volume->object_idx;
+#            my $instance_idx = $volume->instance_idx;
+#            next if $done{"${obj_idx}_${instance_idx}"};
+#            $done{"${obj_idx}_${instance_idx}"} = 1;
+#            if ($obj_idx < 1000) {
+#                # Move a regular object.
+#                my $model_object = $self->{model}->get_object($obj_idx);
+#                $model_object
+#                    ->instances->[$instance_idx]
+#                    ->offset
+#                    ->translate($volume->origin->x, $volume->origin->y); #))
+#                $model_object->invalidate_bounding_box;
+#                $object_moved = 1;
+#            } elsif ($obj_idx == 1000) {
+#                # Move a wipe tower proxy.
+#                $wipe_tower_moved = $volume->origin;
+#            }
+#        }
+#        
+#        $self->{on_instances_moved}->()
+#            if $object_moved && $self->{on_instances_moved};
+#        $self->{on_wipe_tower_moved}->($wipe_tower_moved)
+#            if $wipe_tower_moved && $self->{on_wipe_tower_moved};
+#    });
+#
+#    EVT_KEY_DOWN($self, sub {
+#        my ($s, $event) = @_;
+#        if ($event->HasModifiers) {
+#            $event->Skip;
+#        } else {
+#            my $key = $event->GetKeyCode;
+#            if ($key == WXK_DELETE) {
+#                $self->on_remove_object->() if $self->on_remove_object;
+#            } else {
+#                $event->Skip;
+#            }
+#        }
+#    });
+#
+#    EVT_CHAR($self, sub {
+#        my ($s, $event) = @_;
+#        if ($event->HasModifiers) {
+#            $event->Skip;
+#        } else {
+#            my $key = $event->GetKeyCode;
+#            if ($key == ord('a')) {
+#                $self->on_arrange->() if $self->on_arrange;
+#            } elsif ($key == ord('l')) {
+#                $self->on_rotate_object_left->() if $self->on_rotate_object_left;
+#            } elsif ($key == ord('r')) {
+#                $self->on_rotate_object_right->() if $self->on_rotate_object_right;
+#            } elsif ($key == ord('s')) {
+#                $self->on_scale_object_uniformly->() if $self->on_scale_object_uniformly;
+#            } elsif ($key == ord('+')) {
+#                $self->on_increase_objects->() if $self->on_increase_objects;
+#            } elsif ($key == ord('-')) {
+#                $self->on_decrease_objects->() if $self->on_decrease_objects;
+#            } else {
+#                $event->Skip;
+#            }
+#        }
+#    });
+#==============================================================================================================================
     
     return $self;
 }
 
-sub set_on_select_object {
-    my ($self, $cb) = @_;
-    $self->{on_select_object} = $cb;
-}
-
-sub set_on_double_click {
-    my ($self, $cb) = @_;
-    $self->on_double_click($cb);
-}
-
-sub set_on_right_click {
-    my ($self, $cb) = @_;
-    $self->on_right_click($cb);
-}
-
-sub set_on_arrange {
-    my ($self, $cb) = @_;
-    $self->on_arrange($cb);
-}
-
-sub set_on_rotate_object_left {
-    my ($self, $cb) = @_;
-    $self->on_rotate_object_left($cb);
-}
-
-sub set_on_rotate_object_right {
-    my ($self, $cb) = @_;
-    $self->on_rotate_object_right($cb);
-}
-
-sub set_on_scale_object_uniformly {
-    my ($self, $cb) = @_;
-    $self->on_scale_object_uniformly($cb);
-}
-
-sub set_on_increase_objects {
-    my ($self, $cb) = @_;
-    $self->on_increase_objects($cb);
-}
-
-sub set_on_decrease_objects {
-    my ($self, $cb) = @_;
-    $self->on_decrease_objects($cb);
-}
-
-sub set_on_remove_object {
-    my ($self, $cb) = @_;
-    $self->on_remove_object($cb);
-}
-
-sub set_on_instances_moved {
-    my ($self, $cb) = @_;
-    $self->{on_instances_moved} = $cb;
-}
-
-sub set_on_wipe_tower_moved {
-    my ($self, $cb) = @_;
-    $self->{on_wipe_tower_moved} = $cb;
-}
-
-sub set_on_model_update {
-    my ($self, $cb) = @_;
-    $self->on_model_update($cb);
-}
-
-sub set_on_enable_action_buttons {
-    my ($self, $cb) = @_;
-    $self->on_enable_action_buttons($cb);
-}
-
-sub update_volumes_selection {
-    my ($self) = @_;
-
-    foreach my $obj_idx (0..$#{$self->{model}->objects}) {
-        if ($self->{objects}[$obj_idx]->selected) {
-            my $volume_idxs = $self->{objects_volumes_idxs}->[$obj_idx];
-            $self->select_volume($_) for @{$volume_idxs};
-        }
-    }
-}
-
-sub reload_scene {
-    my ($self, $force) = @_;
-
-    $self->reset_objects;
-    $self->update_bed_size;
-
-    if (! $self->IsShown && ! $force) {
-        $self->{reload_delayed} = 1;
-        return;
-    }
-
-    $self->{reload_delayed} = 0;
-
-    $self->{objects_volumes_idxs} = [];    
-    foreach my $obj_idx (0..$#{$self->{model}->objects}) {
-        my @volume_idxs = $self->load_object($self->{model}, $self->{print}, $obj_idx);
-        push(@{$self->{objects_volumes_idxs}}, \@volume_idxs);
-    }
-    
-    $self->update_volumes_selection;
-        
-    if (defined $self->{config}->nozzle_diameter) {
-        # Should the wipe tower be visualized?
-        my $extruders_count = scalar @{ $self->{config}->nozzle_diameter };
-        # Height of a print.
-        my $height = $self->{model}->bounding_box->z_max;
-        # Show at least a slab.
-        $height = 10 if $height < 10;
-        if ($extruders_count > 1 && $self->{config}->single_extruder_multi_material && $self->{config}->wipe_tower &&
-            ! $self->{config}->complete_objects) {
-            $self->volumes->load_wipe_tower_preview(1000, 
-                $self->{config}->wipe_tower_x, $self->{config}->wipe_tower_y, $self->{config}->wipe_tower_width,
-		#$self->{config}->wipe_tower_per_color_wipe# 15 * ($extruders_count - 1), # this is just a hack when the config parameter became obsolete
-		15 * ($extruders_count - 1),
-                $self->{model}->bounding_box->z_max, $self->{config}->wipe_tower_rotation_angle, $self->UseVBOs);
-        }
-    }
-    
-    $self->update_volumes_colors_by_extruder($self->{config});
-    
-    # checks for geometry outside the print volume to render it accordingly
-    if (scalar @{$self->volumes} > 0)
-    {
-        my $contained = $self->volumes->check_outside_state($self->{config});
-        if (!$contained) {
-            $self->set_warning_enabled(1);
-            Slic3r::GUI::_3DScene::generate_warning_texture(L("Detected object outside print volume"));
-            $self->on_enable_action_buttons->(0) if ($self->on_enable_action_buttons);
-        } else {
-            $self->set_warning_enabled(0);
-            $self->volumes->reset_outside_state();
-            Slic3r::GUI::_3DScene::reset_warning_texture();
-            $self->on_enable_action_buttons->(scalar @{$self->{model}->objects} > 0) if ($self->on_enable_action_buttons);
-        }
-    } else {
-        $self->set_warning_enabled(0);
-        Slic3r::GUI::_3DScene::reset_warning_texture();
-    }
-}
-
-sub update_bed_size {
-    my ($self) = @_;
-    $self->set_bed_shape($self->{config}->bed_shape);
-}
-
-# Called by the Platter wxNotebook when this page is activated.
-sub OnActivate {
-    my ($self) = @_;
-    $self->reload_scene(1) if ($self->{reload_delayed});
-}
+#==============================================================================================================================
+#sub set_on_select_object {
+#    my ($self, $cb) = @_;
+#    $self->{on_select_object} = $cb;
+#}
+#
+#sub set_on_double_click {
+#    my ($self, $cb) = @_;
+#    $self->on_double_click($cb);
+#}
+#
+#sub set_on_right_click {
+#    my ($self, $cb) = @_;
+#    $self->on_right_click($cb);
+#}
+#
+#sub set_on_arrange {
+#    my ($self, $cb) = @_;
+#    $self->on_arrange($cb);
+#}
+#
+#sub set_on_rotate_object_left {
+#    my ($self, $cb) = @_;
+#    $self->on_rotate_object_left($cb);
+#}
+#
+#sub set_on_rotate_object_right {
+#    my ($self, $cb) = @_;
+#    $self->on_rotate_object_right($cb);
+#}
+#
+#sub set_on_scale_object_uniformly {
+#    my ($self, $cb) = @_;
+#    $self->on_scale_object_uniformly($cb);
+#}
+#
+#sub set_on_increase_objects {
+#    my ($self, $cb) = @_;
+#    $self->on_increase_objects($cb);
+#}
+#
+#sub set_on_decrease_objects {
+#    my ($self, $cb) = @_;
+#    $self->on_decrease_objects($cb);
+#}
+#
+#sub set_on_remove_object {
+#    my ($self, $cb) = @_;
+#    $self->on_remove_object($cb);
+#}
+#
+#sub set_on_instances_moved {
+#    my ($self, $cb) = @_;
+#    $self->{on_instances_moved} = $cb;
+#}
+#
+#sub set_on_wipe_tower_moved {
+#    my ($self, $cb) = @_;
+#    $self->{on_wipe_tower_moved} = $cb;
+#}
+#
+#sub set_on_model_update {
+#    my ($self, $cb) = @_;
+#    $self->on_model_update($cb);
+#}
+#
+#sub set_on_enable_action_buttons {
+#    my ($self, $cb) = @_;
+#    $self->on_enable_action_buttons($cb);
+#}
+#
+#sub update_volumes_selection {
+#    my ($self) = @_;
+#
+#    foreach my $obj_idx (0..$#{$self->{model}->objects}) {
+#        if ($self->{objects}[$obj_idx]->selected) {
+#            my $volume_idxs = $self->{objects_volumes_idxs}->[$obj_idx];
+#            $self->select_volume($_) for @{$volume_idxs};
+#        }
+#    }
+#}
+#
+#sub reload_scene {
+#    my ($self, $force) = @_;
+#
+#    $self->reset_objects;
+#    $self->update_bed_size;
+#
+#    if (! $self->IsShown && ! $force) {
+#        $self->{reload_delayed} = 1;
+#        return;
+#    }
+#
+#    $self->{reload_delayed} = 0;
+#
+#    $self->{objects_volumes_idxs} = [];    
+#    foreach my $obj_idx (0..$#{$self->{model}->objects}) {
+#        my @volume_idxs = $self->load_object($self->{model}, $self->{print}, $obj_idx);
+#        push(@{$self->{objects_volumes_idxs}}, \@volume_idxs);
+#    }
+#    
+#    $self->update_volumes_selection;
+#        
+#    if (defined $self->{config}->nozzle_diameter) {
+#        # Should the wipe tower be visualized?
+#        my $extruders_count = scalar @{ $self->{config}->nozzle_diameter };
+#        # Height of a print.
+#        my $height = $self->{model}->bounding_box->z_max;
+#        # Show at least a slab.
+#        $height = 10 if $height < 10;
+#        if ($extruders_count > 1 && $self->{config}->single_extruder_multi_material && $self->{config}->wipe_tower &&
+#            ! $self->{config}->complete_objects) {
+#            $self->volumes->load_wipe_tower_preview(1000, 
+#                $self->{config}->wipe_tower_x, $self->{config}->wipe_tower_y, $self->{config}->wipe_tower_width,
+#		#$self->{config}->wipe_tower_per_color_wipe# 15 * ($extruders_count - 1), # this is just a hack when the config parameter became obsolete
+#		15 * ($extruders_count - 1),
+#                $self->{model}->bounding_box->z_max, $self->{config}->wipe_tower_rotation_angle, $self->UseVBOs);
+#        }
+#    }
+#
+#    $self->update_volumes_colors_by_extruder($self->{config});
+#    
+#    # checks for geometry outside the print volume to render it accordingly
+#    if (scalar @{$self->volumes} > 0)
+#    {
+#        my $contained = $self->volumes->check_outside_state($self->{config});
+#        if (!$contained) {
+#            $self->set_warning_enabled(1);
+#            Slic3r::GUI::_3DScene::generate_warning_texture(L("Detected object outside print volume"));
+#            $self->on_enable_action_buttons->(0) if ($self->on_enable_action_buttons);
+#        } else {
+#            $self->set_warning_enabled(0);
+#            $self->volumes->reset_outside_state();
+#            Slic3r::GUI::_3DScene::reset_warning_texture();
+#            $self->on_enable_action_buttons->(scalar @{$self->{model}->objects} > 0) if ($self->on_enable_action_buttons);
+#        }
+#    } else {
+#        $self->set_warning_enabled(0);
+#        Slic3r::GUI::_3DScene::reset_warning_texture();
+#    }
+#}
+#
+#sub update_bed_size {
+#    my ($self) = @_;
+#    $self->set_bed_shape($self->{config}->bed_shape);
+#}
+#
+## Called by the Platter wxNotebook when this page is activated.
+#sub OnActivate {
+#    my ($self) = @_;
+#    $self->reload_scene(1) if ($self->{reload_delayed});
+#}
+#==============================================================================================================================
 
 1;
diff --git a/lib/Slic3r/GUI/Plater/3DPreview.pm b/lib/Slic3r/GUI/Plater/3DPreview.pm
index de6c6e818..9ed2374ec 100644
--- a/lib/Slic3r/GUI/Plater/3DPreview.pm
+++ b/lib/Slic3r/GUI/Plater/3DPreview.pm
@@ -24,7 +24,7 @@ sub new {
 
     # init GUI elements
     my $canvas = Slic3r::GUI::3DScene->new($self);
-    $canvas->use_plain_shader(1);
+    Slic3r::GUI::_3DScene::enable_shader($canvas, 1);
     $self->canvas($canvas);
     my $slider_low = Wx::Slider->new(
         $self, -1,
@@ -277,8 +277,8 @@ sub new {
 
 sub reload_print {
     my ($self, $force) = @_;
-    
-    $self->canvas->reset_objects;
+
+    Slic3r::GUI::_3DScene::reset_volumes($self->canvas);
     $self->_loaded(0);
 
     if (! $self->IsShown && ! $force) {
@@ -304,7 +304,7 @@ sub refresh_print {
 sub reset_gcode_preview_data {
     my ($self) = @_;
     $self->gcode_preview_data->reset;
-    $self->canvas->reset_legend_texture();
+    Slic3r::GUI::_3DScene::reset_legend_texture();
 }
 
 sub load_print {
@@ -329,7 +329,7 @@ sub load_print {
 
     if ($n_layers == 0) {
         $self->reset_sliders;
-        $self->canvas->reset_legend_texture();
+        Slic3r::GUI::_3DScene::reset_legend_texture();
         $self->canvas->Refresh;  # clears canvas
         return;
     }
@@ -364,23 +364,25 @@ sub load_print {
 
         if ($self->gcode_preview_data->empty) {
             # load skirt and brim
-            $self->canvas->load_print_toolpaths($self->print, \@colors);
-            $self->canvas->load_wipe_tower_toolpaths($self->print, \@colors);        
+            Slic3r::GUI::_3DScene::set_print($self->canvas, $self->print);
+            Slic3r::GUI::_3DScene::load_print_toolpaths($self->canvas);
+            Slic3r::GUI::_3DScene::load_wipe_tower_toolpaths($self->canvas, \@colors);
             foreach my $object (@{$self->print->objects}) {
-                $self->canvas->load_print_object_toolpaths($object, \@colors);            
+                Slic3r::GUI::_3DScene::load_print_object_toolpaths($self->canvas, $object, \@colors);
                 # Show the objects in very transparent color.
                 #my @volume_ids = $self->canvas->load_object($object->model_object);
                 #$self->canvas->volumes->[$_]->color->[3] = 0.2 for @volume_ids;
             }
             $self->show_hide_ui_elements('simple');
-            $self->canvas->reset_legend_texture();
+            Slic3r::GUI::_3DScene::reset_legend_texture();
         } else {
-            $self->{force_sliders_full_range} = (scalar(@{$self->canvas->volumes}) == 0);
-            $self->canvas->load_gcode_preview($self->print, $self->gcode_preview_data, \@colors);
+            $self->{force_sliders_full_range} = (Slic3r::GUI::_3DScene::get_volumes_count($self->canvas) == 0);
+            Slic3r::GUI::_3DScene::set_print($self->canvas, $self->print);
+            Slic3r::GUI::_3DScene::load_gcode_preview($self->canvas, $self->gcode_preview_data, \@colors);
             $self->show_hide_ui_elements('full');
 
             # recalculates zs and update sliders accordingly
-            $self->{layers_z} = $self->canvas->get_current_print_zs(1);
+            $self->{layers_z} = Slic3r::GUI::_3DScene::get_current_print_zs($self->canvas, 1);
             $n_layers = scalar(@{$self->{layers_z}});            
             if ($n_layers == 0) {
                 # all layers filtered out
@@ -466,7 +468,7 @@ sub set_z_range
     $self->{z_label_low}->SetLabel(sprintf '%.2f', $z_low);
     $self->{z_label_high}->SetLabel(sprintf '%.2f', $z_high);
     
-    my $layers_z = $self->canvas->get_current_print_zs(0);
+    my $layers_z = Slic3r::GUI::_3DScene::get_current_print_zs($self->canvas, 0);
     for (my $i = 0; $i < scalar(@{$layers_z}); $i += 1) {
         if (($z_low - 1e-6 < @{$layers_z}[$i]) && (@{$layers_z}[$i] < $z_low + 1e-6)) {
             $self->{z_label_low_idx}->SetLabel(sprintf '%d', $i + 1);
@@ -480,7 +482,7 @@ sub set_z_range
         }
     }
 
-    $self->canvas->set_toolpaths_range($z_low - 1e-6, $z_high + 1e-6);
+    Slic3r::GUI::_3DScene::set_toolpaths_range($self->canvas, $z_low - 1e-6, $z_high + 1e-6);
     $self->canvas->Refresh if $self->IsShown;
 }
 
@@ -510,11 +512,6 @@ sub set_z_idx_high
     }
 }
 
-sub set_bed_shape {
-    my ($self, $bed_shape) = @_;
-    $self->canvas->set_bed_shape($bed_shape);
-}
-
 sub set_number_extruders {
     my ($self, $number_extruders) = @_;
     if ($self->{number_extruders} != $number_extruders) {
diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm
index 4d55e313a..35aa28818 100644
--- a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm
+++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm
@@ -9,6 +9,7 @@ use utf8;
 use Slic3r::Geometry qw(PI X);
 use Wx qw(wxTheApp :dialog :id :misc :sizer wxTAB_TRAVERSAL);
 use Wx::Event qw(EVT_CLOSE EVT_BUTTON);
+use List::Util qw(max);
 use base 'Wx::Dialog';
 
 sub new {
@@ -112,10 +113,13 @@ sub new {
     my $canvas;
     if ($Slic3r::GUI::have_OpenGL) {
         $canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self);
-        $canvas->load_object($self->{model_object}, undef, undef, [0]);
-        $canvas->set_auto_bed_shape;
+        Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $self->{model_object}, 0, [0]);
+        Slic3r::GUI::_3DScene::set_auto_bed_shape($canvas);
+        Slic3r::GUI::_3DScene::set_axes_length($canvas, 2.0 * max(@{ Slic3r::GUI::_3DScene::get_volumes_bounding_box($canvas)->size }));
         $canvas->SetSize([500,500]);
         $canvas->SetMinSize($canvas->GetSize);
+        Slic3r::GUI::_3DScene::set_config($canvas, $self->GetParent->{config});
+        Slic3r::GUI::_3DScene::enable_force_zoom_to_bed($canvas, 1);
     }
     
     $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
@@ -144,6 +148,7 @@ sub new {
         # Note that the window was already closed, so a pending update will not be executed.
         $self->{already_closed} = 1;
         $self->EndModal(wxID_OK);
+        $self->{canvas}->Destroy;
         $self->Destroy();
     });
 
@@ -151,6 +156,7 @@ sub new {
         # Note that the window was already closed, so a pending update will not be executed.
         $self->{already_closed} = 1;
         $self->EndModal(wxID_CANCEL);
+        $self->{canvas}->Destroy;
         $self->Destroy();
     });
 
@@ -241,15 +247,12 @@ sub _update {
                     for @$expolygon;
                 $expolygon->translate(map Slic3r::Geometry::scale($_), @{ $self->{model_object}->instances->[0]->offset });
             }
-            
-            $self->{canvas}->reset_objects;
-            $self->{canvas}->load_object($_, undef, undef, [0]) for @objects;
-            $self->{canvas}->SetCuttingPlane(
-                $self->{cut_options}{z},
-                [@expolygons],
-            );
-            $self->{canvas}->update_volumes_colors_by_extruder($self->GetParent->{config});
-            $self->{canvas}->Render;
+
+            Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
+            Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $_, 0, [0]) for @objects;
+            Slic3r::GUI::_3DScene::set_cutting_plane($self->{canvas}, $self->{cut_options}{z}, [@expolygons]);
+            Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
+            Slic3r::GUI::_3DScene::render($self->{canvas});
         }
     }
     
diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm
index a632edeea..1ec0ce1cb 100644
--- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm
+++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm
@@ -10,6 +10,7 @@ use File::Basename qw(basename);
 use Wx qw(:misc :sizer :treectrl :button :keycode wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxID_CANCEL wxMOD_CONTROL
     wxTheApp);
 use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED EVT_TREE_KEY_DOWN EVT_KEY_DOWN);
+use List::Util qw(max);
 use base 'Wx::Panel';
 
 use constant ICON_OBJECT        => 0;
@@ -150,19 +151,19 @@ sub new {
     my $canvas;
     if ($Slic3r::GUI::have_OpenGL) {
         $canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self);
-        $canvas->enable_picking(1);
-        $canvas->select_by('volume');
-        
-        $canvas->on_select(sub {
+        Slic3r::GUI::_3DScene::enable_picking($canvas, 1);
+        Slic3r::GUI::_3DScene::set_select_by($canvas, 'volume');
+        Slic3r::GUI::_3DScene::register_on_select_object_callback($canvas, sub {
             my ($volume_idx) = @_;
-            # convert scene volume to model object volume
-            $self->reload_tree(($volume_idx == -1) ? undef : $canvas->volumes->[$volume_idx]->volume_idx);
+            $self->reload_tree($volume_idx);
         });
-        
-        $canvas->load_object($self->{model_object}, undef, undef, [0]);
-        $canvas->set_auto_bed_shape;
+        Slic3r::GUI::_3DScene::load_model_object($canvas, $self->{model_object}, 0, [0]);
+        Slic3r::GUI::_3DScene::set_auto_bed_shape($canvas);
+        Slic3r::GUI::_3DScene::set_axes_length($canvas, 2.0 * max(@{ Slic3r::GUI::_3DScene::get_volumes_bounding_box($canvas)->size }));
         $canvas->SetSize([500,700]);
-        $canvas->update_volumes_colors_by_extruder($self->GetParent->GetParent->GetParent->{config});
+        Slic3r::GUI::_3DScene::set_config($canvas, $self->GetParent->GetParent->GetParent->{config});
+        Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($canvas);
+        Slic3r::GUI::_3DScene::enable_force_zoom_to_bed($canvas, 1);
     }
     
     $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
@@ -262,7 +263,7 @@ sub selection_changed {
     
     # deselect all meshes
     if ($self->{canvas}) {
-        $_->set_selected(0) for @{$self->{canvas}->volumes};
+        Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas});
     }
     
     # disable things as if nothing is selected
@@ -290,7 +291,7 @@ sub selection_changed {
         if ($itemData->{type} eq 'volume') {
             # select volume in 3D preview
             if ($self->{canvas}) {
-                $self->{canvas}->volumes->[ $itemData->{volume_id} ]->set_selected(1);
+                Slic3r::GUI::_3DScene::select_volume($self->{canvas}, $itemData->{volume_id});
             }
             $self->{btn_delete}->Enable;
             $self->{btn_split}->Enable;
@@ -333,7 +334,7 @@ sub selection_changed {
         $self->{settings_panel}->enable;
     }
     
-    $self->{canvas}->Render if $self->{canvas};
+    Slic3r::GUI::_3DScene::render($self->{canvas}) if $self->{canvas};
 }
 
 sub on_btn_load {
@@ -429,7 +430,7 @@ sub on_btn_move_up {
     if ($itemData && $itemData->{type} eq 'volume') {
         my $volume_id = $itemData->{volume_id};
         if ($self->{model_object}->move_volume_up($volume_id)) {
-            $self->{canvas}->volumes->move_volume_up($volume_id);
+            Slic3r::GUI::_3DScene::move_volume_up($self->{canvas}, $volume_id);
             $self->{parts_changed} = 1;
             $self->reload_tree($volume_id - 1);
         }
@@ -442,7 +443,7 @@ sub on_btn_move_down {
     if ($itemData && $itemData->{type} eq 'volume') {
         my $volume_id = $itemData->{volume_id};
         if ($self->{model_object}->move_volume_down($volume_id)) {
-            $self->{canvas}->volumes->move_volume_down($volume_id);
+            Slic3r::GUI::_3DScene::move_volume_down($self->{canvas}, $volume_id);
             $self->{parts_changed} = 1;
             $self->reload_tree($volume_id + 1);
         }
@@ -487,11 +488,11 @@ sub _parts_changed {
     
     $self->reload_tree;
     if ($self->{canvas}) {
-        $self->{canvas}->reset_objects;
-        $self->{canvas}->load_object($self->{model_object});
-        $self->{canvas}->zoom_to_volumes;
-        $self->{canvas}->update_volumes_colors_by_extruder($self->GetParent->GetParent->GetParent->{config});
-        $self->{canvas}->Render;
+        Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
+        Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $self->{model_object}, 0, [0]);
+        Slic3r::GUI::_3DScene::zoom_to_volumes($self->{canvas});
+        Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
+        Slic3r::GUI::_3DScene::render($self->{canvas});        
     }
 }
 
@@ -511,6 +512,11 @@ sub CanClose {
     return ! Slic3r::GUI::catch_error($self);
 }
 
+sub Destroy {
+    my ($self) = @_;
+    $self->{canvas}->Destroy if ($self->{canvas});
+}
+
 sub PartsChanged {
     my ($self) = @_;
     return $self->{parts_changed};
@@ -525,18 +531,18 @@ sub _update_canvas {
     my ($self) = @_;
     
     if ($self->{canvas}) {
-        $self->{canvas}->reset_objects;
-        $self->{canvas}->load_object($self->{model_object});
+        Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
+        Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $self->{model_object}, 0, [0]);
 
         # restore selection, if any
         if (my $itemData = $self->get_selection) {
             if ($itemData->{type} eq 'volume') {
-                $self->{canvas}->volumes->[ $itemData->{volume_id} ]->set_selected(1);
+                Slic3r::GUI::_3DScene::select_volume($self->{canvas}, $itemData->{volume_id});
             }
         }
-                
-        $self->{canvas}->update_volumes_colors_by_extruder($self->GetParent->GetParent->GetParent->{config});
-        $self->{canvas}->Render;
+
+        Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
+        Slic3r::GUI::_3DScene::render($self->{canvas});
     }
 }
 
@@ -558,10 +564,10 @@ sub _update {
     $self->{parts_changed} = 1;
     my @objects = ();
     push @objects, $self->{model_object};
-    $self->{canvas}->reset_objects;
-    $self->{canvas}->load_object($_, undef, [0]) for @objects;
-    $self->{canvas}->update_volumes_colors_by_extruder($self->GetParent->GetParent->GetParent->{config});
-    $self->{canvas}->Render;
+    Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
+    Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $_, 0, [0]) for @objects;
+    Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
+    Slic3r::GUI::_3DScene::render($self->{canvas});
 }
 
 1;
diff --git a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm
index 61a8f2a01..3befba708 100644
--- a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm
+++ b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm
@@ -36,6 +36,7 @@ sub new {
         wxTheApp->save_window_pos($self, "object_settings");
         
         $self->EndModal(wxID_OK);
+        $self->{parts}->Destroy;
         $self->Destroy;
     });
     
diff --git a/resources/icons/bed/mk2_bottom.png b/resources/icons/bed/mk2_bottom.png
new file mode 100644
index 000000000..d06de22eb
Binary files /dev/null and b/resources/icons/bed/mk2_bottom.png differ
diff --git a/resources/icons/bed/mk2_top.png b/resources/icons/bed/mk2_top.png
new file mode 100644
index 000000000..142050c3a
Binary files /dev/null and b/resources/icons/bed/mk2_top.png differ
diff --git a/resources/icons/bed/mk3_bottom.png b/resources/icons/bed/mk3_bottom.png
new file mode 100644
index 000000000..072c14dae
Binary files /dev/null and b/resources/icons/bed/mk3_bottom.png differ
diff --git a/resources/icons/bed/mk3_top.png b/resources/icons/bed/mk3_top.png
new file mode 100644
index 000000000..0403c2f05
Binary files /dev/null and b/resources/icons/bed/mk3_top.png differ
diff --git a/resources/icons/overlay/rotate_hover.png b/resources/icons/overlay/rotate_hover.png
new file mode 100644
index 000000000..56d4fd277
Binary files /dev/null and b/resources/icons/overlay/rotate_hover.png differ
diff --git a/resources/icons/overlay/rotate_off.png b/resources/icons/overlay/rotate_off.png
new file mode 100644
index 000000000..8491f7e43
Binary files /dev/null and b/resources/icons/overlay/rotate_off.png differ
diff --git a/resources/icons/overlay/rotate_on.png b/resources/icons/overlay/rotate_on.png
new file mode 100644
index 000000000..e2db5120c
Binary files /dev/null and b/resources/icons/overlay/rotate_on.png differ
diff --git a/resources/icons/overlay/scale_hover.png b/resources/icons/overlay/scale_hover.png
new file mode 100644
index 000000000..d620aee7a
Binary files /dev/null and b/resources/icons/overlay/scale_hover.png differ
diff --git a/resources/icons/overlay/scale_off.png b/resources/icons/overlay/scale_off.png
new file mode 100644
index 000000000..1ae999bbe
Binary files /dev/null and b/resources/icons/overlay/scale_off.png differ
diff --git a/resources/icons/overlay/scale_on.png b/resources/icons/overlay/scale_on.png
new file mode 100644
index 000000000..62e805f12
Binary files /dev/null and b/resources/icons/overlay/scale_on.png differ
diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs
new file mode 100644
index 000000000..9edc8fa76
--- /dev/null
+++ b/resources/shaders/gouraud.fs
@@ -0,0 +1,18 @@
+#version 110
+
+const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+varying vec3 delta_box_min;
+varying vec3 delta_box_max;
+
+uniform vec4 uniform_color;
+
+void main()
+{
+    // if the fragment is outside the print volume -> use darker color
+    vec3 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(uniform_color.rgb, ZERO, 0.3333) : uniform_color.rgb;
+    gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + color * intensity.x, uniform_color.a);
+}
diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs
new file mode 100644
index 000000000..22ba91a93
--- /dev/null
+++ b/resources/shaders/gouraud.vs
@@ -0,0 +1,70 @@
+#version 110
+
+#define INTENSITY_CORRECTION 0.6
+
+// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
+const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+#define LIGHT_TOP_DIFFUSE    (0.8 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SPECULAR   (0.125 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SHININESS  20.0
+
+// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
+const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
+#define LIGHT_FRONT_DIFFUSE  (0.3 * INTENSITY_CORRECTION)
+//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
+//#define LIGHT_FRONT_SHININESS 5.0
+
+#define INTENSITY_AMBIENT    0.3
+
+const vec3 ZERO = vec3(0.0, 0.0, 0.0);
+
+struct PrintBoxDetection
+{
+    vec3 min;
+    vec3 max;
+    // xyz contains the offset, if w == 1.0 detection needs to be performed
+    vec4 volume_origin;
+};
+
+uniform PrintBoxDetection print_box;
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+varying vec3 delta_box_min;
+varying vec3 delta_box_max;
+
+void main()
+{
+    // First transform the normal into camera space and normalize the result.
+    vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
+    
+    // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+    // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
+    float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+
+    intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+    intensity.y = 0.0;
+
+    if (NdotL > 0.0)
+        intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+    // Perform the same lighting calculation for the 2nd light source (no specular applied).
+    NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+    intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+
+    // compute deltas for out of print volume detection (world coordinates)
+    if (print_box.volume_origin.w == 1.0)
+    {
+        vec3 v = gl_Vertex.xyz + print_box.volume_origin.xyz;
+        delta_box_min = v - print_box.min;
+        delta_box_max = v - print_box.max;
+    }
+    else
+    {
+        delta_box_min = ZERO;
+        delta_box_max = ZERO;
+    }    
+
+    gl_Position = ftransform();
+} 
diff --git a/resources/shaders/variable_layer_height.fs b/resources/shaders/variable_layer_height.fs
new file mode 100644
index 000000000..f87e6627e
--- /dev/null
+++ b/resources/shaders/variable_layer_height.fs
@@ -0,0 +1,40 @@
+#version 110
+
+#define M_PI 3.1415926535897932384626433832795
+
+// 2D texture (1D texture split by the rows) of color along the object Z axis.
+uniform sampler2D z_texture;
+// Scaling from the Z texture rows coordinate to the normalized texture row coordinate.
+uniform float z_to_texture_row;
+uniform float z_texture_row_to_normalized;
+uniform float z_cursor;
+uniform float z_cursor_band_width;
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+varying float object_z;
+
+void main()
+{
+    float object_z_row = z_to_texture_row * object_z;
+    // Index of the row in the texture.
+    float z_texture_row = floor(object_z_row);
+    // Normalized coordinate from 0. to 1.
+    float z_texture_col = object_z_row - z_texture_row;
+    float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25;
+    // Calculate level of detail from the object Z coordinate.
+    // This makes the slowly sloping surfaces to be show with high detail (with stripes),
+    // and the vertical surfaces to be shown with low detail (no stripes)
+    float z_in_cells = object_z_row * 190.;
+    // Gradient of Z projected on the screen.
+    float dx_vtc = dFdx(z_in_cells);
+    float dy_vtc = dFdy(z_in_cells);
+    float lod = clamp(0.5 * log2(max(dx_vtc * dx_vtc, dy_vtc * dy_vtc)), 0., 1.);
+    // Sample the Z texture. Texture coordinates are normalized to <0, 1>.
+    vec4 color = mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5    )), -10000.),
+                     texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)),  10000.), lod);
+            
+    // Mix the final color.
+    gl_FragColor = vec4(intensity.y, intensity.y, intensity.y, 1.0) +  intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend);
+}
diff --git a/resources/shaders/variable_layer_height.vs b/resources/shaders/variable_layer_height.vs
new file mode 100644
index 000000000..2c918c0d4
--- /dev/null
+++ b/resources/shaders/variable_layer_height.vs
@@ -0,0 +1,46 @@
+#version 110
+
+#define INTENSITY_CORRECTION 0.6
+
+const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+#define LIGHT_TOP_DIFFUSE    (0.8 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SPECULAR   (0.125 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SHININESS  20.0
+
+const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
+#define LIGHT_FRONT_DIFFUSE  (0.3 * INTENSITY_CORRECTION)
+//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
+//#define LIGHT_FRONT_SHININESS 5.0
+
+#define INTENSITY_AMBIENT    0.3
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+varying float object_z;
+
+void main()
+{
+    // First transform the normal into camera space and normalize the result.
+    vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
+    
+    // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+    // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
+    float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+
+    intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+    intensity.y = 0.0;
+
+    if (NdotL > 0.0)
+        intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(normal, reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+    // Perform the same lighting calculation for the 2nd light source (no specular)
+    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;
+
+    gl_Position = ftransform();
+}
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
index 9c61fa729..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)
@@ -181,7 +188,15 @@ add_library(libslic3r_gui STATIC
     ${LIBDIR}/slic3r/GUI/3DScene.cpp
     ${LIBDIR}/slic3r/GUI/3DScene.hpp
     ${LIBDIR}/slic3r/GUI/GLShader.cpp
-    ${LIBDIR}/slic3r/GUI/GLShader.hpp
+    ${LIBDIR}/slic3r/GUI/GLShader.hpp    
+    ${LIBDIR}/slic3r/GUI/GLCanvas3D.hpp
+    ${LIBDIR}/slic3r/GUI/GLCanvas3D.cpp
+    ${LIBDIR}/slic3r/GUI/GLCanvas3DManager.hpp
+    ${LIBDIR}/slic3r/GUI/GLCanvas3DManager.cpp
+    ${LIBDIR}/slic3r/GUI/GLGizmo.hpp
+    ${LIBDIR}/slic3r/GUI/GLGizmo.cpp    
+    ${LIBDIR}/slic3r/GUI/GLTexture.hpp
+    ${LIBDIR}/slic3r/GUI/GLTexture.cpp    
     ${LIBDIR}/slic3r/GUI/Preferences.cpp
     ${LIBDIR}/slic3r/GUI/Preferences.hpp
     ${LIBDIR}/slic3r/GUI/Preset.cpp
@@ -232,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
@@ -336,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(
@@ -500,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})
@@ -513,6 +528,8 @@ if (WIN32)
     target_link_libraries(XS ${PERL_LIBRARY})
 endif()
 
+add_subdirectory(src/avrdude)
+
 ## REQUIRED packages
 
 # Find and configure boost
@@ -557,13 +574,13 @@ if (SLIC3R_PRUSACONTROL)
     set(wxWidgets_UseAlienWx 1)
     if (wxWidgets_UseAlienWx)
         set(AlienWx_DEBUG 1)
-        find_package(AlienWx REQUIRED COMPONENTS base core adv html)
+        find_package(AlienWx REQUIRED COMPONENTS base core adv html gl)
         include_directories(${AlienWx_INCLUDE_DIRS})
         #add_compile_options(${AlienWx_CXX_FLAGS})
         add_definitions(${AlienWx_DEFINITIONS})
         set(wxWidgets_LIBRARIES ${AlienWx_LIBRARIES})
     else ()
-        find_package(wxWidgets REQUIRED COMPONENTS base core adv html)
+        find_package(wxWidgets REQUIRED COMPONENTS base core adv html gl)
         include(${wxWidgets_USE_FILE})
     endif ()
     add_definitions(-DSLIC3R_GUI -DSLIC3R_PRUS)
diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm
index 06eb041df..a4847fb45 100644
--- a/xs/lib/Slic3r/XS.pm
+++ b/xs/lib/Slic3r/XS.pm
@@ -12,6 +12,8 @@ our $VERSION = '0.01';
 BEGIN {
     if ($^O eq 'MSWin32') {
         eval "use Wx";
+        eval "use Wx::GLCanvas";
+        eval "use Wx::GLContext";
         eval "use Wx::Html";
         eval "use Wx::Print";  # because of some Wx bug, thread creation fails if we don't have this (looks like Wx::Printout is hard-coded in some thread cleanup code)
     }
@@ -280,6 +282,7 @@ for my $class (qw(
         Slic3r::Geometry::BoundingBox
         Slic3r::Geometry::BoundingBoxf
         Slic3r::Geometry::BoundingBoxf3
+        Slic3r::GUI::_3DScene::GLShader        
         Slic3r::GUI::_3DScene::GLVolume
         Slic3r::GUI::Preset
         Slic3r::GUI::PresetCollection
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 91ba88d84..ceb968a50 100644
--- a/xs/src/libslic3r/BoundingBox.cpp
+++ b/xs/src/libslic3r/BoundingBox.cpp
@@ -222,6 +222,14 @@ BoundingBox3Base<PointClass>::center() const
 }
 template Pointf3 BoundingBox3Base<Pointf3>::center() const;
 
+template <class PointClass> coordf_t
+BoundingBox3Base<PointClass>::max_size() const
+{
+    PointClass s = size();
+    return std::max(s.x, std::max(s.y, s.z));
+}
+template coordf_t BoundingBox3Base<Pointf3>::max_size() const;
+
 // Align a coordinate to a grid. The coordinate may be negative,
 // the aligned value will never be bigger than the original one.
 static inline coord_t _align_to_grid(const coord_t coord, const coord_t spacing) {
diff --git a/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp
index 92a2bd451..5de94aa9c 100644
--- a/xs/src/libslic3r/BoundingBox.hpp
+++ b/xs/src/libslic3r/BoundingBox.hpp
@@ -94,6 +94,7 @@ public:
     void translate(const Pointf3 &pos) { this->translate(pos.x, pos.y, pos.z); }
     void offset(coordf_t delta);
     PointClass center() const;
+    coordf_t max_size() const;
 
     bool contains(const PointClass &point) const {
         return BoundingBoxBase<PointClass>::contains(point) && point.z >= this->min.z && point.z <= this->max.z;
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..263363756 100644
--- a/xs/src/libslic3r/Format/AMF.cpp
+++ b/xs/src/libslic3r/Format/AMF.cpp
@@ -13,6 +13,9 @@
 
 #include <boost/filesystem/operations.hpp>
 #include <boost/algorithm/string.hpp>
+//############################################################################################################################################
+#include <boost/nowide/fstream.hpp>
+//############################################################################################################################################
 #include <miniz/miniz_zip.h>
 
 #if 0
@@ -666,10 +669,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 8ce23b1e5..147353abd 100644
--- a/xs/src/libslic3r/Model.cpp
+++ b/xs/src/libslic3r/Model.cpp
@@ -603,7 +603,10 @@ 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 BoundingBoxf3& ModelObject::bounding_box() const
+//const BoundingBoxf3& ModelObject::bounding_box()
+//========================================================================================================
 {
     if (! m_bounding_box_valid) {
         BoundingBoxf3 raw_bbox;
diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp
index 8b63c3641..b148ec29d 100644
--- a/xs/src/libslic3r/Model.hpp
+++ b/xs/src/libslic3r/Model.hpp
@@ -103,7 +103,10 @@ 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 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.
@@ -145,8 +148,10 @@ private:
     // Parent object, owning this ModelObject.
     Model          *m_model;
     // Bounding box, cached.
-    BoundingBoxf3   m_bounding_box;
-    bool            m_bounding_box_valid;
+//========================================================================================================
+    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/Point.hpp b/xs/src/libslic3r/Point.hpp
index 6c9096a3d..a52cdceb6 100644
--- a/xs/src/libslic3r/Point.hpp
+++ b/xs/src/libslic3r/Point.hpp
@@ -238,6 +238,11 @@ inline coordf_t dot(const Pointf &v1, const Pointf &v2) { return v1.x * v2.x + v
 inline coordf_t dot(const Pointf &v) { return v.x * v.x + v.y * v.y; }
 inline double length(const Vectorf &v) { return sqrt(dot(v)); }
 inline double l2(const Vectorf &v) { return dot(v); }
+inline Vectorf normalize(const Vectorf& v)
+{
+    coordf_t len = ::sqrt(sqr(v.x) + sqr(v.y));
+    return (len != 0.0) ? 1.0 / len * v : Vectorf(0.0, 0.0);
+}
 
 class Pointf3 : public Pointf
 {
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/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp
index c5b0edbdd..db92087b7 100644
--- a/xs/src/libslic3r/Print.hpp
+++ b/xs/src/libslic3r/Print.hpp
@@ -184,6 +184,8 @@ public:
 
     void reset_layer_height_profile();
 
+    void adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action);
+
     // Collect the slicing parameters, to be used by variable layer thickness algorithm,
     // by the interactive layer height editor and by the printing process itself.
     // The slicing parameters are dependent on various configuration values
diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp
index b0341db16..ba0876a85 100644
--- a/xs/src/libslic3r/PrintObject.cpp
+++ b/xs/src/libslic3r/PrintObject.cpp
@@ -4,6 +4,7 @@
 #include "Geometry.hpp"
 #include "SupportMaterial.hpp"
 #include "Surface.hpp"
+#include "Slicing.hpp"
 
 #include <utility>
 #include <boost/log/trivial.hpp>
@@ -1961,4 +1962,12 @@ void PrintObject::reset_layer_height_profile()
     this->model_object()->layer_height_profile_valid = false;
 }
 
+void PrintObject::adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action)
+{
+    update_layer_height_profile(_model_object->layer_height_profile);
+    Slic3r::adjust_layer_height_profile(slicing_parameters(), _model_object->layer_height_profile, z, layer_thickness_delta, band_width, LayerHeightEditActionType(action));
+    _model_object->layer_height_profile_valid = true;
+    layer_height_profile_valid = false;
+}
+
 } // namespace Slic3r
diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp
index e5157741e..921841a27 100644
--- a/xs/src/libslic3r/Utils.hpp
+++ b/xs/src/libslic3r/Utils.hpp
@@ -91,10 +91,13 @@ public:
     ~PerlCallback() { this->deregister_callback(); }
     void register_callback(void *sv);
     void deregister_callback();
-    void call();
-    void call(int i);
-    void call(int i, int j);
-//    void call(const std::vector<int> &ints);
+    void call() const;
+    void call(int i) const;
+    void call(int i, int j) const;
+    void call(const std::vector<int>& ints) const;
+    void call(double d) const;
+    void call(double x, double y) const;
+    void call(bool b) const;
 private:
     void *m_callback;
 };
diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp
index 582488c5a..745d07fcd 100644
--- a/xs/src/libslic3r/utils.cpp
+++ b/xs/src/libslic3r/utils.cpp
@@ -184,7 +184,7 @@ void PerlCallback::deregister_callback()
 	}
 }
 
-void PerlCallback::call()
+void PerlCallback::call() const
 {
     if (! m_callback)
         return;
@@ -198,7 +198,7 @@ void PerlCallback::call()
     LEAVE;
 }
 
-void PerlCallback::call(int i)
+void PerlCallback::call(int i) const
 {
     if (! m_callback)
         return;
@@ -213,7 +213,7 @@ void PerlCallback::call(int i)
     LEAVE;
 }
 
-void PerlCallback::call(int i, int j)
+void PerlCallback::call(int i, int j) const
 {
     if (! m_callback)
         return;
@@ -229,8 +229,7 @@ void PerlCallback::call(int i, int j)
     LEAVE;
 }
 
-/*
-void PerlCallback::call(const std::vector<int> &ints)
+void PerlCallback::call(const std::vector<int>& ints) const
 {
     if (! m_callback)
         return;
@@ -238,16 +237,51 @@ void PerlCallback::call(const std::vector<int> &ints)
     ENTER;
     SAVETMPS;
     PUSHMARK(SP);
-    AV* av = newAV();
     for (int i : ints)
-        av_push(av, newSViv(i));
-    XPUSHs(av);
+    {
+        XPUSHs(sv_2mortal(newSViv(i)));
+    }
     PUTBACK;
     perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
     FREETMPS;
     LEAVE;
 }
-*/
+
+void PerlCallback::call(double d) const
+{
+    if (!m_callback)
+        return;
+    dSP;
+    ENTER;
+    SAVETMPS;
+    PUSHMARK(SP);
+    XPUSHs(sv_2mortal(newSVnv(d)));
+    PUTBACK;
+    perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
+    FREETMPS;
+    LEAVE;
+}
+
+void PerlCallback::call(double x, double y) const
+{
+    if (!m_callback)
+        return;
+    dSP;
+    ENTER;
+    SAVETMPS;
+    PUSHMARK(SP);
+    XPUSHs(sv_2mortal(newSVnv(x)));
+    XPUSHs(sv_2mortal(newSVnv(y)));
+    PUTBACK;
+    perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
+    FREETMPS;
+    LEAVE;
+}
+
+void PerlCallback::call(bool b) const
+{
+    call(b ? 1 : 0);
+}
 
 #ifdef WIN32
     #ifndef NOMINMAX
diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
index cb2e08e1f..1879b3082 100644
--- a/xs/src/slic3r/GUI/3DScene.cpp
+++ b/xs/src/slic3r/GUI/3DScene.cpp
@@ -1348,8 +1348,8 @@ static void point_to_indexed_vertex_array(const Point3& point,
     volume.push_triangle(idxs[0], idxs[3], idxs[4]);
 }
 
-static void thick_lines_to_verts(
-    const Lines                 &lines, 
+void _3DScene::thick_lines_to_verts(
+    const Lines                 &lines,
     const std::vector<double>   &widths,
     const std::vector<double>   &heights, 
     bool                         closed,
@@ -1359,7 +1359,7 @@ static void thick_lines_to_verts(
     thick_lines_to_indexed_vertex_array(lines, widths, heights, closed, top_z, volume.indexed_vertex_array);
 }
 
-static void thick_lines_to_verts(const Lines3& lines,
+void _3DScene::thick_lines_to_verts(const Lines3& lines,
     const std::vector<double>& widths,
     const std::vector<double>& heights,
     bool closed,
@@ -1377,7 +1377,7 @@ static void thick_point_to_verts(const Point3& point,
 }
 
 // Fill in the qverts and tverts with quads and triangles for the extrusion_path.
-static inline void extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, GLVolume &volume)
 {
     Lines               lines = extrusion_path.polyline.lines();
     std::vector<double> widths(lines.size(), extrusion_path.width);
@@ -1386,7 +1386,7 @@ static inline void extrusionentity_to_verts(const ExtrusionPath &extrusion_path,
 }
 
 // Fill in the qverts and tverts with quads and triangles for the extrusion_path.
-static inline void extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, const Point &copy, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, const Point &copy, GLVolume &volume)
 {
     Polyline            polyline = extrusion_path.polyline;
     polyline.remove_duplicate_points();
@@ -1398,7 +1398,7 @@ static inline void extrusionentity_to_verts(const ExtrusionPath &extrusion_path,
 }
 
 // Fill in the qverts and tverts with quads and triangles for the extrusion_loop.
-static inline void extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, float print_z, const Point &copy, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, float print_z, const Point &copy, GLVolume &volume)
 {
     Lines               lines;
     std::vector<double> widths;
@@ -1416,7 +1416,7 @@ static inline void extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop,
 }
 
 // Fill in the qverts and tverts with quads and triangles for the extrusion_multi_path.
-static inline void extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_multi_path, float print_z, const Point &copy, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_multi_path, float print_z, const Point &copy, GLVolume &volume)
 {
     Lines               lines;
     std::vector<double> widths;
@@ -1433,15 +1433,13 @@ static inline void extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_
     thick_lines_to_verts(lines, widths, heights, false, print_z, volume);
 }
 
-static void extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point &copy, GLVolume &volume);
-
-static inline void extrusionentity_to_verts(const ExtrusionEntityCollection &extrusion_entity_collection, float print_z, const Point &copy, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionEntityCollection &extrusion_entity_collection, float print_z, const Point &copy, GLVolume &volume)
 {
     for (const ExtrusionEntity *extrusion_entity : extrusion_entity_collection.entities)
         extrusionentity_to_verts(extrusion_entity, print_z, copy, volume);
 }
 
-static void extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point &copy, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point &copy, GLVolume &volume)
 {
     if (extrusion_entity != nullptr) {
         auto *extrusion_path = dynamic_cast<const ExtrusionPath*>(extrusion_entity);
@@ -1468,7 +1466,7 @@ static void extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, fl
     }
 }
 
-static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume)
+void _3DScene::polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume)
 {
     Lines3 lines = polyline.lines();
     std::vector<double> widths(lines.size(), width);
@@ -1476,14 +1474,14 @@ static void polyline3_to_verts(const Polyline3& polyline, double width, double h
     thick_lines_to_verts(lines, widths, heights, false, volume);
 }
 
-static void point3_to_verts(const Point3& point, double width, double height, GLVolume& volume)
+void _3DScene::point3_to_verts(const Point3& point, double width, double height, GLVolume& volume)
 {
     thick_point_to_verts(point, width, height, volume);
 }
 
-_3DScene::GCodePreviewVolumeIndex _3DScene::s_gcode_preview_volume_index;
 _3DScene::LegendTexture _3DScene::s_legend_texture;
 _3DScene::WarningTexture _3DScene::s_warning_texture;
+GUI::GLCanvas3DManager _3DScene::s_canvas_mgr;
 
 unsigned int _3DScene::TextureBase::finalize()
 {
@@ -1720,9 +1718,329 @@ bool _3DScene::LegendTexture::generate(const GCodePreviewData& preview_data, con
     return true;
 }
 
-void _3DScene::_glew_init()
-{ 
-    glewInit();
+void _3DScene::init_gl()
+{
+    s_canvas_mgr.init_gl();
+}
+
+std::string _3DScene::get_gl_info(bool format_as_html, bool extensions)
+{
+    return s_canvas_mgr.get_gl_info(format_as_html, extensions);
+}
+
+bool _3DScene::use_VBOs()
+{
+    return s_canvas_mgr.use_VBOs();
+}
+
+bool _3DScene::add_canvas(wxGLCanvas* canvas)
+{
+    return s_canvas_mgr.add(canvas);
+}
+
+bool _3DScene::remove_canvas(wxGLCanvas* canvas)
+{
+    return s_canvas_mgr.remove(canvas);
+}
+
+void _3DScene::remove_all_canvases()
+{
+    s_canvas_mgr.remove_all();
+}
+
+bool _3DScene::init(wxGLCanvas* canvas)
+{
+    return s_canvas_mgr.init(canvas);
+}
+
+void _3DScene::set_active(wxGLCanvas* canvas, bool active)
+{
+    s_canvas_mgr.set_active(canvas, active);
+}
+
+unsigned int _3DScene::get_volumes_count(wxGLCanvas* canvas)
+{
+    return s_canvas_mgr.get_volumes_count(canvas);
+}
+
+void _3DScene::reset_volumes(wxGLCanvas* canvas)
+{
+    s_canvas_mgr.reset_volumes(canvas);
+}
+
+void _3DScene::deselect_volumes(wxGLCanvas* canvas)
+{
+    s_canvas_mgr.deselect_volumes(canvas);
+}
+
+void _3DScene::select_volume(wxGLCanvas* canvas, unsigned int id)
+{
+    s_canvas_mgr.select_volume(canvas, id);
+}
+
+void _3DScene::update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections)
+{
+    s_canvas_mgr.update_volumes_selection(canvas, selections);
+}
+
+bool _3DScene::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config)
+{
+    return s_canvas_mgr.check_volumes_outside_state(canvas, config);
+}
+
+bool _3DScene::move_volume_up(wxGLCanvas* canvas, unsigned int id)
+{
+    return s_canvas_mgr.move_volume_up(canvas, id);
+}
+
+bool _3DScene::move_volume_down(wxGLCanvas* canvas, unsigned int id)
+{
+    return s_canvas_mgr.move_volume_down(canvas, id);
+}
+
+void _3DScene::set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections)
+{
+    s_canvas_mgr.set_objects_selections(canvas, selections);
+}
+
+void _3DScene::set_config(wxGLCanvas* canvas, DynamicPrintConfig* config)
+{
+    s_canvas_mgr.set_config(canvas, config);
+}
+
+void _3DScene::set_print(wxGLCanvas* canvas, Print* print)
+{
+    s_canvas_mgr.set_print(canvas, print);
+}
+
+void _3DScene::set_model(wxGLCanvas* canvas, Model* model)
+{
+    s_canvas_mgr.set_model(canvas, model);
+}
+
+void _3DScene::set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape)
+{
+    return s_canvas_mgr.set_bed_shape(canvas, shape);
+}
+
+void _3DScene::set_auto_bed_shape(wxGLCanvas* canvas)
+{
+    return s_canvas_mgr.set_auto_bed_shape(canvas);
+}
+
+BoundingBoxf3 _3DScene::get_volumes_bounding_box(wxGLCanvas* canvas)
+{
+    return s_canvas_mgr.get_volumes_bounding_box(canvas);
+}
+
+void _3DScene::set_axes_length(wxGLCanvas* canvas, float length)
+{
+    s_canvas_mgr.set_axes_length(canvas, length);
+}
+
+void _3DScene::set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons)
+{
+    return s_canvas_mgr.set_cutting_plane(canvas, z, polygons);
+}
+
+void _3DScene::set_color_by(wxGLCanvas* canvas, const std::string& value)
+{
+    return s_canvas_mgr.set_color_by(canvas, value);
+}
+
+void _3DScene::set_select_by(wxGLCanvas* canvas, const std::string& value)
+{
+    return s_canvas_mgr.set_select_by(canvas, value);
+}
+
+void _3DScene::set_drag_by(wxGLCanvas* canvas, const std::string& value)
+{
+    return s_canvas_mgr.set_drag_by(canvas, value);
+}
+
+bool _3DScene::is_layers_editing_enabled(wxGLCanvas* canvas)
+{
+    return s_canvas_mgr.is_layers_editing_enabled(canvas);
+}
+
+bool _3DScene::is_layers_editing_allowed(wxGLCanvas* canvas)
+{
+    return s_canvas_mgr.is_layers_editing_allowed(canvas);
+}
+
+bool _3DScene::is_shader_enabled(wxGLCanvas* canvas)
+{
+    return s_canvas_mgr.is_shader_enabled(canvas);
+}
+
+bool _3DScene::is_reload_delayed(wxGLCanvas* canvas)
+{
+    return s_canvas_mgr.is_reload_delayed(canvas);
+}
+
+void _3DScene::enable_layers_editing(wxGLCanvas* canvas, bool enable)
+{
+    s_canvas_mgr.enable_layers_editing(canvas, enable);
+}
+
+void _3DScene::enable_warning_texture(wxGLCanvas* canvas, bool enable)
+{
+    s_canvas_mgr.enable_warning_texture(canvas, enable);
+}
+
+void _3DScene::enable_legend_texture(wxGLCanvas* canvas, bool enable)
+{
+    s_canvas_mgr.enable_legend_texture(canvas, enable);
+}
+
+void _3DScene::enable_picking(wxGLCanvas* canvas, bool enable)
+{
+    s_canvas_mgr.enable_picking(canvas, enable);
+}
+
+void _3DScene::enable_moving(wxGLCanvas* canvas, bool enable)
+{
+    s_canvas_mgr.enable_moving(canvas, enable);
+}
+
+void _3DScene::enable_gizmos(wxGLCanvas* canvas, bool enable)
+{
+    s_canvas_mgr.enable_gizmos(canvas, enable);
+}
+
+void _3DScene::enable_shader(wxGLCanvas* canvas, bool enable)
+{
+    s_canvas_mgr.enable_shader(canvas, enable);
+}
+
+void _3DScene::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable)
+{
+    s_canvas_mgr.enable_force_zoom_to_bed(canvas, enable);
+}
+
+void _3DScene::allow_multisample(wxGLCanvas* canvas, bool allow)
+{
+    s_canvas_mgr.allow_multisample(canvas, allow);
+}
+
+void _3DScene::zoom_to_bed(wxGLCanvas* canvas)
+{
+    s_canvas_mgr.zoom_to_bed(canvas);
+}
+
+void _3DScene::zoom_to_volumes(wxGLCanvas* canvas)
+{
+    s_canvas_mgr.zoom_to_volumes(canvas);
+}
+
+void _3DScene::select_view(wxGLCanvas* canvas, const std::string& direction)
+{
+    s_canvas_mgr.select_view(canvas, direction);
+}
+
+void _3DScene::set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other)
+{
+    s_canvas_mgr.set_viewport_from_scene(canvas, other);
+}
+
+void _3DScene::update_volumes_colors_by_extruder(wxGLCanvas* canvas)
+{
+    s_canvas_mgr.update_volumes_colors_by_extruder(canvas);
+}
+
+void _3DScene::render(wxGLCanvas* canvas)
+{
+    s_canvas_mgr.render(canvas);
+}
+
+std::vector<double> _3DScene::get_current_print_zs(wxGLCanvas* canvas, bool active_only)
+{
+    return s_canvas_mgr.get_current_print_zs(canvas, active_only);
+}
+
+void _3DScene::set_toolpaths_range(wxGLCanvas* canvas, double low, double high)
+{
+    s_canvas_mgr.set_toolpaths_range(canvas, low, high);
+}
+
+void _3DScene::register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_viewport_changed_callback(canvas, callback);
+}
+
+void _3DScene::register_on_double_click_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_double_click_callback(canvas, callback);
+}
+
+void _3DScene::register_on_right_click_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_right_click_callback(canvas, callback);
+}
+
+void _3DScene::register_on_select_object_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_select_object_callback(canvas, callback);
+}
+
+void _3DScene::register_on_model_update_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_model_update_callback(canvas, callback);
+}
+
+void _3DScene::register_on_remove_object_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_remove_object_callback(canvas, callback);
+}
+
+void _3DScene::register_on_arrange_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_arrange_callback(canvas, callback);
+}
+
+void _3DScene::register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_rotate_object_left_callback(canvas, callback);
+}
+
+void _3DScene::register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_rotate_object_right_callback(canvas, callback);
+}
+
+void _3DScene::register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_scale_object_uniformly_callback(canvas, callback);
+}
+
+void _3DScene::register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_increase_objects_callback(canvas, callback);
+}
+
+void _3DScene::register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_decrease_objects_callback(canvas, callback);
+}
+
+void _3DScene::register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_instance_moved_callback(canvas, callback);
+}
+
+void _3DScene::register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_wipe_tower_moved_callback(canvas, callback);
+}
+
+void _3DScene::register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_enable_action_buttons_callback(canvas, callback);
+}
+
+void _3DScene::register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback)
+{
+    s_canvas_mgr.register_on_gizmo_scale_uniformly_callback(canvas, callback);
 }
 
 static inline int hex_digit_to_int(const char c)
@@ -1752,38 +2070,44 @@ static inline std::vector<float> parse_colors(const std::vector<std::string> &sc
     return output;
 }
 
-void _3DScene::load_gcode_preview(const Print* print, const GCodePreviewData* preview_data, GLVolumeCollection* volumes, const std::vector<std::string>& str_tool_colors, bool use_VBOs)
+std::vector<int> _3DScene::load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs)
 {
-    if ((preview_data == nullptr) || (volumes == nullptr))
-        return;
+    return s_canvas_mgr.load_object(canvas, model_object, obj_idx, instance_idxs);
+}
 
-    if (volumes->empty())
-    {
-        std::vector<float> tool_colors = parse_colors(str_tool_colors);
+std::vector<int> _3DScene::load_object(wxGLCanvas* canvas, const Model* model, int obj_idx)
+{
+    return s_canvas_mgr.load_object(canvas, model, obj_idx);
+}
 
-        s_gcode_preview_volume_index.reset();
+void _3DScene::reload_scene(wxGLCanvas* canvas, bool force)
+{
+    s_canvas_mgr.reload_scene(canvas, force);
+}
 
-        _load_gcode_extrusion_paths(*preview_data, *volumes, tool_colors, use_VBOs);
-        _load_gcode_travel_paths(*preview_data, *volumes, tool_colors, use_VBOs);
-        _load_gcode_retractions(*preview_data, *volumes, use_VBOs);
-        _load_gcode_unretractions(*preview_data, *volumes, use_VBOs);
+void _3DScene::load_print_toolpaths(wxGLCanvas* canvas)
+{
+    s_canvas_mgr.load_print_toolpaths(canvas);
+}
 
-        if (volumes->empty())
-            reset_legend_texture();
-        else
-        {
-            _generate_legend_texture(*preview_data, tool_colors);
+void _3DScene::load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& str_tool_colors)
+{
+    s_canvas_mgr.load_print_object_toolpaths(canvas, print_object, str_tool_colors);
+}
 
-            // removes empty volumes
-            volumes->volumes.erase(std::remove_if(volumes->volumes.begin(), volumes->volumes.end(),
-                [](const GLVolume *volume) { return volume->print_zs.empty(); }),
-                volumes->volumes.end());
+void _3DScene::load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors)
+{
+    s_canvas_mgr.load_wipe_tower_toolpaths(canvas, str_tool_colors);
+}
 
-            _load_shells(*print, *volumes, use_VBOs);
-        }
-    }
+void _3DScene::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors)
+{
+    s_canvas_mgr.load_gcode_preview(canvas, preview_data, str_tool_colors);
+}
 
-    _update_gcode_volumes_visibility(*preview_data, *volumes);
+void _3DScene::generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors)
+{
+    s_legend_texture.generate(preview_data, tool_colors);
 }
 
 unsigned int _3DScene::get_legend_texture_width()
@@ -1831,938 +2155,4 @@ unsigned int _3DScene::finalize_warning_texture()
     return s_warning_texture.finalize();
 }
 
-// Create 3D thick extrusion lines for a skirt and brim.
-// Adds a new Slic3r::GUI::3DScene::Volume to volumes.
-void _3DScene::_load_print_toolpaths(
-    const Print                     *print, 
-    GLVolumeCollection              *volumes,
-    const std::vector<std::string>  &tool_colors,
-    bool                             use_VBOs)
-{
-    if (!print->has_skirt() && print->config.brim_width.value == 0)
-        return;
-    
-    const float color[] = { 0.5f, 1.0f, 0.5f, 1.f }; // greenish
-
-    // number of skirt layers
-    size_t total_layer_count = 0;
-    for (const PrintObject *print_object : print->objects)
-        total_layer_count = std::max(total_layer_count, print_object->total_layer_count());
-    size_t skirt_height = print->has_infinite_skirt() ? 
-        total_layer_count :
-        std::min<size_t>(print->config.skirt_height.value, total_layer_count);
-    if (skirt_height == 0 && print->config.brim_width.value > 0)
-        skirt_height = 1;
-
-    // get first skirt_height layers (maybe this should be moved to a PrintObject method?)
-    const PrintObject *object0 = print->objects.front();
-    std::vector<float> print_zs;
-    print_zs.reserve(skirt_height * 2);
-    for (size_t i = 0; i < std::min(skirt_height, object0->layers.size()); ++ i)
-        print_zs.push_back(float(object0->layers[i]->print_z));
-    //FIXME why there are support layers?
-    for (size_t i = 0; i < std::min(skirt_height, object0->support_layers.size()); ++ i)
-        print_zs.push_back(float(object0->support_layers[i]->print_z));
-    sort_remove_duplicates(print_zs);
-    if (print_zs.size() > skirt_height)
-        print_zs.erase(print_zs.begin() + skirt_height, print_zs.end());
-    
-    volumes->volumes.emplace_back(new GLVolume(color));
-    GLVolume &volume = *volumes->volumes.back();
-    for (size_t i = 0; i < skirt_height; ++ i) {
-        volume.print_zs.push_back(print_zs[i]);
-        volume.offsets.push_back(volume.indexed_vertex_array.quad_indices.size());
-        volume.offsets.push_back(volume.indexed_vertex_array.triangle_indices.size());
-        if (i == 0)
-            extrusionentity_to_verts(print->brim, print_zs[i], Point(0, 0), volume);
-        extrusionentity_to_verts(print->skirt, print_zs[i], Point(0, 0), volume);
-    }
-    volume.bounding_box = volume.indexed_vertex_array.bounding_box();
-    volume.indexed_vertex_array.finalize_geometry(use_VBOs);
-}
-
-// Create 3D thick extrusion lines for object forming extrusions.
-// Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes,
-// one for perimeters, one for infill and one for supports.
-void _3DScene::_load_print_object_toolpaths(
-    const PrintObject              *print_object,
-    GLVolumeCollection             *volumes,
-    const std::vector<std::string> &tool_colors_str,
-    bool                            use_VBOs)
-{
-    std::vector<float> tool_colors = parse_colors(tool_colors_str);
-
-    struct Ctxt
-    {
-        const Points                *shifted_copies;
-        std::vector<const Layer*>    layers;
-        bool                         has_perimeters;
-        bool                         has_infill;
-        bool                         has_support;
-        const std::vector<float>*    tool_colors;
-
-        // Number of vertices (each vertex is 6x4=24 bytes long)
-        static const size_t          alloc_size_max    () { return 131072; } // 3.15MB
-//        static const size_t          alloc_size_max    () { return 65536; } // 1.57MB 
-//        static const size_t          alloc_size_max    () { return 32768; } // 786kB
-        static const size_t          alloc_size_reserve() { return alloc_size_max() * 2; }
-
-        static const float*          color_perimeters  () { static float color[4] = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow
-        static const float*          color_infill      () { static float color[4] = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish
-        static const float*          color_support     () { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish
-
-        // For cloring by a tool, return a parsed color.
-        bool                         color_by_tool() const { return tool_colors != nullptr; }
-        size_t                       number_tools()  const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; }
-        const float*                 color_tool(size_t tool) const { return tool_colors->data() + tool * 4; }
-        int                          volume_idx(int extruder, int feature) const 
-            { return this->color_by_tool() ? std::min<int>(this->number_tools() - 1, std::max<int>(extruder - 1, 0)) : feature; }
-    } ctxt;
-
-    ctxt.shifted_copies = &print_object->_shifted_copies;
-
-    // order layers by print_z
-    ctxt.layers.reserve(print_object->layers.size() + print_object->support_layers.size());
-    for (const Layer *layer : print_object->layers)
-        ctxt.layers.push_back(layer);
-    for (const Layer *layer : print_object->support_layers)
-        ctxt.layers.push_back(layer);
-    std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; });
-
-    // Maximum size of an allocation block: 32MB / sizeof(float)
-    ctxt.has_perimeters = print_object->state.is_done(posPerimeters);
-    ctxt.has_infill     = print_object->state.is_done(posInfill);
-    ctxt.has_support    = print_object->state.is_done(posSupportMaterial);
-    ctxt.tool_colors    = tool_colors.empty() ? nullptr : &tool_colors;
-    
-    BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start";
-
-    //FIXME Improve the heuristics for a grain size.
-    size_t          grain_size = std::max(ctxt.layers.size() / 16, size_t(1));
-    tbb::spin_mutex new_volume_mutex;
-    auto            new_volume = [volumes, &new_volume_mutex](const float *color) -> GLVolume* {
-        auto *volume = new GLVolume(color);
-        new_volume_mutex.lock();
-        volume->outside_printer_detection_enabled = false;
-        volumes->volumes.emplace_back(volume);
-        new_volume_mutex.unlock();
-        return volume;
-    };
-    const size_t   volumes_cnt_initial = volumes->volumes.size();
-    std::vector<GLVolumeCollection> volumes_per_thread(ctxt.layers.size());
-    tbb::parallel_for(
-        tbb::blocked_range<size_t>(0, ctxt.layers.size(), grain_size),
-        [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) {
-            std::vector<GLVolume*> vols;
-            if (ctxt.color_by_tool()) {
-                for (size_t i = 0; i < ctxt.number_tools(); ++ i)
-                    vols.emplace_back(new_volume(ctxt.color_tool(i)));
-            } else
-                vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) };
-            for (GLVolume *vol : vols)
-                vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
-            for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
-                const Layer *layer = ctxt.layers[idx_layer];
-                for (size_t i = 0; i < vols.size(); ++ i) {
-                    GLVolume &vol = *vols[i];
-                    if (vol.print_zs.empty() || vol.print_zs.back() != layer->print_z) {
-                        vol.print_zs.push_back(layer->print_z);
-                        vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
-                        vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
-                    }
-                }
-                for (const Point &copy: *ctxt.shifted_copies) {
-                    for (const LayerRegion *layerm : layer->regions) {
-                        if (ctxt.has_perimeters)
-                            extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, 
-                                *vols[ctxt.volume_idx(layerm->region()->config.perimeter_extruder.value, 0)]);
-                        if (ctxt.has_infill) {
-                            for (const ExtrusionEntity *ee : layerm->fills.entities) {
-                                // fill represents infill extrusions of a single island.
-                                const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
-                                if (! fill->entities.empty())
-                                    extrusionentity_to_verts(*fill, float(layer->print_z), copy, 
-                                        *vols[ctxt.volume_idx(
-                                            is_solid_infill(fill->entities.front()->role()) ? 
-                                                layerm->region()->config.solid_infill_extruder : 
-                                                layerm->region()->config.infill_extruder,
-                                        1)]);
-                            }
-                        }
-                    }
-                    if (ctxt.has_support) {
-                        const SupportLayer *support_layer = dynamic_cast<const SupportLayer*>(layer);
-                        if (support_layer) {
-                            for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities)
-                                extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, 
-                                    *vols[ctxt.volume_idx(
-                                            (extrusion_entity->role() == erSupportMaterial) ? 
-                                                support_layer->object()->config.support_material_extruder : 
-                                                support_layer->object()->config.support_material_interface_extruder,
-                                            2)]);
-                        }
-                    }
-                }
-                for (size_t i = 0; i < vols.size(); ++ i) {
-                    GLVolume &vol = *vols[i];
-                    if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) {
-                        // Store the vertex arrays and restart their containers, 
-                        vols[i] = new_volume(vol.color);
-                        GLVolume &vol_new = *vols[i];
-                        // Assign the large pre-allocated buffers to the new GLVolume.
-                        vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array);
-                        // Copy the content back to the old GLVolume.
-                        vol.indexed_vertex_array = vol_new.indexed_vertex_array;
-                        // Finalize a bounding box of the old GLVolume.
-                        vol.bounding_box = vol.indexed_vertex_array.bounding_box();
-                        // Clear the buffers, but keep them pre-allocated.
-                        vol_new.indexed_vertex_array.clear();
-                        // Just make sure that clear did not clear the reserved memory.
-                        vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
-                    }
-                }
-            }
-            for (GLVolume *vol : vols) {
-                vol->bounding_box = vol->indexed_vertex_array.bounding_box();
-                vol->indexed_vertex_array.shrink_to_fit();
-            }
-        });
-
-    BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results";
-    // Remove empty volumes from the newly added volumes.
-    volumes->volumes.erase(
-        std::remove_if(volumes->volumes.begin() + volumes_cnt_initial, volumes->volumes.end(), 
-            [](const GLVolume *volume) { return volume->empty(); }),
-        volumes->volumes.end());
-    for (size_t i = volumes_cnt_initial; i < volumes->volumes.size(); ++ i)
-        volumes->volumes[i]->indexed_vertex_array.finalize_geometry(use_VBOs);
-  
-    BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; 
-}
-
-void _3DScene::_load_wipe_tower_toolpaths(
-    const Print                    *print,
-    GLVolumeCollection             *volumes,
-    const std::vector<std::string> &tool_colors_str,
-    bool                            use_VBOs)
-{
-    if (print->m_wipe_tower_tool_changes.empty())
-        return;
-
-    std::vector<float> tool_colors = parse_colors(tool_colors_str);
-
-    struct Ctxt
-    {
-        const Print                 *print;
-        const std::vector<float>    *tool_colors;
-
-        // Number of vertices (each vertex is 6x4=24 bytes long)
-        static const size_t          alloc_size_max    () { return 131072; } // 3.15MB
-        static const size_t          alloc_size_reserve() { return alloc_size_max() * 2; }
-
-        static const float*          color_support     () { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish
-
-        // For cloring by a tool, return a parsed color.
-        bool                         color_by_tool() const { return tool_colors != nullptr; }
-        size_t                       number_tools()  const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; }
-        const float*                 color_tool(size_t tool) const { return tool_colors->data() + tool * 4; }
-        int                          volume_idx(int tool, int feature) const 
-            { return this->color_by_tool() ? std::min<int>(this->number_tools() - 1, std::max<int>(tool, 0)) : feature; }
-
-        const std::vector<WipeTower::ToolChangeResult>& tool_change(size_t idx) { 
-            return priming.empty() ? 
-                ((idx == print->m_wipe_tower_tool_changes.size()) ? final : print->m_wipe_tower_tool_changes[idx]) :
-                ((idx == 0) ? priming : (idx == print->m_wipe_tower_tool_changes.size() + 1) ? final : print->m_wipe_tower_tool_changes[idx - 1]);
-        }
-        std::vector<WipeTower::ToolChangeResult> priming;
-        std::vector<WipeTower::ToolChangeResult> final;
-    } ctxt;
-
-    ctxt.print          = print;
-    ctxt.tool_colors    = tool_colors.empty() ? nullptr : &tool_colors;
-	if (print->m_wipe_tower_priming)
-		ctxt.priming.emplace_back(*print->m_wipe_tower_priming.get());
-	if (print->m_wipe_tower_final_purge)
-		ctxt.final.emplace_back(*print->m_wipe_tower_final_purge.get());
-    
-    BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start";
-
-    //FIXME Improve the heuristics for a grain size.
-    size_t          n_items    = print->m_wipe_tower_tool_changes.size() + (ctxt.priming.empty() ? 0 : 1);
-    size_t          grain_size = std::max(n_items / 128, size_t(1));
-    tbb::spin_mutex new_volume_mutex;
-    auto            new_volume = [volumes, &new_volume_mutex](const float *color) -> GLVolume* {
-        auto *volume = new GLVolume(color);
-        new_volume_mutex.lock();
-        volume->outside_printer_detection_enabled = false;
-        volumes->volumes.emplace_back(volume);
-        new_volume_mutex.unlock();
-        return volume;
-    };
-    const size_t   volumes_cnt_initial = volumes->volumes.size();
-    std::vector<GLVolumeCollection> volumes_per_thread(n_items);
-    tbb::parallel_for(
-        tbb::blocked_range<size_t>(0, n_items, grain_size),
-        [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) {
-            // Bounding box of this slab of a wipe tower.
-            std::vector<GLVolume*> vols;
-            if (ctxt.color_by_tool()) {
-                for (size_t i = 0; i < ctxt.number_tools(); ++ i)
-                    vols.emplace_back(new_volume(ctxt.color_tool(i)));
-            } else
-                vols = { new_volume(ctxt.color_support()) };
-            for (GLVolume *volume : vols)
-                volume->indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
-            for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
-                const std::vector<WipeTower::ToolChangeResult> &layer = ctxt.tool_change(idx_layer);
-                for (size_t i = 0; i < vols.size(); ++ i) {
-                    GLVolume &vol = *vols[i];
-                    if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) {
-                        vol.print_zs.push_back(layer.front().print_z);
-                        vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
-                        vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
-                    }
-                }
-                for (const WipeTower::ToolChangeResult &extrusions : layer) {
-                    for (size_t i = 1; i < extrusions.extrusions.size();) {
-                        const WipeTower::Extrusion &e = extrusions.extrusions[i];
-                        if (e.width == 0.) {
-                            ++ i;
-                            continue;
-                        }
-                        size_t j = i + 1;
-                        if (ctxt.color_by_tool())
-                            for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++ j) ;
-                        else
-                            for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++ j) ;
-                        size_t              n_lines = j - i;
-                        Lines               lines;
-                        std::vector<double> widths;
-                        std::vector<double> heights;
-                        lines.reserve(n_lines);
-                        widths.reserve(n_lines);
-                        heights.assign(n_lines, extrusions.layer_height);
-                        for (; i < j; ++ i) {
-                            const WipeTower::Extrusion &e = extrusions.extrusions[i];
-                            assert(e.width > 0.f);
-                            const WipeTower::Extrusion &e_prev = *(&e - 1);
-                            lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y));
-                            widths.emplace_back(e.width);
-                        }
-                        thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, 
-                            *vols[ctxt.volume_idx(e.tool, 0)]);
-                    }
-                }
-            }
-            for (size_t i = 0; i < vols.size(); ++ i) {
-                GLVolume &vol = *vols[i];
-                if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) {
-                    // Store the vertex arrays and restart their containers, 
-                    vols[i] = new_volume(vol.color);
-                    GLVolume &vol_new = *vols[i];
-                    // Assign the large pre-allocated buffers to the new GLVolume.
-                    vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array);
-                    // Copy the content back to the old GLVolume.
-                    vol.indexed_vertex_array = vol_new.indexed_vertex_array;
-                    // Finalize a bounding box of the old GLVolume.
-                    vol.bounding_box = vol.indexed_vertex_array.bounding_box();
-                    // Clear the buffers, but keep them pre-allocated.
-                    vol_new.indexed_vertex_array.clear();
-                    // Just make sure that clear did not clear the reserved memory.
-                    vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
-                }
-            }
-            for (GLVolume *vol : vols) {
-                vol->bounding_box = vol->indexed_vertex_array.bounding_box();
-                vol->indexed_vertex_array.shrink_to_fit();
-            }
-        });
-
-    BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results";
-    // Remove empty volumes from the newly added volumes.
-    volumes->volumes.erase(
-        std::remove_if(volumes->volumes.begin() + volumes_cnt_initial, volumes->volumes.end(), 
-            [](const GLVolume *volume) { return volume->empty(); }),
-        volumes->volumes.end());
-    for (size_t i = volumes_cnt_initial; i < volumes->volumes.size(); ++ i)
-        volumes->volumes[i]->indexed_vertex_array.finalize_geometry(use_VBOs);
-  
-    BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; 
-}
-
-void _3DScene::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs)
-{
-    // helper functions to select data in dependence of the extrusion view type
-    struct Helper
-    {
-        static float path_filter(GCodePreviewData::Extrusion::EViewType type, const ExtrusionPath& path)
-        {
-            switch (type)
-            {
-            case GCodePreviewData::Extrusion::FeatureType:
-                return (float)path.role();
-            case GCodePreviewData::Extrusion::Height:
-                return path.height;
-            case GCodePreviewData::Extrusion::Width:
-                return path.width;
-            case GCodePreviewData::Extrusion::Feedrate:
-                return path.feedrate;
-            case GCodePreviewData::Extrusion::VolumetricRate:
-                return path.feedrate * (float)path.mm3_per_mm;
-            case GCodePreviewData::Extrusion::Tool:
-                return (float)path.extruder_id;
-            }
-
-            return 0.0f;
-        }
-
-        static GCodePreviewData::Color path_color(const GCodePreviewData& data, const std::vector<float>& tool_colors, float value)
-        {
-            switch (data.extrusion.view_type)
-            {
-            case GCodePreviewData::Extrusion::FeatureType:
-                return data.get_extrusion_role_color((ExtrusionRole)(int)value);
-            case GCodePreviewData::Extrusion::Height:
-                return data.get_height_color(value);
-            case GCodePreviewData::Extrusion::Width:
-                return data.get_width_color(value);
-            case GCodePreviewData::Extrusion::Feedrate:
-                return data.get_feedrate_color(value);
-            case GCodePreviewData::Extrusion::VolumetricRate:
-                return data.get_volumetric_rate_color(value);
-            case GCodePreviewData::Extrusion::Tool:
-                {
-                    GCodePreviewData::Color color;
-                    ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + (unsigned int)value * 4), 4 * sizeof(float));
-                    return color;
-                }
-            }
-
-            return GCodePreviewData::Color::Dummy;
-        }
-    };
-
-    // Helper structure for filters
-    struct Filter
-    {
-        float value;
-        ExtrusionRole role;
-        GLVolume* volume;
-
-        Filter(float value, ExtrusionRole role)
-            : value(value)
-            , role(role)
-            , volume(nullptr)
-        {
-        }
-
-        bool operator == (const Filter& other) const
-        {
-            if (value != other.value)
-                return false;
-
-            if (role != other.role)
-                return false;
-
-            return true;
-        }
-    };
-
-    typedef std::vector<Filter> FiltersList;
-    size_t initial_volumes_count = volumes.volumes.size();
-
-    // detects filters
-    FiltersList filters;
-    for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers)
-    {
-        for (const ExtrusionPath& path : layer.paths)
-        {
-            ExtrusionRole role = path.role();
-            float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path);
-            if (std::find(filters.begin(), filters.end(), Filter(path_filter, role)) == filters.end())
-                filters.emplace_back(path_filter, role);
-        }
-    }
-
-    // nothing to render, return
-    if (filters.empty())
-        return;
-
-    // creates a new volume for each filter
-    for (Filter& filter : filters)
-    {
-        s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Extrusion, (unsigned int)filter.role, (unsigned int)volumes.volumes.size());
-        GLVolume* volume = new GLVolume(Helper::path_color(preview_data, tool_colors, filter.value).rgba);
-        if (volume != nullptr)
-        {
-            filter.volume = volume;
-            volumes.volumes.emplace_back(volume);
-        }
-        else
-        {
-            // an error occourred - restore to previous state and return
-            s_gcode_preview_volume_index.first_volumes.pop_back();
-            if (initial_volumes_count != volumes.volumes.size())
-            {
-                std::vector<GLVolume*>::iterator begin = volumes.volumes.begin() + initial_volumes_count;
-                std::vector<GLVolume*>::iterator end = volumes.volumes.end();
-                for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it)
-                {
-                    GLVolume* volume = *it;
-                    delete volume;
-                }
-                volumes.volumes.erase(begin, end);
-                return;
-            }
-        }
-    }
-
-    // populates volumes
-    for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers)
-    {
-        for (const ExtrusionPath& path : layer.paths)
-        {
-            float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path);
-            FiltersList::iterator filter = std::find(filters.begin(), filters.end(), Filter(path_filter, path.role()));
-            if (filter != filters.end())
-            {
-                filter->volume->print_zs.push_back(layer.z);
-                filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.quad_indices.size());
-                filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.triangle_indices.size());
-
-                extrusionentity_to_verts(path, layer.z, *filter->volume);
-            }
-        }
-    }
-
-    // finalize volumes and sends geometry to gpu
-    if (volumes.volumes.size() > initial_volumes_count)
-    {
-        for (size_t i = initial_volumes_count; i < volumes.volumes.size(); ++i)
-        {
-            GLVolume* volume = volumes.volumes[i];
-            volume->bounding_box = volume->indexed_vertex_array.bounding_box();
-            volume->indexed_vertex_array.finalize_geometry(use_VBOs);
-        }
-    }
-}
-
-void _3DScene::_load_gcode_travel_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs)
-{
-    size_t initial_volumes_count = volumes.volumes.size();
-    s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Travel, 0, (unsigned int)initial_volumes_count);
-
-    bool res = true;
-    switch (preview_data.extrusion.view_type)
-    {
-    case GCodePreviewData::Extrusion::Feedrate:
-        {
-            res = _travel_paths_by_feedrate(preview_data, volumes);
-            break;
-        }
-    case GCodePreviewData::Extrusion::Tool:
-        {
-            res = _travel_paths_by_tool(preview_data, volumes, tool_colors);
-            break;
-        }
-    default:
-        {
-            res = _travel_paths_by_type(preview_data, volumes);
-            break;
-        }
-    }
-
-    if (!res)
-    {
-        // an error occourred - restore to previous state and return
-        if (initial_volumes_count != volumes.volumes.size())
-        {
-            std::vector<GLVolume*>::iterator begin = volumes.volumes.begin() + initial_volumes_count;
-            std::vector<GLVolume*>::iterator end = volumes.volumes.end();
-            for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it)
-            {
-                GLVolume* volume = *it;
-                delete volume;
-            }
-            volumes.volumes.erase(begin, end);
-        }
-
-        return;
-    }
-
-    // finalize volumes and sends geometry to gpu
-    if (volumes.volumes.size() > initial_volumes_count)
-    {
-        for (size_t i = initial_volumes_count; i < volumes.volumes.size(); ++i)
-        {
-            GLVolume* volume = volumes.volumes[i];
-            volume->bounding_box = volume->indexed_vertex_array.bounding_box();
-            volume->indexed_vertex_array.finalize_geometry(use_VBOs);
-        }
-    }
-}
-
-bool _3DScene::_travel_paths_by_type(const GCodePreviewData& preview_data, GLVolumeCollection& volumes)
-{
-    // Helper structure for types
-    struct Type
-    {
-        GCodePreviewData::Travel::EType value;
-        GLVolume* volume;
-
-        explicit Type(GCodePreviewData::Travel::EType value)
-            : value(value)
-            , volume(nullptr)
-        {
-        }
-
-        bool operator == (const Type& other) const
-        {
-            return value == other.value;
-        }
-    };
-
-    typedef std::vector<Type> TypesList;
-
-    // colors travels by travel type
-
-    // detects types
-    TypesList types;
-    for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
-    {
-        if (std::find(types.begin(), types.end(), Type(polyline.type)) == types.end())
-            types.emplace_back(polyline.type);
-    }
-
-    // nothing to render, return
-    if (types.empty())
-        return true;
-
-    // creates a new volume for each type
-    for (Type& type : types)
-    {
-        GLVolume* volume = new GLVolume(preview_data.travel.type_colors[type.value].rgba);
-        if (volume == nullptr)
-            return false;
-        else
-        {
-            type.volume = volume;
-            volumes.volumes.emplace_back(volume);
-        }
-    }
-
-    // populates volumes
-    for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
-    {
-        TypesList::iterator type = std::find(types.begin(), types.end(), Type(polyline.type));
-        if (type != types.end())
-        {
-            type->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z));
-            type->volume->offsets.push_back(type->volume->indexed_vertex_array.quad_indices.size());
-            type->volume->offsets.push_back(type->volume->indexed_vertex_array.triangle_indices.size());
-
-            polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *type->volume);
-        }
-    }
-
-    return true;
-}
-
-bool _3DScene::_travel_paths_by_feedrate(const GCodePreviewData& preview_data, GLVolumeCollection& volumes)
-{
-    // Helper structure for feedrate
-    struct Feedrate
-    {
-        float value;
-        GLVolume* volume;
-
-        explicit Feedrate(float value)
-            : value(value)
-            , volume(nullptr)
-        {
-        }
-
-        bool operator == (const Feedrate& other) const
-        {
-            return value == other.value;
-        }
-    };
-
-    typedef std::vector<Feedrate> FeedratesList;
-
-    // colors travels by feedrate
-
-    // detects feedrates
-    FeedratesList feedrates;
-    for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
-    {
-        if (std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate)) == feedrates.end())
-            feedrates.emplace_back(polyline.feedrate);
-    }
-
-    // nothing to render, return
-    if (feedrates.empty())
-        return true;
-
-    // creates a new volume for each feedrate
-    for (Feedrate& feedrate : feedrates)
-    {
-        GLVolume* volume = new GLVolume(preview_data.get_feedrate_color(feedrate.value).rgba);
-        if (volume == nullptr)
-            return false;
-        else
-        {
-            feedrate.volume = volume;
-            volumes.volumes.emplace_back(volume);
-        }
-    }
-
-    // populates volumes
-    for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
-    {
-        FeedratesList::iterator feedrate = std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate));
-        if (feedrate != feedrates.end())
-        {
-            feedrate->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z));
-            feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.quad_indices.size());
-            feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.triangle_indices.size());
-
-            polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *feedrate->volume);
-        }
-    }
-
-    return true;
-}
-
-bool _3DScene::_travel_paths_by_tool(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors)
-{
-    // Helper structure for tool
-    struct Tool
-    {
-        unsigned int value;
-        GLVolume* volume;
-
-        explicit Tool(unsigned int value)
-            : value(value)
-            , volume(nullptr)
-        {
-        }
-
-        bool operator == (const Tool& other) const
-        {
-            return value == other.value;
-        }
-    };
-
-    typedef std::vector<Tool> ToolsList;
-
-    // colors travels by tool
-
-    // detects tools
-    ToolsList tools;
-    for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
-    {
-        if (std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id)) == tools.end())
-            tools.emplace_back(polyline.extruder_id);
-    }
-
-    // nothing to render, return
-    if (tools.empty())
-        return true;
-
-    // creates a new volume for each tool
-    for (Tool& tool : tools)
-    {
-        GLVolume* volume = new GLVolume(tool_colors.data() + tool.value * 4);
-        if (volume == nullptr)
-            return false;
-        else
-        {
-            tool.volume = volume;
-            volumes.volumes.emplace_back(volume);
-        }
-    }
-
-    // populates volumes
-    for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
-    {
-        ToolsList::iterator tool = std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id));
-        if (tool != tools.end())
-        {
-            tool->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z));
-            tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.quad_indices.size());
-            tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.triangle_indices.size());
-
-            polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *tool->volume);
-        }
-    }
-
-    return true;
-}
-
-void _3DScene::_load_gcode_retractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs)
-{
-    s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Retraction, 0, (unsigned int)volumes.volumes.size());
-
-    // nothing to render, return
-    if (preview_data.retraction.positions.empty())
-        return;
-
-    GLVolume* volume = new GLVolume(preview_data.retraction.color.rgba);
-    if (volume != nullptr)
-    {
-        volumes.volumes.emplace_back(volume);
-
-        GCodePreviewData::Retraction::PositionsList copy(preview_data.retraction.positions);
-        std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; });
-
-        for (const GCodePreviewData::Retraction::Position& position : copy)
-        {
-            volume->print_zs.push_back(unscale(position.position.z));
-            volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
-            volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
-
-            point3_to_verts(position.position, position.width, position.height, *volume);
-        }
-
-        // finalize volumes and sends geometry to gpu
-        volume->bounding_box = volume->indexed_vertex_array.bounding_box();
-        volume->indexed_vertex_array.finalize_geometry(use_VBOs);
-    }
-}
-
-void _3DScene::_load_gcode_unretractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs)
-{
-    s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Unretraction, 0, (unsigned int)volumes.volumes.size());
-
-    // nothing to render, return
-    if (preview_data.unretraction.positions.empty())
-        return;
-
-    GLVolume* volume = new GLVolume(preview_data.unretraction.color.rgba);
-    if (volume != nullptr)
-    {
-        volumes.volumes.emplace_back(volume);
-
-        GCodePreviewData::Retraction::PositionsList copy(preview_data.unretraction.positions);
-        std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; });
-
-        for (const GCodePreviewData::Retraction::Position& position : copy)
-        {
-            volume->print_zs.push_back(unscale(position.position.z));
-            volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
-            volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
-
-            point3_to_verts(position.position, position.width, position.height, *volume);
-        }
-
-        // finalize volumes and sends geometry to gpu
-        volume->bounding_box = volume->indexed_vertex_array.bounding_box();
-        volume->indexed_vertex_array.finalize_geometry(use_VBOs);
-    }
-}
-
-void _3DScene::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data, GLVolumeCollection& volumes)
-{
-    unsigned int size = (unsigned int)s_gcode_preview_volume_index.first_volumes.size();
-    for (unsigned int i = 0; i < size; ++i)
-    {
-        std::vector<GLVolume*>::iterator begin = volumes.volumes.begin() + s_gcode_preview_volume_index.first_volumes[i].id;
-        std::vector<GLVolume*>::iterator end = (i + 1 < size) ? volumes.volumes.begin() + s_gcode_preview_volume_index.first_volumes[i + 1].id : volumes.volumes.end();
-
-        for (std::vector<GLVolume*>::iterator it = begin; it != end; ++it)
-        {
-            GLVolume* volume = *it;
-            volume->outside_printer_detection_enabled = false;
-
-            switch (s_gcode_preview_volume_index.first_volumes[i].type)
-            {
-            case GCodePreviewVolumeIndex::Extrusion:
-                {
-                    if ((ExtrusionRole)s_gcode_preview_volume_index.first_volumes[i].flag == erCustom)
-                        volume->zoom_to_volumes = false;
-                    
-                    volume->is_active = preview_data.extrusion.is_role_flag_set((ExtrusionRole)s_gcode_preview_volume_index.first_volumes[i].flag);
-                    break;
-                }
-            case GCodePreviewVolumeIndex::Travel:
-                {
-                    volume->is_active = preview_data.travel.is_visible;
-                    volume->zoom_to_volumes = false;
-                    break;
-                }
-            case GCodePreviewVolumeIndex::Retraction:
-                {
-                    volume->is_active = preview_data.retraction.is_visible;
-                    volume->zoom_to_volumes = false;
-                    break;
-                }
-            case GCodePreviewVolumeIndex::Unretraction:
-                {
-                    volume->is_active = preview_data.unretraction.is_visible;
-                    volume->zoom_to_volumes = false;
-                    break;
-                }
-            case GCodePreviewVolumeIndex::Shell:
-                {
-                    volume->is_active = preview_data.shell.is_visible;
-                    volume->color[3] = 0.25f;
-                    volume->zoom_to_volumes = false;
-                    break;
-                }
-            default:
-                {
-                    volume->is_active = false;
-                    volume->zoom_to_volumes = false;
-                    break;
-                }
-            }
-        }
-    }
-}
-
-void _3DScene::_generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors)
-{
-    s_legend_texture.generate(preview_data, tool_colors);
-}
-
-void _3DScene::_load_shells(const Print& print, GLVolumeCollection& volumes, bool use_VBOs)
-{
-    size_t initial_volumes_count = volumes.volumes.size();
-    s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Shell, 0, (unsigned int)initial_volumes_count);
-
-    if (print.objects.empty())
-        // nothing to render, return
-        return;
-
-    // adds objects' volumes 
-    unsigned int object_id = 0;
-    for (PrintObject* obj : print.objects)
-    {
-        ModelObject* model_obj = obj->model_object();
-
-        std::vector<int> instance_ids(model_obj->instances.size());
-        for (int i = 0; i < model_obj->instances.size(); ++i)
-        {
-            instance_ids[i] = i;
-        }
-
-        for (ModelInstance* instance : model_obj->instances)
-        {
-            volumes.load_object(model_obj, object_id, instance_ids, "object", "object", "object", use_VBOs);
-        }
-
-        ++object_id;
-    }
-
-    // adds wipe tower's volume
-    coordf_t max_z = print.objects[0]->model_object()->get_model()->bounding_box().max.z;
-    const PrintConfig& config = print.config;
-    unsigned int extruders_count = config.nozzle_diameter.size();
-    if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) {
-        const float width_per_extruder = 15.f; // a simple workaround after wipe_tower_per_color_wipe got obsolete
-        volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, width_per_extruder * (extruders_count - 1), max_z, config.wipe_tower_rotation_angle, use_VBOs);
-    }
-}
-
 } // namespace Slic3r
diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp
index a417f5f9d..c6a166397 100644
--- a/xs/src/slic3r/GUI/3DScene.hpp
+++ b/xs/src/slic3r/GUI/3DScene.hpp
@@ -6,8 +6,10 @@
 #include "../../libslic3r/Line.hpp"
 #include "../../libslic3r/TriangleMesh.hpp"
 #include "../../libslic3r/Utils.hpp"
+#include "../../slic3r/GUI/GLCanvas3DManager.hpp"
 
 class wxBitmap;
+class wxWindow;
 
 namespace Slic3r {
 
@@ -17,6 +19,11 @@ class Model;
 class ModelObject;
 class GCodePreviewData;
 class DynamicPrintConfig;
+class ExtrusionPath;
+class ExtrusionMultiPath;
+class ExtrusionLoop;
+class ExtrusionEntity;
+class ExtrusionEntityCollection;
 
 // A container for interleaved arrays of 3D vertices and normals,
 // possibly indexed by triangles and / or quads.
@@ -305,9 +312,9 @@ public:
     // Boolean: Is mouse over this object?
     bool                hover;
     // Wheter or not this volume has been generated from a modifier
-    bool                 is_modifier;
+    bool                is_modifier;
     // Wheter or not this volume has been generated from the wipe tower
-    bool                 is_wipe_tower;
+    bool                is_wipe_tower;
 
     // Interleaved triangles & normals with indexed triangles & quads.
     GLIndexedVertexArray        indexed_vertex_array;
@@ -437,35 +444,6 @@ private:
 
 class _3DScene
 {
-    struct GCodePreviewVolumeIndex
-    {
-        enum EType
-        {
-            Extrusion,
-            Travel,
-            Retraction,
-            Unretraction,
-            Shell,
-            Num_Geometry_Types
-        };
-
-        struct FirstVolume
-        {
-            EType type;
-            unsigned int flag;
-            // Index of the first volume in a GLVolumeCollection.
-            unsigned int id;
-
-            FirstVolume(EType type, unsigned int flag, unsigned int id) : type(type), flag(flag), id(id) {}
-        };
-
-        std::vector<FirstVolume> first_volumes;
-
-        void reset() { first_volumes.clear(); }
-    };
-
-    static GCodePreviewVolumeIndex s_gcode_preview_volume_index;
-
     class TextureBase
     {
     protected:
@@ -525,12 +503,106 @@ class _3DScene
 
     static LegendTexture s_legend_texture;
     static WarningTexture s_warning_texture;
+    static GUI::GLCanvas3DManager s_canvas_mgr;
 
 public:
-    static void _glew_init();
+    static void init_gl();
+    static std::string get_gl_info(bool format_as_html, bool extensions);
+    static bool use_VBOs();
 
-    static void load_gcode_preview(const Print* print, const GCodePreviewData* preview_data, GLVolumeCollection* volumes, const std::vector<std::string>& str_tool_colors, bool use_VBOs);
+    static bool add_canvas(wxGLCanvas* canvas);
+    static bool remove_canvas(wxGLCanvas* canvas);
+    static void remove_all_canvases();
 
+    static bool init(wxGLCanvas* canvas);
+
+    static void set_active(wxGLCanvas* canvas, bool active);
+
+    static unsigned int get_volumes_count(wxGLCanvas* canvas);
+    static void reset_volumes(wxGLCanvas* canvas);
+    static void deselect_volumes(wxGLCanvas* canvas);
+    static void select_volume(wxGLCanvas* canvas, unsigned int id);
+    static void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections);
+    static bool check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config);
+    static bool move_volume_up(wxGLCanvas* canvas, unsigned int id);
+    static bool move_volume_down(wxGLCanvas* canvas, unsigned int id);
+
+    static void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections);
+
+    static void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config);
+    static void set_print(wxGLCanvas* canvas, Print* print);
+    static void set_model(wxGLCanvas* canvas, Model* model);
+
+    static void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape);
+    static void set_auto_bed_shape(wxGLCanvas* canvas);
+
+    static BoundingBoxf3 get_volumes_bounding_box(wxGLCanvas* canvas);
+
+    static void set_axes_length(wxGLCanvas* canvas, float length);
+
+    static void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons);
+
+    static void set_color_by(wxGLCanvas* canvas, const std::string& value);
+    static void set_select_by(wxGLCanvas* canvas, const std::string& value);
+    static void set_drag_by(wxGLCanvas* canvas, const std::string& value);
+
+    static bool is_layers_editing_enabled(wxGLCanvas* canvas);
+    static bool is_layers_editing_allowed(wxGLCanvas* canvas);
+    static bool is_shader_enabled(wxGLCanvas* canvas);
+
+    static bool is_reload_delayed(wxGLCanvas* canvas);
+
+    static void enable_layers_editing(wxGLCanvas* canvas, bool enable);
+    static void enable_warning_texture(wxGLCanvas* canvas, bool enable);
+    static void enable_legend_texture(wxGLCanvas* canvas, bool enable);
+    static void enable_picking(wxGLCanvas* canvas, bool enable);
+    static void enable_moving(wxGLCanvas* canvas, bool enable);
+    static void enable_gizmos(wxGLCanvas* canvas, bool enable);
+    static void enable_shader(wxGLCanvas* canvas, bool enable);
+    static void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable);
+    static void allow_multisample(wxGLCanvas* canvas, bool allow);
+
+    static void zoom_to_bed(wxGLCanvas* canvas);
+    static void zoom_to_volumes(wxGLCanvas* canvas);
+    static void select_view(wxGLCanvas* canvas, const std::string& direction);
+    static void set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other);
+
+    static void update_volumes_colors_by_extruder(wxGLCanvas* canvas);
+
+    static void render(wxGLCanvas* canvas);
+
+    static std::vector<double> get_current_print_zs(wxGLCanvas* canvas, bool active_only);
+    static void set_toolpaths_range(wxGLCanvas* canvas, double low, double high);
+
+    static void register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback);
+    static void register_on_double_click_callback(wxGLCanvas* canvas, void* callback);
+    static void register_on_right_click_callback(wxGLCanvas* canvas, void* callback);
+    static void register_on_select_object_callback(wxGLCanvas* canvas, void* callback);
+    static void register_on_model_update_callback(wxGLCanvas* canvas, void* callback);
+    static void register_on_remove_object_callback(wxGLCanvas* canvas, void* callback);
+    static void register_on_arrange_callback(wxGLCanvas* canvas, void* callback);
+    static void register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback);
+    static void register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback);
+    static void register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback);
+    static void register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback);
+    static void register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback);
+    static void register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback);
+    static void register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback);
+    static void register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback);
+    static void register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback);
+
+    static std::vector<int> load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs);
+    static std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx);
+
+    static void reload_scene(wxGLCanvas* canvas, bool force);
+
+    static void load_print_toolpaths(wxGLCanvas* canvas);
+    static void load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& str_tool_colors);
+    static void load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors);
+    static void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors);
+
+    // generates the legend texture in dependence of the current shown view type
+    static void generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
     static unsigned int get_legend_texture_width();
     static unsigned int get_legend_texture_height();
 
@@ -545,42 +617,16 @@ public:
     static void reset_warning_texture();
     static unsigned int finalize_warning_texture();
 
-    static void _load_print_toolpaths(
-        const Print                     *print,
-        GLVolumeCollection              *volumes,
-        const std::vector<std::string>  &tool_colors,
-        bool                             use_VBOs);
-
-    static void _load_print_object_toolpaths(
-        const PrintObject               *print_object,
-        GLVolumeCollection              *volumes,
-        const std::vector<std::string>  &tool_colors,
-        bool                             use_VBOs);
-
-    static void _load_wipe_tower_toolpaths(
-        const Print                    *print,
-        GLVolumeCollection             *volumes,
-        const std::vector<std::string> &tool_colors_str,
-        bool                            use_VBOs);
-
-private:
-    // generates gcode extrusion paths geometry
-    static void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs);
-    // generates gcode travel paths geometry
-    static void _load_gcode_travel_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs);
-    static bool _travel_paths_by_type(const GCodePreviewData& preview_data, GLVolumeCollection& volumes);
-    static bool _travel_paths_by_feedrate(const GCodePreviewData& preview_data, GLVolumeCollection& volumes);
-    static bool _travel_paths_by_tool(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors);
-    // generates gcode retractions geometry
-    static void _load_gcode_retractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs);
-    // generates gcode unretractions geometry
-    static void _load_gcode_unretractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs);
-    // sets gcode geometry visibility according to user selection
-    static void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data, GLVolumeCollection& volumes);
-    // generates the legend texture in dependence of the current shown view type
-    static void _generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
-    // generates objects and wipe tower geometry
-    static void _load_shells(const Print& print, GLVolumeCollection& volumes, bool use_VBOs);
+    static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GLVolume& volume);
+    static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GLVolume& volume);
+    static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume);
+    static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume);
+    static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume);
+    static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GLVolume& volume);
+    static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume);
+    static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume);
+    static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume);
+    static void point3_to_verts(const Point3& point, double width, double height, GLVolume& volume);
 };
 
 }
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
new file mode 100644
index 000000000..f9c10017e
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
@@ -0,0 +1,4308 @@
+#include "GLCanvas3D.hpp"
+
+#include "../../slic3r/GUI/3DScene.hpp"
+#include "../../slic3r/GUI/GLShader.hpp"
+#include "../../slic3r/GUI/GUI.hpp"
+#include "../../slic3r/GUI/PresetBundle.hpp"
+#include "../../slic3r/GUI/GLGizmo.hpp"
+#include "../../libslic3r/ClipperUtils.hpp"
+#include "../../libslic3r/PrintConfig.hpp"
+#include "../../libslic3r/Print.hpp"
+#include "../../libslic3r/GCode/PreviewData.hpp"
+
+#include <GL/glew.h>
+
+#include <wx/glcanvas.h>
+#include <wx/timer.h>
+
+#include <tbb/parallel_for.h>
+#include <tbb/spin_mutex.h>
+
+#include <boost/log/trivial.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <iostream>
+#include <float.h>
+#include <algorithm>
+
+static const float TRACKBALLSIZE = 0.8f;
+static const float GIMBALL_LOCK_THETA_MAX = 180.0f;
+static const float GROUND_Z = -0.02f;
+
+// phi / theta angles to orient the camera.
+static const float VIEW_DEFAULT[2] = { 45.0f, 45.0f };
+static const float VIEW_LEFT[2] = { 90.0f, 90.0f };
+static const float VIEW_RIGHT[2] = { -90.0f, 90.0f };
+static const float VIEW_TOP[2] = { 0.0f, 0.0f };
+static const float VIEW_BOTTOM[2] = { 0.0f, 180.0f };
+static const float VIEW_FRONT[2] = { 0.0f, 90.0f };
+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;
+
+namespace Slic3r {
+namespace GUI {
+
+bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords)
+{
+    m_vertices.clear();
+    m_tex_coords.clear();
+
+    unsigned int v_size = 9 * (unsigned int)triangles.size();
+    unsigned int t_size = 6 * (unsigned int)triangles.size();
+    if (v_size == 0)
+        return false;
+
+    m_vertices = std::vector<float>(v_size, 0.0f);
+    if (generate_tex_coords)
+        m_tex_coords = std::vector<float>(t_size, 0.0f);
+
+    float min_x = (float)unscale(triangles[0].points[0].x);
+    float min_y = (float)unscale(triangles[0].points[0].y);
+    float max_x = min_x;
+    float max_y = min_y;
+
+    unsigned int v_coord = 0;
+    unsigned int t_coord = 0;
+    for (const Polygon& t : triangles)
+    {
+        for (unsigned int v = 0; v < 3; ++v)
+        {
+            const Point& p = t.points[v];
+            float x = (float)unscale(p.x);
+            float y = (float)unscale(p.y);
+
+            m_vertices[v_coord++] = x;
+            m_vertices[v_coord++] = y;
+            m_vertices[v_coord++] = z;
+
+            if (generate_tex_coords)
+            {
+                m_tex_coords[t_coord++] = x;
+                m_tex_coords[t_coord++] = y;
+
+                min_x = std::min(min_x, x);
+                max_x = std::max(max_x, x);
+                min_y = std::min(min_y, y);
+                max_y = std::max(max_y, y);
+            }
+        }
+    }
+
+    if (generate_tex_coords)
+    {
+        float size_x = max_x - min_x;
+        float size_y = max_y - min_y;
+
+        if ((size_x != 0.0f) && (size_y != 0.0f))
+        {
+            float inv_size_x = 1.0f / size_x;
+            float inv_size_y = -1.0f / size_y;
+            for (unsigned int i = 0; i < m_tex_coords.size(); i += 2)
+            {
+                m_tex_coords[i] *= inv_size_x;
+                m_tex_coords[i + 1] *= inv_size_y;
+            }
+        }
+    }
+
+    return true;
+}
+
+bool GeometryBuffer::set_from_lines(const Lines& lines, float z)
+{
+    m_vertices.clear();
+    m_tex_coords.clear();
+
+    unsigned int size = 6 * (unsigned int)lines.size();
+    if (size == 0)
+        return false;
+
+    m_vertices = std::vector<float>(size, 0.0f);
+
+    unsigned int coord = 0;
+    for (const Line& l : lines)
+    {
+        m_vertices[coord++] = (float)unscale(l.a.x);
+        m_vertices[coord++] = (float)unscale(l.a.y);
+        m_vertices[coord++] = z;
+        m_vertices[coord++] = (float)unscale(l.b.x);
+        m_vertices[coord++] = (float)unscale(l.b.y);
+        m_vertices[coord++] = z;
+    }
+
+    return true;
+}
+
+const float* GeometryBuffer::get_vertices() const
+{
+    return m_vertices.data();
+}
+
+const float* GeometryBuffer::get_tex_coords() const
+{
+    return m_tex_coords.data();
+}
+
+unsigned int GeometryBuffer::get_vertices_count() const
+{
+    return (unsigned int)m_vertices.size() / 3;
+}
+
+Size::Size()
+    : m_width(0)
+    , m_height(0)
+{
+}
+
+Size::Size(int width, int height)
+    : m_width(width)
+    , m_height(height)
+{
+}
+
+int Size::get_width() const
+{
+    return m_width;
+}
+
+void Size::set_width(int width)
+{
+    m_width = width;
+}
+
+int Size::get_height() const
+{
+    return m_height;
+}
+
+void Size::set_height(int height)
+{
+    m_height = height;
+}
+
+Rect::Rect()
+    : m_left(0.0f)
+    , m_top(0.0f)
+    , m_right(0.0f)
+    , m_bottom(0.0f)
+{
+}
+
+Rect::Rect(float left, float top, float right, float bottom)
+    : m_left(left)
+    , m_top(top)
+    , m_right(right)
+    , m_bottom(bottom)
+{
+}
+
+float Rect::get_left() const
+{
+    return m_left;
+}
+
+void Rect::set_left(float left)
+{
+    m_left = left;
+}
+
+float Rect::get_top() const
+{
+    return m_top;
+}
+
+void Rect::set_top(float top)
+{
+    m_top = top;
+}
+
+float Rect::get_right() const
+{
+    return m_right;
+}
+
+void Rect::set_right(float right)
+{
+    m_right = right;
+}
+
+float Rect::get_bottom() const
+{
+    return m_bottom;
+}
+
+void Rect::set_bottom(float bottom)
+{
+    m_bottom = bottom;
+}
+
+GLCanvas3D::Camera::Camera()
+    : type(Ortho)
+    , zoom(1.0f)
+    , phi(45.0f)
+//    , distance(0.0f)
+    , target(0.0, 0.0, 0.0)
+    , m_theta(45.0f)
+{
+}
+
+std::string GLCanvas3D::Camera::get_type_as_string() const
+{
+    switch (type)
+    {
+    default:
+    case Unknown:
+        return "unknown";
+//    case Perspective:
+//        return "perspective";
+    case Ortho:
+        return "ortho";
+    };
+}
+
+float GLCanvas3D::Camera::get_theta() const
+{
+    return m_theta;
+}
+
+void GLCanvas3D::Camera::set_theta(float theta)
+{
+    m_theta = clamp(0.0f, GIMBALL_LOCK_THETA_MAX, theta);
+}
+
+GLCanvas3D::Bed::Bed()
+    : m_type(Custom)
+{
+}
+
+bool GLCanvas3D::Bed::is_prusa() const
+{
+    return (m_type == MK2) || (m_type == MK3);
+}
+
+bool GLCanvas3D::Bed::is_custom() const
+{
+    return m_type == Custom;
+}
+
+const Pointfs& GLCanvas3D::Bed::get_shape() const
+{
+    return m_shape;
+}
+
+void GLCanvas3D::Bed::set_shape(const Pointfs& shape)
+{
+    m_shape = shape;
+    m_type = _detect_type();
+
+    _calc_bounding_box();
+
+    ExPolygon poly;
+    for (const Pointf& p : m_shape)
+    {
+        poly.contour.append(Point(scale_(p.x), scale_(p.y)));
+    }
+
+    _calc_triangles(poly);
+
+    const BoundingBox& bed_bbox = poly.contour.bounding_box();
+    _calc_gridlines(poly, bed_bbox);
+
+    m_polygon = offset_ex(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0].contour;
+}
+
+const BoundingBoxf3& GLCanvas3D::Bed::get_bounding_box() const
+{
+    return m_bounding_box;
+}
+
+bool GLCanvas3D::Bed::contains(const Point& point) const
+{
+    return m_polygon.contains(point);
+}
+
+Point GLCanvas3D::Bed::point_projection(const Point& point) const
+{
+    return m_polygon.point_projection(point);
+}
+
+void GLCanvas3D::Bed::render(float theta) const
+{
+    switch (m_type)
+    {
+    case MK2:
+    {
+        _render_mk2(theta);
+        break;
+    }
+    case MK3:
+    {
+        _render_mk3(theta);
+        break;
+    }
+    default:
+    case Custom:
+    {
+        _render_custom();
+        break;
+    }
+    }
+}
+
+void GLCanvas3D::Bed::_calc_bounding_box()
+{
+    m_bounding_box = BoundingBoxf3();
+    for (const Pointf& p : m_shape)
+    {
+        m_bounding_box.merge(Pointf3(p.x, p.y, 0.0));
+    }
+}
+
+void GLCanvas3D::Bed::_calc_triangles(const ExPolygon& poly)
+{
+    Polygons triangles;
+    poly.triangulate(&triangles);
+
+    if (!m_triangles.set_from_triangles(triangles, GROUND_Z, m_type != Custom))
+        printf("Unable to create bed triangles\n");
+}
+
+void GLCanvas3D::Bed::_calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox)
+{
+    Polylines axes_lines;
+    for (coord_t x = bed_bbox.min.x; x <= bed_bbox.max.x; x += scale_(10.0))
+    {
+        Polyline line;
+        line.append(Point(x, bed_bbox.min.y));
+        line.append(Point(x, bed_bbox.max.y));
+        axes_lines.push_back(line);
+    }
+    for (coord_t y = bed_bbox.min.y; y <= bed_bbox.max.y; y += scale_(10.0))
+    {
+        Polyline line;
+        line.append(Point(bed_bbox.min.x, y));
+        line.append(Point(bed_bbox.max.x, y));
+        axes_lines.push_back(line);
+    }
+
+    // clip with a slightly grown expolygon because our lines lay on the contours and may get erroneously clipped
+    Lines gridlines = to_lines(intersection_pl(axes_lines, offset(poly, SCALED_EPSILON)));
+
+    // append bed contours
+    Lines contour_lines = to_lines(poly);
+    std::copy(contour_lines.begin(), contour_lines.end(), std::back_inserter(gridlines));
+
+    if (!m_gridlines.set_from_lines(gridlines, GROUND_Z))
+        printf("Unable to create bed grid lines\n");
+}
+
+GLCanvas3D::Bed::EType GLCanvas3D::Bed::_detect_type() const
+{
+    EType type = Custom;
+
+    const PresetBundle* bundle = get_preset_bundle();
+    if (bundle != nullptr)
+    {
+        const Preset& curr = bundle->printers.get_selected_preset();
+        if (curr.config.has("bed_shape") && _are_equal(m_shape, dynamic_cast<const ConfigOptionPoints*>(curr.config.option("bed_shape"))->values))
+        {
+            if ((curr.vendor != nullptr) && (curr.vendor->name == "Prusa Research"))
+            {
+                if (boost::contains(curr.name, "MK2"))
+                    type = MK2;
+                else if (boost::contains(curr.name, "MK3"))
+                    type = MK3;
+            }
+        }
+    }
+
+    return type;
+}
+
+void GLCanvas3D::Bed::_render_mk2(float theta) const
+{
+    std::string filename = resources_dir() + "/icons/bed/mk2_top.png";
+    if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename))
+    {
+        if (!m_top_texture.load_from_file(filename, true))
+        {
+            _render_custom();
+            return;
+        }
+    }
+
+    filename = resources_dir() + "/icons/bed/mk2_bottom.png";
+    if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename))
+    {
+        if (!m_bottom_texture.load_from_file(filename, true))
+        {
+            _render_custom();
+            return;
+        }
+    }
+
+    _render_prusa(theta);
+}
+
+void GLCanvas3D::Bed::_render_mk3(float theta) const
+{
+    std::string filename = resources_dir() + "/icons/bed/mk3_top.png";
+    if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename))
+    {
+        if (!m_top_texture.load_from_file(filename, true))
+        {
+            _render_custom();
+            return;
+        }
+    }
+
+    filename = resources_dir() + "/icons/bed/mk3_bottom.png";
+    if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename))
+    {
+        if (!m_bottom_texture.load_from_file(filename, true))
+        {
+            _render_custom();
+            return;
+        }
+    }
+
+    _render_prusa(theta);
+}
+
+void GLCanvas3D::Bed::_render_prusa(float theta) const
+{
+    unsigned int triangles_vcount = m_triangles.get_vertices_count();
+    if (triangles_vcount > 0)
+    {
+        ::glEnable(GL_DEPTH_TEST);
+
+        ::glEnable(GL_BLEND);
+        ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+        ::glEnable(GL_TEXTURE_2D);
+
+        ::glEnableClientState(GL_VERTEX_ARRAY);
+        ::glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+
+        if (theta > 90.0f)
+            ::glFrontFace(GL_CW);
+
+        ::glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+        ::glBindTexture(GL_TEXTURE_2D, (theta <= 90.0f) ? (GLuint)m_top_texture.get_id() : (GLuint)m_bottom_texture.get_id());
+        ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices());
+        ::glTexCoordPointer(2, GL_FLOAT, 0, (GLvoid*)m_triangles.get_tex_coords());
+        ::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount);
+
+        if (theta > 90.0f)
+            ::glFrontFace(GL_CCW);
+
+        ::glBindTexture(GL_TEXTURE_2D, 0);
+        ::glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+        ::glDisableClientState(GL_VERTEX_ARRAY);
+
+        ::glDisable(GL_TEXTURE_2D);
+
+        ::glDisable(GL_BLEND);
+    }
+}
+
+void GLCanvas3D::Bed::_render_custom() const
+{
+    m_top_texture.reset();
+    m_bottom_texture.reset();
+
+    unsigned int triangles_vcount = m_triangles.get_vertices_count();
+    if (triangles_vcount > 0)
+    {
+        ::glEnable(GL_LIGHTING);
+        ::glDisable(GL_DEPTH_TEST);
+
+        ::glEnable(GL_BLEND);
+        ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+        ::glEnableClientState(GL_VERTEX_ARRAY);
+
+        ::glColor4f(0.8f, 0.6f, 0.5f, 0.4f);
+        ::glNormal3d(0.0f, 0.0f, 1.0f);
+        ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices());
+        ::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount);
+
+        // draw grid
+        unsigned int gridlines_vcount = m_gridlines.get_vertices_count();
+
+        // we need depth test for grid, otherwise it would disappear when looking the object from below
+        ::glEnable(GL_DEPTH_TEST);
+        ::glLineWidth(3.0f);
+        ::glColor4f(0.2f, 0.2f, 0.2f, 0.4f);
+        ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_gridlines.get_vertices());
+        ::glDrawArrays(GL_LINES, 0, (GLsizei)gridlines_vcount);
+
+        ::glDisableClientState(GL_VERTEX_ARRAY);
+
+        ::glDisable(GL_BLEND);
+    }
+}
+
+bool GLCanvas3D::Bed::_are_equal(const Pointfs& bed_1, const Pointfs& bed_2)
+{
+    if (bed_1.size() != bed_2.size())
+        return false;
+
+    for (unsigned int i = 0; i < (unsigned int)bed_1.size(); ++i)
+    {
+        if (bed_1[i] != bed_2[i])
+            return false;
+    }
+
+    return true;
+}
+
+GLCanvas3D::Axes::Axes()
+    : length(0.0f)
+{
+}
+
+void GLCanvas3D::Axes::render(bool depth_test) const
+{
+    ::glDisable(GL_LIGHTING);
+    if (depth_test)
+        ::glEnable(GL_DEPTH_TEST);
+    else
+        ::glDisable(GL_DEPTH_TEST);
+
+    ::glLineWidth(2.0f);
+    ::glBegin(GL_LINES);
+    // draw line for x axis
+    ::glColor3f(1.0f, 0.0f, 0.0f);
+    ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z);
+    ::glVertex3f((GLfloat)origin.x + length, (GLfloat)origin.y, (GLfloat)origin.z);
+    // draw line for y axis
+    ::glColor3f(0.0f, 1.0f, 0.0f);
+    ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z);
+    ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y + length, (GLfloat)origin.z);
+    ::glEnd();
+    // draw line for Z axis
+    // (re-enable depth test so that axis is correctly shown when objects are behind it)
+    if (!depth_test)
+        ::glEnable(GL_DEPTH_TEST);
+
+    ::glBegin(GL_LINES);
+    ::glColor3f(0.0f, 0.0f, 1.0f);
+    ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z);
+    ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z + length);
+    ::glEnd();
+}
+
+GLCanvas3D::CuttingPlane::CuttingPlane()
+    : m_z(-1.0f)
+{
+}
+
+bool GLCanvas3D::CuttingPlane::set(float z, const ExPolygons& polygons)
+{
+    m_z = z;
+
+    // grow slices in order to display them better
+    ExPolygons expolygons = offset_ex(polygons, scale_(0.1));
+    Lines lines = to_lines(expolygons);
+    return m_lines.set_from_lines(lines, m_z);
+}
+
+void GLCanvas3D::CuttingPlane::render(const BoundingBoxf3& bb) const
+{
+    ::glDisable(GL_LIGHTING);
+    _render_plane(bb);
+    _render_contour();
+}
+
+void GLCanvas3D::CuttingPlane::_render_plane(const BoundingBoxf3& bb) const
+{
+    if (m_z >= 0.0f)
+    {
+        ::glDisable(GL_CULL_FACE);
+        ::glEnable(GL_BLEND);
+        ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+        float margin = 20.0f;
+        float min_x = bb.min.x - margin;
+        float max_x = bb.max.x + margin;
+        float min_y = bb.min.y - margin;
+        float max_y = bb.max.y + margin;
+
+        ::glBegin(GL_QUADS);
+        ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f);
+        ::glVertex3f(min_x, min_y, m_z);
+        ::glVertex3f(max_x, min_y, m_z);
+        ::glVertex3f(max_x, max_y, m_z);
+        ::glVertex3f(min_x, max_y, m_z);
+        ::glEnd();
+
+        ::glEnable(GL_CULL_FACE);
+        ::glDisable(GL_BLEND);
+    }
+}
+
+void GLCanvas3D::CuttingPlane::_render_contour() const
+{
+    ::glEnableClientState(GL_VERTEX_ARRAY);
+
+    if (m_z >= 0.0f)
+    {
+        unsigned int lines_vcount = m_lines.get_vertices_count();
+
+        ::glLineWidth(2.0f);
+        ::glColor3f(0.0f, 0.0f, 0.0f);
+        ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_lines.get_vertices());
+        ::glDrawArrays(GL_LINES, 0, (GLsizei)lines_vcount);
+    }
+
+    ::glDisableClientState(GL_VERTEX_ARRAY);
+}
+
+GLCanvas3D::Shader::Shader()
+    : m_shader(nullptr)
+{
+}
+
+GLCanvas3D::Shader::~Shader()
+{
+    _reset();
+}
+
+bool GLCanvas3D::Shader::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename)
+{
+    if (is_initialized())
+        return true;
+
+    m_shader = new GLShader();
+    if (m_shader != nullptr)
+    {
+        if (!m_shader->load_from_file(fragment_shader_filename.c_str(), vertex_shader_filename.c_str()))
+        {
+            std::cout << "Compilaton of shader failed:" << std::endl;
+            std::cout << m_shader->last_error << std::endl;
+            _reset();
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool GLCanvas3D::Shader::is_initialized() const
+{
+    return (m_shader != nullptr);
+}
+
+bool GLCanvas3D::Shader::start_using() const
+{
+    if (is_initialized())
+    {
+        m_shader->enable();
+        return true;
+    }
+    else
+        return false;
+}
+
+void GLCanvas3D::Shader::stop_using() const
+{
+    if (m_shader != nullptr)
+        m_shader->disable();
+}
+
+void GLCanvas3D::Shader::set_uniform(const std::string& name, float value) const
+{
+    if (m_shader != nullptr)
+        m_shader->set_uniform(name.c_str(), value);
+}
+
+const GLShader* GLCanvas3D::Shader::get_shader() const
+{
+    return m_shader;
+}
+
+void GLCanvas3D::Shader::_reset()
+{
+    if (m_shader != nullptr)
+    {
+        m_shader->release();
+        delete m_shader;
+        m_shader = nullptr;
+    }
+}
+
+GLCanvas3D::LayersEditing::LayersEditing()
+    : m_use_legacy_opengl(false)
+    , m_enabled(false)
+    , m_z_texture_id(0)
+    , state(Unknown)
+    , band_width(2.0f)
+    , strength(0.005f)
+    , last_object_id(-1)
+    , last_z(0.0f)
+    , last_action(0)
+{
+}
+
+GLCanvas3D::LayersEditing::~LayersEditing()
+{
+    if (m_z_texture_id != 0)
+    {
+        ::glDeleteTextures(1, &m_z_texture_id);
+        m_z_texture_id = 0;
+    }
+}
+
+bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename)
+{
+    if (!m_shader.init(vertex_shader_filename, fragment_shader_filename))
+        return false;
+
+    ::glGenTextures(1, (GLuint*)&m_z_texture_id);
+    ::glBindTexture(GL_TEXTURE_2D, m_z_texture_id);
+    ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+    ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+    ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+    ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
+    ::glBindTexture(GL_TEXTURE_2D, 0);
+
+    return true;
+}
+
+bool GLCanvas3D::LayersEditing::is_allowed() const
+{
+    return !m_use_legacy_opengl && m_shader.is_initialized();
+}
+
+void GLCanvas3D::LayersEditing::set_use_legacy_opengl(bool use_legacy_opengl)
+{
+    m_use_legacy_opengl = use_legacy_opengl;
+}
+
+bool GLCanvas3D::LayersEditing::is_enabled() const
+{
+    return m_enabled;
+}
+
+void GLCanvas3D::LayersEditing::set_enabled(bool enabled)
+{
+    m_enabled = is_allowed() && enabled;
+}
+
+unsigned int GLCanvas3D::LayersEditing::get_z_texture_id() const
+{
+    return m_z_texture_id;
+}
+
+void GLCanvas3D::LayersEditing::render(const GLCanvas3D& canvas, const PrintObject& print_object, const GLVolume& volume) const
+{
+    if (!m_enabled)
+        return;
+
+    const Rect& bar_rect = get_bar_rect_viewport(canvas);
+    const Rect& reset_rect = get_reset_rect_viewport(canvas);
+
+    ::glDisable(GL_DEPTH_TEST);
+
+    // The viewport and camera are set to complete view and glOrtho(-$x / 2, $x / 2, -$y / 2, $y / 2, -$depth, $depth),
+    // where x, y is the window size divided by $self->_zoom.
+    ::glPushMatrix();
+    ::glLoadIdentity();
+
+    _render_tooltip_texture(canvas, bar_rect, reset_rect);
+    _render_reset_texture(reset_rect);
+    _render_active_object_annotations(canvas, volume, print_object, bar_rect);
+    _render_profile(print_object, bar_rect);
+
+    // Revert the matrices.
+    ::glPopMatrix();
+
+    ::glEnable(GL_DEPTH_TEST);
+}
+
+int GLCanvas3D::LayersEditing::get_shader_program_id() const
+{
+    const GLShader* shader = m_shader.get_shader();
+    return (shader != nullptr) ? shader->shader_program_id : -1;
+}
+
+float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas)
+{
+    const Point& mouse_pos = canvas.get_local_mouse_position();
+    const Rect& rect = get_bar_rect_screen(canvas);
+    float x = (float)mouse_pos.x;
+    float y = (float)mouse_pos.y;
+    float t = rect.get_top();
+    float b = rect.get_bottom();
+
+    return ((rect.get_left() <= x) && (x <= rect.get_right()) && (t <= y) && (y <= b)) ?
+        // Inside the bar.
+        (b - y - 1.0f) / (b - t - 1.0f) :
+        // Outside the bar.
+        -1000.0f;
+}
+
+bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, float x, float y)
+{
+    const Rect& rect = get_bar_rect_screen(canvas);
+    return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom());
+}
+
+bool GLCanvas3D::LayersEditing::reset_rect_contains(const GLCanvas3D& canvas, float x, float y)
+{
+    const Rect& rect = get_reset_rect_screen(canvas);
+    return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom());
+}
+
+Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas)
+{
+    const Size& cnv_size = canvas.get_canvas_size();
+    float w = (float)cnv_size.get_width();
+    float h = (float)cnv_size.get_height();
+
+    return Rect(w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, 0.0f, w, h - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT);
+}
+
+Rect GLCanvas3D::LayersEditing::get_reset_rect_screen(const GLCanvas3D& canvas)
+{
+    const Size& cnv_size = canvas.get_canvas_size();
+    float w = (float)cnv_size.get_width();
+    float h = (float)cnv_size.get_height();
+
+    return Rect(w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, h - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT, w, h);
+}
+
+Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas)
+{
+    const Size& cnv_size = canvas.get_canvas_size();
+    float half_w = 0.5f * (float)cnv_size.get_width();
+    float half_h = 0.5f * (float)cnv_size.get_height();
+
+    float zoom = canvas.get_camera_zoom();
+    float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+
+    return Rect((half_w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, (-half_h + VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT) * inv_zoom);
+}
+
+Rect GLCanvas3D::LayersEditing::get_reset_rect_viewport(const GLCanvas3D& canvas)
+{
+    const Size& cnv_size = canvas.get_canvas_size();
+    float half_w = 0.5f * (float)cnv_size.get_width();
+    float half_h = 0.5f * (float)cnv_size.get_height();
+
+    float zoom = canvas.get_camera_zoom();
+    float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+
+    return Rect((half_w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH) * inv_zoom, (-half_h + VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT) * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom);
+}
+
+
+bool GLCanvas3D::LayersEditing::_is_initialized() const
+{
+    return m_shader.is_initialized();
+}
+
+void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const
+{
+    if (m_tooltip_texture.get_id() == 0)
+    {
+        std::string filename = resources_dir() + "/icons/variable_layer_height_tooltip.png";
+        if (!m_tooltip_texture.load_from_file(filename, false))
+            return;
+    }
+
+    float zoom = canvas.get_camera_zoom();
+    float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+    float gap = 10.0f * inv_zoom;
+
+    float bar_left = bar_rect.get_left();
+    float reset_bottom = reset_rect.get_bottom();
+
+    float l = bar_left - (float)m_tooltip_texture.get_width() * inv_zoom - gap;
+    float r = bar_left - gap;
+    float t = reset_bottom + (float)m_tooltip_texture.get_height() * inv_zoom + gap;
+    float b = reset_bottom + gap;
+
+    GLTexture::render_texture(m_tooltip_texture.get_id(), l, r, b, t);
+}
+
+void GLCanvas3D::LayersEditing::_render_reset_texture(const Rect& reset_rect) const
+{
+    if (m_reset_texture.get_id() == 0)
+    {
+        std::string filename = resources_dir() + "/icons/variable_layer_height_reset.png";
+        if (!m_reset_texture.load_from_file(filename, false))
+            return;
+    }
+
+    GLTexture::render_texture(m_reset_texture.get_id(), reset_rect.get_left(), reset_rect.get_right(), reset_rect.get_bottom(), reset_rect.get_top());
+}
+
+void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas3D& canvas, const GLVolume& volume, const PrintObject& print_object, const Rect& bar_rect) const
+{
+    float max_z = print_object.model_object()->bounding_box().max.z;
+
+    m_shader.start_using();
+
+    m_shader.set_uniform("z_to_texture_row", (float)volume.layer_height_texture_z_to_row_id());
+    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);
+
+    GLsizei w = (GLsizei)volume.layer_height_texture_width();
+    GLsizei h = (GLsizei)volume.layer_height_texture_height();
+    GLsizei half_w = w / 2;
+    GLsizei half_h = h / 2;
+
+    ::glBindTexture(GL_TEXTURE_2D, m_z_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, 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, volume.layer_height_texture_data_ptr_level0());
+    ::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, volume.layer_height_texture_data_ptr_level1());
+
+    // Render the color bar
+    float l = bar_rect.get_left();
+    float r = bar_rect.get_right();
+    float t = bar_rect.get_top();
+    float b = bar_rect.get_bottom();
+
+    ::glBegin(GL_QUADS);
+    ::glVertex3f(l, b, 0.0f);
+    ::glVertex3f(r, b, 0.0f);
+    ::glVertex3f(r, t, max_z);
+    ::glVertex3f(l, t, max_z);
+    ::glEnd();
+    ::glBindTexture(GL_TEXTURE_2D, 0);
+
+    m_shader.stop_using();
+}
+
+void GLCanvas3D::LayersEditing::_render_profile(const PrintObject& print_object, const Rect& bar_rect) const
+{
+    // FIXME show some kind of legend.
+
+    // Get a maximum layer height value.
+    // FIXME This is a duplicate code of Slicing.cpp.
+    double layer_height_max = DBL_MAX;
+    const PrintConfig& print_config = print_object.print()->config;
+    const std::vector<double>& nozzle_diameters = dynamic_cast<const ConfigOptionFloats*>(print_config.option("nozzle_diameter"))->values;
+    const std::vector<double>& layer_heights_min = dynamic_cast<const ConfigOptionFloats*>(print_config.option("min_layer_height"))->values;
+    const std::vector<double>& layer_heights_max = dynamic_cast<const ConfigOptionFloats*>(print_config.option("max_layer_height"))->values;
+    for (unsigned int i = 0; i < (unsigned int)nozzle_diameters.size(); ++i)
+    {
+        double lh_min = (layer_heights_min[i] == 0.0) ? 0.07 : std::max(0.01, layer_heights_min[i]);
+        double lh_max = (layer_heights_max[i] == 0.0) ? (0.75 * nozzle_diameters[i]) : layer_heights_max[i];
+        layer_height_max = std::min(layer_height_max, std::max(lh_min, lh_max));
+    }
+
+    // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region.
+    layer_height_max *= 1.12;
+
+    coordf_t max_z = unscale(print_object.size.z);
+    double layer_height = dynamic_cast<const ConfigOptionFloat*>(print_object.config.option("layer_height"))->value;
+    float l = bar_rect.get_left();
+    float w = bar_rect.get_right() - l;
+    float b = bar_rect.get_bottom();
+    float t = bar_rect.get_top();
+    float h = t - b;
+    float scale_x = w / (float)layer_height_max;
+    float scale_y = h / (float)max_z;
+    float x = l + (float)layer_height * scale_x;
+
+    // Baseline
+    ::glColor3f(0.0f, 0.0f, 0.0f);
+    ::glBegin(GL_LINE_STRIP);
+    ::glVertex2f(x, b);
+    ::glVertex2f(x, t);
+    ::glEnd();
+
+    // Curve
+    const ModelObject* model_object = print_object.model_object();
+    if (model_object->layer_height_profile_valid)
+    {
+        const std::vector<coordf_t>& profile = model_object->layer_height_profile;
+
+        ::glColor3f(0.0f, 0.0f, 1.0f);
+        ::glBegin(GL_LINE_STRIP);
+        for (unsigned int i = 0; i < profile.size(); i += 2)
+        {
+            ::glVertex2f(l + (float)profile[i + 1] * scale_x, b + (float)profile[i] * scale_y);
+        }
+        ::glEnd();
+    }
+}
+
+const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX);
+const Pointf3 GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX);
+
+GLCanvas3D::Mouse::Drag::Drag()
+    : start_position_2D(Invalid_2D_Point)
+    , start_position_3D(Invalid_3D_Point)
+    , volume_idx(-1)
+{
+}
+
+GLCanvas3D::Mouse::Mouse()
+    : dragging(false)
+    , position(DBL_MAX, DBL_MAX)
+{
+}
+
+void GLCanvas3D::Mouse::set_start_position_2D_as_invalid()
+{
+    drag.start_position_2D = Drag::Invalid_2D_Point;
+}
+
+void GLCanvas3D::Mouse::set_start_position_3D_as_invalid()
+{
+    drag.start_position_3D = Drag::Invalid_3D_Point;
+}
+
+bool GLCanvas3D::Mouse::is_start_position_2D_defined() const
+{
+    return (drag.start_position_2D != Drag::Invalid_2D_Point);
+}
+
+bool GLCanvas3D::Mouse::is_start_position_3D_defined() const
+{
+    return (drag.start_position_3D != Drag::Invalid_3D_Point);
+}
+
+const float GLCanvas3D::Gizmos::OverlayOffsetX = 10.0f;
+const float GLCanvas3D::Gizmos::OverlayGapY = 10.0f;
+
+GLCanvas3D::Gizmos::Gizmos()
+    : m_enabled(false)
+    , m_current(Undefined)
+    , m_dragging(false)
+{
+}
+
+GLCanvas3D::Gizmos::~Gizmos()
+{
+    _reset();
+}
+
+bool GLCanvas3D::Gizmos::init()
+{
+    GLGizmoBase* gizmo = new GLGizmoScale;
+    if (gizmo == nullptr)
+        return false;
+
+    if (!gizmo->init())
+        return false;
+
+    m_gizmos.insert(GizmosMap::value_type(Scale, gizmo));
+
+    gizmo = new GLGizmoRotate;
+    if (gizmo == nullptr)
+    {
+        _reset();
+        return false;
+    }
+
+    if (!gizmo->init())
+    {
+        _reset();
+        return false;
+    }
+
+    m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo));
+
+    return true;
+}
+
+bool GLCanvas3D::Gizmos::is_enabled() const
+{
+    return m_enabled;
+}
+
+void GLCanvas3D::Gizmos::set_enabled(bool enable)
+{
+    m_enabled = enable;
+}
+
+void GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos)
+{
+    if (!m_enabled)
+        return;
+
+    float cnv_h = (float)canvas.get_canvas_size().get_height();
+    float height = _get_total_overlay_height();
+    float top_y = 0.5f * (cnv_h - height);
+    for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
+    {
+        if (it->second == nullptr)
+            continue;
+
+        float tex_size = (float)it->second->get_textures_size();
+        float half_tex_size = 0.5f * tex_size;
+
+        // we currently use circular icons for gizmo, so we check the radius
+        if (it->second->get_state() != GLGizmoBase::On)
+        {
+            bool inside = length(Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size).vector_to(mouse_pos)) < half_tex_size;
+            it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off);
+        }
+        top_y += (tex_size + OverlayGapY);
+    }
+}
+
+void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Pointf& mouse_pos)
+{
+    if (!m_enabled)
+        return;
+
+    float cnv_h = (float)canvas.get_canvas_size().get_height();
+    float height = _get_total_overlay_height();
+    float top_y = 0.5f * (cnv_h - height);
+    for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
+    {
+        if (it->second == nullptr)
+            continue;
+
+        float tex_size = (float)it->second->get_textures_size();
+        float half_tex_size = 0.5f * tex_size;
+
+        // we currently use circular icons for gizmo, so we check the radius
+        if (length(Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size).vector_to(mouse_pos)) < half_tex_size)
+        {
+            if ((it->second->get_state() == GLGizmoBase::On))
+            {
+                it->second->set_state(GLGizmoBase::Off);
+                m_current = Undefined;
+            }
+            else
+            {
+                it->second->set_state(GLGizmoBase::On);
+                m_current = it->first;
+            }
+        }
+        else
+            it->second->set_state(GLGizmoBase::Off);
+
+        top_y += (tex_size + OverlayGapY);
+    }
+}
+
+void GLCanvas3D::Gizmos::reset_all_states()
+{
+    if (!m_enabled)
+        return;
+
+    for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
+    {
+        if (it->second != nullptr)
+        {
+            it->second->set_state(GLGizmoBase::Off);
+            it->second->set_hover_id(-1);
+        }
+    }
+
+    m_current = Undefined;
+}
+
+void GLCanvas3D::Gizmos::set_hover_id(int id)
+{
+    if (!m_enabled)
+        return;
+
+    for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
+    {
+        if ((it->second != nullptr) && (it->second->get_state() == GLGizmoBase::On))
+            it->second->set_hover_id(id);
+    }
+}
+
+bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const
+{
+    if (!m_enabled)
+        return false;
+
+    float cnv_h = (float)canvas.get_canvas_size().get_height();
+    float height = _get_total_overlay_height();
+    float top_y = 0.5f * (cnv_h - height);
+    for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
+    {
+        if (it->second == nullptr)
+            continue;
+
+        float tex_size = (float)it->second->get_textures_size();
+        float half_tex_size = 0.5f * tex_size;
+
+        // we currently use circular icons for gizmo, so we check the radius
+        if (length(Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size).vector_to(mouse_pos)) < half_tex_size)
+            return true;
+
+        top_y += (tex_size + OverlayGapY);
+    }
+
+    return false;
+}
+
+bool GLCanvas3D::Gizmos::grabber_contains_mouse() const
+{
+    if (!m_enabled)
+        return false;
+
+    GLGizmoBase* curr = _get_current();
+    return (curr != nullptr) ? (curr->get_hover_id() != -1) : false;
+}
+
+void GLCanvas3D::Gizmos::update(const Pointf& mouse_pos)
+{
+    if (!m_enabled)
+        return;
+
+    GLGizmoBase* curr = _get_current();
+    if (curr != nullptr)
+        curr->update(mouse_pos);
+}
+
+void GLCanvas3D::Gizmos::update_data(float scale)
+{
+    if (!m_enabled)
+        return;
+
+    GizmosMap::const_iterator it = m_gizmos.find(Scale);
+    if (it != m_gizmos.end())
+        reinterpret_cast<GLGizmoScale*>(it->second)->set_scale(scale);
+}
+
+bool GLCanvas3D::Gizmos::is_running() const
+{
+    if (!m_enabled)
+        return false;
+
+    GLGizmoBase* curr = _get_current();
+    return (curr != nullptr) ? (curr->get_state() == GLGizmoBase::On) : false;
+}
+
+bool GLCanvas3D::Gizmos::is_dragging() const
+{
+    return m_dragging;
+}
+
+void GLCanvas3D::Gizmos::start_dragging()
+{
+    m_dragging = true;
+    GLGizmoBase* curr = _get_current();
+    if (curr != nullptr)
+        curr->start_dragging();
+}
+
+void GLCanvas3D::Gizmos::stop_dragging()
+{
+    m_dragging = false;
+}
+
+float GLCanvas3D::Gizmos::get_scale() const
+{
+    if (!m_enabled)
+        return 1.0f;
+
+    GizmosMap::const_iterator it = m_gizmos.find(Scale);
+    return (it != m_gizmos.end()) ? reinterpret_cast<GLGizmoScale*>(it->second)->get_scale() : 1.0f;
+}
+
+void GLCanvas3D::Gizmos::render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const
+{
+    if (!m_enabled)
+        return;
+
+    ::glDisable(GL_DEPTH_TEST);
+
+    _render_current_gizmo(box);
+
+    ::glPushMatrix();
+    ::glLoadIdentity();
+
+    _render_overlay(canvas);
+
+    ::glPopMatrix();
+}
+
+void GLCanvas3D::Gizmos::render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const
+{
+    if (!m_enabled)
+        return;
+
+    ::glDisable(GL_DEPTH_TEST);
+
+    GLGizmoBase* curr = _get_current();
+    if (curr != nullptr)
+        curr->render_for_picking(box);
+}
+
+void GLCanvas3D::Gizmos::_reset()
+{
+    for (GizmosMap::value_type& gizmo : m_gizmos)
+    {
+        delete gizmo.second;
+        gizmo.second = nullptr;
+    }
+
+    m_gizmos.clear();
+}
+
+void GLCanvas3D::Gizmos::_render_overlay(const GLCanvas3D& canvas) const
+{
+    if (m_gizmos.empty())
+        return;
+
+    float cnv_w = (float)canvas.get_canvas_size().get_width();
+    float zoom = canvas.get_camera_zoom();
+    float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+
+    float height = _get_total_overlay_height();
+    float top_x = (OverlayOffsetX - 0.5f * cnv_w) * inv_zoom;
+    float top_y = 0.5f * height * inv_zoom;
+    float scaled_gap_y = OverlayGapY * inv_zoom;
+    for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
+    {
+        float tex_size = (float)it->second->get_textures_size() * inv_zoom;
+        GLTexture::render_texture(it->second->get_textures_id(), top_x, top_x + tex_size, top_y - tex_size, top_y);
+        top_y -= (tex_size + scaled_gap_y);
+    }
+}
+
+void GLCanvas3D::Gizmos::_render_current_gizmo(const BoundingBoxf3& box) const
+{
+    GLGizmoBase* curr = _get_current();
+    if (curr != nullptr)
+        curr->render(box);
+}
+
+float GLCanvas3D::Gizmos::_get_total_overlay_height() const
+{
+    float height = 0.0f;
+
+    for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
+    {
+        height += (float)it->second->get_textures_size();
+        if (std::distance(it, m_gizmos.end()) > 1)
+            height += OverlayGapY;
+    }
+
+    return height;
+}
+
+GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const
+{
+    GizmosMap::const_iterator it = m_gizmos.find(m_current);
+    return (it != m_gizmos.end()) ? it->second : nullptr;
+}
+
+GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, wxGLContext* context)
+    : m_canvas(canvas)
+    , m_context(context)
+    , m_timer(nullptr)
+    , m_config(nullptr)
+    , m_print(nullptr)
+    , m_model(nullptr)
+    , m_dirty(true)
+    , m_active(true)
+    , m_initialized(false)
+    , m_use_VBOs(false)
+    , m_force_zoom_to_bed_enabled(false)
+    , m_apply_zoom_to_volumes_filter(false)
+    , m_hover_volume_id(-1)
+    , m_warning_texture_enabled(false)
+    , m_legend_texture_enabled(false)
+    , m_picking_enabled(false)
+    , m_moving_enabled(false)
+    , m_shader_enabled(false)
+    , m_multisample_allowed(false)
+    , m_color_by("volume")
+    , m_select_by("object")
+    , m_drag_by("instance")
+    , m_reload_delayed(false)
+{
+    if (m_canvas != nullptr)
+        m_timer = new wxTimer(m_canvas);
+}
+
+GLCanvas3D::~GLCanvas3D()
+{
+    reset_volumes();
+
+    if (m_timer != nullptr)
+    {
+        delete m_timer;
+        m_timer = nullptr;
+    }
+
+    _deregister_callbacks();
+}
+
+bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl)
+{
+    if (m_initialized)
+        return true;
+
+    ::glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+    ::glClearDepth(1.0f);
+
+    ::glDepthFunc(GL_LESS);
+
+    ::glEnable(GL_DEPTH_TEST);
+    ::glEnable(GL_CULL_FACE);
+    ::glEnable(GL_BLEND);
+    ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+    // Set antialiasing / multisampling
+    ::glDisable(GL_LINE_SMOOTH);
+    ::glDisable(GL_POLYGON_SMOOTH);
+
+    // ambient lighting
+    GLfloat ambient[4] = { 0.3f, 0.3f, 0.3f, 1.0f };
+    ::glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
+
+    ::glEnable(GL_LIGHT0);
+    ::glEnable(GL_LIGHT1);
+
+    // light from camera
+    GLfloat specular_cam[4] = { 0.3f, 0.3f, 0.3f, 1.0f };
+    ::glLightfv(GL_LIGHT1, GL_SPECULAR, specular_cam);
+    GLfloat diffuse_cam[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
+    ::glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse_cam);
+
+    // light from above
+    GLfloat specular_top[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
+    ::glLightfv(GL_LIGHT0, GL_SPECULAR, specular_top);
+    GLfloat diffuse_top[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
+    ::glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_top);
+
+    // Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
+    ::glShadeModel(GL_SMOOTH);
+
+    // A handy trick -- have surface material mirror the color.
+    ::glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
+    ::glEnable(GL_COLOR_MATERIAL);
+
+    if (m_multisample_allowed)
+        ::glEnable(GL_MULTISAMPLE);
+
+    if (useVBOs && !m_shader.init("gouraud.vs", "gouraud.fs"))
+        return false;
+
+    if (useVBOs && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs"))
+        return false;
+
+    m_use_VBOs = useVBOs;
+    m_layers_editing.set_use_legacy_opengl(use_legacy_opengl);
+
+    // on linux the gl context is not valid until the canvas is not shown on screen
+    // we defer the geometry finalization of volumes until the first call to render()
+    if (!m_volumes.empty())
+        m_volumes.finalize_geometry(m_use_VBOs);
+
+    if (m_gizmos.is_enabled() && !m_gizmos.init())
+        return false;
+
+    m_initialized = true;
+
+    return true;
+}
+
+bool GLCanvas3D::set_current()
+{
+    if ((m_canvas != nullptr) && (m_context != nullptr))
+    {
+        m_canvas->SetCurrent(*m_context);
+        return true;
+    }
+
+    return false;
+}
+
+void GLCanvas3D::set_active(bool active)
+{
+    m_active = active;
+}
+
+unsigned int GLCanvas3D::get_volumes_count() const
+{
+    return (unsigned int)m_volumes.volumes.size();
+}
+
+void GLCanvas3D::reset_volumes()
+{
+    if (set_current())
+    {
+        m_volumes.release_geometry();
+        m_volumes.clear();
+        m_dirty = true;
+    }
+}
+
+void GLCanvas3D::deselect_volumes()
+{
+    for (GLVolume* vol : m_volumes.volumes)
+    {
+        if (vol != nullptr)
+            vol->selected = false;
+    }
+}
+
+void GLCanvas3D::select_volume(unsigned int id)
+{
+    if (id < (unsigned int)m_volumes.volumes.size())
+    {
+        GLVolume* vol = m_volumes.volumes[id];
+        if (vol != nullptr)
+            vol->selected = true;
+    }
+}
+
+void GLCanvas3D::update_volumes_selection(const std::vector<int>& selections)
+{
+    if (m_model == nullptr)
+        return;
+
+    for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx)
+    {
+        if ((selections[obj_idx] == 1) && (obj_idx < (unsigned int)m_objects_volumes_idxs.size()))
+        {
+            const std::vector<int>& volume_idxs = m_objects_volumes_idxs[obj_idx];
+            for (int v : volume_idxs)
+            {
+                select_volume(v);
+            }
+        }
+    }
+}
+
+bool GLCanvas3D::check_volumes_outside_state(const DynamicPrintConfig* config) const
+{
+    return m_volumes.check_outside_state(config);
+}
+
+bool GLCanvas3D::move_volume_up(unsigned int id)
+{
+    if ((id > 0) && (id < (unsigned int)m_volumes.volumes.size()))
+    {
+        std::swap(m_volumes.volumes[id - 1], m_volumes.volumes[id]);
+        std::swap(m_volumes.volumes[id - 1]->composite_id, m_volumes.volumes[id]->composite_id);
+        std::swap(m_volumes.volumes[id - 1]->select_group_id, m_volumes.volumes[id]->select_group_id);
+        std::swap(m_volumes.volumes[id - 1]->drag_group_id, m_volumes.volumes[id]->drag_group_id);
+        return true;
+    }
+
+    return false;
+}
+
+bool GLCanvas3D::move_volume_down(unsigned int id)
+{
+    if ((id >= 0) && (id + 1 < (unsigned int)m_volumes.volumes.size()))
+    {
+        std::swap(m_volumes.volumes[id + 1], m_volumes.volumes[id]);
+        std::swap(m_volumes.volumes[id + 1]->composite_id, m_volumes.volumes[id]->composite_id);
+        std::swap(m_volumes.volumes[id + 1]->select_group_id, m_volumes.volumes[id]->select_group_id);
+        std::swap(m_volumes.volumes[id + 1]->drag_group_id, m_volumes.volumes[id]->drag_group_id);
+        return true;
+    }
+
+    return false;
+}
+
+void GLCanvas3D::set_objects_selections(const std::vector<int>& selections)
+{
+    m_objects_selections = selections;
+}
+
+void GLCanvas3D::set_config(DynamicPrintConfig* config)
+{
+    m_config = config;
+}
+
+void GLCanvas3D::set_print(Print* print)
+{
+    m_print = print;
+}
+
+void GLCanvas3D::set_model(Model* model)
+{
+    m_model = model;
+}
+
+void GLCanvas3D::set_bed_shape(const Pointfs& shape)
+{
+    m_bed.set_shape(shape);
+
+    // Set the origin and size for painting of the coordinate system axes.
+    m_axes.origin = Pointf3(0.0, 0.0, (coordf_t)GROUND_Z);
+    set_axes_length(0.3f * (float)m_bed.get_bounding_box().max_size());
+}
+
+void GLCanvas3D::set_auto_bed_shape()
+{
+    // draw a default square bed around object center
+    const BoundingBoxf3& bbox = volumes_bounding_box();
+    coordf_t max_size = bbox.max_size();
+    const Pointf3& center = bbox.center();
+
+    Pointfs bed_shape;
+    bed_shape.reserve(4);
+    bed_shape.emplace_back(center.x - max_size, center.y - max_size);
+    bed_shape.emplace_back(center.x + max_size, center.y - max_size);
+    bed_shape.emplace_back(center.x + max_size, center.y + max_size);
+    bed_shape.emplace_back(center.x - max_size, center.y + max_size);
+
+    set_bed_shape(bed_shape);
+
+    // Set the origin for painting of the coordinate system axes.
+    m_axes.origin = Pointf3(center.x, center.y, (coordf_t)GROUND_Z);
+}
+
+void GLCanvas3D::set_axes_length(float length)
+{
+    m_axes.length = length;
+}
+
+void GLCanvas3D::set_cutting_plane(float z, const ExPolygons& polygons)
+{
+    m_cutting_plane.set(z, polygons);
+}
+
+void GLCanvas3D::set_color_by(const std::string& value)
+{
+    m_color_by = value;
+}
+
+void GLCanvas3D::set_select_by(const std::string& value)
+{
+    m_select_by = value;
+}
+
+void GLCanvas3D::set_drag_by(const std::string& value)
+{
+    m_drag_by = value;
+}
+
+float GLCanvas3D::get_camera_zoom() const
+{
+    return m_camera.zoom;
+}
+
+BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const
+{
+    BoundingBoxf3 bb;
+    for (const GLVolume* volume : m_volumes.volumes)
+    {
+        if (!m_apply_zoom_to_volumes_filter || ((volume != nullptr) && volume->zoom_to_volumes))
+            bb.merge(volume->transformed_bounding_box());
+    }
+    return bb;
+}
+
+bool GLCanvas3D::is_layers_editing_enabled() const
+{
+    return m_layers_editing.is_enabled();
+}
+
+bool GLCanvas3D::is_layers_editing_allowed() const
+{
+    return m_layers_editing.is_allowed();
+}
+
+bool GLCanvas3D::is_shader_enabled() const
+{
+    return m_shader_enabled;
+}
+
+bool GLCanvas3D::is_reload_delayed() const
+{
+    return m_reload_delayed;
+}
+
+void GLCanvas3D::enable_layers_editing(bool enable)
+{
+    m_layers_editing.set_enabled(enable);
+}
+
+void GLCanvas3D::enable_warning_texture(bool enable)
+{
+    m_warning_texture_enabled = enable;
+}
+
+void GLCanvas3D::enable_legend_texture(bool enable)
+{
+    m_legend_texture_enabled = enable;
+}
+
+void GLCanvas3D::enable_picking(bool enable)
+{
+    m_picking_enabled = enable;
+}
+
+void GLCanvas3D::enable_moving(bool enable)
+{
+    m_moving_enabled = enable;
+}
+
+void GLCanvas3D::enable_gizmos(bool enable)
+{
+    m_gizmos.set_enabled(enable);
+}
+
+void GLCanvas3D::enable_shader(bool enable)
+{
+    m_shader_enabled = enable;
+}
+
+void GLCanvas3D::enable_force_zoom_to_bed(bool enable)
+{
+    m_force_zoom_to_bed_enabled = enable;
+}
+
+void GLCanvas3D::allow_multisample(bool allow)
+{
+    m_multisample_allowed = allow;
+}
+
+void GLCanvas3D::zoom_to_bed()
+{
+    _zoom_to_bounding_box(m_bed.get_bounding_box());
+}
+
+void GLCanvas3D::zoom_to_volumes()
+{
+    m_apply_zoom_to_volumes_filter = true;
+    _zoom_to_bounding_box(volumes_bounding_box());
+    m_apply_zoom_to_volumes_filter = false;
+}
+
+void GLCanvas3D::select_view(const std::string& direction)
+{
+    const float* dir_vec = nullptr;
+
+    if (direction == "iso")
+        dir_vec = VIEW_DEFAULT;
+    else if (direction == "left")
+        dir_vec = VIEW_LEFT;
+    else if (direction == "right")
+        dir_vec = VIEW_RIGHT;
+    else if (direction == "top")
+        dir_vec = VIEW_TOP;
+    else if (direction == "bottom")
+        dir_vec = VIEW_BOTTOM;
+    else if (direction == "front")
+        dir_vec = VIEW_FRONT;
+    else if (direction == "rear")
+        dir_vec = VIEW_REAR;
+
+    if ((dir_vec != nullptr) && !empty(volumes_bounding_box()))
+    {
+        m_camera.phi = dir_vec[0];
+        m_camera.set_theta(dir_vec[1]);
+
+        m_on_viewport_changed_callback.call();
+        
+        if (m_canvas != nullptr)
+            m_canvas->Refresh();
+    }
+}
+
+void GLCanvas3D::set_viewport_from_scene(const GLCanvas3D& other)
+{
+    m_camera.phi = other.m_camera.phi;
+    m_camera.set_theta(other.m_camera.get_theta());
+    m_camera.target = other.m_camera.target;
+    m_camera.zoom = other.m_camera.zoom;
+    m_dirty = true;
+}
+
+void GLCanvas3D::update_volumes_colors_by_extruder()
+{
+    if (m_config != nullptr)
+        m_volumes.update_colors_by_extruder(m_config);
+}
+
+void GLCanvas3D::render()
+{
+    if (m_canvas == nullptr)
+        return;
+
+    if (!_is_shown_on_screen())
+        return;
+
+    // ensures that the proper context is selected and that this canvas is initialized
+    if (!set_current() || !_3DScene::init(m_canvas))
+        return;
+
+    if (m_force_zoom_to_bed_enabled)
+        _force_zoom_to_bed();
+
+    _camera_tranform();
+
+    GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f };
+    ::glLightfv(GL_LIGHT1, GL_POSITION, position_cam);
+    GLfloat position_top[4] = { -0.5f, -0.5f, 1.0f, 0.0f };
+    ::glLightfv(GL_LIGHT0, GL_POSITION, position_top);
+
+    float theta = m_camera.get_theta();
+    bool is_custom_bed = m_bed.is_custom();
+
+    _picking_pass();
+    _render_background();
+    // untextured bed needs to be rendered before objects
+    if (is_custom_bed)
+    {
+        _render_bed(theta);
+        // disable depth testing so that axes are not covered by ground
+        _render_axes(false);
+    }
+    _render_objects();
+    // textured bed needs to be rendered after objects
+    if (!is_custom_bed)
+    {
+        _render_axes(true);
+        _render_bed(theta);
+    }
+    _render_cutting_plane();
+    _render_warning_texture();
+    _render_legend_texture();
+    _render_gizmo();
+    _render_layer_editing_overlay();
+
+    m_canvas->SwapBuffers();
+}
+
+std::vector<double> GLCanvas3D::get_current_print_zs(bool active_only) const
+{
+    return m_volumes.get_current_print_zs(active_only);
+}
+
+void GLCanvas3D::set_toolpaths_range(double low, double high)
+{
+    m_volumes.set_range(low, high);
+}
+
+std::vector<int> GLCanvas3D::load_object(const ModelObject& model_object, int obj_idx, std::vector<int> instance_idxs)
+{
+    if (instance_idxs.empty())
+    {
+        for (unsigned int i = 0; i < model_object.instances.size(); ++i)
+        {
+            instance_idxs.push_back(i);
+        }
+    }
+    return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_select_by, m_drag_by, m_use_VBOs && m_initialized);
+}
+
+std::vector<int> GLCanvas3D::load_object(const Model& model, int obj_idx)
+{
+    if ((0 <= obj_idx) && (obj_idx < (int)model.objects.size()))
+    {
+        const ModelObject* model_object = model.objects[obj_idx];
+        if (model_object != nullptr)
+            return load_object(*model_object, obj_idx, std::vector<int>());
+    }
+
+    return std::vector<int>();
+}
+
+void GLCanvas3D::reload_scene(bool force)
+{
+    if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr))
+        return;
+
+    reset_volumes();
+    set_bed_shape(dynamic_cast<const ConfigOptionPoints*>(m_config->option("bed_shape"))->values);
+
+    if (!m_canvas->IsShown() && !force)
+    {
+        m_reload_delayed = true;
+        return;
+    }
+
+    m_reload_delayed = false;
+
+    m_objects_volumes_idxs.clear();
+
+    for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx)
+    {
+        m_objects_volumes_idxs.push_back(load_object(*m_model, obj_idx));
+    }
+
+    update_volumes_selection(m_objects_selections);
+
+    if (m_config->has("nozzle_diameter"))
+    {
+        // Should the wipe tower be visualized ?
+        unsigned int extruders_count = (unsigned int)dynamic_cast<const ConfigOptionFloats*>(m_config->option("nozzle_diameter"))->values.size();
+
+        bool semm = dynamic_cast<const ConfigOptionBool*>(m_config->option("single_extruder_multi_material"))->value;
+        bool wt = dynamic_cast<const ConfigOptionBool*>(m_config->option("wipe_tower"))->value;
+        bool co = dynamic_cast<const ConfigOptionBool*>(m_config->option("complete_objects"))->value;
+
+        if ((extruders_count > 1) && semm && wt && !co)
+        {
+            // Height of a print (Show at least a slab)
+            coordf_t height = std::max(m_model->bounding_box().max.z, 10.0);
+
+            float x = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_x"))->value;
+            float y = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_y"))->value;
+            float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_width"))->value;
+            float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
+
+            m_volumes.load_wipe_tower_preview(1000, x, y, w, 15.0f * (float)(extruders_count - 1), (float)height, a, m_use_VBOs && m_initialized);
+        }
+    }
+
+    update_volumes_colors_by_extruder();
+
+    // checks for geometry outside the print volume to render it accordingly
+    if (!m_volumes.empty())
+    {
+        bool contained = m_volumes.check_outside_state(m_config);
+        if (!contained)
+        {
+            enable_warning_texture(true);
+            _3DScene::generate_warning_texture(L("Detected object outside print volume"));
+            m_on_enable_action_buttons_callback.call(false);
+        }
+        else
+        {
+            enable_warning_texture(false);
+            m_volumes.reset_outside_state();
+            _3DScene::reset_warning_texture();
+            m_on_enable_action_buttons_callback.call(!m_model->objects.empty());
+        }
+    }
+    else
+    {
+        enable_warning_texture(false);
+        _3DScene::reset_warning_texture();
+    }
+}
+
+void GLCanvas3D::load_print_toolpaths()
+{
+    if (m_print == nullptr)
+        return;
+
+    if (!m_print->state.is_done(psSkirt) || !m_print->state.is_done(psBrim))
+        return;
+
+    if (!m_print->has_skirt() && (m_print->config.brim_width.value == 0))
+        return;
+
+    const float color[] = { 0.5f, 1.0f, 0.5f, 1.0f }; // greenish
+
+    // number of skirt layers
+    size_t total_layer_count = 0;
+    for (const PrintObject* print_object : m_print->objects)
+    {
+        total_layer_count = std::max(total_layer_count, print_object->total_layer_count());
+    }
+    size_t skirt_height = m_print->has_infinite_skirt() ? total_layer_count : std::min<size_t>(m_print->config.skirt_height.value, total_layer_count);
+    if ((skirt_height == 0) && (m_print->config.brim_width.value > 0))
+        skirt_height = 1;
+
+    // get first skirt_height layers (maybe this should be moved to a PrintObject method?)
+    const PrintObject* object0 = m_print->objects.front();
+    std::vector<float> print_zs;
+    print_zs.reserve(skirt_height * 2);
+    for (size_t i = 0; i < std::min(skirt_height, object0->layers.size()); ++i)
+    {
+        print_zs.push_back(float(object0->layers[i]->print_z));
+    }
+    //FIXME why there are support layers?
+    for (size_t i = 0; i < std::min(skirt_height, object0->support_layers.size()); ++i)
+    {
+        print_zs.push_back(float(object0->support_layers[i]->print_z));
+    }
+    sort_remove_duplicates(print_zs);
+    if (print_zs.size() > skirt_height)
+        print_zs.erase(print_zs.begin() + skirt_height, print_zs.end());
+
+    m_volumes.volumes.emplace_back(new GLVolume(color));
+    GLVolume& volume = *m_volumes.volumes.back();
+    for (size_t i = 0; i < skirt_height; ++i) {
+        volume.print_zs.push_back(print_zs[i]);
+        volume.offsets.push_back(volume.indexed_vertex_array.quad_indices.size());
+        volume.offsets.push_back(volume.indexed_vertex_array.triangle_indices.size());
+        if (i == 0)
+            _3DScene::extrusionentity_to_verts(m_print->brim, print_zs[i], Point(0, 0), volume);
+
+        _3DScene::extrusionentity_to_verts(m_print->skirt, print_zs[i], Point(0, 0), volume);
+    }
+    volume.bounding_box = volume.indexed_vertex_array.bounding_box();
+    volume.indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized);
+}
+
+void GLCanvas3D::load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors)
+{
+    std::vector<float> tool_colors = _parse_colors(str_tool_colors);
+
+    struct Ctxt
+    {
+        const Points                *shifted_copies;
+        std::vector<const Layer*>    layers;
+        bool                         has_perimeters;
+        bool                         has_infill;
+        bool                         has_support;
+        const std::vector<float>*    tool_colors;
+
+        // Number of vertices (each vertex is 6x4=24 bytes long)
+        static const size_t          alloc_size_max() { return 131072; } // 3.15MB
+        //        static const size_t          alloc_size_max    () { return 65536; } // 1.57MB 
+        //        static const size_t          alloc_size_max    () { return 32768; } // 786kB
+        static const size_t          alloc_size_reserve() { return alloc_size_max() * 2; }
+
+        static const float*          color_perimeters() { static float color[4] = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow
+        static const float*          color_infill() { static float color[4] = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish
+        static const float*          color_support() { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish
+
+        // For cloring by a tool, return a parsed color.
+        bool                         color_by_tool() const { return tool_colors != nullptr; }
+        size_t                       number_tools()  const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; }
+        const float*                 color_tool(size_t tool) const { return tool_colors->data() + tool * 4; }
+        int                          volume_idx(int extruder, int feature) const
+        {
+            return this->color_by_tool() ? std::min<int>(this->number_tools() - 1, std::max<int>(extruder - 1, 0)) : feature;
+        }
+    } ctxt;
+
+    ctxt.shifted_copies = &print_object._shifted_copies;
+
+    // order layers by print_z
+    ctxt.layers.reserve(print_object.layers.size() + print_object.support_layers.size());
+    for (const Layer *layer : print_object.layers)
+        ctxt.layers.push_back(layer);
+    for (const Layer *layer : print_object.support_layers)
+        ctxt.layers.push_back(layer);
+    std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; });
+
+    // Maximum size of an allocation block: 32MB / sizeof(float)
+    ctxt.has_perimeters = print_object.state.is_done(posPerimeters);
+    ctxt.has_infill = print_object.state.is_done(posInfill);
+    ctxt.has_support = print_object.state.is_done(posSupportMaterial);
+    ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors;
+
+    BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start";
+
+    //FIXME Improve the heuristics for a grain size.
+    size_t          grain_size = std::max(ctxt.layers.size() / 16, size_t(1));
+    tbb::spin_mutex new_volume_mutex;
+    auto            new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* {
+        auto *volume = new GLVolume(color);
+        new_volume_mutex.lock();
+        volume->outside_printer_detection_enabled = false;
+        m_volumes.volumes.emplace_back(volume);
+        new_volume_mutex.unlock();
+        return volume;
+    };
+    const size_t   volumes_cnt_initial = m_volumes.volumes.size();
+    std::vector<GLVolumeCollection> volumes_per_thread(ctxt.layers.size());
+    tbb::parallel_for(
+        tbb::blocked_range<size_t>(0, ctxt.layers.size(), grain_size),
+        [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) {
+        std::vector<GLVolume*> vols;
+        if (ctxt.color_by_tool()) {
+            for (size_t i = 0; i < ctxt.number_tools(); ++i)
+                vols.emplace_back(new_volume(ctxt.color_tool(i)));
+        }
+        else
+            vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) };
+        for (GLVolume *vol : vols)
+            vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
+        for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) {
+            const Layer *layer = ctxt.layers[idx_layer];
+            for (size_t i = 0; i < vols.size(); ++i) {
+                GLVolume &vol = *vols[i];
+                if (vol.print_zs.empty() || vol.print_zs.back() != layer->print_z) {
+                    vol.print_zs.push_back(layer->print_z);
+                    vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
+                    vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
+                }
+            }
+            for (const Point &copy : *ctxt.shifted_copies) {
+                for (const LayerRegion *layerm : layer->regions) {
+                    if (ctxt.has_perimeters)
+                        _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy,
+                        *vols[ctxt.volume_idx(layerm->region()->config.perimeter_extruder.value, 0)]);
+                    if (ctxt.has_infill) {
+                        for (const ExtrusionEntity *ee : layerm->fills.entities) {
+                            // fill represents infill extrusions of a single island.
+                            const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
+                            if (!fill->entities.empty())
+                                _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy,
+                                *vols[ctxt.volume_idx(
+                                is_solid_infill(fill->entities.front()->role()) ?
+                                layerm->region()->config.solid_infill_extruder :
+                                layerm->region()->config.infill_extruder,
+                                1)]);
+                        }
+                    }
+                }
+                if (ctxt.has_support) {
+                    const SupportLayer *support_layer = dynamic_cast<const SupportLayer*>(layer);
+                    if (support_layer) {
+                        for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities)
+                            _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy,
+                            *vols[ctxt.volume_idx(
+                            (extrusion_entity->role() == erSupportMaterial) ?
+                            support_layer->object()->config.support_material_extruder :
+                            support_layer->object()->config.support_material_interface_extruder,
+                            2)]);
+                    }
+                }
+            }
+            for (size_t i = 0; i < vols.size(); ++i) {
+                GLVolume &vol = *vols[i];
+                if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) {
+                    // Store the vertex arrays and restart their containers, 
+                    vols[i] = new_volume(vol.color);
+                    GLVolume &vol_new = *vols[i];
+                    // Assign the large pre-allocated buffers to the new GLVolume.
+                    vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array);
+                    // Copy the content back to the old GLVolume.
+                    vol.indexed_vertex_array = vol_new.indexed_vertex_array;
+                    // Finalize a bounding box of the old GLVolume.
+                    vol.bounding_box = vol.indexed_vertex_array.bounding_box();
+                    // Clear the buffers, but keep them pre-allocated.
+                    vol_new.indexed_vertex_array.clear();
+                    // Just make sure that clear did not clear the reserved memory.
+                    vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
+                }
+            }
+        }
+        for (GLVolume *vol : vols) {
+            vol->bounding_box = vol->indexed_vertex_array.bounding_box();
+            vol->indexed_vertex_array.shrink_to_fit();
+        }
+    });
+
+    BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results";
+    // Remove empty volumes from the newly added volumes.
+    m_volumes.volumes.erase(
+        std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(),
+        [](const GLVolume *volume) { return volume->empty(); }),
+        m_volumes.volumes.end());
+    for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i)
+        m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized);
+
+    BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end";
+}
+
+void GLCanvas3D::load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors)
+{
+    if ((m_print == nullptr) || m_print->m_wipe_tower_tool_changes.empty())
+        return;
+
+    if (!m_print->state.is_done(psWipeTower))
+        return;
+
+    std::vector<float> tool_colors = _parse_colors(str_tool_colors);
+
+    struct Ctxt
+    {
+        const Print                 *print;
+        const std::vector<float>    *tool_colors;
+
+        // Number of vertices (each vertex is 6x4=24 bytes long)
+        static const size_t          alloc_size_max() { return 131072; } // 3.15MB
+        static const size_t          alloc_size_reserve() { return alloc_size_max() * 2; }
+
+        static const float*          color_support() { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish
+
+        // For cloring by a tool, return a parsed color.
+        bool                         color_by_tool() const { return tool_colors != nullptr; }
+        size_t                       number_tools()  const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; }
+        const float*                 color_tool(size_t tool) const { return tool_colors->data() + tool * 4; }
+        int                          volume_idx(int tool, int feature) const
+        {
+            return this->color_by_tool() ? std::min<int>(this->number_tools() - 1, std::max<int>(tool, 0)) : feature;
+        }
+
+        const std::vector<WipeTower::ToolChangeResult>& tool_change(size_t idx) {
+            return priming.empty() ?
+                ((idx == print->m_wipe_tower_tool_changes.size()) ? final : print->m_wipe_tower_tool_changes[idx]) :
+                ((idx == 0) ? priming : (idx == print->m_wipe_tower_tool_changes.size() + 1) ? final : print->m_wipe_tower_tool_changes[idx - 1]);
+        }
+        std::vector<WipeTower::ToolChangeResult> priming;
+        std::vector<WipeTower::ToolChangeResult> final;
+    } ctxt;
+
+    ctxt.print = m_print;
+    ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors;
+    if (m_print->m_wipe_tower_priming)
+        ctxt.priming.emplace_back(*m_print->m_wipe_tower_priming.get());
+    if (m_print->m_wipe_tower_final_purge)
+        ctxt.final.emplace_back(*m_print->m_wipe_tower_final_purge.get());
+
+    BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start";
+
+    //FIXME Improve the heuristics for a grain size.
+    size_t          n_items = m_print->m_wipe_tower_tool_changes.size() + (ctxt.priming.empty() ? 0 : 1);
+    size_t          grain_size = std::max(n_items / 128, size_t(1));
+    tbb::spin_mutex new_volume_mutex;
+    auto            new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* {
+        auto *volume = new GLVolume(color);
+        new_volume_mutex.lock();
+        volume->outside_printer_detection_enabled = false;
+        m_volumes.volumes.emplace_back(volume);
+        new_volume_mutex.unlock();
+        return volume;
+    };
+    const size_t   volumes_cnt_initial = m_volumes.volumes.size();
+    std::vector<GLVolumeCollection> volumes_per_thread(n_items);
+    tbb::parallel_for(
+        tbb::blocked_range<size_t>(0, n_items, grain_size),
+        [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) {
+        // Bounding box of this slab of a wipe tower.
+        std::vector<GLVolume*> vols;
+        if (ctxt.color_by_tool()) {
+            for (size_t i = 0; i < ctxt.number_tools(); ++i)
+                vols.emplace_back(new_volume(ctxt.color_tool(i)));
+        }
+        else
+            vols = { new_volume(ctxt.color_support()) };
+        for (GLVolume *volume : vols)
+            volume->indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
+        for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) {
+            const std::vector<WipeTower::ToolChangeResult> &layer = ctxt.tool_change(idx_layer);
+            for (size_t i = 0; i < vols.size(); ++i) {
+                GLVolume &vol = *vols[i];
+                if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) {
+                    vol.print_zs.push_back(layer.front().print_z);
+                    vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
+                    vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
+                }
+            }
+            for (const WipeTower::ToolChangeResult &extrusions : layer) {
+                for (size_t i = 1; i < extrusions.extrusions.size();) {
+                    const WipeTower::Extrusion &e = extrusions.extrusions[i];
+                    if (e.width == 0.) {
+                        ++i;
+                        continue;
+                    }
+                    size_t j = i + 1;
+                    if (ctxt.color_by_tool())
+                        for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++j);
+                    else
+                        for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++j);
+                    size_t              n_lines = j - i;
+                    Lines               lines;
+                    std::vector<double> widths;
+                    std::vector<double> heights;
+                    lines.reserve(n_lines);
+                    widths.reserve(n_lines);
+                    heights.assign(n_lines, extrusions.layer_height);
+                    for (; i < j; ++i) {
+                        const WipeTower::Extrusion &e = extrusions.extrusions[i];
+                        assert(e.width > 0.f);
+                        const WipeTower::Extrusion &e_prev = *(&e - 1);
+                        lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y));
+                        widths.emplace_back(e.width);
+                    }
+                    _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z,
+                        *vols[ctxt.volume_idx(e.tool, 0)]);
+                }
+            }
+        }
+        for (size_t i = 0; i < vols.size(); ++i) {
+            GLVolume &vol = *vols[i];
+            if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) {
+                // Store the vertex arrays and restart their containers, 
+                vols[i] = new_volume(vol.color);
+                GLVolume &vol_new = *vols[i];
+                // Assign the large pre-allocated buffers to the new GLVolume.
+                vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array);
+                // Copy the content back to the old GLVolume.
+                vol.indexed_vertex_array = vol_new.indexed_vertex_array;
+                // Finalize a bounding box of the old GLVolume.
+                vol.bounding_box = vol.indexed_vertex_array.bounding_box();
+                // Clear the buffers, but keep them pre-allocated.
+                vol_new.indexed_vertex_array.clear();
+                // Just make sure that clear did not clear the reserved memory.
+                vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
+            }
+        }
+        for (GLVolume *vol : vols) {
+            vol->bounding_box = vol->indexed_vertex_array.bounding_box();
+            vol->indexed_vertex_array.shrink_to_fit();
+        }
+    });
+
+    BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results";
+    // Remove empty volumes from the newly added volumes.
+    m_volumes.volumes.erase(
+        std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(),
+        [](const GLVolume *volume) { return volume->empty(); }),
+        m_volumes.volumes.end());
+    for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i)
+        m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized);
+
+    BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end";
+}
+
+void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors)
+{
+    if ((m_canvas != nullptr) && (m_print != nullptr))
+    {
+        // ensures that the proper context is selected
+        if (!set_current())
+            return;
+
+        if (m_volumes.empty())
+        {
+            std::vector<float> tool_colors = _parse_colors(str_tool_colors);
+            
+            m_gcode_preview_volume_index.reset();
+            
+            _load_gcode_extrusion_paths(preview_data, tool_colors);
+            _load_gcode_travel_paths(preview_data, tool_colors);
+            _load_gcode_retractions(preview_data);
+            _load_gcode_unretractions(preview_data);
+            
+            if (m_volumes.empty())
+                _3DScene::reset_legend_texture();
+            else
+            {
+                _3DScene::generate_legend_texture(preview_data, tool_colors);
+                
+                // removes empty volumes
+                m_volumes.volumes.erase(std::remove_if(m_volumes.volumes.begin(), m_volumes.volumes.end(),
+                    [](const GLVolume* volume) { return volume->print_zs.empty(); }), m_volumes.volumes.end());
+
+                _load_shells();
+            }
+        }
+        
+        _update_gcode_volumes_visibility(preview_data);
+    }
+}
+
+void GLCanvas3D::register_on_viewport_changed_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_viewport_changed_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_double_click_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_double_click_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_right_click_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_right_click_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_select_object_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_select_object_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_model_update_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_model_update_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_remove_object_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_remove_object_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_arrange_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_arrange_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_rotate_object_left_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_rotate_object_left_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_rotate_object_right_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_rotate_object_right_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_scale_object_uniformly_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_scale_object_uniformly_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_increase_objects_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_increase_objects_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_decrease_objects_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_decrease_objects_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_instance_moved_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_instance_moved_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_wipe_tower_moved_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_wipe_tower_moved_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_enable_action_buttons_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_enable_action_buttons_callback.register_callback(callback);
+}
+
+void GLCanvas3D::register_on_gizmo_scale_uniformly_callback(void* callback)
+{
+    if (callback != nullptr)
+        m_on_gizmo_scale_uniformly_callback.register_callback(callback);
+}
+
+void GLCanvas3D::bind_event_handlers()
+{
+    if (m_canvas != nullptr)
+    {
+        m_canvas->Bind(wxEVT_SIZE, &GLCanvas3D::on_size, this);
+        m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this);
+        m_canvas->Bind(wxEVT_CHAR, &GLCanvas3D::on_char, this);
+        m_canvas->Bind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this);
+        m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
+        m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this);
+        m_canvas->Bind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this);
+        m_canvas->Bind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this);
+        m_canvas->Bind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this);
+        m_canvas->Bind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this);
+        m_canvas->Bind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this);
+        m_canvas->Bind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this);
+        m_canvas->Bind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this);
+        m_canvas->Bind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this);
+        m_canvas->Bind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this);
+        m_canvas->Bind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this);
+        m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this);
+        m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
+        m_canvas->Bind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key_down, this);
+    }
+}
+
+void GLCanvas3D::unbind_event_handlers()
+{
+    if (m_canvas != nullptr)
+    {
+        m_canvas->Unbind(wxEVT_SIZE, &GLCanvas3D::on_size, this);
+        m_canvas->Unbind(wxEVT_IDLE, &GLCanvas3D::on_idle, this);
+        m_canvas->Unbind(wxEVT_CHAR, &GLCanvas3D::on_char, this);
+        m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this);
+        m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
+        m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this);
+        m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this);
+        m_canvas->Unbind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this);
+        m_canvas->Unbind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this);
+        m_canvas->Unbind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this);
+        m_canvas->Unbind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this);
+        m_canvas->Unbind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this);
+        m_canvas->Unbind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this);
+        m_canvas->Unbind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this);
+        m_canvas->Unbind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this);
+        m_canvas->Unbind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this);
+        m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this);
+        m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
+        m_canvas->Unbind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key_down, this);
+    }
+}
+
+void GLCanvas3D::on_size(wxSizeEvent& evt)
+{
+    m_dirty = true;
+}
+
+void GLCanvas3D::on_idle(wxIdleEvent& evt)
+{
+    if (!m_dirty)
+        return;
+
+    _refresh_if_shown_on_screen();
+}
+
+void GLCanvas3D::on_char(wxKeyEvent& evt)
+{
+    if (evt.HasModifiers())
+        evt.Skip();
+    else
+    {
+        int keyCode = evt.GetKeyCode();
+        switch (keyCode - 48)
+        {
+        // numerical input
+        case 0: { select_view("iso"); break; }
+        case 1: { select_view("top"); break; }
+        case 2: { select_view("bottom"); break; }
+        case 3: { select_view("front"); break; }
+        case 4: { select_view("rear"); break; }
+        case 5: { select_view("left"); break; }
+        case 6: { select_view("right"); break; }
+        default:
+            {
+                // text input
+                switch (keyCode)
+                {
+                // key +
+                case 43: { m_on_increase_objects_callback.call(); break; }
+                // key -
+                case 45: { m_on_decrease_objects_callback.call(); break; }
+                // key A/a
+                case 65:
+                case 97: { m_on_arrange_callback.call(); break; }
+                // key B/b
+                case 66:
+                case 98: { zoom_to_bed(); break; }
+                // key L/l
+                case 76:
+                case 108: { m_on_rotate_object_left_callback.call(); break; }
+                // key R/r
+                case 82:
+                case 114: { m_on_rotate_object_right_callback.call(); break; }
+                // key S/s
+                case 83:
+                case 115: { m_on_scale_object_uniformly_callback.call(); break; }
+                // key Z/z
+                case 90:
+                case 122: { zoom_to_volumes(); break; }
+                default:
+                {
+                    evt.Skip();
+                    break;
+                }
+                }
+            }
+        }
+    }
+}
+
+void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
+{
+    // Ignore the wheel events if the middle button is pressed.
+    if (evt.MiddleIsDown())
+        return;
+
+    // Performs layers editing updates, if enabled
+    if (is_layers_editing_enabled())
+    {
+        int object_idx_selected = _get_first_selected_object_id();
+        if (object_idx_selected != -1)
+        {
+            // A volume is selected. Test, whether hovering over a layer thickness bar.
+            if (m_layers_editing.bar_rect_contains(*this, (float)evt.GetX(), (float)evt.GetY()))
+            {
+                // Adjust the width of the selection.
+                m_layers_editing.band_width = std::max(std::min(m_layers_editing.band_width * (1.0f + 0.1f * (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta()), 10.0f), 1.5f);
+                if (m_canvas != nullptr)
+                    m_canvas->Refresh();
+                
+                return;
+            }
+        }
+    }
+
+    // Calculate the zoom delta and apply it to the current zoom factor
+    float zoom = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
+    zoom = std::max(std::min(zoom, 4.0f), -4.0f) / 10.0f;
+    zoom = get_camera_zoom() / (1.0f - zoom);
+    
+    // Don't allow to zoom too far outside the scene.
+    float zoom_min = _get_zoom_to_bounding_box_factor(_max_bounding_box());
+    if (zoom_min > 0.0f)
+        zoom = std::max(zoom, zoom_min * 0.8f);
+    
+    m_camera.zoom = zoom;
+    m_on_viewport_changed_callback.call();
+
+    _refresh_if_shown_on_screen();
+}
+
+void GLCanvas3D::on_timer(wxTimerEvent& evt)
+{
+    if (m_layers_editing.state != LayersEditing::Editing)
+        return;
+
+    _perform_layer_editing_action();
+}
+
+void GLCanvas3D::on_mouse(wxMouseEvent& evt)
+{
+    Point pos(evt.GetX(), evt.GetY());
+
+    int selected_object_idx = _get_first_selected_object_id();
+    int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1;
+    m_layers_editing.last_object_id = layer_editing_object_idx;
+    bool gizmos_overlay_contains_mouse = m_gizmos.overlay_contains_mouse(*this, m_mouse.position);
+
+    if (evt.Entering())
+    {
+#if defined(__WXMSW__) || defined(__linux__)
+        // On Windows and Linux needs focus in order to catch key events
+        if (m_canvas != nullptr)
+            m_canvas->SetFocus();
+
+        m_mouse.set_start_position_2D_as_invalid();
+#endif
+    } 
+    else if (evt.LeftDClick() && (m_hover_volume_id != -1))
+        m_on_double_click_callback.call();
+    else if (evt.LeftDown() || evt.RightDown())
+    {
+        // If user pressed left or right button we first check whether this happened
+        // on a volume or not.
+        int volume_idx = m_hover_volume_id;
+        m_layers_editing.state = LayersEditing::Unknown;
+        if ((layer_editing_object_idx != -1) && m_layers_editing.bar_rect_contains(*this, pos.x, pos.y))
+        {
+            // A volume is selected and the mouse is inside the layer thickness bar.
+            // Start editing the layer height.
+            m_layers_editing.state = LayersEditing::Editing;
+            _perform_layer_editing_action(&evt);
+        }
+        else if ((layer_editing_object_idx != -1) && m_layers_editing.reset_rect_contains(*this, pos.x, pos.y))
+        {
+            if (evt.LeftDown())
+            {
+                // A volume is selected and the mouse is inside the reset button.
+                m_print->get_object(layer_editing_object_idx)->reset_layer_height_profile();
+                // Index 2 means no editing, just wait for mouse up event.
+                m_layers_editing.state = LayersEditing::Completed;
+
+                m_dirty = true;
+            }
+        }
+        else if ((selected_object_idx != -1) && gizmos_overlay_contains_mouse)
+        {
+            m_gizmos.update_on_off_state(*this, m_mouse.position);
+            _update_gizmos_data();
+            m_dirty = true;
+        }
+        else if ((selected_object_idx != -1) && m_gizmos.grabber_contains_mouse())
+        {            
+            _update_gizmos_data();
+            m_gizmos.start_dragging();
+            m_dirty = true;
+        }
+        else
+        {
+            // Select volume in this 3D canvas.
+            // Don't deselect a volume if layer editing is enabled. We want the object to stay selected
+            // during the scene manipulation.
+
+            if (m_picking_enabled && ((volume_idx != -1) || !is_layers_editing_enabled()))
+            {
+                if (volume_idx != -1)
+                {
+                    deselect_volumes();
+                    select_volume(volume_idx);
+                    int group_id = m_volumes.volumes[volume_idx]->select_group_id;
+                    if (group_id != -1)
+                    {
+                        for (GLVolume* vol : m_volumes.volumes)
+                        {
+                            if ((vol != nullptr) && (vol->select_group_id == group_id))
+                                vol->selected = true;
+                        }
+                    }
+
+                    if (m_gizmos.is_running())
+                        _update_gizmos_data();
+
+                    m_dirty = true;
+                }
+            }
+
+            // propagate event through callback
+            if (m_picking_enabled && (volume_idx != -1))
+                _on_select(volume_idx);
+
+            if (volume_idx != -1)
+            {
+                if (evt.LeftDown() && m_moving_enabled)
+                {
+                    // The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate pos x, y,
+                    // an converts the screen space coordinate to unscaled object space.
+                    Pointf3 pos3d = (volume_idx == -1) ? Pointf3(DBL_MAX, DBL_MAX) : _mouse_to_3d(pos);
+
+                    // Only accept the initial position, if it is inside the volume bounding box.
+                    BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box();
+                    volume_bbox.offset(1.0);
+                    if (volume_bbox.contains(pos3d))
+                    {
+                        // The dragging operation is initiated.
+                        m_mouse.drag.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.
+                        m_mouse.drag.volume_center_offset = pos3d.vector_to(volume_bbox.center());
+                    }
+                }
+                else if (evt.RightDown())
+                {
+                    // if right clicking on volume, propagate event through callback
+                    if (m_volumes.volumes[volume_idx]->hover)
+                        m_on_right_click_callback.call(pos.x, pos.y);
+                }
+            }
+        }
+    }
+    else if (evt.Dragging() && evt.LeftIsDown() && !gizmos_overlay_contains_mouse && (m_layers_editing.state == LayersEditing::Unknown) && (m_mouse.drag.volume_idx != -1))
+    {
+        m_mouse.dragging = true;
+
+        // Get new position at the same Z of the initial click point.
+        float z0 = 0.0f;
+        float z1 = 1.0f;
+        Pointf3 cur_pos = Linef3(_mouse_to_3d(pos, &z0), _mouse_to_3d(pos, &z1)).intersect_plane(m_mouse.drag.start_position_3D.z);
+
+        // Clip the new position, so the object center remains close to the bed.
+        cur_pos.translate(m_mouse.drag.volume_center_offset);
+        Point cur_pos2(scale_(cur_pos.x), scale_(cur_pos.y));
+        if (!m_bed.contains(cur_pos2))
+        {
+            Point ip = m_bed.point_projection(cur_pos2);
+            cur_pos.x = unscale(ip.x);
+            cur_pos.y = unscale(ip.y);
+        }
+        cur_pos.translate(m_mouse.drag.volume_center_offset.negative());
+
+        // 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];
+        // Get all volumes belonging to the same group, if any.
+        std::vector<GLVolume*> volumes;
+        if (volume->drag_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);
+            }
+        }
+
+        // Apply new temporary volume origin and ignore Z.
+        for (GLVolume* v : volumes)
+        {
+            v->origin.translate(vector.x, vector.y, 0.0);
+        }
+
+        m_mouse.drag.start_position_3D = cur_pos;
+
+        m_dirty = true;
+    }
+    else if (evt.Dragging() && m_gizmos.is_dragging())
+    {
+        m_mouse.dragging = true;
+
+        const Pointf3& cur_pos = _mouse_to_bed_3d(pos);
+        m_gizmos.update(Pointf(cur_pos.x, cur_pos.y));
+
+        m_on_gizmo_scale_uniformly_callback.call((double)m_gizmos.get_scale());
+        m_dirty = true;
+    }
+    else if (evt.Dragging() && !gizmos_overlay_contains_mouse)
+    {
+        m_mouse.dragging = true;
+
+        if ((m_layers_editing.state != LayersEditing::Unknown) && (layer_editing_object_idx != -1))
+        {
+            if (m_layers_editing.state == LayersEditing::Editing)
+                _perform_layer_editing_action(&evt);
+        }
+        else if (evt.LeftIsDown())
+        {
+            // if dragging over blank area with left button, rotate
+            if (m_mouse.is_start_position_3D_defined())
+            {
+                const Pointf3& orig = m_mouse.drag.start_position_3D;
+                m_camera.phi += (((float)pos.x - (float)orig.x) * TRACKBALLSIZE);
+                m_camera.set_theta(m_camera.get_theta() - ((float)pos.y - (float)orig.y) * TRACKBALLSIZE);
+
+                m_on_viewport_changed_callback.call();
+
+                m_dirty = true;
+            }
+            m_mouse.drag.start_position_3D = Pointf3((coordf_t)pos.x, (coordf_t)pos.y, 0.0);
+        }
+        else if (evt.MiddleIsDown() || evt.RightIsDown())
+        {
+            // If dragging over blank area with right button, pan.
+            if (m_mouse.is_start_position_2D_defined())
+            {
+                // get point in model space at Z = 0
+                float z = 0.0f;
+                const Pointf3& cur_pos = _mouse_to_3d(pos, &z);
+                Pointf3 orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z);
+                Pointf3 camera_target = m_camera.target;
+                camera_target.translate(orig.vector_to(cur_pos).negative());
+                m_camera.target = camera_target;
+
+                m_on_viewport_changed_callback.call();
+
+                m_dirty = true;
+            }
+            
+            m_mouse.drag.start_position_2D = pos;
+        }
+    }
+    else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
+    {
+        if (m_layers_editing.state != LayersEditing::Unknown)
+        {
+            m_layers_editing.state = LayersEditing::Unknown;
+            _stop_timer();
+
+            if (layer_editing_object_idx != -1)
+                m_on_model_update_callback.call();
+        }
+        else if ((m_mouse.drag.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;
+            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)
+                        volume_idxs.push_back(i);
+                }
+            }
+            
+            _on_move(volume_idxs);
+        }
+        else if (!m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled())
+        {
+            // deselect and propagate event through callback
+            if (m_picking_enabled)
+            {
+                deselect_volumes();
+                _on_select(-1);
+            }
+        }
+        else if (evt.LeftUp() && m_gizmos.is_dragging())
+        {
+            m_gizmos.stop_dragging();
+        }
+
+        m_mouse.drag.volume_idx = -1;
+        m_mouse.set_start_position_3D_as_invalid();
+        m_mouse.set_start_position_2D_as_invalid();
+        m_mouse.dragging = false;
+    }
+    else if (evt.Moving())
+    {
+        m_mouse.position = Pointf((coordf_t)pos.x, (coordf_t)pos.y);
+        // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over.
+        if (m_picking_enabled)
+            m_dirty = true;
+    }
+    else
+        evt.Skip();
+}
+
+void GLCanvas3D::on_paint(wxPaintEvent& evt)
+{
+    render();
+}
+
+void GLCanvas3D::on_key_down(wxKeyEvent& evt)
+{
+    if (evt.HasModifiers())
+        evt.Skip();
+    else
+    {
+        int key = evt.GetKeyCode();
+        if (key == WXK_DELETE)
+            m_on_remove_object_callback.call();
+        else
+            evt.Skip();
+    }
+}
+
+Size GLCanvas3D::get_canvas_size() const
+{
+    int w = 0;
+    int h = 0;
+
+    if (m_canvas != nullptr)
+        m_canvas->GetSize(&w, &h);
+
+    return Size(w, h);
+}
+
+Point GLCanvas3D::get_local_mouse_position() const
+{
+    if (m_canvas == nullptr)
+        return Point();
+
+    wxPoint mouse_pos = m_canvas->ScreenToClient(wxGetMousePosition());
+    return Point(mouse_pos.x, mouse_pos.y);
+}
+
+bool GLCanvas3D::_is_shown_on_screen() const
+{
+    return (m_canvas != nullptr) ? m_active && m_canvas->IsShownOnScreen() : false;
+}
+
+void GLCanvas3D::_force_zoom_to_bed()
+{
+    zoom_to_bed();
+    m_force_zoom_to_bed_enabled = false;
+}
+
+void GLCanvas3D::_resize(unsigned int w, unsigned int h)
+{
+    if (m_context == nullptr)
+        return;
+
+    set_current();
+    ::glViewport(0, 0, w, h);
+
+    ::glMatrixMode(GL_PROJECTION);
+    ::glLoadIdentity();
+
+    const BoundingBoxf3& bbox = _max_bounding_box();
+
+    switch (m_camera.type)
+    {
+    case Camera::Ortho:
+    {
+        float w2 = w;
+        float h2 = h;
+        float two_zoom = 2.0f * get_camera_zoom();
+        if (two_zoom != 0.0f)
+        {
+            float inv_two_zoom = 1.0f / two_zoom;
+            w2 *= inv_two_zoom;
+            h2 *= inv_two_zoom;
+        }
+
+        // FIXME: calculate a tighter value for depth will improve z-fighting
+        float depth = 5.0f * (float)bbox.max_size();
+        ::glOrtho(-w2, w2, -h2, h2, -depth, depth);
+
+        break;
+    }
+//    case Camera::Perspective:
+//    {
+//        float bbox_r = (float)bbox.radius();
+//        float fov = PI * 45.0f / 180.0f;
+//        float fov_tan = tan(0.5f * fov);
+//        float cam_distance = 0.5f * bbox_r / fov_tan;
+//        m_camera.distance = cam_distance;
+//
+//        float nr = cam_distance - bbox_r * 1.1f;
+//        float fr = cam_distance + bbox_r * 1.1f;
+//        if (nr < 1.0f)
+//            nr = 1.0f;
+//
+//        if (fr < nr + 1.0f)
+//            fr = nr + 1.0f;
+//
+//        float h2 = fov_tan * nr;
+//        float w2 = h2 * w / h;
+//        ::glFrustum(-w2, w2, -h2, h2, nr, fr);
+//
+//        break;
+//    }
+    default:
+    {
+        throw std::runtime_error("Invalid camera type.");
+        break;
+    }
+    }
+
+    ::glMatrixMode(GL_MODELVIEW);
+
+    m_dirty = false;
+}
+
+BoundingBoxf3 GLCanvas3D::_max_bounding_box() const
+{
+    BoundingBoxf3 bb = m_bed.get_bounding_box();
+    bb.merge(volumes_bounding_box());
+    return bb;
+}
+
+BoundingBoxf3 GLCanvas3D::_selected_volumes_bounding_box() const
+{
+    BoundingBoxf3 bb;
+    for (const GLVolume* volume : m_volumes.volumes)
+    {
+        if ((volume != nullptr) && volume->selected)
+            bb.merge(volume->transformed_bounding_box());
+    }
+    return bb;
+}
+
+void GLCanvas3D::_zoom_to_bounding_box(const BoundingBoxf3& bbox)
+{
+    // Calculate the zoom factor needed to adjust viewport to bounding box.
+    float zoom = _get_zoom_to_bounding_box_factor(bbox);
+    if (zoom > 0.0f)
+    {
+        m_camera.zoom = zoom;
+        // center view around bounding box center
+        m_camera.target = bbox.center();
+
+        m_on_viewport_changed_callback.call();
+
+        _refresh_if_shown_on_screen();
+    }
+}
+
+float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const
+{
+    float max_bb_size = bbox.max_size();
+    if (max_bb_size == 0.0f)
+        return -1.0f;
+
+    // project the bbox vertices on a plane perpendicular to the camera forward axis
+    // then calculates the vertices coordinate on this plane along the camera xy axes
+
+    // we need the view matrix, we let opengl calculate it (same as done in render())
+    _camera_tranform();
+
+    // get the view matrix back from opengl
+    GLfloat matrix[16];
+    ::glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
+
+    // camera axes
+    Pointf3 right((coordf_t)matrix[0], (coordf_t)matrix[4], (coordf_t)matrix[8]);
+    Pointf3 up((coordf_t)matrix[1], (coordf_t)matrix[5], (coordf_t)matrix[9]);
+    Pointf3 forward((coordf_t)matrix[2], (coordf_t)matrix[6], (coordf_t)matrix[10]);
+
+    Pointf3 bb_min = bbox.min;
+    Pointf3 bb_max = bbox.max;
+    Pointf3 bb_center = bbox.center();
+
+    // bbox vertices in world space
+    std::vector<Pointf3> vertices;
+    vertices.reserve(8);
+    vertices.push_back(bb_min);
+    vertices.emplace_back(bb_max.x, bb_min.y, bb_min.z);
+    vertices.emplace_back(bb_max.x, bb_max.y, bb_min.z);
+    vertices.emplace_back(bb_min.x, bb_max.y, bb_min.z);
+    vertices.emplace_back(bb_min.x, bb_min.y, bb_max.z);
+    vertices.emplace_back(bb_max.x, bb_min.y, bb_max.z);
+    vertices.push_back(bb_max);
+    vertices.emplace_back(bb_min.x, bb_max.y, bb_max.z);
+
+    coordf_t max_x = 0.0;
+    coordf_t max_y = 0.0;
+
+    // margin factor to give some empty space around the bbox
+    coordf_t margin_factor = 1.25;
+
+    for (const Pointf3 v : vertices)
+    {
+        // project vertex on the plane perpendicular to camera forward axis
+        Pointf3 pos(v.x - bb_center.x, v.y - bb_center.y, v.z - bb_center.z);
+        Pointf3 proj_on_plane = pos - dot(pos, forward) * forward;
+
+        // calculates vertex coordinate along camera xy axes
+        coordf_t x_on_plane = dot(proj_on_plane, right);
+        coordf_t y_on_plane = dot(proj_on_plane, up);
+
+        max_x = std::max(max_x, margin_factor * std::abs(x_on_plane));
+        max_y = std::max(max_y, margin_factor * std::abs(y_on_plane));
+    }
+
+    if ((max_x == 0.0) || (max_y == 0.0))
+        return -1.0f;
+
+    max_x *= 2.0;
+    max_y *= 2.0;
+
+    const Size& cnv_size = get_canvas_size();
+    return (float)std::min((coordf_t)cnv_size.get_width() / max_x, (coordf_t)cnv_size.get_height() / max_y);
+}
+
+void GLCanvas3D::_deregister_callbacks()
+{
+    m_on_viewport_changed_callback.deregister_callback();
+    m_on_double_click_callback.deregister_callback();
+    m_on_right_click_callback.deregister_callback();
+    m_on_select_object_callback.deregister_callback();
+    m_on_model_update_callback.deregister_callback();
+    m_on_remove_object_callback.deregister_callback();
+    m_on_arrange_callback.deregister_callback();
+    m_on_rotate_object_left_callback.deregister_callback();
+    m_on_rotate_object_right_callback.deregister_callback();
+    m_on_scale_object_uniformly_callback.deregister_callback();
+    m_on_increase_objects_callback.deregister_callback();
+    m_on_decrease_objects_callback.deregister_callback();
+    m_on_instance_moved_callback.deregister_callback();
+    m_on_wipe_tower_moved_callback.deregister_callback();
+    m_on_enable_action_buttons_callback.deregister_callback();
+    m_on_gizmo_scale_uniformly_callback.deregister_callback();
+}
+
+void GLCanvas3D::_mark_volumes_for_layer_height() const
+{
+    if (m_print == nullptr)
+        return;
+
+    for (GLVolume* vol : m_volumes.volumes)
+    {
+        int object_id = int(vol->select_group_id / 1000000);
+        int shader_id = m_layers_editing.get_shader_program_id();
+
+        if (is_layers_editing_enabled() && (shader_id != -1) && vol->selected &&
+            vol->has_layer_height_texture() && (object_id < (int)m_print->objects.size()))
+        {
+            vol->set_layer_height_texture_data(m_layers_editing.get_z_texture_id(), shader_id,
+                m_print->get_object(object_id), _get_layers_editing_cursor_z_relative(), m_layers_editing.band_width);
+        }
+        else
+            vol->reset_layer_height_texture_data();
+    }
+}
+
+void GLCanvas3D::_refresh_if_shown_on_screen()
+{
+    if (_is_shown_on_screen())
+    {
+        const Size& cnv_size = get_canvas_size();
+        _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
+        if (m_canvas != nullptr)
+            m_canvas->Refresh();
+    }
+}
+
+void GLCanvas3D::_camera_tranform() const
+{
+    ::glMatrixMode(GL_MODELVIEW);
+    ::glLoadIdentity();
+
+    ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); //�pitch
+    ::glRotatef(m_camera.phi, 0.0f, 0.0f, 1.0f);          // yaw
+
+    Pointf3 neg_target = m_camera.target.negative();
+    ::glTranslatef((GLfloat)neg_target.x, (GLfloat)neg_target.y, (GLfloat)neg_target.z);
+}
+
+void GLCanvas3D::_picking_pass() const
+{
+    const Pointf& pos = m_mouse.position;
+
+    if (m_picking_enabled && !m_mouse.dragging && (pos != Pointf(DBL_MAX, DBL_MAX)))
+    {
+        // Render the object for picking.
+        // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing.
+        // Better to use software ray - casting on a bounding - box hierarchy.
+
+        if (m_multisample_allowed)
+            ::glDisable(GL_MULTISAMPLE);
+
+        ::glDisable(GL_LIGHTING);
+        ::glDisable(GL_BLEND);
+
+        ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+        _render_volumes(true);
+        m_gizmos.render_current_gizmo_for_picking_pass(_selected_volumes_bounding_box());
+
+        if (m_multisample_allowed)
+            ::glEnable(GL_MULTISAMPLE);
+
+        const Size& cnv_size = get_canvas_size();
+
+        GLubyte color[4];
+        ::glReadPixels(pos.x, cnv_size.get_height() - pos.y - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color);
+        int volume_id = color[0] + color[1] * 256 + color[2] * 256 * 256;
+
+        m_hover_volume_id = -1;
+
+        for (GLVolume* vol : m_volumes.volumes)
+        {
+            vol->hover = false;
+        }
+
+        if (volume_id < (int)m_volumes.volumes.size())
+        {
+            m_hover_volume_id = volume_id;
+            m_volumes.volumes[volume_id]->hover = true;
+            int group_id = m_volumes.volumes[volume_id]->select_group_id;
+            if (group_id != -1)
+            {
+                for (GLVolume* vol : m_volumes.volumes)
+                {
+                    if (vol->select_group_id == group_id)
+                        vol->hover = true;
+                }
+            }
+            m_gizmos.set_hover_id(-1);
+        }
+        else
+            m_gizmos.set_hover_id(254 - (int)color[2]);
+
+        // updates gizmos overlay
+        if (_get_first_selected_object_id() != -1)
+            m_gizmos.update_hover_state(*this, pos);
+        else
+            m_gizmos.reset_all_states();
+    }
+}
+
+void GLCanvas3D::_render_background() const
+{
+    ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+    static const float COLOR[3] = { 10.0f / 255.0f, 98.0f / 255.0f, 144.0f / 255.0f };
+
+    ::glDisable(GL_LIGHTING);
+
+    ::glPushMatrix();
+    ::glLoadIdentity();
+    ::glMatrixMode(GL_PROJECTION);
+    ::glPushMatrix();
+    ::glLoadIdentity();
+
+    // Draws a bluish bottom to top gradient over the complete screen.
+    ::glDisable(GL_DEPTH_TEST);
+
+    ::glBegin(GL_QUADS);
+    ::glColor3f(0.0f, 0.0f, 0.0f);
+    ::glVertex3f(-1.0f, -1.0f, 1.0f);
+    ::glVertex3f(1.0f, -1.0f, 1.0f);
+    ::glColor3f(COLOR[0], COLOR[1], COLOR[2]);
+    ::glVertex3f(1.0f, 1.0f, 1.0f);
+    ::glVertex3f(-1.0f, 1.0f, 1.0f);
+    ::glEnd();
+
+    ::glEnable(GL_DEPTH_TEST);
+
+    ::glPopMatrix();
+    ::glMatrixMode(GL_MODELVIEW);
+    ::glPopMatrix();
+}
+
+void GLCanvas3D::_render_bed(float theta) const
+{
+    m_bed.render(theta);
+}
+
+void GLCanvas3D::_render_axes(bool depth_test) const
+{
+    m_axes.render(depth_test);
+}
+
+void GLCanvas3D::_render_objects() const
+{
+    if (m_volumes.empty())
+        return;
+
+    ::glEnable(GL_LIGHTING);
+
+    if (!m_shader_enabled)
+        _render_volumes(false);
+    else if (m_use_VBOs)
+    {
+        if (m_picking_enabled)
+        {
+            _mark_volumes_for_layer_height();
+
+            if (m_config != nullptr)
+            {
+                const BoundingBoxf3& bed_bb = m_bed.get_bounding_box();
+                m_volumes.set_print_box((float)bed_bb.min.x, (float)bed_bb.min.y, 0.0f, (float)bed_bb.max.x, (float)bed_bb.max.y, (float)m_config->opt_float("max_print_height"));
+                m_volumes.check_outside_state(m_config);
+            }
+            // do not cull backfaces to show broken geometry, if any
+            ::glDisable(GL_CULL_FACE);
+        }
+
+        m_shader.start_using();
+        m_volumes.render_VBOs();
+        m_shader.stop_using();
+
+        if (m_picking_enabled)
+            ::glEnable(GL_CULL_FACE);
+    }
+    else
+    {
+        // do not cull backfaces to show broken geometry, if any
+        if (m_picking_enabled)
+            ::glDisable(GL_CULL_FACE);
+
+        m_volumes.render_legacy();
+
+        if (m_picking_enabled)
+            ::glEnable(GL_CULL_FACE);
+    }
+}
+
+void GLCanvas3D::_render_cutting_plane() const
+{
+    m_cutting_plane.render(volumes_bounding_box());
+}
+
+void GLCanvas3D::_render_warning_texture() const
+{
+    if (!m_warning_texture_enabled)
+        return;
+
+    // If the warning texture has not been loaded into the GPU, do it now.
+    unsigned int tex_id = _3DScene::finalize_warning_texture();
+    if (tex_id > 0)
+    {
+        unsigned int w = _3DScene::get_warning_texture_width();
+        unsigned int h = _3DScene::get_warning_texture_height();
+        if ((w > 0) && (h > 0))
+        {
+            ::glDisable(GL_DEPTH_TEST);
+            ::glPushMatrix();
+            ::glLoadIdentity();
+
+            const Size& cnv_size = get_canvas_size();
+            float zoom = get_camera_zoom();
+            float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+            float l = (-0.5f * (float)w) * inv_zoom;
+            float t = (-0.5f * (float)cnv_size.get_height() + (float)h) * inv_zoom;
+            float r = l + (float)w * inv_zoom;
+            float b = t - (float)h * inv_zoom;
+
+            GLTexture::render_texture(tex_id, l, r, b, t);
+
+            ::glPopMatrix();
+            ::glEnable(GL_DEPTH_TEST);
+        }
+    }
+}
+
+void GLCanvas3D::_render_legend_texture() const
+{
+    if (!m_legend_texture_enabled)
+        return;
+
+    // If the legend texture has not been loaded into the GPU, do it now.
+    unsigned int tex_id = _3DScene::finalize_legend_texture();
+    if (tex_id > 0)
+    {
+        unsigned int w = _3DScene::get_legend_texture_width();
+        unsigned int h = _3DScene::get_legend_texture_height();
+        if ((w > 0) && (h > 0))
+        {
+            ::glDisable(GL_DEPTH_TEST);
+            ::glPushMatrix();
+            ::glLoadIdentity();
+
+            const Size& cnv_size = get_canvas_size();
+            float zoom = get_camera_zoom();
+            float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+            float l = (-0.5f * (float)cnv_size.get_width()) * inv_zoom;
+            float t = (0.5f * (float)cnv_size.get_height()) * inv_zoom;
+            float r = l + (float)w * inv_zoom;
+            float b = t - (float)h * inv_zoom;
+            GLTexture::render_texture(tex_id, l, r, b, t);
+
+            ::glPopMatrix();
+            ::glEnable(GL_DEPTH_TEST);
+        }
+    }
+}
+
+void GLCanvas3D::_render_layer_editing_overlay() const
+{
+    if (m_print == nullptr)
+        return;
+
+    GLVolume* volume = nullptr;
+
+    for (GLVolume* vol : m_volumes.volumes)
+    {
+        if ((vol != nullptr) && vol->selected && vol->has_layer_height_texture())
+        {
+            volume = vol;
+            break;
+        }
+    }
+
+    if (volume == nullptr)
+        return;
+
+    // If the active object was not allocated at the Print, go away.This should only be a momentary case between an object addition / deletion
+    // and an update by Platter::async_apply_config.
+    int object_idx = int(volume->select_group_id / 1000000);
+    if ((int)m_print->objects.size() < object_idx)
+        return;
+
+    const PrintObject* print_object = m_print->get_object(object_idx);
+    if (print_object == nullptr)
+        return;
+
+    m_layers_editing.render(*this, *print_object, *volume);
+}
+
+void GLCanvas3D::_render_volumes(bool fake_colors) const
+{
+    static const GLfloat INV_255 = 1.0f / 255.0f;
+
+    if (fake_colors)
+        ::glDisable(GL_LIGHTING);
+    else
+        ::glEnable(GL_LIGHTING);
+
+    // do not cull backfaces to show broken geometry, if any
+    ::glDisable(GL_CULL_FACE);
+
+    ::glEnable(GL_BLEND);
+    ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+    ::glEnableClientState(GL_VERTEX_ARRAY);
+    ::glEnableClientState(GL_NORMAL_ARRAY);
+
+    unsigned int volume_id = 0;
+    for (GLVolume* vol : m_volumes.volumes)
+    {
+        if (fake_colors)
+        {
+            // Object picking mode. Render the object with a color encoding the object index.
+            unsigned int r = (volume_id & 0x000000FF) >> 0;
+            unsigned int g = (volume_id & 0x0000FF00) >> 8;
+            unsigned int b = (volume_id & 0x00FF0000) >> 16;
+            ::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255);
+        }
+        else
+        {
+            vol->set_render_color();
+            ::glColor4f(vol->render_color[0], vol->render_color[1], vol->render_color[2], vol->render_color[3]);
+        }
+
+        vol->render();
+        ++volume_id;
+    }
+
+    ::glDisableClientState(GL_NORMAL_ARRAY);
+    ::glDisableClientState(GL_VERTEX_ARRAY);
+    ::glDisable(GL_BLEND);
+
+    ::glEnable(GL_CULL_FACE);
+}
+
+void GLCanvas3D::_render_gizmo() const
+{
+    m_gizmos.render(*this, _selected_volumes_bounding_box());
+}
+
+float GLCanvas3D::_get_layers_editing_cursor_z_relative() const
+{
+    return m_layers_editing.get_cursor_z_relative(*this);
+}
+
+void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt)
+{
+    int object_idx_selected = m_layers_editing.last_object_id;
+    if (object_idx_selected == -1)
+        return;
+
+    if (m_print == nullptr)
+        return;
+
+    PrintObject* selected_obj = m_print->get_object(object_idx_selected);
+    if (selected_obj == nullptr)
+        return;
+
+    // A volume is selected. Test, whether hovering over a layer thickness bar.
+    if (evt != nullptr)
+    {
+        const Rect& rect = LayersEditing::get_bar_rect_screen(*this);
+        float b = rect.get_bottom();
+        m_layers_editing.last_z = unscale(selected_obj->size.z) * (b - evt->GetY() - 1.0f) / (b - rect.get_top());
+        m_layers_editing.last_action = evt->ShiftDown() ? (evt->RightIsDown() ? 3 : 2) : (evt->RightIsDown() ? 0 : 1);
+    }
+
+    // Mark the volume as modified, so Print will pick its layer height profile ? Where to mark it ?
+    // Start a timer to refresh the print ? schedule_background_process() ?
+    // The PrintObject::adjust_layer_height_profile() call adjusts the profile of its associated ModelObject, it does not modify the profile of the PrintObject itself.
+    selected_obj->adjust_layer_height_profile(m_layers_editing.last_z, m_layers_editing.strength, m_layers_editing.band_width, m_layers_editing.last_action);
+
+    // searches the id of the first volume of the selected object
+    int volume_idx = 0;
+    for (int i = 0; i < object_idx_selected; ++i)
+    {
+        PrintObject* obj = m_print->get_object(i);
+        if (obj != nullptr)
+        {
+            for (int j = 0; j < (int)obj->region_volumes.size(); ++j)
+            {
+                volume_idx += (int)obj->region_volumes[j].size();
+            }
+        }
+    }
+
+    m_volumes.volumes[volume_idx]->generate_layer_height_texture(selected_obj, 1);
+    _refresh_if_shown_on_screen();
+
+    // Automatic action on mouse down with the same coordinate.
+    _start_timer();
+}
+
+Pointf3 GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z)
+{
+    if (!set_current())
+        return Pointf3(DBL_MAX, DBL_MAX, DBL_MAX);
+
+    GLint viewport[4];
+    ::glGetIntegerv(GL_VIEWPORT, viewport);
+    GLdouble modelview_matrix[16];
+    ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix);
+    GLdouble projection_matrix[16];
+    ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix);
+
+    GLint y = viewport[3] - (GLint)mouse_pos.y;
+    GLfloat mouse_z;
+    if (z == nullptr)
+        ::glReadPixels((GLint)mouse_pos.x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, (void*)&mouse_z);
+    else
+        mouse_z = *z;
+
+    GLdouble out_x, out_y, out_z;
+    ::gluUnProject((GLdouble)mouse_pos.x, (GLdouble)y, (GLdouble)mouse_z, modelview_matrix, projection_matrix, viewport, &out_x, &out_y, &out_z);
+    return Pointf3((coordf_t)out_x, (coordf_t)out_y, (coordf_t)out_z);
+}
+
+Pointf3 GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos)
+{
+    float z0 = 0.0f;
+    float z1 = 1.0f;
+    return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1)).intersect_plane(0.0);
+}
+
+void GLCanvas3D::_start_timer()
+{
+    if (m_timer != nullptr)
+        m_timer->Start(100, wxTIMER_CONTINUOUS);
+}
+
+void GLCanvas3D::_stop_timer()
+{
+    if (m_timer != nullptr)
+        m_timer->Stop();
+}
+
+int GLCanvas3D::_get_first_selected_object_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)
+                    return (object_id >= objects_count) ? -1 : object_id;
+            }
+        }
+    }
+    return -1;
+}
+
+static inline int hex_digit_to_int(const char c)
+{
+    return
+        (c >= '0' && c <= '9') ? int(c - '0') :
+        (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
+        (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
+}
+
+void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors)
+{
+    // helper functions to select data in dependence of the extrusion view type
+    struct Helper
+    {
+        static float path_filter(GCodePreviewData::Extrusion::EViewType type, const ExtrusionPath& path)
+        {
+            switch (type)
+            {
+            case GCodePreviewData::Extrusion::FeatureType:
+                return (float)path.role();
+            case GCodePreviewData::Extrusion::Height:
+                return path.height;
+            case GCodePreviewData::Extrusion::Width:
+                return path.width;
+            case GCodePreviewData::Extrusion::Feedrate:
+                return path.feedrate;
+            case GCodePreviewData::Extrusion::VolumetricRate:
+                return path.feedrate * (float)path.mm3_per_mm;
+            case GCodePreviewData::Extrusion::Tool:
+                return (float)path.extruder_id;
+            default:
+                return 0.0f;
+            }
+
+            return 0.0f;
+        }
+
+        static GCodePreviewData::Color path_color(const GCodePreviewData& data, const std::vector<float>& tool_colors, float value)
+        {
+            switch (data.extrusion.view_type)
+            {
+            case GCodePreviewData::Extrusion::FeatureType:
+                return data.get_extrusion_role_color((ExtrusionRole)(int)value);
+            case GCodePreviewData::Extrusion::Height:
+                return data.get_height_color(value);
+            case GCodePreviewData::Extrusion::Width:
+                return data.get_width_color(value);
+            case GCodePreviewData::Extrusion::Feedrate:
+                return data.get_feedrate_color(value);
+            case GCodePreviewData::Extrusion::VolumetricRate:
+                return data.get_volumetric_rate_color(value);
+            case GCodePreviewData::Extrusion::Tool:
+            {
+                GCodePreviewData::Color color;
+                ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + (unsigned int)value * 4), 4 * sizeof(float));
+                return color;
+            }
+            default:
+                return GCodePreviewData::Color::Dummy;
+            }
+
+            return GCodePreviewData::Color::Dummy;
+        }
+    };
+
+    // Helper structure for filters
+    struct Filter
+    {
+        float value;
+        ExtrusionRole role;
+        GLVolume* volume;
+
+        Filter(float value, ExtrusionRole role)
+            : value(value)
+            , role(role)
+            , volume(nullptr)
+        {
+        }
+
+        bool operator == (const Filter& other) const
+        {
+            if (value != other.value)
+                return false;
+
+            if (role != other.role)
+                return false;
+
+            return true;
+        }
+    };
+
+    typedef std::vector<Filter> FiltersList;
+    size_t initial_volumes_count = m_volumes.volumes.size();
+
+    // detects filters
+    FiltersList filters;
+    for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers)
+    {
+        for (const ExtrusionPath& path : layer.paths)
+        {
+            ExtrusionRole role = path.role();
+            float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path);
+            if (std::find(filters.begin(), filters.end(), Filter(path_filter, role)) == filters.end())
+                filters.emplace_back(path_filter, role);
+        }
+    }
+
+    // nothing to render, return
+    if (filters.empty())
+        return;
+
+    // creates a new volume for each filter
+    for (Filter& filter : filters)
+    {
+        m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Extrusion, (unsigned int)filter.role, (unsigned int)m_volumes.volumes.size());
+        GLVolume* volume = new GLVolume(Helper::path_color(preview_data, tool_colors, filter.value).rgba);
+        if (volume != nullptr)
+        {
+            filter.volume = volume;
+            m_volumes.volumes.emplace_back(volume);
+        }
+        else
+        {
+            // an error occourred - restore to previous state and return
+            m_gcode_preview_volume_index.first_volumes.pop_back();
+            if (initial_volumes_count != m_volumes.volumes.size())
+            {
+                std::vector<GLVolume*>::iterator begin = m_volumes.volumes.begin() + initial_volumes_count;
+                std::vector<GLVolume*>::iterator end = m_volumes.volumes.end();
+                for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it)
+                {
+                    GLVolume* volume = *it;
+                    delete volume;
+                }
+                m_volumes.volumes.erase(begin, end);
+                return;
+            }
+        }
+    }
+
+    // populates volumes
+    for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers)
+    {
+        for (const ExtrusionPath& path : layer.paths)
+        {
+            float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path);
+            FiltersList::iterator filter = std::find(filters.begin(), filters.end(), Filter(path_filter, path.role()));
+            if (filter != filters.end())
+            {
+                filter->volume->print_zs.push_back(layer.z);
+                filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.quad_indices.size());
+                filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.triangle_indices.size());
+
+                _3DScene::extrusionentity_to_verts(path, layer.z, *filter->volume);
+            }
+        }
+    }
+
+    // finalize volumes and sends geometry to gpu
+    if (m_volumes.volumes.size() > initial_volumes_count)
+    {
+        for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i)
+        {
+            GLVolume* volume = m_volumes.volumes[i];
+            volume->bounding_box = volume->indexed_vertex_array.bounding_box();
+            volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized);
+        }
+    }
+}
+
+void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors)
+{
+    size_t initial_volumes_count = m_volumes.volumes.size();
+    m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Travel, 0, (unsigned int)initial_volumes_count);
+
+    bool res = true;
+    switch (preview_data.extrusion.view_type)
+    {
+    case GCodePreviewData::Extrusion::Feedrate:
+    {
+        res = _travel_paths_by_feedrate(preview_data);
+        break;
+    }
+    case GCodePreviewData::Extrusion::Tool:
+    {
+        res = _travel_paths_by_tool(preview_data, tool_colors);
+        break;
+    }
+    default:
+    {
+        res = _travel_paths_by_type(preview_data);
+        break;
+    }
+    }
+
+    if (!res)
+    {
+        // an error occourred - restore to previous state and return
+        if (initial_volumes_count != m_volumes.volumes.size())
+        {
+            std::vector<GLVolume*>::iterator begin = m_volumes.volumes.begin() + initial_volumes_count;
+            std::vector<GLVolume*>::iterator end = m_volumes.volumes.end();
+            for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it)
+            {
+                GLVolume* volume = *it;
+                delete volume;
+            }
+            m_volumes.volumes.erase(begin, end);
+        }
+
+        return;
+    }
+
+    // finalize volumes and sends geometry to gpu
+    if (m_volumes.volumes.size() > initial_volumes_count)
+    {
+        for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i)
+        {
+            GLVolume* volume = m_volumes.volumes[i];
+            volume->bounding_box = volume->indexed_vertex_array.bounding_box();
+            volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized);
+        }
+    }
+}
+
+bool GLCanvas3D::_travel_paths_by_type(const GCodePreviewData& preview_data)
+{
+    // Helper structure for types
+    struct Type
+    {
+        GCodePreviewData::Travel::EType value;
+        GLVolume* volume;
+
+        explicit Type(GCodePreviewData::Travel::EType value)
+            : value(value)
+            , volume(nullptr)
+        {
+        }
+
+        bool operator == (const Type& other) const
+        {
+            return value == other.value;
+        }
+    };
+
+    typedef std::vector<Type> TypesList;
+
+    // colors travels by travel type
+
+    // detects types
+    TypesList types;
+    for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
+    {
+        if (std::find(types.begin(), types.end(), Type(polyline.type)) == types.end())
+            types.emplace_back(polyline.type);
+    }
+
+    // nothing to render, return
+    if (types.empty())
+        return true;
+
+    // creates a new volume for each type
+    for (Type& type : types)
+    {
+        GLVolume* volume = new GLVolume(preview_data.travel.type_colors[type.value].rgba);
+        if (volume == nullptr)
+            return false;
+        else
+        {
+            type.volume = volume;
+            m_volumes.volumes.emplace_back(volume);
+        }
+    }
+
+    // populates volumes
+    for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
+    {
+        TypesList::iterator type = std::find(types.begin(), types.end(), Type(polyline.type));
+        if (type != types.end())
+        {
+            type->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z));
+            type->volume->offsets.push_back(type->volume->indexed_vertex_array.quad_indices.size());
+            type->volume->offsets.push_back(type->volume->indexed_vertex_array.triangle_indices.size());
+
+            _3DScene::polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *type->volume);
+        }
+    }
+
+    return true;
+}
+
+bool GLCanvas3D::_travel_paths_by_feedrate(const GCodePreviewData& preview_data)
+{
+    // Helper structure for feedrate
+    struct Feedrate
+    {
+        float value;
+        GLVolume* volume;
+
+        explicit Feedrate(float value)
+            : value(value)
+            , volume(nullptr)
+        {
+        }
+
+        bool operator == (const Feedrate& other) const
+        {
+            return value == other.value;
+        }
+    };
+
+    typedef std::vector<Feedrate> FeedratesList;
+
+    // colors travels by feedrate
+
+    // detects feedrates
+    FeedratesList feedrates;
+    for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
+    {
+        if (std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate)) == feedrates.end())
+            feedrates.emplace_back(polyline.feedrate);
+    }
+
+    // nothing to render, return
+    if (feedrates.empty())
+        return true;
+
+    // creates a new volume for each feedrate
+    for (Feedrate& feedrate : feedrates)
+    {
+        GLVolume* volume = new GLVolume(preview_data.get_feedrate_color(feedrate.value).rgba);
+        if (volume == nullptr)
+            return false;
+        else
+        {
+            feedrate.volume = volume;
+            m_volumes.volumes.emplace_back(volume);
+        }
+    }
+
+    // populates volumes
+    for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
+    {
+        FeedratesList::iterator feedrate = std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate));
+        if (feedrate != feedrates.end())
+        {
+            feedrate->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z));
+            feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.quad_indices.size());
+            feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.triangle_indices.size());
+
+            _3DScene::polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *feedrate->volume);
+        }
+    }
+
+    return true;
+}
+
+bool GLCanvas3D::_travel_paths_by_tool(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors)
+{
+    // Helper structure for tool
+    struct Tool
+    {
+        unsigned int value;
+        GLVolume* volume;
+
+        explicit Tool(unsigned int value)
+            : value(value)
+            , volume(nullptr)
+        {
+        }
+
+        bool operator == (const Tool& other) const
+        {
+            return value == other.value;
+        }
+    };
+
+    typedef std::vector<Tool> ToolsList;
+
+    // colors travels by tool
+
+    // detects tools
+    ToolsList tools;
+    for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
+    {
+        if (std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id)) == tools.end())
+            tools.emplace_back(polyline.extruder_id);
+    }
+
+    // nothing to render, return
+    if (tools.empty())
+        return true;
+
+    // creates a new volume for each tool
+    for (Tool& tool : tools)
+    {
+        GLVolume* volume = new GLVolume(tool_colors.data() + tool.value * 4);
+        if (volume == nullptr)
+            return false;
+        else
+        {
+            tool.volume = volume;
+            m_volumes.volumes.emplace_back(volume);
+        }
+    }
+
+    // populates volumes
+    for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines)
+    {
+        ToolsList::iterator tool = std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id));
+        if (tool != tools.end())
+        {
+            tool->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z));
+            tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.quad_indices.size());
+            tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.triangle_indices.size());
+
+            _3DScene::polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *tool->volume);
+        }
+    }
+
+    return true;
+}
+
+void GLCanvas3D::_load_gcode_retractions(const GCodePreviewData& preview_data)
+{
+    m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Retraction, 0, (unsigned int)m_volumes.volumes.size());
+
+    // nothing to render, return
+    if (preview_data.retraction.positions.empty())
+        return;
+
+    GLVolume* volume = new GLVolume(preview_data.retraction.color.rgba);
+    if (volume != nullptr)
+    {
+        m_volumes.volumes.emplace_back(volume);
+
+        GCodePreviewData::Retraction::PositionsList copy(preview_data.retraction.positions);
+        std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; });
+
+        for (const GCodePreviewData::Retraction::Position& position : copy)
+        {
+            volume->print_zs.push_back(unscale(position.position.z));
+            volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
+            volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
+
+            _3DScene::point3_to_verts(position.position, position.width, position.height, *volume);
+        }
+
+        // finalize volumes and sends geometry to gpu
+        volume->bounding_box = volume->indexed_vertex_array.bounding_box();
+        volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized);
+    }
+}
+
+void GLCanvas3D::_load_gcode_unretractions(const GCodePreviewData& preview_data)
+{
+    m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Unretraction, 0, (unsigned int)m_volumes.volumes.size());
+
+    // nothing to render, return
+    if (preview_data.unretraction.positions.empty())
+        return;
+
+    GLVolume* volume = new GLVolume(preview_data.unretraction.color.rgba);
+    if (volume != nullptr)
+    {
+        m_volumes.volumes.emplace_back(volume);
+
+        GCodePreviewData::Retraction::PositionsList copy(preview_data.unretraction.positions);
+        std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; });
+
+        for (const GCodePreviewData::Retraction::Position& position : copy)
+        {
+            volume->print_zs.push_back(unscale(position.position.z));
+            volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
+            volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
+
+            _3DScene::point3_to_verts(position.position, position.width, position.height, *volume);
+        }
+
+        // finalize volumes and sends geometry to gpu
+        volume->bounding_box = volume->indexed_vertex_array.bounding_box();
+        volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized);
+    }
+}
+
+void GLCanvas3D::_load_shells()
+{
+    size_t initial_volumes_count = m_volumes.volumes.size();
+    m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Shell, 0, (unsigned int)initial_volumes_count);
+
+    if (m_print->objects.empty())
+        // nothing to render, return
+        return;
+
+    // adds objects' volumes 
+    unsigned int object_id = 0;
+    for (PrintObject* obj : m_print->objects)
+    {
+        ModelObject* model_obj = obj->model_object();
+
+        std::vector<int> instance_ids(model_obj->instances.size());
+        for (int i = 0; i < (int)model_obj->instances.size(); ++i)
+        {
+            instance_ids[i] = i;
+        }
+
+        m_volumes.load_object(model_obj, object_id, instance_ids, "object", "object", "object", m_use_VBOs && m_initialized);
+
+        ++object_id;
+    }
+
+    // adds wipe tower's volume
+    coordf_t max_z = m_print->objects[0]->model_object()->get_model()->bounding_box().max.z;
+    const PrintConfig& config = m_print->config;
+    unsigned int extruders_count = config.nozzle_diameter.size();
+    if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) {
+        const float width_per_extruder = 15.0f; // a simple workaround after wipe_tower_per_color_wipe got obsolete
+        m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, width_per_extruder * (extruders_count - 1), max_z, config.wipe_tower_rotation_angle, m_use_VBOs && m_initialized);
+    }
+}
+
+void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data)
+{
+    unsigned int size = (unsigned int)m_gcode_preview_volume_index.first_volumes.size();
+    for (unsigned int i = 0; i < size; ++i)
+    {
+        std::vector<GLVolume*>::iterator begin = m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i].id;
+        std::vector<GLVolume*>::iterator end = (i + 1 < size) ? m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i + 1].id : m_volumes.volumes.end();
+
+        for (std::vector<GLVolume*>::iterator it = begin; it != end; ++it)
+        {
+            GLVolume* volume = *it;
+            volume->outside_printer_detection_enabled = false;
+
+            switch (m_gcode_preview_volume_index.first_volumes[i].type)
+            {
+            case GCodePreviewVolumeIndex::Extrusion:
+            {
+                if ((ExtrusionRole)m_gcode_preview_volume_index.first_volumes[i].flag == erCustom)
+                    volume->zoom_to_volumes = false;
+
+                volume->is_active = preview_data.extrusion.is_role_flag_set((ExtrusionRole)m_gcode_preview_volume_index.first_volumes[i].flag);
+                break;
+            }
+            case GCodePreviewVolumeIndex::Travel:
+            {
+                volume->is_active = preview_data.travel.is_visible;
+                volume->zoom_to_volumes = false;
+                break;
+            }
+            case GCodePreviewVolumeIndex::Retraction:
+            {
+                volume->is_active = preview_data.retraction.is_visible;
+                volume->zoom_to_volumes = false;
+                break;
+            }
+            case GCodePreviewVolumeIndex::Unretraction:
+            {
+                volume->is_active = preview_data.unretraction.is_visible;
+                volume->zoom_to_volumes = false;
+                break;
+            }
+            case GCodePreviewVolumeIndex::Shell:
+            {
+                volume->is_active = preview_data.shell.is_visible;
+                volume->color[3] = 0.25f;
+                volume->zoom_to_volumes = false;
+                break;
+            }
+            default:
+            {
+                volume->is_active = false;
+                volume->zoom_to_volumes = false;
+                break;
+            }
+            }
+        }
+    }
+}
+
+void GLCanvas3D::_on_move(const std::vector<int>& volume_idxs)
+{
+    if (m_model == nullptr)
+        return;
+
+    std::set<std::string> done;  //�prevent moving instances twice
+    bool object_moved = false;
+    Pointf3 wipe_tower_origin(0.0, 0.0, 0.0);
+    for (int volume_idx : volume_idxs)
+    {
+        GLVolume* volume = m_volumes.volumes[volume_idx];
+        int obj_idx = volume->object_idx();
+        int instance_idx = volume->instance_idx();
+
+        //�prevent moving instances twice
+        char done_id[64];
+        ::sprintf(done_id, "%d_%d", obj_idx, instance_idx);
+        if (done.find(done_id) != done.end())
+            continue;
+
+        done.insert(done_id);
+
+        if (obj_idx < 1000)
+        {
+            // 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);
+            model_object->invalidate_bounding_box();
+            object_moved = true;
+        }
+        else if (obj_idx == 1000)
+            // Move a wipe tower proxy.
+            wipe_tower_origin = volume->origin;
+    }
+
+    if (object_moved)
+        m_on_instance_moved_callback.call();
+
+    if (wipe_tower_origin != Pointf3(0.0, 0.0, 0.0))
+        m_on_wipe_tower_moved_callback.call(wipe_tower_origin.x, wipe_tower_origin.y);
+}
+
+void GLCanvas3D::_on_select(int volume_idx)
+{
+    int id = -1;
+    if ((volume_idx != -1) && (volume_idx < (int)m_volumes.volumes.size()))
+    {
+        if (m_select_by == "volume")
+            id = m_volumes.volumes[volume_idx]->volume_idx();
+        else if (m_select_by == "object")
+            id = m_volumes.volumes[volume_idx]->object_idx();
+    }
+    m_on_select_object_callback.call(id);
+}
+
+void GLCanvas3D::_update_gizmos_data()
+{
+    int id = _get_first_selected_object_id();
+    if ((id != -1) && (m_model != nullptr))
+    {
+        ModelObject* model_object = m_model->objects[id];
+        if (model_object != nullptr)
+        {
+            ModelInstance* model_instance = model_object->instances[0];
+            if (model_instance != nullptr)
+                m_gizmos.update_data(model_instance->scaling_factor);
+        }
+    }
+}
+
+std::vector<float> GLCanvas3D::_parse_colors(const std::vector<std::string>& colors)
+{
+    static const float INV_255 = 1.0f / 255.0f;
+
+    std::vector<float> output(colors.size() * 4, 1.0f);
+    for (size_t i = 0; i < colors.size(); ++i)
+    {
+        const std::string& color = colors[i];
+        const char* c = color.data() + 1;
+        if ((color.size() == 7) && (color.front() == '#'))
+        {
+            for (size_t j = 0; j < 3; ++j)
+            {
+                int digit1 = hex_digit_to_int(*c++);
+                int digit2 = hex_digit_to_int(*c++);
+                if ((digit1 == -1) || (digit2 == -1))
+                    break;
+
+                output[i * 4 + j] = float(digit1 * 16 + digit2) * INV_255;
+            }
+        }
+    }
+    return output;
+}
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp
new file mode 100644
index 000000000..c503d1845
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp
@@ -0,0 +1,639 @@
+#ifndef slic3r_GLCanvas3D_hpp_
+#define slic3r_GLCanvas3D_hpp_
+
+#include "../../slic3r/GUI/3DScene.hpp"
+#include "../../slic3r/GUI/GLTexture.hpp"
+
+class wxTimer;
+class wxSizeEvent;
+class wxIdleEvent;
+class wxKeyEvent;
+class wxMouseEvent;
+class wxTimerEvent;
+class wxPaintEvent;
+
+namespace Slic3r {
+
+class GLShader;
+class ExPolygon;
+
+namespace GUI {
+
+class GLGizmoBase;
+
+class GeometryBuffer
+{
+    std::vector<float> m_vertices;
+    std::vector<float> m_tex_coords;
+
+public:
+    bool set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords);
+    bool set_from_lines(const Lines& lines, float z);
+
+    const float* get_vertices() const;
+    const float* get_tex_coords() const;
+
+    unsigned int get_vertices_count() const;
+};
+
+class Size
+{
+    int m_width;
+    int m_height;
+
+public:
+    Size();
+    Size(int width, int height);
+
+    int get_width() const;
+    void set_width(int width);
+
+    int get_height() const;
+    void set_height(int height);
+};
+
+class Rect
+{
+    float m_left;
+    float m_top;
+    float m_right;
+    float m_bottom;
+
+public:
+    Rect();
+    Rect(float left, float top, float right, float bottom);
+
+    float get_left() const;
+    void set_left(float left);
+
+    float get_top() const;
+    void set_top(float top);
+
+    float get_right() const;
+    void set_right(float right);
+
+    float get_bottom() const;
+    void set_bottom(float bottom);
+};
+
+class GLCanvas3D
+{
+    struct GCodePreviewVolumeIndex
+    {
+        enum EType
+        {
+            Extrusion,
+            Travel,
+            Retraction,
+            Unretraction,
+            Shell,
+            Num_Geometry_Types
+        };
+
+        struct FirstVolume
+        {
+            EType type;
+            unsigned int flag;
+            // Index of the first volume in a GLVolumeCollection.
+            unsigned int id;
+
+            FirstVolume(EType type, unsigned int flag, unsigned int id) : type(type), flag(flag), id(id) {}
+        };
+
+        std::vector<FirstVolume> first_volumes;
+
+        void reset() { first_volumes.clear(); }
+    };
+
+public:
+    struct Camera
+    {
+        enum EType : unsigned char
+        {
+            Unknown,
+//            Perspective,
+            Ortho,
+            Num_types
+        };
+
+        EType type;
+        float zoom;
+        float phi;
+//        float distance;
+        Pointf3 target;
+
+    private:
+        float m_theta;
+
+    public:
+        Camera();
+
+        std::string get_type_as_string() const;
+
+        float get_theta() const;
+        void set_theta(float theta);
+    };
+
+    class Bed
+    {
+    public:
+        enum EType : unsigned char
+        {
+            MK2,
+            MK3,
+            Custom,
+            Num_Types
+        };
+
+    private:
+        EType m_type;
+        Pointfs m_shape;
+        BoundingBoxf3 m_bounding_box;
+        Polygon m_polygon;
+        GeometryBuffer m_triangles;
+        GeometryBuffer m_gridlines;
+        mutable GLTexture m_top_texture;
+        mutable GLTexture m_bottom_texture;
+
+    public:
+        Bed();
+
+        bool is_prusa() const;
+        bool is_custom() const;
+
+        const Pointfs& get_shape() const;
+        void set_shape(const Pointfs& shape);
+
+        const BoundingBoxf3& get_bounding_box() const;
+        bool contains(const Point& point) const;
+        Point point_projection(const Point& point) const;
+
+        void render(float theta) const;
+
+    private:
+        void _calc_bounding_box();
+        void _calc_triangles(const ExPolygon& poly);
+        void _calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox);
+        EType _detect_type() const;
+        void _render_mk2(float theta) const;
+        void _render_mk3(float theta) const;
+        void _render_prusa(float theta) const;
+        void _render_custom() const;
+        static bool _are_equal(const Pointfs& bed_1, const Pointfs& bed_2);
+    };
+
+    struct Axes
+    {
+        Pointf3 origin;
+        float length;
+
+        Axes();
+
+        void render(bool depth_test) const;
+    };
+
+    class CuttingPlane
+    {
+        float m_z;
+        GeometryBuffer m_lines;
+
+    public:
+        CuttingPlane();
+
+        bool set(float z, const ExPolygons& polygons);
+
+        void render(const BoundingBoxf3& bb) const;
+
+    private:
+        void _render_plane(const BoundingBoxf3& bb) const;
+        void _render_contour() const;
+    };
+
+    class Shader
+    {
+        GLShader* m_shader;
+
+    public:
+        Shader();
+        ~Shader();
+
+        bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename);
+
+        bool is_initialized() const;
+
+        bool start_using() const;
+        void stop_using() const;
+
+        void set_uniform(const std::string& name, float value) const;
+
+        const GLShader* get_shader() const;
+
+    private:
+        void _reset();
+    };
+
+    class LayersEditing
+    {
+    public:
+        enum EState : unsigned char
+        {
+            Unknown,
+            Editing,
+            Completed,
+            Num_States
+        };
+
+    private:
+        bool m_use_legacy_opengl;
+        bool m_enabled;
+        Shader m_shader;
+        unsigned int m_z_texture_id;
+        mutable GLTexture m_tooltip_texture;
+        mutable GLTexture m_reset_texture;
+
+    public:
+        EState state;
+        float band_width;
+        float strength;
+        int last_object_id;
+        float last_z;
+        unsigned int last_action;
+
+        LayersEditing();
+        ~LayersEditing();
+
+        bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename);
+
+        bool is_allowed() const;
+        void set_use_legacy_opengl(bool use_legacy_opengl);
+
+        bool is_enabled() const;
+        void set_enabled(bool enabled);
+
+        unsigned int get_z_texture_id() const;
+
+        void render(const GLCanvas3D& canvas, const PrintObject& print_object, const GLVolume& volume) const;
+
+        int get_shader_program_id() const;
+
+        static float get_cursor_z_relative(const GLCanvas3D& canvas);
+        static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y);
+        static bool reset_rect_contains(const GLCanvas3D& canvas, float x, float y);
+        static Rect get_bar_rect_screen(const GLCanvas3D& canvas);
+        static Rect get_reset_rect_screen(const GLCanvas3D& canvas);
+        static Rect get_bar_rect_viewport(const GLCanvas3D& canvas);
+        static Rect get_reset_rect_viewport(const GLCanvas3D& canvas);
+
+    private:
+        bool _is_initialized() const;
+        void _render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const;
+        void _render_reset_texture(const Rect& reset_rect) const;
+        void _render_active_object_annotations(const GLCanvas3D& canvas, const GLVolume& volume, const PrintObject& print_object, const Rect& bar_rect) const;
+        void _render_profile(const PrintObject& print_object, const Rect& bar_rect) const;
+    };
+
+    struct Mouse
+    {
+        struct Drag
+        {
+            static const Point Invalid_2D_Point;
+            static const Pointf3 Invalid_3D_Point;
+
+            Point start_position_2D;
+            Pointf3 start_position_3D;
+            Vectorf3 volume_center_offset;
+            int volume_idx;
+
+        public:
+            Drag();
+        };
+
+        bool dragging;
+        Pointf position;
+        Drag drag;
+
+        Mouse();
+
+        void set_start_position_2D_as_invalid();
+        void set_start_position_3D_as_invalid();
+
+        bool is_start_position_2D_defined() const;
+        bool is_start_position_3D_defined() const;
+    };
+
+    class Gizmos
+    {
+        static const float OverlayOffsetX;
+        static const float OverlayGapY;
+
+    public:
+        enum EType : unsigned char
+        {
+            Undefined,
+            Scale,
+            Rotate,
+            Num_Types
+        };
+
+    private:
+        bool m_enabled;
+        typedef std::map<EType, GLGizmoBase*> GizmosMap;
+        GizmosMap m_gizmos;
+        EType m_current;
+        bool m_dragging;
+
+    public:
+        Gizmos();
+        ~Gizmos();
+
+        bool init();
+
+        bool is_enabled() const;
+        void set_enabled(bool enable);
+
+        void update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos);
+        void update_on_off_state(const GLCanvas3D& canvas, const Pointf& mouse_pos);
+        void reset_all_states();
+
+        void set_hover_id(int id);
+
+        bool overlay_contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const;
+        bool grabber_contains_mouse() const;
+        void update(const Pointf& mouse_pos);
+        void update_data(float scale);
+
+        bool is_running() const;
+        bool is_dragging() const;
+        void start_dragging();
+        void stop_dragging();
+
+        float get_scale() const;
+
+        void render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const;
+        void render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const;
+
+    private:
+        void _reset();
+
+        void _render_overlay(const GLCanvas3D& canvas) const;
+        void _render_current_gizmo(const BoundingBoxf3& box) const;
+
+        float _get_total_overlay_height() const;
+        GLGizmoBase* _get_current() const;
+    };
+
+private:
+    wxGLCanvas* m_canvas;
+    wxGLContext* m_context;
+    wxTimer* m_timer;
+    Camera m_camera;
+    Bed m_bed;
+    Axes m_axes;
+    CuttingPlane m_cutting_plane;
+    LayersEditing m_layers_editing;
+    Shader m_shader;
+    Mouse m_mouse;
+    mutable Gizmos m_gizmos;
+
+    mutable GLVolumeCollection m_volumes;
+    DynamicPrintConfig* m_config;
+    Print* m_print;
+    Model* m_model;
+
+    bool m_dirty;
+    // the active member has been introduced to overcome a bug in wxWidgets method IsShownOnScreen() which always return true
+    // when a window is inside a wxNotebook
+    bool m_active;
+    bool m_initialized;
+    bool m_use_VBOs;
+    bool m_force_zoom_to_bed_enabled;
+    bool m_apply_zoom_to_volumes_filter;
+    mutable int m_hover_volume_id;
+    bool m_warning_texture_enabled;
+    bool m_legend_texture_enabled;
+    bool m_picking_enabled;
+    bool m_moving_enabled;
+    bool m_shader_enabled;
+    bool m_multisample_allowed;
+
+    std::string m_color_by;
+    std::string m_select_by;
+    std::string m_drag_by;
+
+    bool m_reload_delayed;
+    std::vector<std::vector<int>> m_objects_volumes_idxs;
+    std::vector<int> m_objects_selections;
+
+    GCodePreviewVolumeIndex m_gcode_preview_volume_index;
+
+    PerlCallback m_on_viewport_changed_callback;
+    PerlCallback m_on_double_click_callback;
+    PerlCallback m_on_right_click_callback;
+    PerlCallback m_on_select_object_callback;
+    PerlCallback m_on_model_update_callback;
+    PerlCallback m_on_remove_object_callback;
+    PerlCallback m_on_arrange_callback;
+    PerlCallback m_on_rotate_object_left_callback;
+    PerlCallback m_on_rotate_object_right_callback;
+    PerlCallback m_on_scale_object_uniformly_callback;
+    PerlCallback m_on_increase_objects_callback;
+    PerlCallback m_on_decrease_objects_callback;
+    PerlCallback m_on_instance_moved_callback;
+    PerlCallback m_on_wipe_tower_moved_callback;
+    PerlCallback m_on_enable_action_buttons_callback;
+    PerlCallback m_on_gizmo_scale_uniformly_callback;
+
+public:
+    GLCanvas3D(wxGLCanvas* canvas, wxGLContext* context);
+    ~GLCanvas3D();
+
+    bool init(bool useVBOs, bool use_legacy_opengl);
+
+    bool set_current();
+
+    void set_active(bool active);
+
+    unsigned int get_volumes_count() const;
+    void reset_volumes();
+    void deselect_volumes();
+    void select_volume(unsigned int id);
+    void update_volumes_selection(const std::vector<int>& selections);
+    bool check_volumes_outside_state(const DynamicPrintConfig* config) const;
+    bool move_volume_up(unsigned int id);
+    bool move_volume_down(unsigned int id);
+
+    void set_objects_selections(const std::vector<int>& selections);
+
+    void set_config(DynamicPrintConfig* config);
+    void set_print(Print* print);
+    void set_model(Model* model);
+
+    // Set the bed shape to a single closed 2D polygon(array of two element arrays),
+    // triangulate the bed and store the triangles into m_bed.m_triangles,
+    // fills the m_bed.m_grid_lines and sets m_bed.m_origin.
+    // Sets m_bed.m_polygon to limit the object placement.
+    void set_bed_shape(const Pointfs& shape);
+    // Used by ObjectCutDialog and ObjectPartsPanel to generate a rectangular ground plane to support the scene objects.
+    void set_auto_bed_shape();
+
+    void set_axes_length(float length);
+
+    void set_cutting_plane(float z, const ExPolygons& polygons);
+
+    void set_color_by(const std::string& value);
+    void set_select_by(const std::string& value);
+    void set_drag_by(const std::string& value);
+
+    float get_camera_zoom() const;
+
+    BoundingBoxf3 volumes_bounding_box() const;
+
+    bool is_layers_editing_enabled() const;
+    bool is_layers_editing_allowed() const;
+    bool is_shader_enabled() const;
+
+    bool is_reload_delayed() const;
+
+    void enable_layers_editing(bool enable);
+    void enable_warning_texture(bool enable);
+    void enable_legend_texture(bool enable);
+    void enable_picking(bool enable);
+    void enable_moving(bool enable);
+    void enable_gizmos(bool enable);
+    void enable_shader(bool enable);
+    void enable_force_zoom_to_bed(bool enable);
+    void allow_multisample(bool allow);
+
+    void zoom_to_bed();
+    void zoom_to_volumes();
+    void select_view(const std::string& direction);
+    void set_viewport_from_scene(const GLCanvas3D& other);
+
+    void update_volumes_colors_by_extruder();
+
+    void render();
+
+    std::vector<double> get_current_print_zs(bool active_only) const;
+    void set_toolpaths_range(double low, double high);
+
+    std::vector<int> load_object(const ModelObject& model_object, int obj_idx, std::vector<int> instance_idxs);
+    std::vector<int> load_object(const Model& model, int obj_idx);
+
+    void reload_scene(bool force);
+
+    // Create 3D thick extrusion lines for a skirt and brim.
+    // Adds a new Slic3r::GUI::3DScene::Volume to volumes.
+    void load_print_toolpaths();
+    // Create 3D thick extrusion lines for object forming extrusions.
+    // Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes,
+    // one for perimeters, one for infill and one for supports.
+    void load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors);
+    // Create 3D thick extrusion lines for wipe tower extrusions
+    void load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors);
+    void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors);
+
+    void register_on_viewport_changed_callback(void* callback);
+    void register_on_double_click_callback(void* callback);
+    void register_on_right_click_callback(void* callback);
+    void register_on_select_object_callback(void* callback);
+    void register_on_model_update_callback(void* callback);
+    void register_on_remove_object_callback(void* callback);
+    void register_on_arrange_callback(void* callback);
+    void register_on_rotate_object_left_callback(void* callback);
+    void register_on_rotate_object_right_callback(void* callback);
+    void register_on_scale_object_uniformly_callback(void* callback);
+    void register_on_increase_objects_callback(void* callback);
+    void register_on_decrease_objects_callback(void* callback);
+    void register_on_instance_moved_callback(void* callback);
+    void register_on_wipe_tower_moved_callback(void* callback);
+    void register_on_enable_action_buttons_callback(void* callback);
+    void register_on_gizmo_scale_uniformly_callback(void* callback);
+
+    void bind_event_handlers();
+    void unbind_event_handlers();
+
+    void on_size(wxSizeEvent& evt);
+    void on_idle(wxIdleEvent& evt);
+    void on_char(wxKeyEvent& evt);
+    void on_mouse_wheel(wxMouseEvent& evt);
+    void on_timer(wxTimerEvent& evt);
+    void on_mouse(wxMouseEvent& evt);
+    void on_paint(wxPaintEvent& evt);
+    void on_key_down(wxKeyEvent& evt);
+
+    Size get_canvas_size() const;
+    Point get_local_mouse_position() const;
+
+private:
+    bool _is_shown_on_screen() const;
+    void _force_zoom_to_bed();
+
+    void _resize(unsigned int w, unsigned int h);
+
+    BoundingBoxf3 _max_bounding_box() const;
+    BoundingBoxf3 _selected_volumes_bounding_box() const;
+
+    void _zoom_to_bounding_box(const BoundingBoxf3& bbox);
+    float _get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const;
+
+    void _deregister_callbacks();
+
+    void _mark_volumes_for_layer_height() const;
+    void _refresh_if_shown_on_screen();
+
+    void _camera_tranform() const;
+    void _picking_pass() const;
+    void _render_background() const;
+    void _render_bed(float theta) const;
+    void _render_axes(bool depth_test) const;
+    void _render_objects() const;
+    void _render_cutting_plane() const;
+    void _render_warning_texture() const;
+    void _render_legend_texture() const;
+    void _render_layer_editing_overlay() const;
+    void _render_volumes(bool fake_colors) const;
+    void _render_gizmo() const;
+
+    float _get_layers_editing_cursor_z_relative() const;
+    void _perform_layer_editing_action(wxMouseEvent* evt = nullptr);
+
+    // Convert the screen space coordinate to an object space coordinate.
+    // If the Z screen space coordinate is not provided, a depth buffer value is substituted.
+    Pointf3 _mouse_to_3d(const Point& mouse_pos, float* z = nullptr);
+
+    // Convert the screen space coordinate to world coordinate on the bed.
+    Pointf3 _mouse_to_bed_3d(const Point& mouse_pos);
+
+    void _start_timer();
+    void _stop_timer();
+
+    int _get_first_selected_object_id() const;
+
+    // generates gcode extrusion paths geometry
+    void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
+    // generates gcode travel paths geometry
+    void _load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
+    bool _travel_paths_by_type(const GCodePreviewData& preview_data);
+    bool _travel_paths_by_feedrate(const GCodePreviewData& preview_data);
+    bool _travel_paths_by_tool(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
+    // generates gcode retractions geometry
+    void _load_gcode_retractions(const GCodePreviewData& preview_data);
+    // generates gcode unretractions geometry
+    void _load_gcode_unretractions(const GCodePreviewData& preview_data);
+    // generates objects and wipe tower geometry
+    void _load_shells();
+    // sets gcode geometry visibility according to user selection
+    void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data);
+
+    void _on_move(const std::vector<int>& volume_idxs);
+    void _on_select(int volume_idx);
+
+    void _update_gizmos_data();
+
+    static std::vector<float> _parse_colors(const std::vector<std::string>& colors);
+};
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_GLCanvas3D_hpp_
diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp
new file mode 100644
index 000000000..f288ee456
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp
@@ -0,0 +1,707 @@
+#include "GLCanvas3DManager.hpp"
+#include "../../slic3r/GUI/GUI.hpp"
+#include "../../slic3r/GUI/AppConfig.hpp"
+#include "../../slic3r/GUI/GLCanvas3D.hpp"
+
+#include <GL/glew.h>
+
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/classification.hpp>
+
+#include <wx/glcanvas.h>
+#include <wx/timer.h>
+
+#include <vector>
+#include <string>
+#include <iostream>
+
+namespace Slic3r {
+namespace GUI {
+
+GLCanvas3DManager::GLInfo::GLInfo()
+    : version("")
+    , glsl_version("")
+    , vendor("")
+    , renderer("")
+{
+}
+
+bool GLCanvas3DManager::GLInfo::detect()
+{
+    const char* data = (const char*)::glGetString(GL_VERSION);
+    if (data == nullptr)
+        return false;
+
+    version = data;
+
+    data = (const char*)::glGetString(GL_SHADING_LANGUAGE_VERSION);
+    if (data == nullptr)
+        return false;
+
+    glsl_version = data;
+
+    data = (const char*)::glGetString(GL_VENDOR);
+    if (data == nullptr)
+        return false;
+
+    vendor = data;
+
+    data = (const char*)::glGetString(GL_RENDERER);
+    if (data == nullptr)
+        return false;
+
+    renderer = data;
+
+    return true;
+}
+
+bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
+{
+    std::vector<std::string> tokens;
+    boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on);
+
+    if (tokens.empty())
+        return false;
+
+    std::vector<std::string> numbers;
+    boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on);
+
+    unsigned int gl_major = 0;
+    unsigned int gl_minor = 0;
+
+    if (numbers.size() > 0)
+        gl_major = ::atoi(numbers[0].c_str());
+
+    if (numbers.size() > 1)
+        gl_minor = ::atoi(numbers[1].c_str());
+
+    if (gl_major < major)
+        return false;
+    else if (gl_major > major)
+        return true;
+    else
+        return gl_minor >= minor;
+}
+
+std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool extensions) const
+{
+    std::stringstream out;
+
+    std::string h2_start = format_as_html ? "<b>" : "";
+    std::string h2_end   = format_as_html ? "</b>" : "";
+    std::string b_start  = format_as_html ? "<b>" : "";
+    std::string b_end    = format_as_html ? "</b>" : "";
+    std::string line_end = format_as_html ? "<br>" : "\n";
+
+    out << h2_start << "OpenGL installation" << h2_end << line_end;
+    out << b_start  << "GL version:   " << b_end << version << line_end;
+    out << b_start  << "Vendor:       " << b_end << vendor << line_end;
+    out << b_start  << "Renderer:     " << b_end << renderer << line_end;
+    out << b_start  << "GLSL version: " << b_end << glsl_version << line_end;
+
+    if (extensions)
+    {
+        out << h2_start << "Installed extensions:" << h2_end << line_end;
+
+        std::vector<std::string> extensions_list;
+        GLint num_extensions;
+        ::glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
+         
+        for (GLint i = 0; i < num_extensions; ++i)
+        {
+            const char* e = (const char*)::glGetStringi(GL_EXTENSIONS, i);
+            extensions_list.push_back(e);
+        }
+
+        std::sort(extensions_list.begin(), extensions_list.end());
+        for (const std::string& ext : extensions_list)
+        {
+            out << ext << line_end;
+        }
+    }
+
+    return out.str();
+}
+
+GLCanvas3DManager::GLCanvas3DManager()
+    : m_context(nullptr)
+    , m_gl_initialized(false)
+    , m_use_legacy_opengl(false)
+    , m_use_VBOs(false)
+{
+}
+
+GLCanvas3DManager::~GLCanvas3DManager()
+{
+    if (m_context != nullptr)
+        delete m_context;
+}
+
+bool GLCanvas3DManager::add(wxGLCanvas* canvas)
+{
+    if (canvas == nullptr)
+        return false;
+
+    if (_get_canvas(canvas) != m_canvases.end())
+        return false;
+
+    if (m_context == nullptr)
+    {
+        m_context = new wxGLContext(canvas);
+        if (m_context == nullptr)
+            return false;
+    }
+
+    GLCanvas3D* canvas3D = new GLCanvas3D(canvas, m_context);
+    if (canvas3D == nullptr)
+        return false;
+
+    canvas3D->bind_event_handlers();
+    m_canvases.insert(CanvasesMap::value_type(canvas, canvas3D));
+
+    return true;
+}
+
+bool GLCanvas3DManager::remove(wxGLCanvas* canvas)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it == m_canvases.end())
+        return false;
+
+    it->second->unbind_event_handlers();
+    delete it->second;
+    m_canvases.erase(it);
+
+    return true;
+}
+
+void GLCanvas3DManager::remove_all()
+{
+    for (CanvasesMap::value_type& item : m_canvases)
+    {
+        item.second->unbind_event_handlers();
+        delete item.second;
+    }
+    m_canvases.clear();
+}
+
+unsigned int GLCanvas3DManager::count() const
+{
+    return (unsigned int)m_canvases.size();
+}
+
+void GLCanvas3DManager::init_gl()
+{
+    if (!m_gl_initialized)
+    {
+        glewInit();
+        if (m_gl_info.detect())
+        {
+            const AppConfig* config = GUI::get_app_config();
+            m_use_legacy_opengl = (config == nullptr) || (config->get("use_legacy_opengl") == "1");
+            m_use_VBOs = !m_use_legacy_opengl && m_gl_info.is_version_greater_or_equal_to(2, 0);
+            m_gl_initialized = true;
+        }
+        else
+            throw std::runtime_error(std::string("Unable to initialize OpenGL driver\n"));
+    }
+}
+
+std::string GLCanvas3DManager::get_gl_info(bool format_as_html, bool extensions) const
+{
+    return m_gl_info.to_string(format_as_html, extensions);
+}
+
+bool GLCanvas3DManager::use_VBOs() const
+{
+    return m_use_VBOs;
+}
+
+bool GLCanvas3DManager::init(wxGLCanvas* canvas)
+{
+    CanvasesMap::const_iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        return (it->second != nullptr) ? _init(*it->second) : false;
+    else
+        return false;
+}
+
+void GLCanvas3DManager::set_active(wxGLCanvas* canvas, bool active)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->set_active(active);
+}
+
+unsigned int GLCanvas3DManager::get_volumes_count(wxGLCanvas* canvas) const
+{
+    CanvasesMap::const_iterator it = _get_canvas(canvas);
+    return (it != m_canvases.end()) ? it->second->get_volumes_count() : 0;
+}
+
+void GLCanvas3DManager::reset_volumes(wxGLCanvas* canvas)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->reset_volumes();
+}
+
+void GLCanvas3DManager::deselect_volumes(wxGLCanvas* canvas)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->deselect_volumes();
+}
+
+void GLCanvas3DManager::select_volume(wxGLCanvas* canvas, unsigned int id)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->select_volume(id);
+}
+
+void GLCanvas3DManager::update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->update_volumes_selection(selections);
+}
+
+bool GLCanvas3DManager::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const
+{
+    CanvasesMap::const_iterator it = _get_canvas(canvas);
+    return (it != m_canvases.end()) ? it->second->check_volumes_outside_state(config) : false;
+}
+
+bool GLCanvas3DManager::move_volume_up(wxGLCanvas* canvas, unsigned int id)
+{
+    CanvasesMap::const_iterator it = _get_canvas(canvas);
+    return (it != m_canvases.end()) ? it->second->move_volume_up(id) : false;
+}
+
+bool GLCanvas3DManager::move_volume_down(wxGLCanvas* canvas, unsigned int id)
+{
+    CanvasesMap::const_iterator it = _get_canvas(canvas);
+    return (it != m_canvases.end()) ? it->second->move_volume_down(id) : false;
+}
+
+void GLCanvas3DManager::set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->set_objects_selections(selections);
+}
+
+void GLCanvas3DManager::set_config(wxGLCanvas* canvas, DynamicPrintConfig* config)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->set_config(config);
+}
+
+void GLCanvas3DManager::set_print(wxGLCanvas* canvas, Print* print)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->set_print(print);
+}
+
+void GLCanvas3DManager::set_model(wxGLCanvas* canvas, Model* model)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->set_model(model);
+}
+
+void GLCanvas3DManager::set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->set_bed_shape(shape);
+}
+
+void GLCanvas3DManager::set_auto_bed_shape(wxGLCanvas* canvas)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->set_auto_bed_shape();
+}
+
+BoundingBoxf3 GLCanvas3DManager::get_volumes_bounding_box(wxGLCanvas* canvas)
+{
+    CanvasesMap::const_iterator it = _get_canvas(canvas);
+    return (it != m_canvases.end()) ? it->second->volumes_bounding_box() : BoundingBoxf3();
+}
+
+void GLCanvas3DManager::set_axes_length(wxGLCanvas* canvas, float length)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->set_axes_length(length);
+}
+
+void GLCanvas3DManager::set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->set_cutting_plane(z, polygons);
+}
+
+void GLCanvas3DManager::set_color_by(wxGLCanvas* canvas, const std::string& value)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->set_color_by(value);
+}
+
+void GLCanvas3DManager::set_select_by(wxGLCanvas* canvas, const std::string& value)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->set_select_by(value);
+}
+
+void GLCanvas3DManager::set_drag_by(wxGLCanvas* canvas, const std::string& value)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->set_drag_by(value);
+}
+
+bool GLCanvas3DManager::is_layers_editing_enabled(wxGLCanvas* canvas) const
+{
+    CanvasesMap::const_iterator it = _get_canvas(canvas);
+    return (it != m_canvases.end()) ? it->second->is_layers_editing_enabled() : false;
+}
+
+bool GLCanvas3DManager::is_layers_editing_allowed(wxGLCanvas* canvas) const
+{
+    CanvasesMap::const_iterator it = _get_canvas(canvas);
+    return (it != m_canvases.end()) ? it->second->is_layers_editing_allowed() : false;
+}
+
+bool GLCanvas3DManager::is_shader_enabled(wxGLCanvas* canvas) const
+{
+    CanvasesMap::const_iterator it = _get_canvas(canvas);
+    return (it != m_canvases.end()) ? it->second->is_shader_enabled() : false;
+}
+
+bool GLCanvas3DManager::is_reload_delayed(wxGLCanvas* canvas) const
+{
+    CanvasesMap::const_iterator it = _get_canvas(canvas);
+    return (it != m_canvases.end()) ? it->second->is_reload_delayed() : false;
+}
+
+void GLCanvas3DManager::enable_layers_editing(wxGLCanvas* canvas, bool enable)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->enable_layers_editing(enable);
+}
+
+void GLCanvas3DManager::enable_warning_texture(wxGLCanvas* canvas, bool enable)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->enable_warning_texture(enable);
+}
+
+void GLCanvas3DManager::enable_legend_texture(wxGLCanvas* canvas, bool enable)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->enable_legend_texture(enable);
+}
+
+void GLCanvas3DManager::enable_picking(wxGLCanvas* canvas, bool enable)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->enable_picking(enable);
+}
+
+void GLCanvas3DManager::enable_moving(wxGLCanvas* canvas, bool enable)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->enable_moving(enable);
+}
+
+void GLCanvas3DManager::enable_gizmos(wxGLCanvas* canvas, bool enable)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->enable_gizmos(enable);
+}
+
+void GLCanvas3DManager::enable_shader(wxGLCanvas* canvas, bool enable)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->enable_shader(enable);
+}
+
+void GLCanvas3DManager::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->enable_force_zoom_to_bed(enable);
+}
+
+void GLCanvas3DManager::allow_multisample(wxGLCanvas* canvas, bool allow)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->allow_multisample(allow);
+}
+
+void GLCanvas3DManager::zoom_to_bed(wxGLCanvas* canvas)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->zoom_to_bed();
+}
+
+void GLCanvas3DManager::zoom_to_volumes(wxGLCanvas* canvas)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->zoom_to_volumes();
+}
+
+void GLCanvas3DManager::select_view(wxGLCanvas* canvas, const std::string& direction)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->select_view(direction);
+}
+
+void GLCanvas3DManager::set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+    {
+        CanvasesMap::iterator other_it = _get_canvas(other);
+        if (other_it != m_canvases.end())
+            it->second->set_viewport_from_scene(*other_it->second);
+    }
+}
+
+void GLCanvas3DManager::update_volumes_colors_by_extruder(wxGLCanvas* canvas)
+{
+    CanvasesMap::const_iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->update_volumes_colors_by_extruder();
+}
+
+void GLCanvas3DManager::render(wxGLCanvas* canvas) const
+{
+    CanvasesMap::const_iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->render();
+}
+
+std::vector<double> GLCanvas3DManager::get_current_print_zs(wxGLCanvas* canvas, bool active_only) const
+{
+    CanvasesMap::const_iterator it = _get_canvas(canvas);
+    return (it != m_canvases.end()) ? it->second->get_current_print_zs(active_only) : std::vector<double>();
+}
+
+void GLCanvas3DManager::set_toolpaths_range(wxGLCanvas* canvas, double low, double high)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->set_toolpaths_range(low, high);
+}
+
+std::vector<int> GLCanvas3DManager::load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs)
+{
+    if (model_object == nullptr)
+        return std::vector<int>();
+
+    CanvasesMap::const_iterator it = _get_canvas(canvas);
+    return (it != m_canvases.end()) ? it->second->load_object(*model_object, obj_idx, instance_idxs) : std::vector<int>();
+}
+
+std::vector<int> GLCanvas3DManager::load_object(wxGLCanvas* canvas, const Model* model, int obj_idx)
+{
+    if (model == nullptr)
+        return std::vector<int>();
+
+    CanvasesMap::const_iterator it = _get_canvas(canvas);
+    return (it != m_canvases.end()) ? it->second->load_object(*model, obj_idx) : std::vector<int>();
+}
+
+void GLCanvas3DManager::reload_scene(wxGLCanvas* canvas, bool force)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->reload_scene(force);
+}
+
+void GLCanvas3DManager::load_print_toolpaths(wxGLCanvas* canvas)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->load_print_toolpaths();
+}
+
+void GLCanvas3DManager::load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& tool_colors)
+{
+    if (print_object == nullptr)
+        return;
+
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->load_print_object_toolpaths(*print_object, tool_colors);
+}
+
+void GLCanvas3DManager::load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->load_wipe_tower_toolpaths(str_tool_colors);
+}
+
+void GLCanvas3DManager::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors)
+{
+    if (preview_data == nullptr)
+        return;
+
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->load_gcode_preview(*preview_data, str_tool_colors);
+}
+
+void GLCanvas3DManager::register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_viewport_changed_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_double_click_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_double_click_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_right_click_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_right_click_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_select_object_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_select_object_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_model_update_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_model_update_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_remove_object_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_remove_object_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_arrange_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_arrange_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_rotate_object_left_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_rotate_object_right_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_scale_object_uniformly_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_increase_objects_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_decrease_objects_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_instance_moved_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_wipe_tower_moved_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_enable_action_buttons_callback(callback);
+}
+
+void GLCanvas3DManager::register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback)
+{
+    CanvasesMap::iterator it = _get_canvas(canvas);
+    if (it != m_canvases.end())
+        it->second->register_on_gizmo_scale_uniformly_callback(callback);
+}
+
+GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas)
+{
+    return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas);
+}
+
+GLCanvas3DManager::CanvasesMap::const_iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) const
+{
+    return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas);
+}
+
+bool GLCanvas3DManager::_init(GLCanvas3D& canvas)
+{
+    if (!m_gl_initialized)
+        init_gl();
+
+    return canvas.init(m_use_VBOs, m_use_legacy_opengl);
+}
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp
new file mode 100644
index 000000000..6989da791
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp
@@ -0,0 +1,167 @@
+#ifndef slic3r_GLCanvas3DManager_hpp_
+#define slic3r_GLCanvas3DManager_hpp_
+
+#include "../../libslic3r/BoundingBox.hpp"
+
+#include <map>
+#include <vector>
+
+class wxGLCanvas;
+class wxGLContext;
+
+namespace Slic3r {
+
+class DynamicPrintConfig;
+class Print;
+class Model;
+class ExPolygon;
+typedef std::vector<ExPolygon> ExPolygons;
+class ModelObject;
+class PrintObject;
+class GCodePreviewData;
+    
+namespace GUI {
+
+class GLCanvas3D;
+
+class GLCanvas3DManager
+{
+    struct GLInfo
+    {
+        std::string version;
+        std::string glsl_version;
+        std::string vendor;
+        std::string renderer;
+
+        GLInfo();
+
+        bool detect();
+        bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const;
+
+        std::string to_string(bool format_as_html, bool extensions) const;
+    };
+
+    typedef std::map<wxGLCanvas*, GLCanvas3D*> CanvasesMap;
+
+    wxGLContext* m_context;
+    CanvasesMap m_canvases;
+    GLInfo m_gl_info;
+    bool m_gl_initialized;
+    bool m_use_legacy_opengl;
+    bool m_use_VBOs;
+
+public:
+    GLCanvas3DManager();
+    ~GLCanvas3DManager();
+
+    bool add(wxGLCanvas* canvas);
+    bool remove(wxGLCanvas* canvas);
+
+    void remove_all();
+
+    unsigned int count() const;
+
+    void init_gl();
+    std::string get_gl_info(bool format_as_html, bool extensions) const;
+
+    bool use_VBOs() const;
+    bool layer_editing_allowed() const;
+
+    bool init(wxGLCanvas* canvas);
+
+    void set_active(wxGLCanvas* canvas, bool active);
+
+    unsigned int get_volumes_count(wxGLCanvas* canvas) const;
+    void reset_volumes(wxGLCanvas* canvas);
+    void deselect_volumes(wxGLCanvas* canvas);
+    void select_volume(wxGLCanvas* canvas, unsigned int id);
+    void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections);
+    bool check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const;
+    bool move_volume_up(wxGLCanvas* canvas, unsigned int id);
+    bool move_volume_down(wxGLCanvas* canvas, unsigned int id);
+
+    void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections);
+
+    void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config);
+    void set_print(wxGLCanvas* canvas, Print* print);
+    void set_model(wxGLCanvas* canvas, Model* model);
+
+    void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape);
+    void set_auto_bed_shape(wxGLCanvas* canvas);
+
+    BoundingBoxf3 get_volumes_bounding_box(wxGLCanvas* canvas);
+
+    void set_axes_length(wxGLCanvas* canvas, float length);
+
+    void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons);
+
+    void set_color_by(wxGLCanvas* canvas, const std::string& value);
+    void set_select_by(wxGLCanvas* canvas, const std::string& value);
+    void set_drag_by(wxGLCanvas* canvas, const std::string& value);
+
+    bool is_layers_editing_enabled(wxGLCanvas* canvas) const;
+    bool is_layers_editing_allowed(wxGLCanvas* canvas) const;
+    bool is_shader_enabled(wxGLCanvas* canvas) const;
+
+    bool is_reload_delayed(wxGLCanvas* canvas) const;
+
+    void enable_layers_editing(wxGLCanvas* canvas, bool enable);
+    void enable_warning_texture(wxGLCanvas* canvas, bool enable);
+    void enable_legend_texture(wxGLCanvas* canvas, bool enable);
+    void enable_picking(wxGLCanvas* canvas, bool enable);
+    void enable_moving(wxGLCanvas* canvas, bool enable);
+    void enable_gizmos(wxGLCanvas* canvas, bool enable);
+    void enable_shader(wxGLCanvas* canvas, bool enable);
+    void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable);
+    void allow_multisample(wxGLCanvas* canvas, bool allow);
+
+    void zoom_to_bed(wxGLCanvas* canvas);
+    void zoom_to_volumes(wxGLCanvas* canvas);
+    void select_view(wxGLCanvas* canvas, const std::string& direction);
+    void set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other);
+
+    void update_volumes_colors_by_extruder(wxGLCanvas* canvas);
+
+    void render(wxGLCanvas* canvas) const;
+
+    std::vector<double> get_current_print_zs(wxGLCanvas* canvas, bool active_only) const;
+    void set_toolpaths_range(wxGLCanvas* canvas, double low, double high);
+
+    std::vector<int> load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs);
+    std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx);
+
+    void reload_scene(wxGLCanvas* canvas, bool force);
+
+    void load_print_toolpaths(wxGLCanvas* canvas);
+    void load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& tool_colors);
+    void load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors);
+    void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors);
+
+    void register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback);
+    void register_on_double_click_callback(wxGLCanvas* canvas, void* callback);
+    void register_on_right_click_callback(wxGLCanvas* canvas, void* callback);
+    void register_on_select_object_callback(wxGLCanvas* canvas, void* callback);
+    void register_on_model_update_callback(wxGLCanvas* canvas, void* callback);
+    void register_on_remove_object_callback(wxGLCanvas* canvas, void* callback);
+    void register_on_arrange_callback(wxGLCanvas* canvas, void* callback);
+    void register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback);
+    void register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback);
+    void register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback);
+    void register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback);
+    void register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback);
+    void register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback);
+    void register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback);
+    void register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback);
+    void register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback);
+
+private:
+    CanvasesMap::iterator _get_canvas(wxGLCanvas* canvas);
+    CanvasesMap::const_iterator _get_canvas(wxGLCanvas* canvas) const;
+
+    bool _init(GLCanvas3D& canvas);
+};
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_GLCanvas3DManager_hpp_
diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
new file mode 100644
index 000000000..d3aae33e8
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLGizmo.cpp
@@ -0,0 +1,454 @@
+#include "GLGizmo.hpp"
+
+#include "../../libslic3r/Utils.hpp"
+#include "../../libslic3r/BoundingBox.hpp"
+
+#include <GL/glew.h>
+
+#include <iostream>
+
+namespace Slic3r {
+namespace GUI {
+
+const float GLGizmoBase::Grabber::HalfSize = 2.0f;
+const float GLGizmoBase::Grabber::HoverOffset = 0.5f;
+const float GLGizmoBase::BaseColor[3] = { 1.0f, 1.0f, 1.0f };
+const float GLGizmoBase::HighlightColor[3] = { 1.0f, 0.38f, 0.0f };
+
+GLGizmoBase::Grabber::Grabber()
+    : center(Pointf(0.0, 0.0))
+    , angle_z(0.0f)
+{
+    color[0] = 1.0f;
+    color[1] = 1.0f;
+    color[2] = 1.0f;
+}
+
+void GLGizmoBase::Grabber::render(bool hover) const
+{
+    float min_x = -HalfSize;
+    float max_x = +HalfSize;
+    float min_y = -HalfSize;
+    float max_y = +HalfSize;
+
+    ::glColor3f((GLfloat)color[0], (GLfloat)color[1], (GLfloat)color[2]);
+
+    float angle_z_in_deg = angle_z * 180.0f / (float)PI;
+    ::glPushMatrix();
+    ::glTranslatef((GLfloat)center.x, (GLfloat)center.y, 0.0f);
+    ::glRotatef((GLfloat)angle_z_in_deg, 0.0f, 0.0f, 1.0f);
+
+    ::glDisable(GL_CULL_FACE);
+    ::glBegin(GL_TRIANGLES);
+    ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f);
+    ::glVertex3f((GLfloat)max_x, (GLfloat)min_y, 0.0f);
+    ::glVertex3f((GLfloat)max_x, (GLfloat)max_y, 0.0f);
+    ::glVertex3f((GLfloat)max_x, (GLfloat)max_y, 0.0f);
+    ::glVertex3f((GLfloat)min_x, (GLfloat)max_y, 0.0f);
+    ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f);
+    ::glEnd();
+    ::glEnable(GL_CULL_FACE);
+
+    if (hover)
+    {
+        min_x -= HoverOffset;
+        max_x += HoverOffset;
+        min_y -= HoverOffset;
+        max_y += HoverOffset;
+
+        ::glBegin(GL_LINE_LOOP);
+        ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f);
+        ::glVertex3f((GLfloat)max_x, (GLfloat)min_y, 0.0f);
+        ::glVertex3f((GLfloat)max_x, (GLfloat)max_y, 0.0f);
+        ::glVertex3f((GLfloat)min_x, (GLfloat)max_y, 0.0f);
+        ::glEnd();
+    }
+
+    ::glPopMatrix();
+}
+
+GLGizmoBase::GLGizmoBase()
+    : m_state(Off)
+    , m_hover_id(-1)
+{
+}
+
+GLGizmoBase::~GLGizmoBase()
+{
+}
+
+bool GLGizmoBase::init()
+{
+    return on_init();
+}
+
+GLGizmoBase::EState GLGizmoBase::get_state() const
+{
+    return m_state;
+}
+
+void GLGizmoBase::set_state(GLGizmoBase::EState state)
+{
+    m_state = state;
+}
+
+unsigned int GLGizmoBase::get_textures_id() const
+{
+    return m_textures[m_state].get_id();
+}
+
+int GLGizmoBase::get_textures_size() const
+{
+    return m_textures[Off].get_width();
+}
+
+int GLGizmoBase::get_hover_id() const
+{
+    return m_hover_id;
+}
+
+void GLGizmoBase::set_hover_id(int id)
+{
+    if (id < (int)m_grabbers.size())
+        m_hover_id = id;
+}
+
+void GLGizmoBase::start_dragging()
+{
+    on_start_dragging();
+}
+
+void GLGizmoBase::update(const Pointf& mouse_pos)
+{
+    if (m_hover_id != -1)
+        on_update(mouse_pos);
+}
+
+void GLGizmoBase::render(const BoundingBoxf3& box) const
+{
+    on_render(box);
+}
+
+void GLGizmoBase::render_for_picking(const BoundingBoxf3& box) const
+{
+    on_render_for_picking(box);
+}
+
+void GLGizmoBase::on_start_dragging()
+{
+}
+
+void GLGizmoBase::render_grabbers() const
+{
+    for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i)
+    {
+        m_grabbers[i].render(m_hover_id == i);
+    }
+}
+
+const float GLGizmoRotate::Offset = 5.0f;
+const unsigned int GLGizmoRotate::CircleResolution = 64;
+const unsigned int GLGizmoRotate::AngleResolution = 64;
+const unsigned int GLGizmoRotate::ScaleStepsCount = 60;
+const float GLGizmoRotate::ScaleStepRad = 2.0f * (float)PI / GLGizmoRotate::ScaleStepsCount;
+const unsigned int GLGizmoRotate::ScaleLongEvery = 5;
+const float GLGizmoRotate::ScaleLongTooth = 2.0f;
+const float GLGizmoRotate::ScaleShortTooth = 1.0f;
+const unsigned int GLGizmoRotate::SnapRegionsCount = 8;
+const float GLGizmoRotate::GrabberOffset = 5.0f;
+
+GLGizmoRotate::GLGizmoRotate()
+    : GLGizmoBase()
+    , m_angle_z(0.0f)
+    , m_center(Pointf(0.0, 0.0))
+    , m_radius(0.0f)
+{
+}
+
+bool GLGizmoRotate::on_init()
+{
+    std::string path = resources_dir() + "/icons/overlay/";
+
+    std::string filename = path + "rotate_off.png";
+    if (!m_textures[Off].load_from_file(filename, false))
+        return false;
+
+    filename = path + "rotate_hover.png";
+    if (!m_textures[Hover].load_from_file(filename, false))
+        return false;
+
+    filename = path + "rotate_on.png";
+    if (!m_textures[On].load_from_file(filename, false))
+        return false;
+
+    m_grabbers.push_back(Grabber());
+
+    return true;
+}
+
+void GLGizmoRotate::on_update(const Pointf& mouse_pos)
+{
+    Vectorf orig_dir(1.0, 0.0);
+    Vectorf new_dir = normalize(mouse_pos - m_center);
+    coordf_t theta = ::acos(clamp(-1.0, 1.0, dot(new_dir, orig_dir)));
+    if (cross(orig_dir, new_dir) < 0.0)
+        theta = 2.0 * (coordf_t)PI - theta;
+
+    if (length(m_center.vector_to(mouse_pos)) < 2.0 * (double)m_radius / 3.0)
+    {
+        coordf_t step = 2.0 * (coordf_t)PI / (coordf_t)SnapRegionsCount;
+        theta = step * (coordf_t)std::round(theta / step);
+    }
+
+    if (theta == 2.0 * (coordf_t)PI)
+        theta = 0.0;
+    
+    m_angle_z = (float)theta;
+}
+
+void GLGizmoRotate::on_render(const BoundingBoxf3& box) const
+{
+    ::glDisable(GL_LIGHTING);
+    ::glDisable(GL_DEPTH_TEST);
+
+    const Pointf3& size = box.size();
+    m_center = box.center();
+    m_radius = Offset + ::sqrt(sqr(0.5f * size.x) + sqr(0.5f * size.y));
+
+    ::glLineWidth(2.0f);
+    ::glColor3fv(BaseColor);
+
+    _render_circle();
+    _render_scale();
+    _render_snap_radii();
+    _render_reference_radius();
+
+    ::glColor3fv(HighlightColor);
+    _render_angle_z();
+    _render_grabber();
+}
+
+void GLGizmoRotate::on_render_for_picking(const BoundingBoxf3& box) const
+{
+    ::glDisable(GL_LIGHTING);
+    ::glDisable(GL_DEPTH_TEST);
+
+    m_grabbers[0].color[0] = 1.0f;
+    m_grabbers[0].color[1] = 1.0f;
+    m_grabbers[0].color[2] = 254.0f / 255.0f;
+    render_grabbers();
+}
+
+void GLGizmoRotate::_render_circle() const
+{
+    ::glBegin(GL_LINE_LOOP);
+    for (unsigned int i = 0; i < ScaleStepsCount; ++i)
+    {
+        float angle = (float)i * ScaleStepRad;
+        float x = m_center.x + ::cos(angle) * m_radius;
+        float y = m_center.y + ::sin(angle) * m_radius;
+        ::glVertex3f((GLfloat)x, (GLfloat)y, 0.0f);
+    }
+    ::glEnd();
+}
+
+void GLGizmoRotate::_render_scale() const
+{
+    float out_radius_long = m_radius + ScaleLongTooth;
+    float out_radius_short = m_radius + ScaleShortTooth;
+
+    ::glBegin(GL_LINES);
+    for (unsigned int i = 0; i < ScaleStepsCount; ++i)
+    {
+        float angle = (float)i * ScaleStepRad;
+        float cosa = ::cos(angle);
+        float sina = ::sin(angle);
+        float in_x = m_center.x + cosa * m_radius;
+        float in_y = m_center.y + sina * m_radius;
+        float out_x = (i % ScaleLongEvery == 0) ? m_center.x + cosa * out_radius_long : m_center.x + cosa * out_radius_short;
+        float out_y = (i % ScaleLongEvery == 0) ? m_center.y + sina * out_radius_long : m_center.y + sina * out_radius_short;
+        ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, 0.0f);
+        ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, 0.0f);
+    }
+    ::glEnd();
+}
+
+void GLGizmoRotate::_render_snap_radii() const
+{
+    float step = 2.0f * (float)PI / (float)SnapRegionsCount;
+
+    float in_radius = m_radius / 3.0f;
+    float out_radius = 2.0f * in_radius;
+
+    ::glBegin(GL_LINES);
+    for (unsigned int i = 0; i < SnapRegionsCount; ++i)
+    {
+        float angle = (float)i * step;
+        float cosa = ::cos(angle);
+        float sina = ::sin(angle);
+        float in_x = m_center.x + cosa * in_radius;
+        float in_y = m_center.y + sina * in_radius;
+        float out_x = m_center.x + cosa * out_radius;
+        float out_y = m_center.y + sina * out_radius;
+        ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, 0.0f);
+        ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, 0.0f);
+    }
+    ::glEnd();
+}
+
+void GLGizmoRotate::_render_reference_radius() const
+{
+    ::glBegin(GL_LINES);
+    ::glVertex3f((GLfloat)m_center.x, (GLfloat)m_center.y, 0.0f);
+    ::glVertex3f((GLfloat)m_center.x + m_radius + GrabberOffset, (GLfloat)m_center.y, 0.0f);
+    ::glEnd();
+}
+
+void GLGizmoRotate::_render_angle_z() const
+{
+    float step_angle = m_angle_z / AngleResolution;
+    float ex_radius = m_radius + GrabberOffset;
+
+    ::glBegin(GL_LINE_STRIP);
+    for (unsigned int i = 0; i <= AngleResolution; ++i)
+    {
+        float angle = (float)i * step_angle;
+        float x = m_center.x + ::cos(angle) * ex_radius;
+        float y = m_center.y + ::sin(angle) * ex_radius;
+        ::glVertex3f((GLfloat)x, (GLfloat)y, 0.0f);
+    }
+    ::glEnd();
+}
+
+void GLGizmoRotate::_render_grabber() const
+{
+    float grabber_radius = m_radius + GrabberOffset;
+    m_grabbers[0].center.x = m_center.x + ::cos(m_angle_z) * grabber_radius;
+    m_grabbers[0].center.y = m_center.y + ::sin(m_angle_z) * grabber_radius;
+    m_grabbers[0].angle_z = m_angle_z;
+
+    ::glColor3fv(BaseColor);
+    ::glBegin(GL_LINES);
+    ::glVertex3f((GLfloat)m_center.x, (GLfloat)m_center.y, 0.0f);
+    ::glVertex3f((GLfloat)m_grabbers[0].center.x, (GLfloat)m_grabbers[0].center.y, 0.0f);
+    ::glEnd();
+
+    ::memcpy((void*)m_grabbers[0].color, (const void*)HighlightColor, 3 * sizeof(float));
+    render_grabbers();
+}
+
+const float GLGizmoScale::Offset = 5.0f;
+
+GLGizmoScale::GLGizmoScale()
+    : GLGizmoBase()
+    , m_scale(1.0f)
+    , m_starting_scale(1.0f)
+{
+}
+
+float GLGizmoScale::get_scale() const
+{
+    return m_scale;
+}
+
+void GLGizmoScale::set_scale(float scale)
+{
+    m_starting_scale = scale;
+}
+
+bool GLGizmoScale::on_init()
+{
+    std::string path = resources_dir() + "/icons/overlay/";
+
+    std::string filename = path + "scale_off.png";
+    if (!m_textures[Off].load_from_file(filename, false))
+        return false;
+
+    filename = path + "scale_hover.png";
+    if (!m_textures[Hover].load_from_file(filename, false))
+        return false;
+
+    filename = path + "scale_on.png";
+    if (!m_textures[On].load_from_file(filename, false))
+        return false;
+
+    for (unsigned int i = 0; i < 4; ++i)
+    {
+        m_grabbers.push_back(Grabber());
+    }
+
+    return true;
+}
+
+void GLGizmoScale::on_start_dragging()
+{
+    if (m_hover_id != -1)
+        m_starting_drag_position = m_grabbers[m_hover_id].center;
+}
+
+void GLGizmoScale::on_update(const Pointf& mouse_pos)
+{
+    Pointf center(0.5 * (m_grabbers[1].center.x + m_grabbers[0].center.x), 0.5 * (m_grabbers[3].center.y + m_grabbers[0].center.y));
+
+    coordf_t orig_len = length(m_starting_drag_position - center);
+    coordf_t new_len = length(mouse_pos - center);
+    coordf_t ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0;
+
+    m_scale = m_starting_scale * (float)ratio;
+}
+
+void GLGizmoScale::on_render(const BoundingBoxf3& box) const
+{
+    ::glDisable(GL_LIGHTING);
+    ::glDisable(GL_DEPTH_TEST);
+
+    coordf_t min_x = box.min.x - (coordf_t)Offset;
+    coordf_t max_x = box.max.x + (coordf_t)Offset;
+    coordf_t min_y = box.min.y - (coordf_t)Offset;
+    coordf_t max_y = box.max.y + (coordf_t)Offset;
+
+    m_grabbers[0].center.x = min_x;
+    m_grabbers[0].center.y = min_y;
+    m_grabbers[1].center.x = max_x;
+    m_grabbers[1].center.y = min_y;
+    m_grabbers[2].center.x = max_x;
+    m_grabbers[2].center.y = max_y;
+    m_grabbers[3].center.x = min_x;
+    m_grabbers[3].center.y = max_y;
+
+    ::glLineWidth(2.0f);
+    ::glColor3fv(BaseColor);
+    // draw outline
+    ::glBegin(GL_LINE_LOOP);
+    for (unsigned int i = 0; i < 4; ++i)
+    {
+        ::glVertex3f((GLfloat)m_grabbers[i].center.x, (GLfloat)m_grabbers[i].center.y, 0.0f);
+    }
+    ::glEnd();
+
+    // draw grabbers
+    for (unsigned int i = 0; i < 4; ++i)
+    {
+        ::memcpy((void*)m_grabbers[i].color, (const void*)HighlightColor, 3 * sizeof(float));
+    }
+    render_grabbers();
+}
+
+void GLGizmoScale::on_render_for_picking(const BoundingBoxf3& box) const
+{
+    static const GLfloat INV_255 = 1.0f / 255.0f;
+
+    ::glDisable(GL_LIGHTING);
+    ::glDisable(GL_DEPTH_TEST);
+
+    for (unsigned int i = 0; i < 4; ++i)
+    {
+        m_grabbers[i].color[0] = 1.0f;
+        m_grabbers[i].color[1] = 1.0f;
+        m_grabbers[i].color[2] = (254.0f - (float)i) * INV_255;
+    }
+    render_grabbers();
+}
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp
new file mode 100644
index 000000000..2baec8f9b
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLGizmo.hpp
@@ -0,0 +1,145 @@
+#ifndef slic3r_GLGizmo_hpp_
+#define slic3r_GLGizmo_hpp_
+
+#include "../../slic3r/GUI/GLTexture.hpp"
+#include "../../libslic3r/Point.hpp"
+
+#include <vector>
+
+namespace Slic3r {
+
+class BoundingBoxf3;
+class Pointf3;
+
+namespace GUI {
+
+class GLGizmoBase
+{
+protected:
+    static const float BaseColor[3];
+    static const float HighlightColor[3];
+
+    struct Grabber
+    {
+        static const float HalfSize;
+        static const float HoverOffset;
+
+        Pointf center;
+        float angle_z;
+        float color[3];
+
+        Grabber();
+        void render(bool hover) const;
+    };
+
+public:
+    enum EState
+    {
+        Off,
+        Hover,
+        On,
+        Num_States
+    };
+
+protected:
+    EState m_state;
+    // textures are assumed to be square and all with the same size in pixels, no internal check is done
+    GLTexture m_textures[Num_States];
+    int m_hover_id;
+    mutable std::vector<Grabber> m_grabbers;
+
+public:
+    GLGizmoBase();
+    virtual ~GLGizmoBase();
+
+    bool init();
+
+    EState get_state() const;
+    void set_state(EState state);
+
+    unsigned int get_textures_id() const;
+    int get_textures_size() const;
+
+    int get_hover_id() const;
+    void set_hover_id(int id);
+
+    void start_dragging();
+    void update(const Pointf& mouse_pos);
+
+    void render(const BoundingBoxf3& box) const;
+    void render_for_picking(const BoundingBoxf3& box) const;
+
+protected:
+    virtual bool on_init() = 0;
+    virtual void on_start_dragging();
+    virtual void on_update(const Pointf& mouse_pos) = 0;
+    virtual void on_render(const BoundingBoxf3& box) const = 0;
+    virtual void on_render_for_picking(const BoundingBoxf3& box) const = 0;
+
+    void render_grabbers() const;
+};
+
+class GLGizmoRotate : public GLGizmoBase
+{
+    static const float Offset;
+    static const unsigned int CircleResolution;
+    static const unsigned int AngleResolution;
+    static const unsigned int ScaleStepsCount;
+    static const float ScaleStepRad;
+    static const unsigned int ScaleLongEvery;
+    static const float ScaleLongTooth;
+    static const float ScaleShortTooth;
+    static const unsigned int SnapRegionsCount;
+    static const float GrabberOffset;
+
+    float m_angle_z;
+
+    mutable Pointf m_center;
+    mutable float m_radius;
+
+public:
+    GLGizmoRotate();
+
+protected:
+    virtual bool on_init();
+    virtual void on_update(const Pointf& mouse_pos);
+    virtual void on_render(const BoundingBoxf3& box) const;
+    virtual void on_render_for_picking(const BoundingBoxf3& box) const;
+
+private:
+    void _render_circle() const;
+    void _render_scale() const;
+    void _render_snap_radii() const;
+    void _render_reference_radius() const;
+    void _render_angle_z() const;
+    void _render_grabber() const;
+};
+
+class GLGizmoScale : public GLGizmoBase
+{
+    static const float Offset;
+
+    float m_scale;
+
+    Pointf m_starting_drag_position;
+    float m_starting_scale;
+
+public:
+    GLGizmoScale();
+
+    float get_scale() const;
+    void set_scale(float scale);
+
+protected:
+    virtual bool on_init();
+    virtual void on_start_dragging();
+    virtual void on_update(const Pointf& mouse_pos);
+    virtual void on_render(const BoundingBoxf3& box) const;
+    virtual void on_render_for_picking(const BoundingBoxf3& box) const;
+};
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_GLGizmo_hpp_
+
diff --git a/xs/src/slic3r/GUI/GLShader.cpp b/xs/src/slic3r/GUI/GLShader.cpp
index ce9a80f05..903f6c347 100644
--- a/xs/src/slic3r/GUI/GLShader.cpp
+++ b/xs/src/slic3r/GUI/GLShader.cpp
@@ -2,6 +2,9 @@
 
 #include "GLShader.hpp"
 
+#include "../../libslic3r/Utils.hpp"
+#include <boost/nowide/fstream.hpp>
+
 #include <string>
 #include <utility>
 #include <assert.h>
@@ -22,7 +25,7 @@ inline std::string gl_get_string_safe(GLenum param)
     return std::string(value ? value : "N/A");
 }
 
-bool GLShader::load(const char *fragment_shader, const char *vertex_shader)
+bool GLShader::load_from_text(const char *fragment_shader, const char *vertex_shader)
 {
     std::string gl_version = gl_get_string_safe(GL_VERSION);
     int major = atoi(gl_version.c_str());
@@ -123,6 +126,41 @@ bool GLShader::load(const char *fragment_shader, const char *vertex_shader)
     return true;
 }
 
+bool GLShader::load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename)
+{
+    const std::string& path = resources_dir() + "/shaders/";
+
+    boost::nowide::ifstream vs(path + std::string(vertex_shader_filename), boost::nowide::ifstream::binary);
+    if (!vs.good())
+        return false;
+
+    vs.seekg(0, vs.end);
+    int file_length = vs.tellg();
+    vs.seekg(0, vs.beg);
+    std::string vertex_shader(file_length, '\0');
+    vs.read(const_cast<char*>(vertex_shader.data()), file_length);
+    if (!vs.good())
+        return false;
+
+    vs.close();
+
+    boost::nowide::ifstream fs(path + std::string(fragment_shader_filename), boost::nowide::ifstream::binary);
+    if (!fs.good())
+        return false;
+
+    fs.seekg(0, fs.end);
+    file_length = fs.tellg();
+    fs.seekg(0, fs.beg);
+    std::string fragment_shader(file_length, '\0');
+    fs.read(const_cast<char*>(fragment_shader.data()), file_length);
+    if (!fs.good())
+        return false;
+
+    fs.close();
+
+    return load_from_text(fragment_shader.c_str(), vertex_shader.c_str());
+}
+
 void GLShader::release()
 {
     if (this->shader_program_id) {
diff --git a/xs/src/slic3r/GUI/GLShader.hpp b/xs/src/slic3r/GUI/GLShader.hpp
index d91463f19..032640d8d 100644
--- a/xs/src/slic3r/GUI/GLShader.hpp
+++ b/xs/src/slic3r/GUI/GLShader.hpp
@@ -16,7 +16,9 @@ public:
         {}
     ~GLShader();
 
-    bool load(const char *fragment_shader, const char *vertex_shader);
+    bool load_from_text(const char *fragment_shader, const char *vertex_shader);
+    bool load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename);
+
     void release();
 
     int  get_attrib_location(const char *name) const;
diff --git a/xs/src/slic3r/GUI/GLTexture.cpp b/xs/src/slic3r/GUI/GLTexture.cpp
new file mode 100644
index 000000000..593362e54
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLTexture.cpp
@@ -0,0 +1,185 @@
+#include "GLTexture.hpp"
+
+#include <GL/glew.h>
+
+#include <wx/image.h>
+
+#include <vector>
+#include <algorithm>
+
+namespace Slic3r {
+namespace GUI {
+
+GLTexture::GLTexture()
+    : m_id(0)
+    , m_width(0)
+    , m_height(0)
+    , m_source("")
+{
+}
+
+GLTexture::~GLTexture()
+{
+    reset();
+}
+
+bool GLTexture::load_from_file(const std::string& filename, bool generate_mipmaps)
+{
+    reset();
+
+    // Load a PNG with an alpha channel.
+    wxImage image;
+    if (!image.LoadFile(filename, wxBITMAP_TYPE_PNG))
+    {
+        reset();
+        return false;
+    }
+
+    m_width = image.GetWidth();
+    m_height = image.GetHeight();
+    int n_pixels = m_width * m_height;
+
+    if (n_pixels <= 0)
+    {
+        reset();
+        return false;
+    }
+
+    // Get RGB & alpha raw data from wxImage, pack them into an array.
+    unsigned char* img_rgb = image.GetData();
+    if (img_rgb == nullptr)
+    {
+        reset();
+        return false;
+    }
+
+    unsigned char* img_alpha = image.GetAlpha();
+
+    std::vector<unsigned char> data(n_pixels * 4, 0);
+    for (int i = 0; i < n_pixels; ++i)
+    {
+        int data_id = i * 4;
+        int img_id = i * 3;
+        data[data_id + 0] = img_rgb[img_id + 0];
+        data[data_id + 1] = img_rgb[img_id + 1];
+        data[data_id + 2] = img_rgb[img_id + 2];
+        data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255;
+    }
+
+    // sends data to gpu
+    ::glGenTextures(1, &m_id);
+    ::glBindTexture(GL_TEXTURE_2D, m_id);
+    ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data());
+    if (generate_mipmaps)
+    {
+        // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
+        _generate_mipmaps(image);
+        ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+    }
+    else
+    {
+        ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+        ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
+    }
+    ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+    ::glBindTexture(GL_TEXTURE_2D, 0);
+
+    m_source = filename;
+    return true;
+}
+
+void GLTexture::reset()
+{
+    if (m_id != 0)
+        ::glDeleteTextures(1, &m_id);
+
+    m_id = 0;
+    m_width = 0;
+    m_height = 0;
+    m_source = "";
+}
+
+unsigned int GLTexture::get_id() const
+{
+    return m_id;
+}
+
+int GLTexture::get_width() const
+{
+    return m_width;
+}
+
+int GLTexture::get_height() const
+{
+    return m_height;
+}
+
+const std::string& GLTexture::get_source() const
+{
+    return m_source;
+}
+
+void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top)
+{
+    ::glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+
+    ::glDisable(GL_LIGHTING);
+    ::glEnable(GL_BLEND);
+    ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    ::glEnable(GL_TEXTURE_2D);
+
+    ::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id);
+
+    ::glBegin(GL_QUADS);
+    ::glTexCoord2d(0.0f, 1.0f); ::glVertex3f(left, bottom, 0.0f);
+    ::glTexCoord2d(1.0f, 1.0f); ::glVertex3f(right, bottom, 0.0f);
+    ::glTexCoord2d(1.0f, 0.0f); ::glVertex3f(right, top, 0.0f);
+    ::glTexCoord2d(0.0f, 0.0f); ::glVertex3f(left, top, 0.0f);
+    ::glEnd();
+
+    ::glBindTexture(GL_TEXTURE_2D, 0);
+
+    ::glDisable(GL_TEXTURE_2D);
+    ::glDisable(GL_BLEND);
+    ::glEnable(GL_LIGHTING);
+}
+
+void GLTexture::_generate_mipmaps(wxImage& image)
+{
+    int w = image.GetWidth();
+    int h = image.GetHeight();
+    GLint level = 0;
+    std::vector<unsigned char> data(w * h * 4, 0);
+
+    while ((w > 1) && (h > 1))
+    {
+        ++level;
+
+        w = std::max(w / 2, 1);
+        h = std::max(h / 2, 1);
+
+        int n_pixels = w * h;
+
+        image = image.ResampleBicubic(w, h);
+
+        unsigned char* img_rgb = image.GetData();
+        unsigned char* img_alpha = image.GetAlpha();
+
+        data.resize(n_pixels * 4);
+        for (int i = 0; i < n_pixels; ++i)
+        {
+            int data_id = i * 4;
+            int img_id = i * 3;
+            data[data_id + 0] = img_rgb[img_id + 0];
+            data[data_id + 1] = img_rgb[img_id + 1];
+            data[data_id + 2] = img_rgb[img_id + 2];
+            data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255;
+        }
+
+        ::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data());
+    }
+}
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/xs/src/slic3r/GUI/GLTexture.hpp b/xs/src/slic3r/GUI/GLTexture.hpp
new file mode 100644
index 000000000..70480c605
--- /dev/null
+++ b/xs/src/slic3r/GUI/GLTexture.hpp
@@ -0,0 +1,41 @@
+#ifndef slic3r_GLTexture_hpp_
+#define slic3r_GLTexture_hpp_
+
+#include <string>
+
+class wxImage;
+
+namespace Slic3r {
+namespace GUI {
+
+    class GLTexture
+    {
+    private:
+        unsigned int m_id;
+        int m_width;
+        int m_height;
+        std::string m_source;
+
+    public:
+        GLTexture();
+        ~GLTexture();
+
+        bool load_from_file(const std::string& filename, bool generate_mipmaps);
+        void reset();
+
+        unsigned int get_id() const;
+        int get_width() const;
+        int get_height() const;
+        const std::string& get_source() const;
+
+        static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top);
+
+    private:
+        void _generate_mipmaps(wxImage& image);
+    };
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_GLTexture_hpp_
+
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 6a907291e..29f35293b 100644
--- a/xs/xsp/GUI_3DScene.xsp
+++ b/xs/xsp/GUI_3DScene.xsp
@@ -8,7 +8,8 @@
     GLShader();
     ~GLShader();
 
-    bool load(const char *fragment_shader, const char *vertex_shader);
+    bool load_from_text(const char *fragment_shader, const char *vertex_shader);
+    bool load_from_file(const char *fragment_shader, const char *vertex_shader);
     void release();
 
     int  get_attrib_location(const char *name) const;
@@ -92,9 +93,6 @@
     int count()
         %code{% RETVAL = THIS->volumes.size(); %};
         
-    std::vector<double> get_current_print_zs(bool active_only)
-        %code{% RETVAL = THIS->get_current_print_zs(active_only); %};
-
     void set_range(double low, double high);
 
     void render_VBOs() const;
@@ -155,11 +153,457 @@ GLVolumeCollection::arrayref()
 %package{Slic3r::GUI::_3DScene};
 %{
 
+std::string
+get_gl_info(format_as_html, extensions)
+        bool format_as_html;
+        bool extensions;
+    CODE:
+        RETVAL = _3DScene::get_gl_info(format_as_html, extensions);
+    OUTPUT:
+        RETVAL
+
+bool
+use_VBOs()
+    CODE:
+        RETVAL = _3DScene::use_VBOs();
+    OUTPUT:
+        RETVAL
+
+bool
+add_canvas(canvas)
+        SV *canvas;
+    CODE:
+        RETVAL = _3DScene::add_canvas((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+    OUTPUT:
+        RETVAL
+
+bool
+remove_canvas(canvas)
+        SV *canvas;
+    CODE:
+        RETVAL = _3DScene::remove_canvas((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+    OUTPUT:
+        RETVAL
 
 void
-_glew_init()
+remove_all_canvases()
     CODE:
-        _3DScene::_glew_init();
+        _3DScene::remove_all_canvases();
+
+void
+set_active(canvas, active)
+        SV   *canvas;
+        bool active;
+    CODE:
+        _3DScene::set_active((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), active);
+
+unsigned int
+get_volumes_count(canvas)
+        SV *canvas;
+    CODE:
+        RETVAL = _3DScene::get_volumes_count((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+    OUTPUT:
+        RETVAL
+
+void
+reset_volumes(canvas)
+        SV *canvas;
+    CODE:
+        _3DScene::reset_volumes((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+
+void
+deselect_volumes(canvas)
+        SV *canvas;
+    CODE:
+        _3DScene::deselect_volumes((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+
+void
+select_volume(canvas, id)
+        SV           *canvas;
+        unsigned int id;
+    CODE:
+        _3DScene::select_volume((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), id);
+
+void
+update_volumes_selection(canvas, selections)
+        SV               *canvas;
+        std::vector<int> selections;
+    CODE:
+        _3DScene::update_volumes_selection((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), selections);
+
+bool
+check_volumes_outside_state(canvas, config)
+        SV                 *canvas;
+        DynamicPrintConfig *config;
+    CODE:
+        RETVAL = _3DScene::check_volumes_outside_state((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), config);
+    OUTPUT:
+        RETVAL
+
+bool
+move_volume_up(canvas, id)
+        SV           *canvas;
+        unsigned int id;
+    CODE:
+        RETVAL = _3DScene::move_volume_up((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), id);
+    OUTPUT:
+        RETVAL
+
+bool
+move_volume_down(canvas, id)
+        SV           *canvas;
+        unsigned int id;
+    CODE:
+        RETVAL = _3DScene::move_volume_down((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), id);
+    OUTPUT:
+        RETVAL
+
+void
+set_objects_selections(canvas, selections)
+        SV               *canvas;
+        std::vector<int> selections;
+    CODE:
+        _3DScene::set_objects_selections((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), selections);
+
+void
+set_config(canvas, config)
+        SV                 *canvas;
+        DynamicPrintConfig *config;
+    CODE:
+        _3DScene::set_config((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), config);
+
+void
+set_print(canvas, print)
+        SV    *canvas;
+        Print *print;
+    CODE:
+        _3DScene::set_print((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), print);
+
+void
+set_model(canvas, model)
+        SV    *canvas;
+        Model *model;
+    CODE:
+        _3DScene::set_model((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model);
+
+void
+set_bed_shape(canvas, shape)
+        SV      *canvas;
+        Pointfs shape;
+    CODE:
+        _3DScene::set_bed_shape((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), shape);
+
+void
+set_auto_bed_shape(canvas)
+        SV *canvas;
+    CODE:
+        _3DScene::set_auto_bed_shape((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+
+Clone<BoundingBoxf3>
+get_volumes_bounding_box(canvas)
+        SV *canvas;
+    CODE:
+        RETVAL = _3DScene::get_volumes_bounding_box((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+    OUTPUT:
+        RETVAL
+
+void
+set_axes_length(canvas, length)
+        SV    *canvas;
+        float length;
+    CODE:
+        _3DScene::set_axes_length((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), length);
+
+void
+set_cutting_plane(canvas, z, polygons)
+        SV         *canvas;
+        float      z;
+        ExPolygons polygons;
+    CODE:
+        _3DScene::set_cutting_plane((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), z, polygons);
+
+void
+set_color_by(canvas, value)
+        SV          *canvas;
+        std::string value;
+    CODE:
+        _3DScene::set_color_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), value);
+
+void
+set_select_by(canvas, value)
+        SV          *canvas;
+        std::string value;
+    CODE:
+        _3DScene::set_select_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), value);
+
+void
+set_drag_by(canvas, value)
+        SV          *canvas;
+        std::string value;
+    CODE:
+        _3DScene::set_drag_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), value);
+
+bool
+is_layers_editing_enabled(canvas)
+        SV *canvas;
+    CODE:
+        RETVAL = _3DScene::is_layers_editing_enabled((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+    OUTPUT:
+        RETVAL
+
+bool
+is_layers_editing_allowed(canvas)
+        SV *canvas;
+    CODE:
+        RETVAL = _3DScene::is_layers_editing_allowed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+    OUTPUT:
+        RETVAL
+
+bool
+is_shader_enabled(canvas)
+        SV *canvas;
+    CODE:
+        RETVAL = _3DScene::is_shader_enabled((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+    OUTPUT:
+        RETVAL
+
+bool
+is_reload_delayed(canvas)
+        SV *canvas;
+    CODE:
+        RETVAL = _3DScene::is_reload_delayed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+    OUTPUT:
+        RETVAL
+
+void
+enable_layers_editing(canvas, enable)
+        SV   *canvas;
+        bool enable;
+    CODE:
+        _3DScene::enable_layers_editing((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+    
+void
+enable_warning_texture(canvas, enable)
+        SV   *canvas;
+        bool enable;
+    CODE:
+        _3DScene::enable_warning_texture((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+    
+void
+enable_legend_texture(canvas, enable)
+        SV   *canvas;
+        bool enable;
+    CODE:
+        _3DScene::enable_legend_texture((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+
+void
+enable_picking(canvas, enable)
+        SV   *canvas;
+        bool enable;
+    CODE:
+        _3DScene::enable_picking((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+
+void
+enable_moving(canvas, enable)
+        SV   *canvas;
+        bool enable;
+    CODE:
+        _3DScene::enable_moving((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+
+void
+enable_gizmos(canvas, enable)
+        SV   *canvas;
+        bool enable;
+    CODE:
+        _3DScene::enable_gizmos((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+
+void
+enable_shader(canvas, enable)
+        SV   *canvas;
+        bool enable;
+    CODE:
+        _3DScene::enable_shader((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+
+void
+enable_force_zoom_to_bed(canvas, enable)
+        SV   *canvas;
+        bool enable;
+    CODE:
+        _3DScene::enable_force_zoom_to_bed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable);
+
+void
+allow_multisample(canvas, allow)
+        SV   *canvas;
+        bool allow;
+    CODE:
+        _3DScene::allow_multisample((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), allow);
+
+void
+zoom_to_bed(canvas)
+        SV *canvas;
+    CODE:
+        _3DScene::zoom_to_bed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+
+void
+zoom_to_volumes(canvas)
+        SV *canvas;
+    CODE:
+        _3DScene::zoom_to_volumes((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+    
+void
+select_view(canvas, direction)
+        SV          *canvas;
+        std::string direction;
+    CODE:
+        _3DScene::select_view((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), direction);
+
+void
+set_viewport_from_scene(canvas, other)
+        SV *canvas;
+        SV *other;
+    CODE:
+        _3DScene::set_viewport_from_scene((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (wxGLCanvas*)wxPli_sv_2_object(aTHX_ other, "Wx::GLCanvas"));
+
+void
+update_volumes_colors_by_extruder(canvas)
+        SV *canvas;
+    CODE:
+        _3DScene::update_volumes_colors_by_extruder((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+
+void
+render(canvas)
+        SV *canvas;
+    CODE:
+        _3DScene::render((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
+
+std::vector<double>
+get_current_print_zs(canvas, active_only)
+        SV   *canvas;
+        bool active_only;
+    CODE:
+        RETVAL = _3DScene::get_current_print_zs((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), active_only);
+    OUTPUT:
+        RETVAL
+
+void
+set_toolpaths_range(canvas, low, high)
+        SV *canvas;
+        double low;
+        double high;
+    CODE:
+        _3DScene::set_toolpaths_range((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), low, high);
+
+void
+register_on_viewport_changed_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_viewport_changed_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_double_click_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_double_click_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_right_click_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_right_click_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_select_object_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_select_object_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_model_update_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_model_update_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_remove_object_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_remove_object_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_arrange_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_arrange_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_rotate_object_left_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_rotate_object_left_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_rotate_object_right_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_rotate_object_right_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+        
+void
+register_on_scale_object_uniformly_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_scale_object_uniformly_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_increase_objects_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_increase_objects_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_decrease_objects_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_decrease_objects_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_instance_moved_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_instance_moved_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_wipe_tower_moved_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_wipe_tower_moved_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_enable_action_buttons_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_enable_action_buttons_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
+
+void
+register_on_gizmo_scale_uniformly_callback(canvas, callback)
+        SV *canvas;
+        SV *callback;
+    CODE:
+        _3DScene::register_on_gizmo_scale_uniformly_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback);
 
 unsigned int
 finalize_legend_texture()
@@ -218,41 +662,61 @@ reset_warning_texture()
     CODE:
         _3DScene::reset_warning_texture();
 
-void
-_load_print_toolpaths(print, volumes, tool_colors, use_VBOs)
-        Print               *print;
-        GLVolumeCollection  *volumes;
-        std::vector<std::string> tool_colors;
-        int                  use_VBOs;
+std::vector<int>
+load_model_object(canvas, model_object, obj_idx, instance_idxs)
+        SV               *canvas;
+        ModelObject      *model_object;
+        int              obj_idx;
+        std::vector<int> instance_idxs;
     CODE:
-        _3DScene::_load_print_toolpaths(print, volumes, tool_colors, use_VBOs != 0);
+        RETVAL = _3DScene::load_object((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model_object, obj_idx, instance_idxs);
+    OUTPUT:
+        RETVAL
 
-void 
-_load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs)
-        PrintObject        *print_object;
-        GLVolumeCollection *volumes;
-        std::vector<std::string> tool_colors;
-        int                 use_VBOs;
+std::vector<int>
+load_model(canvas, model, obj_idx)
+        SV               *canvas;
+        Model            *model;
+        int              obj_idx;
     CODE:
-        _3DScene::_load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs != 0);
+        RETVAL = _3DScene::load_object((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model, obj_idx);
+    OUTPUT:
+        RETVAL
 
 void
-_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs)
-        Print               *print;
-        GLVolumeCollection  *volumes;
-        std::vector<std::string> tool_colors;
-        int                  use_VBOs;
+reload_scene(canvas, force)
+        SV   *canvas;
+        bool force;
     CODE:
-        _3DScene::_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs != 0);
+        _3DScene::reload_scene((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), force);
+
+void
+load_print_toolpaths(canvas)
+        SV *canvas;
+    CODE:
+        _3DScene::load_print_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"));
 
 void 
-load_gcode_preview(print, preview_data, volumes, str_tool_colors, use_VBOs)
-        Print                    *print;
+load_print_object_toolpaths(canvas, print_object, tool_colors)
+        SV                       *canvas;
+        PrintObject              *print_object;
+        std::vector<std::string> tool_colors;
+    CODE:
+        _3DScene::load_print_object_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), print_object, tool_colors);
+
+void
+load_wipe_tower_toolpaths(canvas, tool_colors)
+        SV                       *canvas;
+        std::vector<std::string> tool_colors;
+    CODE:
+        _3DScene::load_wipe_tower_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), tool_colors);
+
+void 
+load_gcode_preview(canvas, preview_data, str_tool_colors)
+        SV                       *canvas;
         GCodePreviewData         *preview_data;
-        GLVolumeCollection       *volumes;
         std::vector<std::string> str_tool_colors;
-        int                      use_VBOs;
     CODE:
-        _3DScene::load_gcode_preview(print, preview_data, volumes, str_tool_colors, use_VBOs != 0);
+        _3DScene::load_gcode_preview((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), preview_data, str_tool_colors);
         
 %}
diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp
index 7ccbe0111..80b881c7b 100644
--- a/xs/xsp/Print.xsp
+++ b/xs/xsp/Print.xsp
@@ -52,6 +52,8 @@ _constant()
     int region_count()
         %code%{ RETVAL = THIS->print()->regions.size(); %};
 
+    int region_volumes_count()
+        %code%{ RETVAL = THIS->region_volumes.size(); %};
     Ref<Print> print();
     Ref<ModelObject> model_object();
     Ref<StaticPrintConfig> config()
@@ -119,15 +121,6 @@ _constant()
             RETVAL.push_back(slicing_params.layer_height);
         %};
 
-    void adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action)
-        %code%{
-            THIS->update_layer_height_profile(THIS->model_object()->layer_height_profile);
-            adjust_layer_height_profile(
-                THIS->slicing_parameters(), THIS->model_object()->layer_height_profile, z, layer_thickness_delta, band_width, LayerHeightEditActionType(action));
-            THIS->model_object()->layer_height_profile_valid = true;
-            THIS->layer_height_profile_valid = false;
-        %};
-
     void reset_layer_height_profile();
     
     int ptr()