diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm
index 730216a8b..f63dad079 100644
--- a/lib/Slic3r/GUI/3DScene.pm
+++ b/lib/Slic3r/GUI/3DScene.pm
@@ -66,8 +66,11 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init
                               _camera_target
                               _camera_distance
                               _zoom
+                              
+                              _legend_enabled
+                                                            
                               ) );
-
+                              
 use constant TRACKBALLSIZE  => 0.8;
 use constant TURNTABLE_MODE => 1;
 use constant GROUND_Z       => -0.02;
@@ -137,6 +140,7 @@ sub new {
     $self->_stheta(45);
     $self->_sphi(45);
     $self->_zoom(1);
+    $self->_legend_enabled(0);
     $self->use_plain_shader(0);
 
     # Collection of GLVolume objects
@@ -209,6 +213,11 @@ sub new {
     return $self;
 }
 
+sub set_legend_enabled {
+    my ($self, $value) = @_;
+   $self->_legend_enabled($value);
+}
+
 sub Destroy {
     my ($self) = @_;
     $self->{layer_height_edit_timer}->Stop;
@@ -1316,6 +1325,9 @@ sub Render {
         glDisable(GL_BLEND);
     }
 
+    # draw gcode preview legend
+    $self->draw_legend;
+    
     $self->draw_active_object_annotations;
     
     $self->SwapBuffers();
@@ -1449,12 +1461,18 @@ sub _variable_layer_thickness_load_reset_image {
 # 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, $image->{texture_id});
+    glBindTexture(GL_TEXTURE_2D, $tex_id);
     glBegin(GL_QUADS);
     glTexCoord2d(0.,1.); glVertex3f($l, $b, 0);
     glTexCoord2d(1.,1.); glVertex3f($r, $b, 0);
@@ -1579,6 +1597,37 @@ sub draw_active_object_annotations {
     glEnable(GL_DEPTH_TEST);
 }
 
+sub draw_legend {
+    my ($self) = @_;
+ 
+    if ($self->_legend_enabled)
+    {
+        my $tex_id = Slic3r::GUI::_3DScene::get_legend_texture_id;
+        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 opengl_info
 {
     my ($self, %params) = @_;
@@ -1979,9 +2028,20 @@ sub load_wipe_tower_toolpaths {
         if ($print->step_done(STEP_WIPE_TOWER));
 }
 
+sub load_gcode_preview {
+    my ($self, $print, $colors) = @_;
+
+    $self->SetCurrent($self->GetContext) if $self->UseVBOs;
+    Slic3r::GUI::_3DScene::load_gcode_preview($print, $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();
+}
+
 1;
diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index 8902a5513..ba59e89be 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -156,8 +156,15 @@ sub new {
     
     EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{preview_notebook}, sub {
         my $preview = $self->{preview_notebook}->GetCurrentPage;
-        $self->{preview3D}->load_print(1) if ($preview == $self->{preview3D});
-        $preview->OnActivate if $preview->can('OnActivate');
+        if ($preview == $self->{preview3D})
+        {
+            $self->{preview3D}->canvas->set_legend_enabled(1);
+            $self->{preview3D}->load_print(1);
+        } else {
+            $self->{preview3D}->canvas->set_legend_enabled(0);
+        }
+
+        $preview->OnActivate if $preview->can('OnActivate');        
     });
     
     # toolbar for object manipulation
@@ -778,6 +785,7 @@ sub remove {
     splice @{$self->{objects}}, $obj_idx, 1;
     $self->{model}->delete_object($obj_idx);
     $self->{print}->delete_object($obj_idx);
+    $self->{print}->clear_gcode_preview_data;
     $self->{list}->DeleteItem($obj_idx);
     $self->object_list_changed;
     
@@ -798,6 +806,7 @@ sub reset {
     @{$self->{objects}} = ();
     $self->{model}->clear_objects;
     $self->{print}->clear_objects;
+    $self->{print}->clear_gcode_preview_data;
     $self->{list}->DeleteAllItems;
     $self->object_list_changed;
     
@@ -1257,6 +1266,8 @@ sub reslice {
         $self->stop_background_process;
         # Rather perform one additional unnecessary update of the print object instead of skipping a pending async update.
         $self->async_apply_config;
+        # Reset gcode data
+        $self->{print}->clear_gcode_preview_data;
         $self->statusbar->SetCancelCallback(sub {
             $self->stop_background_process;
             $self->statusbar->SetStatusText("Slicing cancelled");
@@ -1447,6 +1458,10 @@ sub on_export_completed {
 
     # this updates buttons status
     $self->object_list_changed;
+    
+    # refresh preview
+    $self->{toolpaths2D}->reload_print if $self->{toolpaths2D};
+    $self->{preview3D}->reload_print if $self->{preview3D};
 }
 
 sub do_print {
diff --git a/lib/Slic3r/GUI/Plater/3DPreview.pm b/lib/Slic3r/GUI/Plater/3DPreview.pm
index c7d4cc4a8..6f96b8a96 100644
--- a/lib/Slic3r/GUI/Plater/3DPreview.pm
+++ b/lib/Slic3r/GUI/Plater/3DPreview.pm
@@ -4,11 +4,11 @@ use warnings;
 use utf8;
 
 use Slic3r::Print::State ':steps';
-use Wx qw(:misc :sizer :slider :statictext :keycode wxWHITE);
-use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN EVT_CHECKBOX);
+use Wx qw(:misc :sizer :slider :statictext :keycode wxWHITE wxCB_READONLY);
+use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN EVT_CHECKBOX EVT_CHOICE EVT_CHECKLISTBOX);
 use base qw(Wx::Panel Class::Accessor);
 
-__PACKAGE__->mk_accessors(qw(print enabled _loaded canvas slider_low slider_high single_layer));
+__PACKAGE__->mk_accessors(qw(print enabled _loaded canvas slider_low slider_high single_layer auto_zoom));
 
 sub new {
     my $class = shift;
@@ -17,7 +17,7 @@ sub new {
     my $self = $class->SUPER::new($parent, -1, wxDefaultPosition);
     $self->{config} = $config;
     $self->{number_extruders} = 1;
-    $self->{preferred_color_mode} = 'feature';
+    $self->auto_zoom(1);
 
     # init GUI elements
     my $canvas = Slic3r::GUI::3DScene->new($self);
@@ -56,10 +56,35 @@ sub new {
     $z_label_high->SetFont($Slic3r::GUI::small_font);
 
     $self->single_layer(0);
-    $self->{color_by_extruder} = 0;
     my $checkbox_singlelayer = $self->{checkbox_singlelayer} = Wx::CheckBox->new($self, -1, "1 Layer");
-    my $checkbox_color_by_extruder = $self->{checkbox_color_by_extruder} = Wx::CheckBox->new($self, -1, "Tool");
     
+    my $label_view_type = $self->{label_view_type} = Wx::StaticText->new($self, -1, "View");
+    
+    my $choice_view_type = Wx::Choice->new($self, -1);
+    $choice_view_type->Append("Feature type");
+    $choice_view_type->Append("Height");
+    $choice_view_type->Append("Width");
+    $choice_view_type->Append("Speed");
+    $choice_view_type->Append("Tool");
+    $choice_view_type->SetSelection(0);
+
+    my $label_show_features = $self->{label_show_features} = Wx::StaticText->new($self, -1, "Show");
+    
+    my $combochecklist_features = Wx::ComboCtrl->new();
+    $combochecklist_features->Create($self, -1, "Feature types", wxDefaultPosition, [200, -1], wxCB_READONLY);
+    #FIXME If the following line is removed, the combo box popup list will not react to mouse clicks.
+    # On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10.
+    $combochecklist_features->UseAltPopupWindow();
+    $combochecklist_features->EnablePopupAnimation(0);
+    my $feature_text = "Feature types";
+    my $feature_items = "Perimeter|External perimeter|Overhang perimeter|Internal infill|Solid infill|Top solid infill|Bridge infill|Gap fill|Skirt|Support material|Support material interface|Wipe tower";
+    Slic3r::GUI::create_combochecklist($combochecklist_features, $feature_text, $feature_items, 1);
+    
+    my $checkbox_travel = Wx::CheckBox->new($self, -1, "Travel");
+    my $checkbox_retractions = Wx::CheckBox->new($self, -1, "Retractions");    
+    my $checkbox_unretractions = Wx::CheckBox->new($self, -1, "Unretractions");
+    my $checkbox_shells  = Wx::CheckBox->new($self, -1, "Shells");
+
     my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
     my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
     my $vsizer_outer = Wx::BoxSizer->new(wxVERTICAL);
@@ -72,11 +97,29 @@ sub new {
     $hsizer->Add($vsizer, 0, wxEXPAND, 0);
     $vsizer_outer->Add($hsizer, 3, wxALIGN_CENTER_HORIZONTAL, 0);
     $vsizer_outer->Add($checkbox_singlelayer, 0, wxTOP | wxALIGN_CENTER_HORIZONTAL, 5);
-    $vsizer_outer->Add($checkbox_color_by_extruder, 0, wxTOP | wxALIGN_CENTER_HORIZONTAL, 5);
 
+    my $bottom_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
+    $bottom_sizer->Add($label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5);
+    $bottom_sizer->Add($choice_view_type, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5);
+    $bottom_sizer->AddSpacer(10);
+    $bottom_sizer->Add($label_show_features, 0, wxALIGN_CENTER_VERTICAL, 5);
+    $bottom_sizer->Add($combochecklist_features, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5);
+    $bottom_sizer->AddSpacer(20);
+    $bottom_sizer->Add($checkbox_travel, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5);
+    $bottom_sizer->AddSpacer(10);
+    $bottom_sizer->Add($checkbox_retractions, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5);
+    $bottom_sizer->AddSpacer(10);
+    $bottom_sizer->Add($checkbox_unretractions, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5);
+    $bottom_sizer->AddSpacer(10);
+    $bottom_sizer->Add($checkbox_shells, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5);
+    
     my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
     $sizer->Add($canvas, 1, wxALL | wxEXPAND, 0);
     $sizer->Add($vsizer_outer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5);
+
+    my $main_sizer = Wx::BoxSizer->new(wxVERTICAL);
+    $main_sizer->Add($sizer, 1, wxALL | wxEXPAND, 0);
+    $main_sizer->Add($bottom_sizer, 0, wxALL | wxEXPAND, 0); 
     
     EVT_SLIDER($self, $slider_low,  sub {
         $slider_high->SetValue($slider_low->GetValue) if $self->single_layer;
@@ -147,18 +190,70 @@ sub new {
             $self->set_z_idx_high($slider_high->GetValue);
         }
     });
-    EVT_CHECKBOX($self, $checkbox_color_by_extruder, sub {
-        $self->{color_by_extruder} = $checkbox_color_by_extruder->GetValue();
-        $self->{preferred_color_mode} = $self->{color_by_extruder} ? 'tool' : 'feature';
+    EVT_CHOICE($self, $choice_view_type, sub {
+        my $selection = $choice_view_type->GetCurrentSelection();
+        $self->print->set_gcode_preview_type($selection);
+        $self->auto_zoom(0);
         $self->reload_print;
+        $self->auto_zoom(1);
+    });
+    EVT_CHECKLISTBOX($self, $combochecklist_features, sub {
+        my $flags = Slic3r::GUI::combochecklist_get_flags($combochecklist_features);
+        
+        $self->print->set_gcode_preview_extrusion_flags($flags);
+        $self->auto_zoom(0);
+        $self->refresh_print;
+        $self->auto_zoom(1);
+    });    
+    EVT_CHECKBOX($self, $checkbox_travel, sub {
+        $self->print->set_gcode_preview_travel_visible($checkbox_travel->IsChecked());
+        $self->auto_zoom(0);
+        $self->refresh_print;
+        $self->auto_zoom(1);
+    });    
+    EVT_CHECKBOX($self, $checkbox_retractions, sub {
+        $self->print->set_gcode_preview_retractions_visible($checkbox_retractions->IsChecked());
+        $self->auto_zoom(0);
+        $self->refresh_print;
+        $self->auto_zoom(1);
+    });
+    EVT_CHECKBOX($self, $checkbox_unretractions, sub {
+        $self->print->set_gcode_preview_unretractions_visible($checkbox_unretractions->IsChecked());
+        $self->auto_zoom(0);
+        $self->refresh_print;
+        $self->auto_zoom(1);
+    });
+    EVT_CHECKBOX($self, $checkbox_shells, sub {
+        $self->print->set_gcode_preview_shells_visible($checkbox_shells->IsChecked());
+        $self->auto_zoom(0);
+        $self->refresh_print;
+        $self->auto_zoom(1);
     });
     
-    $self->SetSizer($sizer);
+    $self->SetSizer($main_sizer);
     $self->SetMinSize($self->GetSize);
     $sizer->SetSizeHints($self);
     
     # init canvas
     $self->print($print);
+    
+    # sets colors for gcode preview extrusion roles
+    my @extrusion_roles_colors = (
+                                    'Perimeter'                  => 'FF0000',
+                                    'External perimeter'         => '00FF00',
+                                    'Overhang perimeter'         => '0000FF',
+                                    'Internal infill'            => 'FFFF00',
+                                    'Solid infill'               => 'FF00FF',
+                                    'Top solid infill'           => '00FFFF',
+                                    'Bridge infill'              => '7F7F7F',
+                                    'Gap fill'                   => 'FFFFFF',
+                                    'Skirt'                      => '7F0000',
+                                    'Support material'           => '007F00',
+                                    'Support material interface' => '00007F',
+                                    'Wipe tower'                 => 'B3E3AB',
+                                 );
+    $self->print->set_gcode_extrusion_paths_colors(\@extrusion_roles_colors);
+    
     $self->reload_print;
     
     return $self;
@@ -171,7 +266,19 @@ sub reload_print {
     $self->_loaded(0);
 
     if (! $self->IsShown && ! $force) {
-        $self->{reload_delayed} = 1;
+#        $self->{reload_delayed} = 1;
+        return;
+    }
+
+    $self->load_print;
+}
+
+sub refresh_print {
+    my ($self) = @_;
+
+    $self->_loaded(0);
+    
+    if (! $self->IsShown) {
         return;
     }
 
@@ -203,6 +310,9 @@ sub load_print {
         $self->set_z_range(0,0);
         $self->slider_low->Hide;
         $self->slider_high->Hide;
+        $self->{z_label_low}->SetLabel("");
+        $self->{z_label_high}->SetLabel("");
+        $self->canvas->reset_legend_texture();
         $self->canvas->Refresh;  # clears canvas
         return;
     }
@@ -232,44 +342,35 @@ sub load_print {
     $self->slider_high->Show;
     $self->Layout;
 
-    my $by_tool = $self->{color_by_extruder};
-    if ($self->{preferred_color_mode} eq 'tool_or_feature') {
-        # It is left to Slic3r to decide whether the print shall be colored by the tool or by the feature.
-        # Color by feature if it is a single extruder print.
-        my $extruders = $self->{print}->extruders;
-        $by_tool = scalar(@{$extruders}) > 1;
-        $self->{color_by_extruder} = $by_tool;
-        $self->{checkbox_color_by_extruder}->SetValue($by_tool);
-        $self->{preferred_color_mode} = 'tool_or_feature';
-    }
-
     # Collect colors per extruder.
-    # Leave it empty, if the print should be colored by a feature.
     my @colors = ();
-    if ($by_tool) {
-        my @extruder_colors = @{$self->{config}->extruder_colour};
-        my @filament_colors = @{$self->{config}->filament_colour};
-        for (my $i = 0; $i <= $#extruder_colors; $i += 1) {
-            my $color = $extruder_colors[$i];
-            $color = $filament_colors[$i] if (! defined($color) || $color !~ m/^#[[:xdigit:]]{6}/);
-            $color = '#FFFFFF' if (! defined($color) || $color !~ m/^#[[:xdigit:]]{6}/);
-            push @colors, $color;
-        }
+    my @extruder_colors = @{$self->{config}->extruder_colour};
+    my @filament_colors = @{$self->{config}->filament_colour};
+    for (my $i = 0; $i <= $#extruder_colors; $i += 1) {
+        my $color = $extruder_colors[$i];
+        $color = $filament_colors[$i] if (! defined($color) || $color !~ m/^#[[:xdigit:]]{6}/);
+        $color = '#FFFFFF' if (! defined($color) || $color !~ m/^#[[:xdigit:]]{6}/);
+        push @colors, $color;
     }
 
     if ($self->IsShown) {
-        # load skirt and brim
-        $self->canvas->load_print_toolpaths($self->print, \@colors);
-        $self->canvas->load_wipe_tower_toolpaths($self->print, \@colors);
-        
-        foreach my $object (@{$self->print->objects}) {
-            $self->canvas->load_print_object_toolpaths($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->canvas->load_gcode_preview($self->print, \@colors);
+
+#        # load skirt and brim
+#        $self->canvas->load_print_toolpaths($self->print, \@colors);
+#        $self->canvas->load_wipe_tower_toolpaths($self->print, \@colors);
+#        
+#        foreach my $object (@{$self->print->objects}) {
+#            $self->canvas->load_print_object_toolpaths($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;
+#        }
+        if ($self->auto_zoom)
+        {
+            $self->canvas->zoom_to_volumes;
         }
-        $self->canvas->zoom_to_volumes;
         $self->_loaded(1);
     }
     
@@ -322,17 +423,13 @@ sub set_number_extruders {
     my ($self, $number_extruders) = @_;
     if ($self->{number_extruders} != $number_extruders) {
         $self->{number_extruders} = $number_extruders;
-        my $by_tool = $number_extruders > 1;
-        $self->{color_by_extruder} = $by_tool;
-        $self->{checkbox_color_by_extruder}->SetValue($by_tool);
-        $self->{preferred_color_mode} = $by_tool ? 'tool_or_feature' : 'feature';
     }
 }
 
 # Called by the Platter wxNotebook when this page is activated.
 sub OnActivate {
-    my ($self) = @_;
-    $self->reload_print(1) if ($self->{reload_delayed});
+#    my ($self) = @_;
+#    $self->reload_print(1) if ($self->{reload_delayed});
 }
 
 1;
diff --git a/lib/Slic3r/GUI/Plater/3DToolpaths.pm b/lib/Slic3r/GUI/Plater/3DToolpaths.pm
index fb904b0fd..776d8217b 100644
--- a/lib/Slic3r/GUI/Plater/3DToolpaths.pm
+++ b/lib/Slic3r/GUI/Plater/3DToolpaths.pm
@@ -122,16 +122,19 @@ sub load_print {
     }
     
     if ($self->IsShown) {
-        # load skirt and brim
-        $self->canvas->load_print_toolpaths($self->print);
-        
-        foreach my $object (@{$self->print->objects}) {
-            $self->canvas->load_print_object_toolpaths($object);
-            
-            # 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->canvas->load_gcode_preview($self->print);
+
+#        # load skirt and brim
+#        $self->canvas->load_print_toolpaths($self->print);
+#        
+#        foreach my $object (@{$self->print->objects}) {
+#            $self->canvas->load_print_object_toolpaths($object);
+#            
+#            # 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->canvas->zoom_to_volumes;
         $self->_loaded(1);
     }
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
index 9e23112c5..de475eaa0 100644
--- a/xs/CMakeLists.txt
+++ b/xs/CMakeLists.txt
@@ -177,6 +177,8 @@ add_library(libslic3r_gui STATIC
     ${LIBDIR}/slic3r/GUI/PresetHints.hpp
     ${LIBDIR}/slic3r/GUI/GUI.cpp
     ${LIBDIR}/slic3r/GUI/GUI.hpp
+    ${LIBDIR}/slic3r/GUI/wxExtensions.cpp
+    ${LIBDIR}/slic3r/GUI/wxExtensions.hpp
 )
 
 add_library(admesh STATIC
diff --git a/xs/src/libslic3r/BoundingBox.cpp b/xs/src/libslic3r/BoundingBox.cpp
index 46a43ca69..91ba88d84 100644
--- a/xs/src/libslic3r/BoundingBox.cpp
+++ b/xs/src/libslic3r/BoundingBox.cpp
@@ -4,38 +4,9 @@
 
 namespace Slic3r {
 
-template <class PointClass>
-BoundingBoxBase<PointClass>::BoundingBoxBase(const std::vector<PointClass> &points)
-{
-    if (points.empty()) 
-        CONFESS("Empty point set supplied to BoundingBoxBase constructor");
-    typename std::vector<PointClass>::const_iterator it = points.begin();
-    this->min.x = this->max.x = it->x;
-    this->min.y = this->max.y = it->y;
-    for (++it; it != points.end(); ++it) {
-        this->min.x = std::min(it->x, this->min.x);
-        this->min.y = std::min(it->y, this->min.y);
-        this->max.x = std::max(it->x, this->max.x);
-        this->max.y = std::max(it->y, this->max.y);
-    }
-    this->defined = true;
-}
 template BoundingBoxBase<Point>::BoundingBoxBase(const std::vector<Point> &points);
 template BoundingBoxBase<Pointf>::BoundingBoxBase(const std::vector<Pointf> &points);
 
-template <class PointClass>
-BoundingBox3Base<PointClass>::BoundingBox3Base(const std::vector<PointClass> &points)
-    : BoundingBoxBase<PointClass>(points)
-{
-    if (points.empty())
-        CONFESS("Empty point set supplied to BoundingBox3Base constructor");
-    typename std::vector<PointClass>::const_iterator it = points.begin();
-    this->min.z = this->max.z = it->z;
-    for (++it; it != points.end(); ++it) {
-        this->min.z = std::min(it->z, this->min.z);
-        this->max.z = std::max(it->z, this->max.z);
-    }
-}
 template BoundingBox3Base<Pointf3>::BoundingBox3Base(const std::vector<Pointf3> &points);
 
 BoundingBox::BoundingBox(const Lines &lines)
diff --git a/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp
index 90179d3f9..a7334308c 100644
--- a/xs/src/libslic3r/BoundingBox.hpp
+++ b/xs/src/libslic3r/BoundingBox.hpp
@@ -23,7 +23,23 @@ public:
     BoundingBoxBase() : defined(false) {};
     BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) : 
         min(pmin), max(pmax), defined(pmin.x < pmax.x && pmin.y < pmax.y) {}
-    BoundingBoxBase(const std::vector<PointClass> &points);
+    BoundingBoxBase(const std::vector<PointClass>& points)
+    {
+        if (points.empty())
+            CONFESS("Empty point set supplied to BoundingBoxBase constructor");
+
+        typename std::vector<PointClass>::const_iterator it = points.begin();
+        this->min.x = this->max.x = it->x;
+        this->min.y = this->max.y = it->y;
+        for (++it; it != points.end(); ++it)
+        {
+            this->min.x = std::min(it->x, this->min.x);
+            this->min.y = std::min(it->y, this->min.y);
+            this->max.x = std::max(it->x, this->max.x);
+            this->max.y = std::max(it->y, this->max.y);
+        }
+        this->defined = (this->min.x < this->max.x) && (this->min.y < this->max.y);
+    }
     void merge(const PointClass &point);
     void merge(const std::vector<PointClass> &points);
     void merge(const BoundingBoxBase<PointClass> &bb);
@@ -54,7 +70,21 @@ public:
     BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) : 
         BoundingBoxBase<PointClass>(pmin, pmax) 
         { if (pmin.z >= pmax.z) BoundingBoxBase<PointClass>::defined = false; }
-    BoundingBox3Base(const std::vector<PointClass> &points);
+    BoundingBox3Base(const std::vector<PointClass>& points)
+        : BoundingBoxBase<PointClass>(points)
+    {
+        if (points.empty())
+            CONFESS("Empty point set supplied to BoundingBox3Base constructor");
+
+        typename std::vector<PointClass>::const_iterator it = points.begin();
+        this->min.z = this->max.z = it->z;
+        for (++it; it != points.end(); ++it)
+        {
+            this->min.z = std::min(it->z, this->min.z);
+            this->max.z = std::max(it->z, this->max.z);
+        }
+        this->defined &= (this->min.z < this->max.z);
+    }
     void merge(const PointClass &point);
     void merge(const std::vector<PointClass> &points);
     void merge(const BoundingBox3Base<PointClass> &bb);
@@ -92,7 +122,7 @@ class BoundingBox3  : public BoundingBox3Base<Point3>
 public:
     BoundingBox3() : BoundingBox3Base<Point3>() {};
     BoundingBox3(const Point3 &pmin, const Point3 &pmax) : BoundingBox3Base<Point3>(pmin, pmax) {};
-    BoundingBox3(const std::vector<Point3> &points) : BoundingBox3Base<Point3>(points) {};
+    BoundingBox3(const Points3& points) : BoundingBox3Base<Point3>(points) {};
 };
 
 class BoundingBoxf : public BoundingBoxBase<Pointf> 
diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp
index 6e5b123dc..f5e243419 100644
--- a/xs/src/libslic3r/ExtrusionEntity.hpp
+++ b/xs/src/libslic3r/ExtrusionEntity.hpp
@@ -25,6 +25,7 @@ enum ExtrusionRole {
     erSkirt,
     erSupportMaterial,
     erSupportMaterialInterface,
+    erWipeTower,
     // Extrusion role for a collection with multiple extrusion roles.
     erMixed,
 };
@@ -104,15 +105,19 @@ public:
     float width;
     // Height of the extrusion, used for visualization purposed.
     float height;
-    
-    ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {};
-    ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {};
-    ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
-    ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
-//    ExtrusionPath(ExtrusionRole role, const Flow &flow) : m_role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height) {};
+    // Feedrate of the extrusion, used for visualization purposed.
+    float feedrate;
+    // Id of the extruder, used for visualization purposed.
+    unsigned int extruder_id;
 
-    ExtrusionPath& operator=(const ExtrusionPath &rhs) { this->m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = rhs.polyline; return *this; }
-    ExtrusionPath& operator=(ExtrusionPath &&rhs) { this->m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = std::move(rhs.polyline); return *this; }
+    ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), feedrate(0.0f), extruder_id(0), m_role(role) {};
+    ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), feedrate(0.0f), extruder_id(0), m_role(role) {};
+    ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), m_role(rhs.m_role) {}
+    ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), m_role(rhs.m_role) {}
+//    ExtrusionPath(ExtrusionRole role, const Flow &flow) : m_role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height), feedrate(0.0f), extruder_id(0) {};
+
+    ExtrusionPath& operator=(const ExtrusionPath &rhs) { this->m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->polyline = rhs.polyline; return *this; }
+    ExtrusionPath& operator=(ExtrusionPath &&rhs) { this->m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->polyline = std::move(rhs.polyline); return *this; }
 
     ExtrusionPath* clone() const { return new ExtrusionPath (*this); }
     void reverse() { this->polyline.reverse(); }
diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp
index d22040799..c256268e5 100644
--- a/xs/src/libslic3r/GCode.cpp
+++ b/xs/src/libslic3r/GCode.cpp
@@ -402,6 +402,14 @@ void GCode::_do_export(Print &print, FILE *file)
     m_time_estimator.reset();
     m_time_estimator.set_dialect(print.config.gcode_flavor);
 
+    // resets analyzer
+    m_analyzer.reset();
+
+    // resets analyzer's tracking data
+    m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm;
+    m_last_width = GCodeAnalyzer::Default_Width;
+    m_last_height = GCodeAnalyzer::Default_Height;
+
     // How many times will be change_layer() called?
     // change_layer() in turn increments the progress bar status.
     m_layer_count = 0;
@@ -810,6 +818,9 @@ void GCode::_do_export(Print &print, FILE *file)
                 _write_format(file, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str());
         }
     }
+
+    // starts analizer calculations
+    m_analyzer.calc_gcode_preview_data(print);
 }
 
 std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override)
@@ -1301,12 +1312,7 @@ void GCode::process_layer(
 			if (print_object == nullptr)
 				// This layer is empty for this particular object, it has neither object extrusions nor support extrusions at this print_z.
 				continue;
-            if (m_enable_analyzer_markers) {
-                // Store the binary pointer to the layer object directly into the G-code to be accessed by the GCodeAnalyzer.
-                char buf[64];
-                sprintf(buf, ";_LAYEROBJ:%p\n", m_layer);
-                gcode += buf;
-            }
+
             m_config.apply(print_object->config, true);
             m_layer = layers[layer_id].layer();
             if (m_config.avoid_crossing_perimeters)
@@ -1448,7 +1454,9 @@ static inline const char* ExtrusionRole2String(const ExtrusionRole role)
     case erSkirt:                       return "erSkirt";
     case erSupportMaterial:             return "erSupportMaterial";
     case erSupportMaterialInterface:    return "erSupportMaterialInterface";
+    case erWipeTower:                   return "erWipeTower";
     case erMixed:                       return "erMixed";
+
     default:                            return "erInvalid";
     };
 }
@@ -1998,13 +2006,16 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill
     return gcode;
 }
 
-void GCode::_write(FILE* file, const char *what, size_t size)
+void GCode::_write(FILE* file, const char *what)
 {
-    if (size > 0) {
+    if (what != nullptr) {
+        // apply analyzer, if enabled
+        const char* gcode = m_enable_analyzer ? m_analyzer.process_gcode(what).c_str() : what;
+
         // writes string to file
-        fwrite(what, 1, size, file);
+        fwrite(gcode, 1, ::strlen(gcode), file);
         // updates time estimator and gcode lines vector
-        m_time_estimator.add_gcode_block(what);
+        m_time_estimator.add_gcode_block(gcode);
     }
 }
 
@@ -2038,7 +2049,8 @@ void GCode::_write_format(FILE* file, const char* format, ...)
     char *bufptr = buffer_dynamic ? (char*)malloc(buflen) : buffer;
     int res = ::vsnprintf(bufptr, buflen, format, args);
     if (res > 0)
-        _write(file, bufptr, res);
+        _write(file, bufptr);
+
     if (buffer_dynamic)
         free(bufptr);
 
@@ -2123,14 +2135,57 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
     double F = speed * 60;  // convert mm/sec to mm/min
     
     // extrude arc or line
-    if (m_enable_extrusion_role_markers || m_enable_analyzer_markers) {
-        if (path.role() != m_last_extrusion_role) {
+    if (m_enable_extrusion_role_markers || m_enable_analyzer)
+    {
+        if (path.role() != m_last_extrusion_role)
+        {
             m_last_extrusion_role = path.role();
+            if (m_enable_extrusion_role_markers)
+            {
+                char buf[32];
+                sprintf(buf, ";_EXTRUSION_ROLE:%d\n", int(m_last_extrusion_role));
+                gcode += buf;
+            }
+            if (m_enable_analyzer)
+            {
+                char buf[32];
+                sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), int(m_last_extrusion_role));
+                gcode += buf;
+            }
+        }
+    }
+
+    // adds analyzer tags and updates analyzer's tracking data
+    if (m_enable_analyzer)
+    {
+        if (m_last_mm3_per_mm != path.mm3_per_mm)
+        {
+            m_last_mm3_per_mm = path.mm3_per_mm;
+
             char buf[32];
-            sprintf(buf, ";_EXTRUSION_ROLE:%d\n", int(path.role()));
+            sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm);
+            gcode += buf;
+        }
+
+        if (m_last_width != path.width)
+        {
+            m_last_width = path.width;
+
+            char buf[32];
+            sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), m_last_width);
+            gcode += buf;
+        }
+
+        if (m_last_height != path.height)
+        {
+            m_last_height = path.height;
+
+            char buf[32];
+            sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_last_height);
             gcode += buf;
         }
     }
+
     std::string comment;
     if (m_enable_cooling_markers) {
         if (is_bridge(path.role()))
diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp
index 5c622ec2d..6e42c52d0 100644
--- a/xs/src/libslic3r/GCode.hpp
+++ b/xs/src/libslic3r/GCode.hpp
@@ -17,6 +17,7 @@
 #include "GCode/WipeTower.hpp"
 #include "GCodeTimeEstimator.hpp"
 #include "EdgeGrid.hpp"
+#include "GCode/Analyzer.hpp"
 
 #include <memory>
 #include <string>
@@ -118,13 +119,16 @@ public:
         m_enable_loop_clipping(true), 
         m_enable_cooling_markers(false), 
         m_enable_extrusion_role_markers(false), 
-        m_enable_analyzer_markers(false),
+        m_enable_analyzer(true),
         m_layer_count(0),
         m_layer_index(-1), 
         m_layer(nullptr), 
         m_volumetric_speed(0),
         m_last_pos_defined(false),
         m_last_extrusion_role(erNone),
+        m_last_mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm),
+        m_last_width(GCodeAnalyzer::Default_Width),
+        m_last_height(GCodeAnalyzer::Default_Height),
         m_brim_done(false),
         m_second_layer_things_done(false),
         m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max()))
@@ -149,6 +153,8 @@ public:
     // inside the generated string and after the G-code export finishes.
     std::string     placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr);
     bool            enable_cooling_markers() const { return m_enable_cooling_markers; }
+    bool            enable_analyzer() const { return m_enable_analyzer; }
+    void            enable_analyzer(bool enable) { m_enable_analyzer = enable; }
 
     // For Perl bindings, to be used exclusively by unit tests.
     unsigned int    layer_count() const { return m_layer_count; }
@@ -241,9 +247,10 @@ protected:
     // Markers for the Pressure Equalizer to recognize the extrusion type.
     // The Pressure Equalizer removes the markers from the final G-code.
     bool                                m_enable_extrusion_role_markers;
-    // Extended markers for the G-code Analyzer.
+    // Enableds the G-code Analyzer.
+    // Extended markers will be added during G-code generation.
     // The G-code Analyzer will remove these comments from the final G-code.
-    bool                                m_enable_analyzer_markers;
+    bool                                m_enable_analyzer;
     // How many times will change_layer() be called?
     // change_layer() will update the progress bar.
     unsigned int                        m_layer_count;
@@ -256,6 +263,10 @@ protected:
     double                              m_volumetric_speed;
     // Support for the extrusion role markers. Which marker is active?
     ExtrusionRole                       m_last_extrusion_role;
+    // Support for G-Code Analyzer
+    double                              m_last_mm3_per_mm;
+    float                               m_last_width;
+    float                               m_last_height;
 
     Point                               m_last_pos;
     bool                                m_last_pos_defined;
@@ -277,9 +288,12 @@ protected:
     // Time estimator
     GCodeTimeEstimator m_time_estimator;
 
+    // Analyzer
+    GCodeAnalyzer m_analyzer;
+
     // Write a string into a file.
-    void _write(FILE* file, const std::string& what) { this->_write(file, what.c_str(), what.size()); }
-    void _write(FILE* file, const char *what, size_t size);
+    void _write(FILE* file, const std::string& what) { this->_write(file, what.c_str()); }
+    void _write(FILE* file, const char *what);
 
     // Write a string into a file. 
     // Add a newline, if the string does not end with a newline already.
diff --git a/xs/src/libslic3r/GCode/Analyzer.cpp b/xs/src/libslic3r/GCode/Analyzer.cpp
index ab2955eb5..02e999bb4 100644
--- a/xs/src/libslic3r/GCode/Analyzer.cpp
+++ b/xs/src/libslic3r/GCode/Analyzer.cpp
@@ -4,323 +4,1146 @@
 
 #include "../libslic3r.h"
 #include "../PrintConfig.hpp"
+#include "Print.hpp"
 
 #include "Analyzer.hpp"
 
+static const std::string AXIS_STR = "XYZE";
+static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
+static const float INCHES_TO_MM = 25.4f;
+static const float DEFAULT_FEEDRATE = 0.0f;
+static const unsigned int DEFAULT_EXTRUDER_ID = 0;
+static const Slic3r::Pointf3 DEFAULT_START_POSITION = Slic3r::Pointf3(0.0f, 0.0f, 0.0f);
+static const float DEFAULT_START_EXTRUSION = 0.0f;
+
 namespace Slic3r {
 
-void GCodeMovesDB::reset()
+const std::string GCodeAnalyzer::Extrusion_Role_Tag = "_ANALYZER_EXTR_ROLE:";
+const std::string GCodeAnalyzer::Mm3_Per_Mm_Tag = "_ANALYZER_MM3_PER_MM:";
+const std::string GCodeAnalyzer::Width_Tag = "_ANALYZER_WIDTH:";
+const std::string GCodeAnalyzer::Height_Tag = "_ANALYZER_HEIGHT:";
+
+const double GCodeAnalyzer::Default_mm3_per_mm = 0.0;
+const float GCodeAnalyzer::Default_Width = 0.0f;
+const float GCodeAnalyzer::Default_Height = 0.0f;
+
+GCodeAnalyzer::Metadata::Metadata()
+    : extrusion_role(erNone)
+    , extruder_id(DEFAULT_EXTRUDER_ID)
+    , mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm)
+    , width(GCodeAnalyzer::Default_Width)
+    , height(GCodeAnalyzer::Default_Height)
+    , feedrate(DEFAULT_FEEDRATE)
 {
-    for (size_t i = 0; i < m_layers.size(); ++ i)
-        delete m_layers[i];
-    m_layers.clear();
 }
 
-GCodeAnalyzer::GCodeAnalyzer(const Slic3r::GCodeConfig *config) : 
-    m_config(config)
+GCodeAnalyzer::Metadata::Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate)
+    : extrusion_role(extrusion_role)
+    , extruder_id(extruder_id)
+    , mm3_per_mm(mm3_per_mm)
+    , width(width)
+    , height(height)
+    , feedrate(feedrate)
+{
+}
+
+bool GCodeAnalyzer::Metadata::operator != (const GCodeAnalyzer::Metadata& other) const
+{
+    if (extrusion_role != other.extrusion_role)
+        return true;
+
+    if (extruder_id != other.extruder_id)
+        return true;
+
+    if (mm3_per_mm != other.mm3_per_mm)
+        return true;
+
+    if (width != other.width)
+        return true;
+
+    if (height != other.height)
+        return true;
+
+    if (feedrate != other.feedrate)
+        return true;
+
+    return false;
+}
+
+GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Pointf3& start_position, const Pointf3& end_position, float delta_extruder)
+    : type(type)
+    , data(extrusion_role, extruder_id, mm3_per_mm, width, height, feedrate)
+    , start_position(start_position)
+    , end_position(end_position)
+    , delta_extruder(delta_extruder)
+{
+}
+
+GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, const GCodeAnalyzer::Metadata& data, const Pointf3& start_position, const Pointf3& end_position, float delta_extruder)
+    : type(type)
+    , data(data)
+    , start_position(start_position)
+    , end_position(end_position)
+    , delta_extruder(delta_extruder)
+{
+}
+
+const GCodeAnalyzer::PreviewData::Color GCodeAnalyzer::PreviewData::Color::Dummy(0.0f, 0.0f, 0.0f, 0.0f);
+
+GCodeAnalyzer::PreviewData::Color::Color()
+{
+    rgba[0] = 1.0f;
+    rgba[1] = 1.0f;
+    rgba[2] = 1.0f;
+    rgba[3] = 1.0f;
+}
+
+GCodeAnalyzer::PreviewData::Color::Color(float r, float g, float b, float a)
+{
+    rgba[0] = r;
+    rgba[1] = g;
+    rgba[2] = b;
+    rgba[3] = a;
+}
+
+std::vector<unsigned char> GCodeAnalyzer::PreviewData::Color::as_bytes() const
+{
+    std::vector<unsigned char> ret;
+    for (unsigned int i = 0; i < 4; ++i)
+    {
+        ret.push_back((unsigned char)(255.0f * rgba[i]));
+    }
+    return ret;
+}
+
+GCodeAnalyzer::PreviewData::Extrusion::Layer::Layer(float z, const ExtrusionPaths& paths)
+    : z(z)
+    , paths(paths)
+{
+}
+
+GCodeAnalyzer::PreviewData::Travel::Polyline::Polyline(EType type, EDirection direction, float feedrate, unsigned int extruder_id, const Polyline3& polyline)
+    : type(type)
+    , direction(direction)
+    , feedrate(feedrate)
+    , extruder_id(extruder_id)
+    , polyline(polyline)
+{
+}
+
+const GCodeAnalyzer::PreviewData::Color GCodeAnalyzer::PreviewData::Range::Default_Colors[Colors_Count] =
+{
+    Color(0.043f, 0.173f, 0.478f, 1.0f),
+    Color(0.075f, 0.349f, 0.522f, 1.0f),
+    Color(0.110f, 0.533f, 0.569f, 1.0f),
+    Color(0.016f, 0.839f, 0.059f, 1.0f),
+    Color(0.667f, 0.949f, 0.000f, 1.0f),
+    Color(0.988f, 0.975f, 0.012f, 1.0f),
+    Color(0.961f, 0.808f, 0.039f, 1.0f),
+    Color(0.890f, 0.533f, 0.125f, 1.0f),
+    Color(0.820f, 0.408f, 0.188f, 1.0f),
+    Color(0.761f, 0.322f, 0.235f, 1.0f)
+};
+
+GCodeAnalyzer::PreviewData::Range::Range()
 {
     reset();
-    m_moves = new GCodeMovesDB();
 }
 
-GCodeAnalyzer::~GCodeAnalyzer()
+void GCodeAnalyzer::PreviewData::Range::reset()
 {
-    delete m_moves;
+    min = FLT_MAX;
+    max = -FLT_MAX;
+}
+
+bool GCodeAnalyzer::PreviewData::Range::empty() const
+{
+    return min == max;
+}
+
+void GCodeAnalyzer::PreviewData::Range::update_from(float value)
+{
+    min = std::min(min, value);
+    max = std::max(max, value);
+}
+
+void GCodeAnalyzer::PreviewData::Range::set_from(const Range& other)
+{
+    min = other.min;
+    max = other.max;
+}
+
+float GCodeAnalyzer::PreviewData::Range::step_size() const
+{
+    return (max - min) / (float)Colors_Count;
+}
+
+const GCodeAnalyzer::PreviewData::Color& GCodeAnalyzer::PreviewData::Range::get_color_at_max() const
+{
+    return colors[Colors_Count - 1];
+}
+
+const GCodeAnalyzer::PreviewData::Color& GCodeAnalyzer::PreviewData::Range::get_color_at(float value) const
+{
+    return empty() ? get_color_at_max() : colors[clamp((unsigned int)0, Colors_Count - 1, (unsigned int)((value - min) / step_size()))];
+}
+
+GCodeAnalyzer::PreviewData::LegendItem::LegendItem(const std::string& text, const GCodeAnalyzer::PreviewData::Color& color)
+    : text(text)
+    , color(color)
+{
+}
+
+const GCodeAnalyzer::PreviewData::Color GCodeAnalyzer::PreviewData::Extrusion::Default_Extrusion_Role_Colors[Num_Extrusion_Roles] =
+{
+    Color(0.0f, 0.0f, 0.0f, 1.0f),   // erNone
+    Color(1.0f, 0.0f, 0.0f, 1.0f),   // erPerimeter
+    Color(0.0f, 1.0f, 0.0f, 1.0f),   // erExternalPerimeter
+    Color(0.0f, 0.0f, 1.0f, 1.0f),   // erOverhangPerimeter
+    Color(1.0f, 1.0f, 0.0f, 1.0f),   // erInternalInfill
+    Color(1.0f, 0.0f, 1.0f, 1.0f),   // erSolidInfill
+    Color(0.0f, 1.0f, 1.0f, 1.0f),   // erTopSolidInfill
+    Color(0.5f, 0.5f, 0.5f, 1.0f),   // erBridgeInfill
+    Color(1.0f, 1.0f, 1.0f, 1.0f),   // erGapFill
+    Color(0.5f, 0.0f, 0.0f, 1.0f),   // erSkirt
+    Color(0.0f, 0.5f, 0.0f, 1.0f),   // erSupportMaterial
+    Color(0.0f, 0.0f, 0.5f, 1.0f),   // erSupportMaterialInterface
+    Color(0.7f, 0.89f, 0.67f, 1.0f), // erWipeTower
+    Color(0.0f, 0.0f, 0.0f, 1.0f)    // erMixed
+};
+
+// todo: merge with Slic3r::ExtrusionRole2String() from GCode.cpp
+const std::string GCodeAnalyzer::PreviewData::Extrusion::Default_Extrusion_Role_Names[Num_Extrusion_Roles]
+{
+    "None",
+    "Perimeter",
+    "External perimeter",
+    "Overhang perimeter",
+    "Internal infill",
+    "Solid infill",
+    "Top solid infill",
+    "Bridge infill",
+    "Gap fill",
+    "Skirt",
+    "Support material",
+    "Support material interface",
+    "Wipe tower",
+    "Mixed"
+};
+
+const GCodeAnalyzer::PreviewData::Extrusion::EViewType GCodeAnalyzer::PreviewData::Extrusion::Default_View_Type = GCodeAnalyzer::PreviewData::Extrusion::FeatureType;
+
+void GCodeAnalyzer::PreviewData::Extrusion::set_default()
+{
+    view_type = Default_View_Type;
+
+    ::memcpy((void*)role_colors, (const void*)Default_Extrusion_Role_Colors, Num_Extrusion_Roles * sizeof(Color));
+    ::memcpy((void*)ranges.height.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
+    ::memcpy((void*)ranges.width.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
+    ::memcpy((void*)ranges.feedrate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
+
+    for (unsigned int i = 0; i < Num_Extrusion_Roles; ++i)
+    {
+        role_names[i] = Default_Extrusion_Role_Names[i];
+    }
+
+    role_flags = 0;
+    for (unsigned int i = 0; i < Num_Extrusion_Roles; ++i)
+    {
+        role_flags += (unsigned int)::exp2((double)i);
+    }
+}
+
+bool GCodeAnalyzer::PreviewData::Extrusion::is_role_flag_set(ExtrusionRole role) const
+{
+    return is_role_flag_set(role_flags, role);
+}
+
+bool GCodeAnalyzer::PreviewData::Extrusion::is_role_flag_set(unsigned int flags, ExtrusionRole role)
+{
+    if (!is_valid_extrusion_role(role))
+        return false;
+
+    unsigned int flag = (unsigned int)::exp2((double)(role - erPerimeter));
+    return (flags & flag) == flag;
+}
+
+const float GCodeAnalyzer::PreviewData::Travel::Default_Width = 0.075f;
+const float GCodeAnalyzer::PreviewData::Travel::Default_Height = 0.075f;
+const GCodeAnalyzer::PreviewData::Color GCodeAnalyzer::PreviewData::Travel::Default_Type_Colors[Num_Types] =
+{
+    Color(0.0f, 0.0f, 0.75f, 1.0f), // Move
+    Color(0.0f, 0.75f, 0.0f, 1.0f), // Extrude
+    Color(0.75f, 0.0f, 0.0f, 1.0f), // Retract
+};
+
+void GCodeAnalyzer::PreviewData::Travel::set_default()
+{
+    width = Default_Width;
+    height = Default_Height;
+    ::memcpy((void*)type_colors, (const void*)Default_Type_Colors, Num_Types * sizeof(Color));
+    is_visible = false;
+}
+
+const GCodeAnalyzer::PreviewData::Color GCodeAnalyzer::PreviewData::Retraction::Default_Color = GCodeAnalyzer::PreviewData::Color(1.0f, 1.0f, 1.0f, 1.0f);
+
+GCodeAnalyzer::PreviewData::Retraction::Position::Position(const Point3& position, float width, float height)
+    : position(position)
+    , width(width)
+    , height(height)
+{
+}
+
+void GCodeAnalyzer::PreviewData::Retraction::set_default()
+{
+    color = Default_Color;
+    is_visible = false;
+}
+
+void GCodeAnalyzer::PreviewData::Shell::set_default()
+{
+    is_visible = false;
+}
+
+GCodeAnalyzer::PreviewData::PreviewData()
+{
+    set_default();
+}
+
+void GCodeAnalyzer::PreviewData::set_default()
+{
+    extrusion.set_default();
+    travel.set_default();
+    retraction.set_default();
+    unretraction.set_default();
+    shell.set_default();
+}
+
+void GCodeAnalyzer::PreviewData::reset()
+{
+    extrusion.layers.clear();
+    travel.polylines.clear();
+    retraction.positions.clear();
+    unretraction.positions.clear();
+}
+
+const GCodeAnalyzer::PreviewData::Color& GCodeAnalyzer::PreviewData::get_extrusion_role_color(ExtrusionRole role) const
+{
+    return extrusion.role_colors[role];
+}
+
+const GCodeAnalyzer::PreviewData::Color& GCodeAnalyzer::PreviewData::get_extrusion_height_color(float height) const
+{
+    return extrusion.ranges.height.get_color_at(height);
+}
+
+const GCodeAnalyzer::PreviewData::Color& GCodeAnalyzer::PreviewData::get_extrusion_width_color(float width) const
+{
+    return extrusion.ranges.width.get_color_at(width);
+}
+
+const GCodeAnalyzer::PreviewData::Color& GCodeAnalyzer::PreviewData::get_extrusion_feedrate_color(float feedrate) const
+{
+    return extrusion.ranges.feedrate.get_color_at(feedrate);
+}
+
+void GCodeAnalyzer::PreviewData::set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha)
+{
+    for (unsigned int i = 0; i < Extrusion::Num_Extrusion_Roles; ++i)
+    {
+        if (role_name == extrusion.role_names[i])
+        {
+            extrusion.role_colors[i] = Color(red, green, blue, alpha);
+            break;
+        }
+    }
+}
+
+std::string GCodeAnalyzer::PreviewData::get_legend_title() const
+{
+    switch (extrusion.view_type)
+    {
+    case Extrusion::FeatureType:
+        return "Feature type";
+    case Extrusion::Height:
+        return "Height (mm)";
+    case Extrusion::Width:
+        return "Width (mm)";
+    case Extrusion::Feedrate:
+        return "Speed (mm/s)";
+    case Extrusion::Tool:
+        return "Tool";
+    }
+
+    return "";
+}
+
+GCodeAnalyzer::PreviewData::LegendItemsList GCodeAnalyzer::PreviewData::get_legend_items(const std::vector<float>& tool_colors) const
+{
+    struct Helper
+    {
+        static void FillListFromRange(LegendItemsList& list, const Range& range, unsigned int decimals, float scale_factor)
+        {
+            list.reserve(Range::Colors_Count);
+            float step = range.step_size();
+            for (unsigned int i = 0; i < Range::Colors_Count; ++i)
+            {
+                char buf[32];
+                sprintf(buf, "%.*f/%.*f", decimals, scale_factor * (range.min + (float)i * step), decimals, scale_factor * (range.min + (float)(i + 1) * step));
+                list.emplace_back(buf, range.colors[i]);
+            }
+        }
+    };
+
+    LegendItemsList items;
+
+    switch (extrusion.view_type)
+    {
+    case Extrusion::FeatureType:
+        {
+            items.reserve(erMixed - erPerimeter + 1);
+            for (unsigned int i = (unsigned int)erPerimeter; i < (unsigned int)erMixed; ++i)
+            {
+                items.emplace_back(extrusion.role_names[i], extrusion.role_colors[i]);
+            }
+
+            break;
+        }
+    case Extrusion::Height:
+        {
+            Helper::FillListFromRange(items, extrusion.ranges.height, 3, 1.0f);            
+            break;
+        }
+    case Extrusion::Width:
+        {
+            Helper::FillListFromRange(items, extrusion.ranges.width, 3, 1.0f);
+            break;
+        }
+    case Extrusion::Feedrate:
+        {
+            Helper::FillListFromRange(items, extrusion.ranges.feedrate, 0, 1.0f);
+            break;
+        }
+    case Extrusion::Tool:
+        {
+            unsigned int tools_colors_count = tool_colors.size() / 4;
+            items.reserve(tools_colors_count);
+            for (unsigned int i = 0; i < tools_colors_count; ++i)
+            {
+                char buf[32];
+                sprintf(buf, "Extruder %d", i + 1);
+
+                GCodeAnalyzer::PreviewData::Color color;
+                ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + i * 4), 4 * sizeof(float));
+
+                items.emplace_back(buf, color);
+            }
+
+            break;
+        }
+    }
+
+    return items;
+}
+
+GCodeAnalyzer::GCodeAnalyzer()
+{
+    reset();
 }
 
 void GCodeAnalyzer::reset()
 {
-    output_buffer.clear();
-    output_buffer_length = 0;
+    _set_units(Millimeters);
+    _set_positioning_xyz_type(Absolute);
+    _set_positioning_e_type(Relative);
+    _set_extrusion_role(erNone);
+    _set_extruder_id(DEFAULT_EXTRUDER_ID);
+    _set_mm3_per_mm(Default_mm3_per_mm);
+    _set_width(Default_Width);
+    _set_height(Default_Height);
+    _set_feedrate(DEFAULT_FEEDRATE);
+    _set_start_position(DEFAULT_START_POSITION);
+    _set_start_extrusion(DEFAULT_START_EXTRUSION);
+    _reset_axes_position();
 
-    m_current_extruder = 0;
-    // Zero the position of the XYZE axes + the current feed
-    memset(m_current_pos, 0, sizeof(float) * 5);
-    m_current_extrusion_role = erNone;
-    m_current_extrusion_width = 0;
-    m_current_extrusion_height = 0;
-    // Expect the first command to fill the nozzle (deretract).
-    m_retracted = true;
-    m_moves->reset();
+    m_moves_map.clear();
 }
 
-const char* GCodeAnalyzer::process(const char *szGCode, bool flush)
+const std::string& GCodeAnalyzer::process_gcode(const std::string& gcode)
 {
-    // Reset length of the output_buffer.
-    output_buffer_length = 0;
+    m_process_output = "";
 
-    if (szGCode != 0) {
-        const char *p = szGCode;
-        while (*p != 0) {
-            // Find end of the line.
-            const char *endl = p;
-            // Slic3r always generates end of lines in a Unix style.
-            for (; *endl != 0 && *endl != '\n'; ++ endl) ;
-            // Process a G-code line, store it into the provided GCodeLine object.
-            bool should_output = process_line(p, endl - p);
-            if (*endl == '\n') 
-                ++ endl;
-            if (should_output)
-                push_to_output(p, endl - p);
-            p = endl;
-        }
-    }
+    m_parser.parse_buffer(gcode,
+        [this](GCodeReader& reader, const GCodeReader::GCodeLine& line)
+    { this->_process_gcode_line(reader, line); });
 
-    return output_buffer.data();
+    return m_process_output;
 }
 
-// Is a white space?
-static inline bool is_ws(const char c) { return c == ' ' || c == '\t'; }
-// Is it an end of line? Consider a comment to be an end of line as well.
-static inline bool is_eol(const char c) { return c == 0 || c == '\r' || c == '\n' || c == ';'; };
-// Is it a white space or end of line?
-static inline bool is_ws_or_eol(const char c) { return is_ws(c) || is_eol(c); };
-
-// Eat whitespaces.
-static void eatws(const char *&line)
+void GCodeAnalyzer::calc_gcode_preview_data(Print& print)
 {
-    while (is_ws(*line)) 
-        ++ line;
+    // resets preview data
+    print.gcode_preview.reset();
+
+    // calculates extrusion layers
+    _calc_gcode_preview_extrusion_layers(print);
+
+    // calculates travel
+    _calc_gcode_preview_travel(print);
+
+    // calculates retractions
+    _calc_gcode_preview_retractions(print);
+
+    // calculates unretractions
+    _calc_gcode_preview_unretractions(print);
 }
 
-// Parse an int starting at the current position of a line.
-// If succeeded, the line pointer is advanced.
-static inline int parse_int(const char *&line)
+bool GCodeAnalyzer::is_valid_extrusion_role(ExtrusionRole role)
 {
-    char *endptr = NULL;
-    long result = strtol(line, &endptr, 10);
-    if (endptr == NULL || !is_ws_or_eol(*endptr))
-        throw std::runtime_error("GCodeAnalyzer: Error parsing an int");
-    line = endptr;
-    return int(result);
-};
+    return ((erPerimeter <= role) && (role < erMixed));
+}
 
-// Parse an int starting at the current position of a line.
-// If succeeded, the line pointer is advanced.
-static inline float parse_float(const char *&line)
+void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line)
 {
-    char *endptr = NULL;
-    float result = strtof(line, &endptr);
-    if (endptr == NULL || !is_ws_or_eol(*endptr))
-        throw std::runtime_error("GCodeAnalyzer: Error parsing a float");
-    line = endptr;
-    return result;
-};
+    // processes 'special' comments contained in line
+    if (_process_tags(line))
+        return;
 
-#define EXTRUSION_ROLE_TAG ";_EXTRUSION_ROLE:"
-bool GCodeAnalyzer::process_line(const char *line, const size_t len)
-{
-    if (strncmp(line, EXTRUSION_ROLE_TAG, strlen(EXTRUSION_ROLE_TAG)) == 0) {
-        line += strlen(EXTRUSION_ROLE_TAG);
-        int role = atoi(line);
-        this->m_current_extrusion_role = ExtrusionRole(role);
-        return false;
-    }
+    // sets new start position/extrusion
+    _set_start_position(_get_end_position());
+    _set_start_extrusion(_get_axis_position(E));
 
-/*
-    // Set the type, copy the line to the buffer.
-    buf.type = GCODE_MOVE_TYPE_OTHER;
-    buf.modified = false;
-    if (buf.raw.size() < len + 1)
-        buf.raw.assign(line, line + len + 1);
-    else
-        memcpy(buf.raw.data(), line, len);
-    buf.raw[len] = 0;
-    buf.raw_length = len;
-
-    memcpy(buf.pos_start, m_current_pos, sizeof(float)*5);
-    memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
-    memset(buf.pos_provided, 0, 5);
-
-    buf.volumetric_extrusion_rate = 0.f;
-    buf.volumetric_extrusion_rate_start = 0.f;
-    buf.volumetric_extrusion_rate_end = 0.f;
-    buf.max_volumetric_extrusion_rate_slope_positive = 0.f;
-    buf.max_volumetric_extrusion_rate_slope_negative = 0.f;
-	buf.extrusion_role = m_current_extrusion_role;
-
-    // Parse the G-code line, store the result into the buf.
-    switch (toupper(*line ++)) {
-    case 'G': {
-        int gcode = parse_int(line);
-        eatws(line);
-        switch (gcode) {
-        case 0:
-        case 1:
+    // processes 'normal' gcode lines
+    std::string cmd = line.cmd();
+    if (cmd.length() > 1)
+    {
+        switch (::toupper(cmd[0]))
         {
-            // G0, G1: A FFF 3D printer does not make a difference between the two.
-            float new_pos[5];
-            memcpy(new_pos, m_current_pos, sizeof(float)*5);
-            bool  changed[5] = { false, false, false, false, false };
-            while (!is_eol(*line)) {
-                char axis = toupper(*line++);
-                int  i = -1;
-                switch (axis) {
-                case 'X':
-                case 'Y':
-                case 'Z':
-                    i = axis - 'X';
-                    break;
-                case 'E':
-                    i = 3;
-                    break;
-                case 'F':
-                    i = 4;
-                    break;
-                default:
-                    assert(false);
-                }
-                if (i == -1)
-                    throw std::runtime_error(std::string("GCodeAnalyzer: Invalid axis for G0/G1: ") + axis);
-                buf.pos_provided[i] = true;
-                new_pos[i] = parse_float(line);
-                if (i == 3 && m_config->use_relative_e_distances.value)
-                    new_pos[i] += m_current_pos[i];
-                changed[i] = new_pos[i] != m_current_pos[i];
-                eatws(line);
-            }
-            if (changed[3]) {
-                // Extrusion, retract or unretract.
-                float diff = new_pos[3] - m_current_pos[3];
-                if (diff < 0) {
-                    buf.type = GCODE_MOVE_TYPE_RETRACT;
-                    m_retracted = true;
-                } else if (! changed[0] && ! changed[1] && ! changed[2]) {
-                    // assert(m_retracted);
-                    buf.type = GCODE_MOVE_TYPE_UNRETRACT;
-                    m_retracted = false;
-                } else {
-                    assert(changed[0] || changed[1]);
-                    // Moving in XY plane.
-                    buf.type = GCODE_MOVE_TYPE_EXTRUDE;
-                    // Calculate the volumetric extrusion rate.
-                    float diff[4];
-                    for (size_t i = 0; i < 4; ++ i)
-                        diff[i] = new_pos[i] - m_current_pos[i];
-                    // volumetric extrusion rate = A_filament * F_xyz * L_e / L_xyz [mm^3/min]
-                    float len2 = diff[0]*diff[0]+diff[1]*diff[1]+diff[2]*diff[2];
-                    float rate = m_filament_crossections[m_current_extruder] * new_pos[4] * sqrt((diff[3]*diff[3])/len2);
-                    buf.volumetric_extrusion_rate       = rate;
-                    buf.volumetric_extrusion_rate_start = rate;
-                    buf.volumetric_extrusion_rate_end   = rate;
-                    m_stat.update(rate, sqrt(len2));
-                    if (rate < 10.f) {
-                    	printf("Extremely low flow rate: %f\n", rate);
+        case 'G':
+            {
+                switch (::atoi(&cmd[1]))
+                {
+                case 1: // Move
+                    {
+                        _processG1(line);
+                        break;
+                    }
+                case 22: // Firmware controlled Retract
+                    {
+                        _processG22(line);
+                        break;
+                    }
+                case 23: // Firmware controlled Unretract
+                    {
+                        _processG23(line);
+                        break;
+                    }
+                case 90: // Set to Absolute Positioning
+                    {
+                        _processG90(line);
+                        break;
+                    }
+                case 91: // Set to Relative Positioning
+                    {
+                        _processG91(line);
+                        break;
+                    }
+                case 92: // Set Position
+                    {
+                        _processG92(line);
+                        break;
                     }
                 }
-            } else if (changed[0] || changed[1] || changed[2]) {
-                // Moving without extrusion.
-                buf.type = GCODE_MOVE_TYPE_MOVE;
+
+                break;
             }
-            memcpy(m_current_pos, new_pos, sizeof(float) * 5);
-            break;
-        }
-        case 92:
-        {
-            // G92 : Set Position
-            // Set a logical coordinate position to a new value without actually moving the machine motors.
-            // Which axes to set?
-            bool set = false;
-            while (!is_eol(*line)) {
-                char axis = toupper(*line++);
-                switch (axis) {
-                case 'X':
-                case 'Y':
-                case 'Z':
-                    m_current_pos[axis - 'X'] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f;
-                    set = true;
-                    break;
-                case 'E':
-                    m_current_pos[3] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f;
-                    set = true;
-                    break;
-                default:
-                    throw std::runtime_error(std::string("GCodeAnalyzer: Incorrect axis in a G92 G-code: ") + axis);
+        case 'M':
+            {
+                switch (::atoi(&cmd[1]))
+                {
+                case 82: // Set extruder to absolute mode
+                    {
+                        _processM82(line);
+                        break;
+                    }
+                case 83: // Set extruder to relative mode
+                    {
+                        _processM83(line);
+                        break;
+                    }
                 }
-                eatws(line);
+
+                break;
+            }
+        case 'T': // Select Tools
+            {
+                _processT(line);
+                break;
             }
-            assert(set);
-            break;
         }
-        case 10:
-        case 22:
-            // Firmware retract.
-            buf.type = GCODE_MOVE_TYPE_RETRACT;
-            m_retracted = true;
-            break;
-        case 11:
-        case 23:
-            // Firmware unretract.
-            buf.type = GCODE_MOVE_TYPE_UNRETRACT;
-            m_retracted = false;
-            break;
-        default:
-            // Ignore the rest.
-        break;
-        }
-        break;
-    }
-    case 'M': {
-        int mcode = parse_int(line);
-        eatws(line);
-        switch (mcode) {
-        default:
-            // Ignore the rest of the M-codes.
-        break;
-        }
-        break;
-    }
-    case 'T':
-    {
-        // Activate an extruder head.
-        int new_extruder = parse_int(line);
-        if (new_extruder != m_current_extruder) {
-            m_current_extruder = new_extruder;
-            m_retracted = true;
-            buf.type = GCODE_MOVE_TYPE_TOOL_CHANGE;
-        } else {
-            buf.type = GCODE_MOVE_TYPE_NOOP;
-        }
-        break;
-    }
     }
 
-    buf.extruder_id = m_current_extruder;
-    memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
-*/
-	return true;
+    // puts the line back into the gcode
+    m_process_output += line.raw() + "\n";
 }
 
-void GCodeAnalyzer::push_to_output(const char *text, const size_t len, bool add_eol)
+// Returns the new absolute position on the given axis in dependence of the given parameters
+float axis_absolute_position_from_G1_line(GCodeAnalyzer::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeAnalyzer::EUnits units, GCodeAnalyzer::EPositioningType type, float current_absolute_position)
 {
-    // New length of the output buffer content.
-    size_t len_new = output_buffer_length + len + 1;
-    if (add_eol)
-        ++ len_new;
+    float lengthsScaleFactor = (units == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f;
+    if (lineG1.has(Slic3r::Axis(axis)))
+    {
+        float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
+        return (type == GCodeAnalyzer::Absolute) ? ret : current_absolute_position + ret;
+    }
+    else
+        return current_absolute_position;
+}
 
-    // Resize the output buffer to a power of 2 higher than the required memory.
-    if (output_buffer.size() < len_new) {
-        size_t v = len_new;
-        // Compute the next highest power of 2 of 32-bit v
-        // http://graphics.stanford.edu/~seander/bithacks.html
-        v--;
-        v |= v >> 1;
-        v |= v >> 2;
-        v |= v >> 4;
-        v |= v >> 8;
-        v |= v >> 16;
-        v++;
-        output_buffer.resize(v);
+void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line)
+{
+    // updates axes positions from line
+    EUnits units = _get_units();
+    float new_pos[Num_Axis];
+    for (unsigned char a = X; a < Num_Axis; ++a)
+    {
+        new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, (a == E) ? _get_positioning_e_type() : _get_positioning_xyz_type(), _get_axis_position((EAxis)a));
     }
 
-    // Copy the text to the output.
-    if (len != 0) {
-        memcpy(output_buffer.data() + output_buffer_length, text, len);
-        output_buffer_length += len;
+    // updates feedrate from line, if present
+    if (line.has_f())
+        _set_feedrate(line.f() * MMMIN_TO_MMSEC);
+
+    // calculates movement deltas
+    float delta_pos[Num_Axis];
+    for (unsigned char a = X; a < Num_Axis; ++a)
+    {
+        delta_pos[a] = new_pos[a] - _get_axis_position((EAxis)a);
     }
-    if (add_eol)
-        output_buffer[output_buffer_length ++] = '\n';
-    output_buffer[output_buffer_length] = 0;
+
+    // Detects move type
+    GCodeMove::EType type = GCodeMove::Noop;
+
+    if (delta_pos[E] < 0.0f)
+    {
+        if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f))
+            type = GCodeMove::Move;
+        else
+            type = GCodeMove::Retract;
+    }
+    else if (delta_pos[E] > 0.0f)
+    {
+        if ((delta_pos[X] == 0.0f) && (delta_pos[Y] == 0.0f) && (delta_pos[Z] == 0.0f))
+            type = GCodeMove::Unretract;
+        else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f))
+            type = GCodeMove::Extrude;
+    }
+    else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f))
+        type = GCodeMove::Move;
+
+    ExtrusionRole role = _get_extrusion_role();
+    if ((type == GCodeMove::Extrude) && ((_get_width() == 0.0f) || (_get_height() == 0.0f) || !is_valid_extrusion_role(role)))
+        type = GCodeMove::Move;
+
+    // updates axis positions
+    for (unsigned char a = X; a < Num_Axis; ++a)
+    {
+        _set_axis_position((EAxis)a, new_pos[a]);
+    }
+
+    // stores the move
+    if (type != GCodeMove::Noop)
+        _store_move(type);
+}
+
+void GCodeAnalyzer::_processG22(const GCodeReader::GCodeLine& line)
+{
+    // stores retract move
+    _store_move(GCodeMove::Retract);
+}
+
+void GCodeAnalyzer::_processG23(const GCodeReader::GCodeLine& line)
+{
+    // stores unretract move
+    _store_move(GCodeMove::Unretract);
+}
+
+void GCodeAnalyzer::_processG90(const GCodeReader::GCodeLine& line)
+{
+    _set_positioning_xyz_type(Absolute);
+}
+
+void GCodeAnalyzer::_processG91(const GCodeReader::GCodeLine& line)
+{
+    _set_positioning_xyz_type(Relative);
+}
+
+void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line)
+{
+    float lengthsScaleFactor = (_get_units() == Inches) ? INCHES_TO_MM : 1.0f;
+    bool anyFound = false;
+
+    if (line.has_x())
+    {
+        _set_axis_position(X, line.x() * lengthsScaleFactor);
+        anyFound = true;
+    }
+
+    if (line.has_y())
+    {
+        _set_axis_position(Y, line.y() * lengthsScaleFactor);
+        anyFound = true;
+    }
+
+    if (line.has_z())
+    {
+        _set_axis_position(Z, line.z() * lengthsScaleFactor);
+        anyFound = true;
+    }
+
+    if (line.has_e())
+    {
+        _set_axis_position(E, line.e() * lengthsScaleFactor);
+        anyFound = true;
+    }
+
+    if (!anyFound)
+    {
+        for (unsigned char a = X; a < Num_Axis; ++a)
+        {
+            _set_axis_position((EAxis)a, 0.0f);
+        }
+    }
+}
+
+void GCodeAnalyzer::_processM82(const GCodeReader::GCodeLine& line)
+{
+    _set_positioning_e_type(Absolute);
+}
+
+void GCodeAnalyzer::_processM83(const GCodeReader::GCodeLine& line)
+{
+    _set_positioning_e_type(Relative);
+}
+
+void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line)
+{
+    std::string cmd = line.cmd();
+    if (cmd.length() > 1)
+    {
+        unsigned int id = (unsigned int)::strtol(cmd.substr(1).c_str(), nullptr, 10);
+        if (_get_extruder_id() != id)
+        {
+            _set_extruder_id(id);
+
+            // stores tool change move
+            _store_move(GCodeMove::Tool_change);
+        }
+    }
+}
+
+bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line)
+{
+    std::string comment = line.comment();
+
+    // extrusion role tag
+    size_t pos = comment.find(Extrusion_Role_Tag);
+    if (pos != comment.npos)
+    {
+        _process_extrusion_role_tag(comment, pos);
+        return true;
+    }
+
+    // mm3 per mm tag
+    pos = comment.find(Mm3_Per_Mm_Tag);
+    if (pos != comment.npos)
+    {
+        _process_mm3_per_mm_tag(comment, pos);
+        return true;
+    }
+
+    // width tag
+    pos = comment.find(Width_Tag);
+    if (pos != comment.npos)
+    {
+        _process_width_tag(comment, pos);
+        return true;
+    }
+
+    // height tag
+    pos = comment.find(Height_Tag);
+    if (pos != comment.npos)
+    {
+        _process_height_tag(comment, pos);
+        return true;
+    }
+
+    return false;
+}
+
+void GCodeAnalyzer::_process_extrusion_role_tag(const std::string& comment, size_t pos)
+{
+    int role = (int)::strtol(comment.substr(pos + Extrusion_Role_Tag.length()).c_str(), nullptr, 10);
+    if (_is_valid_extrusion_role(role))
+        _set_extrusion_role((ExtrusionRole)role);
+    else
+    {
+        // todo: show some error ?
+    }
+}
+
+void GCodeAnalyzer::_process_mm3_per_mm_tag(const std::string& comment, size_t pos)
+{
+    _set_mm3_per_mm(::strtod(comment.substr(pos + Mm3_Per_Mm_Tag.length()).c_str(), nullptr));
+}
+
+void GCodeAnalyzer::_process_width_tag(const std::string& comment, size_t pos)
+{
+    _set_width((float)::strtod(comment.substr(pos + Width_Tag.length()).c_str(), nullptr));
+}
+
+void GCodeAnalyzer::_process_height_tag(const std::string& comment, size_t pos)
+{
+    _set_height((float)::strtod(comment.substr(pos + Height_Tag.length()).c_str(), nullptr));
+}
+
+void GCodeAnalyzer::_set_units(GCodeAnalyzer::EUnits units)
+{
+    m_state.units = units;
+}
+
+GCodeAnalyzer::EUnits GCodeAnalyzer::_get_units() const
+{
+    return m_state.units;
+}
+
+void GCodeAnalyzer::_set_positioning_xyz_type(GCodeAnalyzer::EPositioningType type)
+{
+    m_state.positioning_xyz_type = type;
+}
+
+GCodeAnalyzer::EPositioningType GCodeAnalyzer::_get_positioning_xyz_type() const
+{
+    return m_state.positioning_xyz_type;
+}
+
+void GCodeAnalyzer::_set_positioning_e_type(GCodeAnalyzer::EPositioningType type)
+{
+    m_state.positioning_e_type = type;
+}
+
+GCodeAnalyzer::EPositioningType GCodeAnalyzer::_get_positioning_e_type() const
+{
+    return m_state.positioning_e_type;
+}
+
+void GCodeAnalyzer::_set_extrusion_role(ExtrusionRole extrusion_role)
+{
+    m_state.data.extrusion_role = extrusion_role;
+}
+
+ExtrusionRole GCodeAnalyzer::_get_extrusion_role() const
+{
+    return m_state.data.extrusion_role;
+}
+
+void GCodeAnalyzer::_set_extruder_id(unsigned int id)
+{
+    m_state.data.extruder_id = id;
+}
+
+unsigned int GCodeAnalyzer::_get_extruder_id() const
+{
+    return m_state.data.extruder_id;
+}
+
+void GCodeAnalyzer::_set_mm3_per_mm(double value)
+{
+    m_state.data.mm3_per_mm = value;
+}
+
+double GCodeAnalyzer::_get_mm3_per_mm() const
+{
+    return m_state.data.mm3_per_mm;
+}
+
+void GCodeAnalyzer::_set_width(float width)
+{
+    m_state.data.width = width;
+}
+
+float GCodeAnalyzer::_get_width() const
+{
+    return m_state.data.width;
+}
+
+void GCodeAnalyzer::_set_height(float height)
+{
+    m_state.data.height = height;
+}
+
+float GCodeAnalyzer::_get_height() const
+{
+    return m_state.data.height;
+}
+
+void GCodeAnalyzer::_set_feedrate(float feedrate_mm_sec)
+{
+    m_state.data.feedrate = feedrate_mm_sec;
+}
+
+float GCodeAnalyzer::_get_feedrate() const
+{
+    return m_state.data.feedrate;
+}
+
+void GCodeAnalyzer::_set_axis_position(EAxis axis, float position)
+{
+    m_state.position[axis] = position;
+}
+
+float GCodeAnalyzer::_get_axis_position(EAxis axis) const
+{
+    return m_state.position[axis];
+}
+
+void GCodeAnalyzer::_reset_axes_position()
+{
+    ::memset((void*)m_state.position, 0, Num_Axis * sizeof(float));
+}
+
+void GCodeAnalyzer::_set_start_position(const Pointf3& position)
+{
+    m_state.start_position = position;
+}
+
+const Pointf3& GCodeAnalyzer::_get_start_position() const
+{
+    return m_state.start_position;
+}
+
+void GCodeAnalyzer::_set_start_extrusion(float extrusion)
+{
+    m_state.start_extrusion = extrusion;
+}
+
+float GCodeAnalyzer::_get_start_extrusion() const
+{
+    return m_state.start_extrusion;
+}
+
+float GCodeAnalyzer::_get_delta_extrusion() const
+{
+    return _get_axis_position(E) - m_state.start_extrusion;
+}
+
+Pointf3 GCodeAnalyzer::_get_end_position() const
+{
+    return Pointf3(m_state.position[X], m_state.position[Y], m_state.position[Z]);
+}
+
+void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type)
+{
+    // if type non mapped yet, map it
+    TypeToMovesMap::iterator it = m_moves_map.find(type);
+    if (it == m_moves_map.end())
+        it = m_moves_map.insert(TypeToMovesMap::value_type(type, GCodeMovesList())).first;
+
+    // store move
+    it->second.emplace_back(type, _get_extrusion_role(), _get_extruder_id(), _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), _get_start_position(), _get_end_position(), _get_delta_extrusion());
+}
+
+bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const
+{
+    return ((int)erNone <= value) && (value <= (int)erMixed);
+}
+
+void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(Print& print)
+{
+    struct Helper
+    {
+        static PreviewData::Extrusion::Layer& get_layer_at_z(PreviewData::Extrusion::LayersList& layers, float z)
+        {
+            for (PreviewData::Extrusion::Layer& layer : layers)
+            {
+                // if layer found, return it
+                if (layer.z == z)
+                    return layer;
+            }
+
+            // if layer not found, create and return it
+            layers.emplace_back(z, ExtrusionPaths());
+            return layers.back();
+        }
+
+        static void store_polyline(const Polyline& polyline, const Metadata& data, float z, Print& print)
+        {
+            // if the polyline is valid, create the extrusion path from it and store it
+            if (polyline.is_valid())
+            {
+                ExtrusionPath path(data.extrusion_role, data.mm3_per_mm, data.width, data.height);
+                path.polyline = polyline;
+                path.feedrate = data.feedrate;
+                path.extruder_id = data.extruder_id;
+
+                get_layer_at_z(print.gcode_preview.extrusion.layers, z).paths.push_back(path);
+            }
+        }
+    };
+
+    TypeToMovesMap::iterator extrude_moves = m_moves_map.find(GCodeMove::Extrude);
+    if (extrude_moves == m_moves_map.end())
+        return;
+
+    Metadata data;
+    float z = FLT_MAX;
+    Polyline polyline;
+    Pointf3 position(FLT_MAX, FLT_MAX, FLT_MAX);
+    PreviewData::Range height_range;
+    PreviewData::Range width_range;
+    PreviewData::Range feedrate_range;
+
+    // constructs the polylines while traversing the moves
+    for (const GCodeMove& move : extrude_moves->second)
+    {
+        if ((data != move.data) || (data.feedrate != move.data.feedrate) || (z != move.start_position.z) || (position != move.start_position))
+        {
+            // store current polyline
+            polyline.remove_duplicate_points();
+            Helper::store_polyline(polyline, data, z, print);
+
+            // reset current polyline
+            polyline = Polyline();
+
+            // add both vertices of the move
+            polyline.append(Point(scale_(move.start_position.x), scale_(move.start_position.y)));
+            polyline.append(Point(scale_(move.end_position.x), scale_(move.end_position.y)));
+
+            // update current values
+            data = move.data;
+            z = move.start_position.z;
+            height_range.update_from(move.data.height);
+            width_range.update_from(move.data.width);
+            feedrate_range.update_from(move.data.feedrate);
+        }
+        else
+            // append end vertex of the move to current polyline
+            polyline.append(Point(scale_(move.end_position.x), scale_(move.end_position.y)));
+
+        // update current values
+        position = move.end_position;
+    }
+
+    // store last polyline
+    polyline.remove_duplicate_points();
+    Helper::store_polyline(polyline, data, z, print);
+
+    // updates preview ranges data
+    print.gcode_preview.extrusion.ranges.height.set_from(height_range);
+    print.gcode_preview.extrusion.ranges.width.set_from(width_range);
+    print.gcode_preview.extrusion.ranges.feedrate.set_from(feedrate_range);
+}
+
+void GCodeAnalyzer::_calc_gcode_preview_travel(Print& print)
+{
+    struct Helper
+    {
+        static void store_polyline(const Polyline3& polyline, PreviewData::Travel::EType type, PreviewData::Travel::Polyline::EDirection direction, float feedrate, unsigned int extruder_id, Print& print)
+        {
+            // if the polyline is valid, store it
+            if (polyline.is_valid())
+                print.gcode_preview.travel.polylines.emplace_back(type, direction, feedrate, extruder_id, polyline);
+        }
+    };
+
+    TypeToMovesMap::iterator travel_moves = m_moves_map.find(GCodeMove::Move);
+    if (travel_moves == m_moves_map.end())
+        return;
+
+    Polyline3 polyline;
+    Pointf3 position(FLT_MAX, FLT_MAX, FLT_MAX);
+    PreviewData::Travel::EType type = PreviewData::Travel::Num_Types;
+    PreviewData::Travel::Polyline::EDirection direction = PreviewData::Travel::Polyline::Num_Directions;
+    float feedrate = FLT_MAX;
+    unsigned int extruder_id = -1;
+
+    // constructs the polylines while traversing the moves
+    for (const GCodeMove& move : travel_moves->second)
+    {
+        PreviewData::Travel::EType move_type = (move.delta_extruder < 0.0f) ? PreviewData::Travel::Retract : ((move.delta_extruder > 0.0f) ? PreviewData::Travel::Extrude : PreviewData::Travel::Move);
+        PreviewData::Travel::Polyline::EDirection move_direction = ((move.start_position.x != move.end_position.x) || (move.start_position.y != move.end_position.y)) ? PreviewData::Travel::Polyline::Generic : PreviewData::Travel::Polyline::Vertical;
+
+        if ((type != move_type) || (direction != move_direction) || (feedrate != move.data.feedrate) || (position != move.start_position) || (extruder_id != move.data.extruder_id))
+        {
+            // store current polyline
+            polyline.remove_duplicate_points();
+            Helper::store_polyline(polyline, type, direction, feedrate, extruder_id, print);
+
+            // reset current polyline
+            polyline = Polyline3();
+
+            // add both vertices of the move
+            polyline.append(Point3(scale_(move.start_position.x), scale_(move.start_position.y), scale_(move.start_position.z)));
+            polyline.append(Point3(scale_(move.end_position.x), scale_(move.end_position.y), scale_(move.end_position.z)));
+        }
+        else
+            // append end vertex of the move to current polyline
+            polyline.append(Point3(scale_(move.end_position.x), scale_(move.end_position.y), scale_(move.end_position.z)));
+
+        // update current values
+        position = move.end_position;
+        type = move_type;
+        feedrate = move.data.feedrate;
+        extruder_id = move.data.extruder_id;
+    }
+
+    // store last polyline
+    polyline.remove_duplicate_points();
+    Helper::store_polyline(polyline, type, direction, feedrate, extruder_id, print);
+}
+
+void GCodeAnalyzer::_calc_gcode_preview_retractions(Print& print)
+{
+    TypeToMovesMap::iterator retraction_moves = m_moves_map.find(GCodeMove::Retract);
+    if (retraction_moves == m_moves_map.end())
+        return;
+
+    for (const GCodeMove& move : retraction_moves->second)
+    {
+        // store position
+        Point3 position(scale_(move.start_position.x), scale_(move.start_position.y), scale_(move.start_position.z));
+        print.gcode_preview.retraction.positions.emplace_back(position, move.data.width, move.data.height);
+    }
+}
+
+void GCodeAnalyzer::_calc_gcode_preview_unretractions(Print& print)
+{
+    TypeToMovesMap::iterator unretraction_moves = m_moves_map.find(GCodeMove::Unretract);
+    if (unretraction_moves == m_moves_map.end())
+        return;
+
+    for (const GCodeMove& move : unretraction_moves->second)
+    {
+        // store position
+        Point3 position(scale_(move.start_position.x), scale_(move.start_position.y), scale_(move.start_position.z));
+        print.gcode_preview.unretraction.positions.emplace_back(position, move.data.width, move.data.height);
+    }
+}
+
+GCodeAnalyzer::PreviewData::Color operator + (const GCodeAnalyzer::PreviewData::Color& c1, const GCodeAnalyzer::PreviewData::Color& c2)
+{
+    return GCodeAnalyzer::PreviewData::Color(clamp(0.0f, 1.0f, c1.rgba[0] + c2.rgba[0]),
+        clamp(0.0f, 1.0f, c1.rgba[1] + c2.rgba[1]),
+        clamp(0.0f, 1.0f, c1.rgba[2] + c2.rgba[2]),
+        clamp(0.0f, 1.0f, c1.rgba[3] + c2.rgba[3]));
+}
+
+GCodeAnalyzer::PreviewData::Color operator * (float f, const GCodeAnalyzer::PreviewData::Color& color)
+{
+    return GCodeAnalyzer::PreviewData::Color(clamp(0.0f, 1.0f, f * color.rgba[0]),
+        clamp(0.0f, 1.0f, f * color.rgba[1]),
+        clamp(0.0f, 1.0f, f * color.rgba[2]),
+        clamp(0.0f, 1.0f, f * color.rgba[3]));
 }
 
 } // namespace Slic3r
diff --git a/xs/src/libslic3r/GCode/Analyzer.hpp b/xs/src/libslic3r/GCode/Analyzer.hpp
index 9e84e33c4..121360dbc 100644
--- a/xs/src/libslic3r/GCode/Analyzer.hpp
+++ b/xs/src/libslic3r/GCode/Analyzer.hpp
@@ -1,152 +1,417 @@
-#ifndef slic3r_GCode_PressureEqualizer_hpp_
-#define slic3r_GCode_PressureEqualizer_hpp_
+#ifndef slic3r_GCode_Analyzer_hpp_
+#define slic3r_GCode_Analyzer_hpp_
 
 #include "../libslic3r.h"
 #include "../PrintConfig.hpp"
 #include "../ExtrusionEntity.hpp"
 
+#include "Point.hpp"
+#include "GCodeReader.hpp"
+
 namespace Slic3r {
 
-enum GCodeMoveType
-{
-    GCODE_MOVE_TYPE_NOOP,
-    GCODE_MOVE_TYPE_RETRACT,
-    GCODE_MOVE_TYPE_UNRETRACT,
-    GCODE_MOVE_TYPE_TOOL_CHANGE,
-    GCODE_MOVE_TYPE_MOVE,
-    GCODE_MOVE_TYPE_EXTRUDE,
-};
+class Print;
 
-// For visualization purposes, for the purposes of the G-code analysis and timing.
-// The size of this structure is 56B.
-// Keep the size of this structure as small as possible, because all moves of a complete print
-// may be held in RAM.
-struct GCodeMove
-{
-    bool        moving_xy(const float* pos_start)   const { return fabs(pos_end[0] - pos_start[0]) > 0.f || fabs(pos_end[1] - pos_start[1]) > 0.f; }
-    bool        moving_xy()                         const { return moving_xy(get_pos_start()); }
-    bool        moving_z (const float* pos_start)   const { return fabs(pos_end[2] - pos_start[2]) > 0.f; }
-    bool        moving_z ()                         const { return moving_z(get_pos_start()); }
-    bool        extruding(const float* pos_start)   const { return moving_xy() && pos_end[3] > pos_start[3]; }
-    bool        extruding()                         const { return extruding(get_pos_start()); }
-    bool        retracting(const float* pos_start)  const { return pos_end[3] < pos_start[3]; }
-    bool        retracting()                        const { return retracting(get_pos_start()); }    
-    bool        deretracting(const float* pos_start)  const { return ! moving_xy() && pos_end[3] > pos_start[3]; }
-    bool        deretracting()                      const { return deretracting(get_pos_start()); }
-
-    float       dist_xy2(const float* pos_start)    const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]); }
-    float       dist_xy2()                          const { return dist_xy2(get_pos_start()); }
-    float       dist_xyz2(const float* pos_start)   const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]) + (pos_end[2] - pos_start[2]) * (pos_end[2] - pos_start[2]); }
-    float       dist_xyz2()                         const { return dist_xyz2(get_pos_start()); }
-
-    float       dist_xy(const float* pos_start)     const { return sqrt(dist_xy2(pos_start)); }
-    float       dist_xy()                           const { return dist_xy(get_pos_start()); }
-    float       dist_xyz(const float* pos_start)    const { return sqrt(dist_xyz2(pos_start)); }
-    float       dist_xyz()                          const { return dist_xyz(get_pos_start()); }
-
-    float       dist_e(const float* pos_start)      const { return fabs(pos_end[3] - pos_start[3]); }
-    float       dist_e()                            const { return dist_e(get_pos_start()); }
-
-    float       feedrate()                          const { return pos_end[4]; }
-    float       time(const float* pos_start)        const { return dist_xyz(pos_start) / feedrate(); }
-    float       time()                              const { return time(get_pos_start()); }
-    float       time_inv(const float* pos_start)    const { return feedrate() / dist_xyz(pos_start); }
-    float       time_inv()                          const { return time_inv(get_pos_start()); }
-
-    const float*    get_pos_start() const { assert(type != GCODE_MOVE_TYPE_NOOP); return this[-1].pos_end; }
-
-    // Pack the enums to conserve space. With C++x11 the allocation size could be declared for enums, but for old C++ this is the only portable way.
-    // GCodeLineType
-    uint8_t         type;
-    // Index of the active extruder.
-    uint8_t         extruder_id;
-    // ExtrusionRole
-    uint8_t         extrusion_role;
-    // For example, is it a bridge flow? Is the fan on?
-    uint8_t         flags;
-    // X,Y,Z,E,F. Storing the state of the currently active extruder only.
-    float           pos_end[5];
-    // Extrusion width, height for this segment in um.
-    uint16_t        extrusion_width;
-    uint16_t        extrusion_height;
-};
-
-typedef std::vector<GCodeMove> GCodeMoves;
-
-struct GCodeLayer
-{
-    // Index of an object printed.
-    size_t                  object_idx;
-    // Index of an object instance printed.
-    size_t                  object_instance_idx;
-    // Index of the layer printed.
-    size_t                  layer_idx;
-    // Top z coordinate of the layer printed.
-    float                   layer_z_top;
-
-    // Moves over this layer. The 0th move is always of type GCODELINETYPE_NOOP and
-    // it sets the initial position and tool for the layer.
-    GCodeMoves              moves;
-
-    // Indices into m_moves, where the tool changes happen.
-    // This is useful, if one wants to display just only a piece of the path quickly.
-    std::vector<size_t>     tool_changes;
-};
-
-typedef std::vector<GCodeLayer*> GCodeLayerPtrs;
-
-class GCodeMovesDB
-{
-public:
-    GCodeMovesDB() {};
-    ~GCodeMovesDB() { reset(); }
-    void reset();
-    GCodeLayerPtrs      m_layers;
-};
-
-// Processes a G-code to extract moves and their types.
-// This information is then used to render the print simulation colored by the extrusion type
-// or various speeds.
-// The GCodeAnalyzer is employed as a G-Code filter. It reads the G-code as it is generated,
-// parses the comments generated by Slic3r just for the analyzer, and removes these comments.
 class GCodeAnalyzer
 {
 public:
-    GCodeAnalyzer(const Slic3r::GCodeConfig *config);
-    ~GCodeAnalyzer();
+    static const std::string Extrusion_Role_Tag;
+    static const std::string Mm3_Per_Mm_Tag;
+    static const std::string Width_Tag;
+    static const std::string Height_Tag;
 
-    void reset();
+    static const double Default_mm3_per_mm;
+    static const float Default_Width;
+    static const float Default_Height;
 
-    // Process a next batch of G-code lines. Flush the internal buffers if asked for.
-    const char* process(const char *szGCode, bool flush);
-    // Length of the buffer returned by process().
-    size_t get_output_buffer_length() const { return output_buffer_length; }
+    enum EUnits : unsigned char
+    {
+        Millimeters,
+        Inches
+    };
+
+    enum EAxis : unsigned char
+    {
+        X,
+        Y,
+        Z,
+        E,
+        Num_Axis
+    };
+
+    enum EPositioningType : unsigned char
+    {
+        Absolute,
+        Relative
+    };
+
+    struct Metadata
+    {
+        ExtrusionRole extrusion_role;
+        unsigned int extruder_id;
+        double mm3_per_mm;
+        float width;     // mm
+        float height;    // mm
+        float feedrate;  // mm/s
+
+        Metadata();
+        Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate);
+
+        bool operator != (const Metadata& other) const;
+    };
+
+    struct GCodeMove
+    {
+        enum EType : unsigned char
+        {
+            Noop,
+            Retract,
+            Unretract,
+            Tool_change,
+            Move,
+            Extrude,
+            Num_Types
+        };
+
+        EType type;
+        Metadata data;
+        Pointf3 start_position;
+        Pointf3 end_position;
+        float delta_extruder;
+
+        GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Pointf3& start_position, const Pointf3& end_position, float delta_extruder);
+        GCodeMove(EType type, const Metadata& data, const Pointf3& start_position, const Pointf3& end_position, float delta_extruder);
+    };
+
+    typedef std::vector<GCodeMove> GCodeMovesList;
+    typedef std::map<GCodeMove::EType, GCodeMovesList> TypeToMovesMap;
 
 private:
-    // Keeps the reference, does not own the config.
-    const Slic3r::GCodeConfig      *m_config;
+    struct State
+    {
+        EUnits units;
+        EPositioningType positioning_xyz_type;
+        EPositioningType positioning_e_type;
+        Metadata data;
+        Pointf3 start_position;
+        float start_extrusion;
+        float position[Num_Axis];
+    };
 
-    // Internal data.
-    // X,Y,Z,E,F
-    float                           m_current_pos[5];
-    size_t                          m_current_extruder;
-    ExtrusionRole                   m_current_extrusion_role;
-    uint16_t                        m_current_extrusion_width;
-    uint16_t                        m_current_extrusion_height;
-    bool                            m_retracted;
+public:
+    struct PreviewData
+    {
+        struct Color
+        {
+            float rgba[4];
 
-    GCodeMovesDB                   *m_moves;
+            Color();
+            Color(float r, float g, float b, float a);
 
-    // Output buffer will only grow. It will not be reallocated over and over.
-    std::vector<char>               output_buffer;
-    size_t                          output_buffer_length;
+            std::vector<unsigned char> as_bytes() const;
 
-    bool process_line(const char *line, const size_t len);
+            static const Color Dummy;
+        };
 
-    // Push the text to the end of the output_buffer.
-    void push_to_output(const char *text, const size_t len, bool add_eol = true);
+        struct Range
+        {
+            static const unsigned int Colors_Count = 10;
+            static const Color Default_Colors[Colors_Count];
+
+            Color colors[Colors_Count];
+            float min;
+            float max;
+
+            Range();
+
+            void reset();
+            bool empty() const;
+            void update_from(float value);
+            void set_from(const Range& other);
+            float step_size() const;
+
+            const Color& get_color_at(float value) const;
+            const Color& get_color_at_max() const;
+        };
+
+        struct LegendItem
+        {
+            std::string text;
+            Color color;
+
+            LegendItem(const std::string& text, const Color& color);
+        };
+
+        typedef std::vector<LegendItem> LegendItemsList;
+
+        struct Extrusion
+        {
+            enum EViewType : unsigned char
+            {
+                FeatureType,
+                Height,
+                Width,
+                Feedrate,
+                Tool,
+                Num_View_Types
+            };
+
+            static const unsigned int Num_Extrusion_Roles = (unsigned int)erMixed + 1;
+            static const Color Default_Extrusion_Role_Colors[Num_Extrusion_Roles];
+            static const std::string Default_Extrusion_Role_Names[Num_Extrusion_Roles];
+            static const EViewType Default_View_Type;
+
+            struct Ranges
+            {
+                Range height;
+                Range width;
+                Range feedrate;
+            };
+
+            struct Layer
+            {
+                float z;
+                ExtrusionPaths paths;
+
+                Layer(float z, const ExtrusionPaths& paths);
+            };
+
+            typedef std::vector<Layer> LayersList;
+
+            EViewType view_type;
+            Color role_colors[Num_Extrusion_Roles];
+            std::string role_names[Num_Extrusion_Roles];
+            Ranges ranges;
+            LayersList layers;
+            unsigned int role_flags;
+
+            void set_default();
+            bool is_role_flag_set(ExtrusionRole role) const;
+
+            static bool is_role_flag_set(unsigned int flags, ExtrusionRole role);
+        };
+
+        struct Travel
+        {
+            enum EType : unsigned char
+            {
+                Move,
+                Extrude,
+                Retract,
+                Num_Types
+            };
+
+            static const float Default_Width;
+            static const float Default_Height;
+            static const Color Default_Type_Colors[Num_Types];
+
+            struct Polyline
+            {
+                enum EDirection
+                {
+                    Vertical,
+                    Generic,
+                    Num_Directions
+                };
+
+                EType type;
+                EDirection direction;
+                float feedrate;
+                unsigned int extruder_id;
+                Polyline3 polyline;
+
+                Polyline(EType type, EDirection direction, float feedrate, unsigned int extruder_id, const Polyline3& polyline);
+            };
+
+            typedef std::vector<Polyline> PolylinesList;
+
+            PolylinesList polylines;
+            float width;
+            float height;
+            Color type_colors[Num_Types];
+            bool is_visible;
+
+            void set_default();
+        };
+
+        struct Retraction
+        {
+            static const Color Default_Color;
+
+            struct Position
+            {
+                Point3 position;
+                float width;
+                float height;
+
+                Position(const Point3& position, float width, float height);
+            };
+
+            typedef std::vector<Position> PositionsList;
+
+            PositionsList positions;
+            Color color;
+            bool is_visible;
+
+            void set_default();
+        };
+
+        struct Shell
+        {
+            bool is_visible;
+
+            void set_default();
+        };
+
+        Extrusion extrusion;
+        Travel travel;
+        Retraction retraction;
+        Retraction unretraction;
+        Shell shell;
+
+        PreviewData();
+
+        void set_default();
+        void reset();
+
+        const Color& get_extrusion_role_color(ExtrusionRole role) const;
+        const Color& get_extrusion_height_color(float height) const;
+        const Color& get_extrusion_width_color(float width) const;
+        const Color& get_extrusion_feedrate_color(float feedrate) const;
+
+        void set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha);
+
+        std::string get_legend_title() const;
+        LegendItemsList get_legend_items(const std::vector<float>& tool_colors) const;
+    };
+
+private:
+    State m_state;
+    GCodeReader m_parser;
+    TypeToMovesMap m_moves_map;
+
+    // The output of process_layer()
+    std::string m_process_output;
+
+public:
+    GCodeAnalyzer();
+
+    // Reinitialize the analyzer
+    void reset();
+
+    // Adds the gcode contained in the given string to the analysis and returns it after removing the workcodes
+    const std::string& process_gcode(const std::string& gcode);
+
+    // Calculates all data needed for gcode visualization
+    void calc_gcode_preview_data(Print& print);
+
+    static bool is_valid_extrusion_role(ExtrusionRole role);
+
+private:
+    // Processes the given gcode line
+    void _process_gcode_line(GCodeReader& reader, const GCodeReader::GCodeLine& line);
+
+    // Move
+    void _processG1(const GCodeReader::GCodeLine& line);
+
+    // Firmware controlled Retract
+    void _processG22(const GCodeReader::GCodeLine& line);
+
+    // Firmware controlled Unretract
+    void _processG23(const GCodeReader::GCodeLine& line);
+
+    // Set to Absolute Positioning
+    void _processG90(const GCodeReader::GCodeLine& line);
+
+    // Set to Relative Positioning
+    void _processG91(const GCodeReader::GCodeLine& line);
+
+    // Set Position
+    void _processG92(const GCodeReader::GCodeLine& line);
+
+    // Set extruder to absolute mode
+    void _processM82(const GCodeReader::GCodeLine& line);
+
+    // Set extruder to relative mode
+    void _processM83(const GCodeReader::GCodeLine& line);
+
+    // Processes T line (Select Tool)
+    void _processT(const GCodeReader::GCodeLine& line);
+
+    // Processes the tags
+    // Returns true if any tag has been processed
+    bool _process_tags(const GCodeReader::GCodeLine& line);
+
+    // Processes extrusion role tag
+    void _process_extrusion_role_tag(const std::string& comment, size_t pos);
+
+    // Processes mm3_per_mm tag
+    void _process_mm3_per_mm_tag(const std::string& comment, size_t pos);
+
+    // Processes width tag
+    void _process_width_tag(const std::string& comment, size_t pos);
+
+    // Processes height tag
+    void _process_height_tag(const std::string& comment, size_t pos);
+
+    void _set_units(EUnits units);
+    EUnits _get_units() const;
+
+    void _set_positioning_xyz_type(EPositioningType type);
+    EPositioningType _get_positioning_xyz_type() const;
+
+    void _set_positioning_e_type(EPositioningType type);
+    EPositioningType _get_positioning_e_type() const;
+
+    void _set_extrusion_role(ExtrusionRole extrusion_role);
+    ExtrusionRole _get_extrusion_role() const;
+
+    void _set_extruder_id(unsigned int id);
+    unsigned int _get_extruder_id() const;
+
+    void _set_mm3_per_mm(double value);
+    double _get_mm3_per_mm() const;
+
+    void _set_width(float width);
+    float _get_width() const;
+
+    void _set_height(float height);
+    float _get_height() const;
+
+    void _set_feedrate(float feedrate_mm_sec);
+    float _get_feedrate() const;
+
+    void _set_axis_position(EAxis axis, float position);
+    float _get_axis_position(EAxis axis) const;
+
+    // Sets axes position to zero
+    void _reset_axes_position();
+
+    void _set_start_position(const Pointf3& position);
+    const Pointf3& _get_start_position() const;
+
+    void _set_start_extrusion(float extrusion);
+    float _get_start_extrusion() const;
+    float _get_delta_extrusion() const;
+
+    // Returns current xyz position (from m_state.position[])
+    Pointf3 _get_end_position() const;
+
+    // Adds a new move with the given data
+    void _store_move(GCodeMove::EType type);
+
+    // Checks if the given int is a valid extrusion role (contained into enum ExtrusionRole)
+    bool _is_valid_extrusion_role(int value) const;
+
+    void _calc_gcode_preview_extrusion_layers(Print& print);
+    void _calc_gcode_preview_travel(Print& print);
+    void _calc_gcode_preview_retractions(Print& print);
+    void _calc_gcode_preview_unretractions(Print& print);
 };
 
+GCodeAnalyzer::PreviewData::Color operator + (const GCodeAnalyzer::PreviewData::Color& c1, const GCodeAnalyzer::PreviewData::Color& c2);
+GCodeAnalyzer::PreviewData::Color operator * (float f, const GCodeAnalyzer::PreviewData::Color& color);
+
 } // namespace Slic3r
 
-#endif /* slic3r_GCode_PressureEqualizer_hpp_ */
+#endif /* slic3r_GCode_Analyzer_hpp_ */
diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
index 858845e3d..99c6c757f 100644
--- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
+++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
@@ -6,6 +6,8 @@
 #include <iostream>
 #include <vector>
 
+#include "Analyzer.hpp"
+
 #if defined(__linux) || defined(__GNUC__ )
 #include <strings.h>
 #endif /* __linux */
@@ -419,6 +421,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
 	// Increase the extruder driver current to allow fast ramming.
 		  .set_extruder_trimpot(750);
 
+    // adds tag for analyzer
+    char buf[32];
+    sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower);
+    writer.append(buf);
+
 	if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) {
 		float y_end = 0.f;
 		for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) {
@@ -554,6 +561,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
 		writer.set_initial_position(initial_position);
 	}
 
+    // adds tag for analyzer
+    char buf[32];
+    sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower);
+    writer.append(buf);
+
 	if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) {
 		// Increase the extruder driver current to allow fast ramming.
 		writer.set_extruder_trimpot(750);
@@ -637,6 +649,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(Purpose purpose, b
 	else 
 		writer.set_initial_position(initial_position);
 
+    // adds tag for analyzer
+    char buf[32];
+    sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower);
+    writer.append(buf);
+
 	if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) {
 		// Prime the extruder 10*m_perimeter_width left along the vertical edge of the wipe tower.
 		writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), 
diff --git a/xs/src/libslic3r/Line.cpp b/xs/src/libslic3r/Line.cpp
index c7afc80c7..e9d5d7742 100644
--- a/xs/src/libslic3r/Line.cpp
+++ b/xs/src/libslic3r/Line.cpp
@@ -218,6 +218,16 @@ Line::ccw(const Point& point) const
     return point.ccw(*this);
 }
 
+double Line3::length() const
+{
+    return a.distance_to(b);
+}
+
+Vector3 Line3::vector() const
+{
+    return Vector3(b.x - a.x, b.y - a.y, b.z - a.z);
+}
+
 Pointf3
 Linef3::intersect_plane(double z) const
 {
diff --git a/xs/src/libslic3r/Line.hpp b/xs/src/libslic3r/Line.hpp
index 1be508f11..4826017ab 100644
--- a/xs/src/libslic3r/Line.hpp
+++ b/xs/src/libslic3r/Line.hpp
@@ -7,10 +7,12 @@
 namespace Slic3r {
 
 class Line;
+class Line3;
 class Linef3;
 class Polyline;
 class ThickLine;
 typedef std::vector<Line> Lines;
+typedef std::vector<Line3> Lines3;
 typedef std::vector<ThickLine> ThickLines;
 
 class Line
@@ -56,6 +58,19 @@ class ThickLine : public Line
     ThickLine(Point _a, Point _b) : Line(_a, _b), a_width(0), b_width(0) {};
 };
 
+class Line3
+{
+public:
+    Point3 a;
+    Point3 b;
+
+    Line3() {}
+    Line3(const Point3& _a, const Point3& _b) : a(_a), b(_b) {}
+
+    double length() const;
+    Vector3 vector() const;
+};
+
 class Linef
 {
     public:
diff --git a/xs/src/libslic3r/MultiPoint.cpp b/xs/src/libslic3r/MultiPoint.cpp
index 7929747a0..2e65492cd 100644
--- a/xs/src/libslic3r/MultiPoint.cpp
+++ b/xs/src/libslic3r/MultiPoint.cpp
@@ -214,6 +214,61 @@ MultiPoint::_douglas_peucker(const Points &points, const double tolerance)
     return results;
 }
 
+void MultiPoint3::translate(double x, double y)
+{
+    for (Point3& p : points)
+    {
+        p.translate(x, y);
+    }
+}
+
+void MultiPoint3::translate(const Point& vector)
+{
+    translate(vector.x, vector.y);
+}
+
+double MultiPoint3::length() const
+{
+    Lines3 lines = this->lines();
+    double len = 0.0;
+    for (const Line3& line : lines)
+    {
+        len += line.length();
+    }
+    return len;
+}
+
+BoundingBox3 MultiPoint3::bounding_box() const
+{
+    return BoundingBox3(points);
+}
+
+bool MultiPoint3::remove_duplicate_points()
+{
+    size_t j = 0;
+    for (size_t i = 1; i < points.size(); ++i)
+    {
+        if (points[j].coincides_with(points[i]))
+        {
+            // Just increase index i.
+        }
+        else
+        {
+            ++j;
+            if (j < i)
+                points[j] = points[i];
+        }
+    }
+
+    if (++j < points.size())
+    {
+        points.erase(points.begin() + j, points.end());
+        return true;
+    }
+
+    return false;
+}
+
 BoundingBox get_extents(const MultiPoint &mp)
 { 
     return BoundingBox(mp.points);
diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp
index 3d1346e4c..0970e9a67 100644
--- a/xs/src/libslic3r/MultiPoint.hpp
+++ b/xs/src/libslic3r/MultiPoint.hpp
@@ -10,6 +10,7 @@
 namespace Slic3r {
 
 class BoundingBox;
+class BoundingBox3;
 
 class MultiPoint
 {
@@ -79,6 +80,25 @@ public:
     static Points _douglas_peucker(const Points &points, const double tolerance);
 };
 
+class MultiPoint3
+{
+public:
+    Points3 points;
+
+    void append(const Point3& point) { this->points.push_back(point); }
+
+    void translate(double x, double y);
+    void translate(const Point& vector);
+    virtual Lines3 lines() const = 0;
+    double length() const;
+    bool is_valid() const { return this->points.size() >= 2; }
+
+    BoundingBox3 bounding_box() const;
+
+    // Remove exact duplicates, return true if any duplicate has been removed.
+    bool remove_duplicate_points();
+};
+
 extern BoundingBox get_extents(const MultiPoint &mp);
 extern BoundingBox get_extents_rotated(const std::vector<Point> &points, double angle);
 extern BoundingBox get_extents_rotated(const MultiPoint &mp, double angle);
diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp
index 09c292b5a..6c9096a3d 100644
--- a/xs/src/libslic3r/Point.hpp
+++ b/xs/src/libslic3r/Point.hpp
@@ -14,14 +14,17 @@ class Line;
 class Linef;
 class MultiPoint;
 class Point;
+class Point3;
 class Pointf;
 class Pointf3;
 typedef Point Vector;
+typedef Point3 Vector3;
 typedef Pointf Vectorf;
 typedef Pointf3 Vectorf3;
 typedef std::vector<Point> Points;
 typedef std::vector<Point*> PointPtrs;
 typedef std::vector<const Point*> PointConstPtrs;
+typedef std::vector<Point3> Points3;
 typedef std::vector<Pointf> Pointfs;
 typedef std::vector<Pointf3> Pointf3s;
 
@@ -185,10 +188,11 @@ public:
     static Point3 new_scale(coordf_t x, coordf_t y, coordf_t z) { return Point3(coord_t(scale_(x)), coord_t(scale_(y)), coord_t(scale_(z))); }
     bool operator==(const Point3 &rhs) const { return this->x == rhs.x && this->y == rhs.y && this->z == rhs.z; }
     bool operator!=(const Point3 &rhs) const { return ! (*this == rhs); }
+    bool coincides_with(const Point3& rhs) const { return this->x == rhs.x && this->y == rhs.y && this->z == rhs.z; }
 private:
     // Hide the following inherited methods:
-    bool operator==(const Point &rhs);
-    bool operator!=(const Point &rhs);
+    bool operator==(const Point &rhs) const;
+    bool operator!=(const Point &rhs) const;
 };
 
 std::ostream& operator<<(std::ostream &stm, const Pointf &pointf);
@@ -243,6 +247,7 @@ public:
     static Pointf3 new_unscale(coord_t x, coord_t y, coord_t z) {
         return Pointf3(unscale(x), unscale(y), unscale(z));
     };
+    static Pointf3 new_unscale(const Point3& p) { return Pointf3(unscale(p.x), unscale(p.y), unscale(p.z)); }
     void scale(double factor);
     void translate(const Vectorf3 &vector);
     void translate(double x, double y, double z);
@@ -255,10 +260,23 @@ public:
 
 private:
     // Hide the following inherited methods:
-    bool operator==(const Pointf &rhs);
-    bool operator!=(const Pointf &rhs);
+    bool operator==(const Pointf &rhs) const;
+    bool operator!=(const Pointf &rhs) const;
 };
 
+inline Pointf3 operator+(const Pointf3& p1, const Pointf3& p2) { return Pointf3(p1.x + p2.x, p1.y + p2.y, p1.z + p2.z); }
+inline Pointf3 operator-(const Pointf3& p1, const Pointf3& p2) { return Pointf3(p1.x - p2.x, p1.y - p2.y, p1.z - p2.z); }
+inline Pointf3 operator-(const Pointf3& p) { return Pointf3(-p.x, -p.y, -p.z); }
+inline Pointf3 operator*(double scalar, const Pointf3& p) { return Pointf3(scalar * p.x, scalar * p.y, scalar * p.z); }
+inline Pointf3 operator*(const Pointf3& p, double scalar) { return Pointf3(scalar * p.x, scalar * p.y, scalar * p.z); }
+inline Pointf3 cross(const Pointf3& v1, const Pointf3& v2) { return Pointf3(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x); }
+inline coordf_t dot(const Pointf3& v1, const Pointf3& v2) { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; }
+inline Pointf3 normalize(const Pointf3& v)
+{
+    coordf_t len = ::sqrt(sqr(v.x) + sqr(v.y) + sqr(v.z));
+    return (len != 0.0) ? 1.0 / len * v : Pointf3(0.0, 0.0, 0.0);
+}
+
 template<typename TO> inline TO convert_to(const Point &src) { return TO(typename TO::coord_type(src.x), typename TO::coord_type(src.y)); }
 template<typename TO> inline TO convert_to(const Pointf &src) { return TO(typename TO::coord_type(src.x), typename TO::coord_type(src.y)); }
 template<typename TO> inline TO convert_to(const Point3 &src) { return TO(typename TO::coord_type(src.x), typename TO::coord_type(src.y), typename TO::coord_type(src.z)); }
diff --git a/xs/src/libslic3r/Polyline.cpp b/xs/src/libslic3r/Polyline.cpp
index 672777ce1..3432506c6 100644
--- a/xs/src/libslic3r/Polyline.cpp
+++ b/xs/src/libslic3r/Polyline.cpp
@@ -278,4 +278,18 @@ ThickPolyline::reverse()
     std::swap(this->endpoints.first, this->endpoints.second);
 }
 
+Lines3 Polyline3::lines() const
+{
+    Lines3 lines;
+    if (points.size() >= 2)
+    {
+        lines.reserve(points.size() - 1);
+        for (Points3::const_iterator it = points.begin(); it != points.end() - 1; ++it)
+        {
+            lines.emplace_back(*it, *(it + 1));
+        }
+    }
+    return lines;
+}
+
 }
diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp
index ac59c6378..a1d777692 100644
--- a/xs/src/libslic3r/Polyline.hpp
+++ b/xs/src/libslic3r/Polyline.hpp
@@ -129,6 +129,14 @@ class ThickPolyline : public Polyline {
     void reverse();
 };
 
+class Polyline3 : public MultiPoint3
+{
+public:
+    virtual Lines3 lines() const;
+};
+
+typedef std::vector<Polyline3> Polylines3;
+
 }
 
 #endif
diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp
index c689929c6..fd0964e57 100644
--- a/xs/src/libslic3r/Print.cpp
+++ b/xs/src/libslic3r/Print.cpp
@@ -66,6 +66,89 @@ bool Print::reload_model_instances()
     return invalidated;
 }
 
+void Print::clear_gcode_preview_data()
+{
+    gcode_preview.reset();
+}
+
+void Print::set_gcode_preview_type(unsigned char type)
+{
+    if ((0 <= type) && (type < GCodeAnalyzer::PreviewData::Extrusion::Num_View_Types))
+        gcode_preview.extrusion.view_type = (GCodeAnalyzer::PreviewData::Extrusion::EViewType)type;
+}
+
+void Print::set_gcode_preview_extrusion_flags(unsigned int flags)
+{
+    gcode_preview.extrusion.role_flags = flags;
+}
+
+bool Print::is_gcode_preview_extrusion_role_enabled(ExtrusionRole role)
+{
+    return gcode_preview.extrusion.is_role_flag_set(role);
+}
+
+void Print::set_gcode_preview_travel_visible(bool visible)
+{
+    gcode_preview.travel.is_visible = visible;
+}
+
+void Print::set_gcode_preview_retractions_visible(bool visible)
+{
+    gcode_preview.retraction.is_visible = visible;
+}
+
+void Print::set_gcode_preview_unretractions_visible(bool visible)
+{
+    gcode_preview.unretraction.is_visible = visible;
+}
+
+void Print::set_gcode_preview_shells_visible(bool visible)
+{
+    gcode_preview.shell.is_visible = visible;
+}
+
+void Print::set_gcode_extrusion_paths_colors(const std::vector<std::string>& colors)
+{
+    unsigned int size = (unsigned int)colors.size();
+
+    if (size % 2 != 0)
+        return;
+
+    for (unsigned int i = 0; i < size; i += 2)
+    {
+        const std::string& color_str = colors[i + 1];
+
+        if (color_str.size() == 6)
+        {
+            bool valid = true;
+            for (int c = 0; c < 6; ++c)
+            {
+                if (::isxdigit(color_str[c]) == 0)
+                {
+                    valid = false;
+                    break;
+                }
+            }
+
+            if (valid)
+            {
+                unsigned int color;
+                std::stringstream ss;
+                ss << std::hex << color_str;
+                ss >> color;
+
+                float den = 1.0f / 255.0f;
+
+                float r = (float)((color & 0xFF0000) >> 16) * den;
+                float g = (float)((color & 0x00FF00) >> 8) * den;
+                float b = (float)(color & 0x0000FF) * den;
+
+                gcode_preview.set_extrusion_role_color(colors[i], r, g, b, 1.0f);
+            }
+        }
+    }
+}
+
 PrintRegion* Print::add_region()
 {
     regions.push_back(new PrintRegion(this));
diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp
index c56e64c6c..da94917fc 100644
--- a/xs/src/libslic3r/Print.hpp
+++ b/xs/src/libslic3r/Print.hpp
@@ -15,6 +15,7 @@
 #include "Slicing.hpp"
 #include "GCode/ToolOrdering.hpp"
 #include "GCode/WipeTower.hpp"
+#include "GCode/Analyzer.hpp"
 
 #include "tbb/atomic.h"
 
@@ -240,6 +241,7 @@ public:
 
     // ordered collections of extrusion paths to build skirt loops and brim
     ExtrusionEntityCollection skirt, brim;
+    GCodeAnalyzer::PreviewData gcode_preview;
 
     Print() : total_used_filament(0), total_extruded_volume(0) { restart(); }
     ~Print() { clear_objects(); }
@@ -253,6 +255,27 @@ public:
     void reload_object(size_t idx);
     bool reload_model_instances();
 
+    void clear_gcode_preview_data();
+    void set_gcode_preview_type(unsigned char type);
+    void set_gcode_preview_extrusion_flags(unsigned int flags);
+    bool is_gcode_preview_extrusion_role_enabled(ExtrusionRole role);
+    void set_gcode_preview_travel_visible(bool visible);
+    void set_gcode_preview_retractions_visible(bool visible);
+    void set_gcode_preview_unretractions_visible(bool visible);
+    void set_gcode_preview_shells_visible(bool visible);
+
+    // Sets the extrusion path colors from the given strings vector.
+    // Data in the vector should be formatted as follows:
+    // std::vector<std::string> role_colors = 
+    // { <role_1>, <color_1>,
+    //   <role_2>, <color_2>,
+    //   <role_3>, <color_3>,
+    //   ...
+    //   <role_N>, <color_N> };
+    // where <role_X> should be a string from GCodeAnalyzer::PreviewData::Extrusion::Default_Extrusion_Role_Names[]
+    // and <color_X> an RGB color in hex format (i.e. red = FF0000)
+    void set_gcode_extrusion_paths_colors(const std::vector<std::string>& colors);
+
     // methods for handling regions
     PrintRegion* get_region(size_t idx) { return regions.at(idx); }
     const PrintRegion* get_region(size_t idx) const  { return regions.at(idx); }
diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h
index e58b01582..0f192c37c 100644
--- a/xs/src/libslic3r/libslic3r.h
+++ b/xs/src/libslic3r/libslic3r.h
@@ -163,11 +163,11 @@ static inline T clamp(const T low, const T high, const T value)
     return std::max(low, std::min(high, value));
 }
 
-template <typename T>
-static inline T lerp(const T a, const T b, const T t)
+template <typename T, typename Number>
+static inline T lerp(const T& a, const T& b, Number t)
 {
-    assert(t >= T(-EPSILON) && t <= T(1.+EPSILON));
-    return (1. - t) * a + t * b;
+    assert((t >= Number(-EPSILON)) && (t <= Number(1) + Number(EPSILON)));
+    return (Number(1) - t) * a + t * b;
 }
 
 } // namespace Slic3r
diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
index 81dbcdacc..553239a6f 100644
--- a/xs/src/slic3r/GUI/3DScene.cpp
+++ b/xs/src/slic3r/GUI/3DScene.cpp
@@ -8,6 +8,7 @@
 #include "../../libslic3r/Geometry.hpp"
 #include "../../libslic3r/Print.hpp"
 #include "../../libslic3r/Slicing.hpp"
+#include "GCode/Analyzer.hpp"
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -20,6 +21,11 @@
 #include <tbb/parallel_for.h>
 #include <tbb/spin_mutex.h>
 
+#include <wx/bitmap.h>
+#include <wx/dcmemory.h>
+#include <wx/image.h>
+#include <wx/settings.h>
+
 namespace Slic3r {
 
 void GLIndexedVertexArray::load_mesh_flat_shading(const TriangleMesh &mesh)
@@ -201,6 +207,9 @@ void GLVolume::set_range(double min_z, double max_z)
 
 void GLVolume::render() const
 {
+    if (!is_active)
+        return;
+
     glCullFace(GL_BACK);
     glPushMatrix();
     glTranslated(this->origin.x, this->origin.y, this->origin.z);
@@ -331,12 +340,40 @@ void GLVolumeCollection::render_VBOs() const
     GLint color_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "uniform_color") : -1;
 
     for (GLVolume *volume : this->volumes) {
-        if (! volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id)
+        if (!volume->is_active)
+            continue;
+
+        if (!volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id)
             continue;
         GLsizei n_triangles = GLsizei(std::min(volume->indexed_vertex_array.triangle_indices_size, volume->tverts_range.second - volume->tverts_range.first));
         GLsizei n_quads     = GLsizei(std::min(volume->indexed_vertex_array.quad_indices_size,     volume->qverts_range.second - volume->qverts_range.first));
         if (n_triangles + n_quads == 0)
+        {
+            if (_render_interleaved_only_volumes.enabled)
+            {
+                ::glDisableClientState(GL_VERTEX_ARRAY);
+                ::glDisableClientState(GL_NORMAL_ARRAY);
+                ::glEnable(GL_BLEND);
+                ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+                if (color_id >= 0)
+                {
+                    float color[4];
+                    ::memcpy((void*)color, (const void*)volume->color, 3 * sizeof(float));
+                    color[3] = _render_interleaved_only_volumes.alpha;
+                    ::glUniform4fv(color_id, 1, (const GLfloat*)color);
+                }
+                else
+                    ::glColor4f(volume->color[0], volume->color[1], volume->color[2], _render_interleaved_only_volumes.alpha);
+
+                volume->render();
+
+                ::glDisable(GL_BLEND);
+                ::glEnableClientState(GL_VERTEX_ARRAY);
+                ::glEnableClientState(GL_NORMAL_ARRAY);
+            }
             continue;
+        }
         if (color_id >= 0)
             glUniform4fv(color_id, 1, (const GLfloat*)volume->color);
         else
@@ -369,10 +406,29 @@ void GLVolumeCollection::render_legacy() const
  
     for (GLVolume *volume : this->volumes) {
         assert(! volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id);
+        if (!volume->is_active)
+            continue;
+
         GLsizei n_triangles = GLsizei(std::min(volume->indexed_vertex_array.triangle_indices_size, volume->tverts_range.second - volume->tverts_range.first));
         GLsizei n_quads     = GLsizei(std::min(volume->indexed_vertex_array.quad_indices_size,     volume->qverts_range.second - volume->qverts_range.first));
         if (n_triangles + n_quads == 0)
+        {
+            if (_render_interleaved_only_volumes.enabled)
+            {
+                ::glDisableClientState(GL_VERTEX_ARRAY);
+                ::glDisableClientState(GL_NORMAL_ARRAY);
+                ::glEnable(GL_BLEND);
+                ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+                ::glColor4f(volume->color[0], volume->color[1], volume->color[2], _render_interleaved_only_volumes.alpha);
+                volume->render();
+
+                ::glDisable(GL_BLEND);
+                ::glEnableClientState(GL_VERTEX_ARRAY);
+                ::glEnableClientState(GL_NORMAL_ARRAY);
+            }
             continue;
+        }
         glColor4f(volume->color[0], volume->color[1], volume->color[2], volume->color[3]);
         glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), volume->indexed_vertex_array.vertices_and_normals_interleaved.data() + 3);
         glNormalPointer(GL_FLOAT, 6 * sizeof(float), volume->indexed_vertex_array.vertices_and_normals_interleaved.data());
@@ -605,6 +661,321 @@ static void thick_lines_to_indexed_vertex_array(
 #undef BOTTOM
 }
 
+// caller is responsible for supplying NO lines with zero length
+static void thick_lines_to_indexed_vertex_array(const Lines3& lines,
+    const std::vector<double>& widths,
+    const std::vector<double>& heights,
+    bool closed,
+    GLIndexedVertexArray& volume)
+{
+    assert(!lines.empty());
+    if (lines.empty())
+        return;
+
+#define LEFT    0
+#define RIGHT   1
+#define TOP     2
+#define BOTTOM  3
+
+    // left, right, top, bottom
+    int      idx_initial[4] = { -1, -1, -1, -1 };
+    int      idx_prev[4] = { -1, -1, -1, -1 };
+    double   z_prev = 0.0;
+    Vectorf3 n_right_prev;
+    Vectorf3 n_top_prev;
+    Vectorf3 unit_v_prev;
+    double   width_initial = 0.0;
+
+    // new vertices around the line endpoints
+    // left, right, top, bottom
+    Pointf3 a[4];
+    Pointf3 b[4];
+
+    // loop once more in case of closed loops
+    size_t lines_end = closed ? (lines.size() + 1) : lines.size();
+    for (size_t ii = 0; ii < lines_end; ++ii)
+    {
+        size_t i = (ii == lines.size()) ? 0 : ii;
+
+        const Line3& line = lines[i];
+        double height = heights[i];
+        double width = widths[i];
+
+        Vectorf3 unit_v = normalize(Vectorf3::new_unscale(line.vector()));
+
+        Vectorf3 n_top;
+        Vectorf3 n_right;
+        Vectorf3 unit_positive_z(0.0, 0.0, 1.0);
+
+        if ((line.a.x == line.b.x) && (line.a.y == line.b.y))
+        {
+            // vertical segment
+            n_right = (line.a.z < line.b.z) ? Vectorf3(-1.0, 0.0, 0.0) : Vectorf3(1.0, 0.0, 0.0);
+            n_top = Vectorf3(0.0, 1.0, 0.0);
+        }
+        else
+        {
+            // generic segment
+            n_right = normalize(cross(unit_v, unit_positive_z));
+            n_top = normalize(cross(n_right, unit_v));
+        }
+
+        Vectorf3 rl_displacement = 0.5 * width * n_right;
+        Vectorf3 tb_displacement = 0.5 * height * n_top;
+        Pointf3 l_a = Pointf3::new_unscale(line.a);
+        Pointf3 l_b = Pointf3::new_unscale(line.b);
+
+        a[RIGHT] = l_a + rl_displacement;
+        a[LEFT] = l_a - rl_displacement;
+        a[TOP] = l_a + tb_displacement;
+        a[BOTTOM] = l_a - tb_displacement;
+        b[RIGHT] = l_b + rl_displacement;
+        b[LEFT] = l_b - rl_displacement;
+        b[TOP] = l_b + tb_displacement;
+        b[BOTTOM] = l_b - tb_displacement;
+
+        Vectorf3 n_bottom = -n_top;
+        Vectorf3 n_left = -n_right;
+
+        int idx_a[4];
+        int idx_b[4];
+        int idx_last = int(volume.vertices_and_normals_interleaved.size() / 6);
+
+        bool z_different = (z_prev != l_a.z);
+        z_prev = l_b.z;
+
+        // Share top / bottom vertices if possible.
+        if (ii == 0)
+        {
+            idx_a[TOP] = idx_last++;
+            volume.push_geometry(a[TOP], n_top);
+        }
+        else
+            idx_a[TOP] = idx_prev[TOP];
+
+        if ((ii == 0) || z_different)
+        {
+            // Start of the 1st line segment or a change of the layer thickness while maintaining the print_z.
+            idx_a[BOTTOM] = idx_last++;
+            volume.push_geometry(a[BOTTOM], n_bottom);
+            idx_a[LEFT] = idx_last++;
+            volume.push_geometry(a[LEFT], n_left);
+            idx_a[RIGHT] = idx_last++;
+            volume.push_geometry(a[RIGHT], n_right);
+        }
+        else
+            idx_a[BOTTOM] = idx_prev[BOTTOM];
+
+        if (ii == 0)
+        {
+            // Start of the 1st line segment.
+            width_initial = width;
+            ::memcpy(idx_initial, idx_a, sizeof(int) * 4);
+        }
+        else
+        {
+            // Continuing a previous segment.
+            // Share left / right vertices if possible.
+            double v_dot = dot(unit_v_prev, unit_v);
+            bool is_sharp = v_dot < 0.707; // sin(45 degrees)
+            bool is_right_turn = dot(n_top_prev, cross(unit_v_prev, unit_v)) > 0.0;
+
+            if (is_sharp)
+            {
+                // Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn.
+                idx_a[RIGHT] = idx_last++;
+                volume.push_geometry(a[RIGHT], n_right);
+                idx_a[LEFT] = idx_last++;
+                volume.push_geometry(a[LEFT], n_left);
+            }
+
+            if (v_dot > 0.9)
+            {
+                // The two successive segments are nearly collinear.
+                idx_a[LEFT] = idx_prev[LEFT];
+                idx_a[RIGHT] = idx_prev[RIGHT];
+            }
+            else if (!is_sharp)
+            {
+                // Create a sharp corner with an overshot and average the left / right normals.
+                // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc.
+
+                // averages normals
+                Vectorf3 average_n_right = normalize(0.5 * (n_right + n_right_prev));
+                Vectorf3 average_n_left = -average_n_right;
+                Vectorf3 average_rl_displacement = 0.5 * width * average_n_right;
+
+                // updates vertices around a
+                a[RIGHT] = l_a + average_rl_displacement;
+                a[LEFT] = l_a - average_rl_displacement;
+
+                // updates previous line normals
+                float* normal_left_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT] * 6;
+                normal_left_prev[0] = float(average_n_left.x);
+                normal_left_prev[1] = float(average_n_left.y);
+                normal_left_prev[2] = float(average_n_left.z);
+
+                float* normal_right_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6;
+                normal_right_prev[0] = float(average_n_right.x);
+                normal_right_prev[1] = float(average_n_right.y);
+                normal_right_prev[2] = float(average_n_right.z);
+
+                // updates previous line's vertices around b
+                float* b_left_prev = normal_left_prev + 3;
+                b_left_prev[0] = float(a[LEFT].x);
+                b_left_prev[1] = float(a[LEFT].y);
+                b_left_prev[2] = float(a[LEFT].z);
+
+                float* b_right_prev = normal_right_prev + 3;
+                b_right_prev[0] = float(a[RIGHT].x);
+                b_right_prev[1] = float(a[RIGHT].y);
+                b_right_prev[2] = float(a[RIGHT].z);
+
+                idx_a[LEFT] = idx_prev[LEFT];
+                idx_a[RIGHT] = idx_prev[RIGHT];
+            }
+            else if (is_right_turn)
+            {
+                // Right turn. Fill in the right turn wedge.
+                volume.push_triangle(idx_prev[RIGHT], idx_a[RIGHT], idx_prev[TOP]);
+                volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a[RIGHT]);
+            }
+            else
+            {
+                // Left turn. Fill in the left turn wedge.
+                volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a[LEFT]);
+                volume.push_triangle(idx_prev[LEFT], idx_a[LEFT], idx_prev[BOTTOM]);
+            }
+
+            if (ii == lines.size())
+            {
+                if (!is_sharp)
+                {
+                    // Closing a loop with smooth transition. Unify the closing left / right vertices.
+                    ::memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[LEFT] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT] * 6, sizeof(float) * 6);
+                    ::memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[RIGHT] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6, sizeof(float) * 6);
+                    volume.vertices_and_normals_interleaved.erase(volume.vertices_and_normals_interleaved.end() - 12, volume.vertices_and_normals_interleaved.end());
+                    // Replace the left / right vertex indices to point to the start of the loop. 
+                    for (size_t u = volume.quad_indices.size() - 16; u < volume.quad_indices.size(); ++u)
+                    {
+                        if (volume.quad_indices[u] == idx_prev[LEFT])
+                            volume.quad_indices[u] = idx_initial[LEFT];
+                        else if (volume.quad_indices[u] == idx_prev[RIGHT])
+                            volume.quad_indices[u] = idx_initial[RIGHT];
+                    }
+                }
+
+                // This is the last iteration, only required to solve the transition.
+                break;
+            }
+        }
+
+        // Only new allocate top / bottom vertices, if not closing a loop.
+        if (closed && (ii + 1 == lines.size()))
+            idx_b[TOP] = idx_initial[TOP];
+        else
+        {
+            idx_b[TOP] = idx_last++;
+            volume.push_geometry(b[TOP], n_top);
+        }
+
+        if (closed && (ii + 1 == lines.size()) && (width == width_initial))
+            idx_b[BOTTOM] = idx_initial[BOTTOM];
+        else
+        {
+            idx_b[BOTTOM] = idx_last++;
+            volume.push_geometry(b[BOTTOM], n_bottom);
+        }
+
+        // Generate new vertices for the end of this line segment.
+        idx_b[LEFT] = idx_last++;
+        volume.push_geometry(b[LEFT], n_left);
+        idx_b[RIGHT] = idx_last++;
+        volume.push_geometry(b[RIGHT], n_right);
+
+        ::memcpy(idx_prev, idx_b, 4 * sizeof(int));
+        n_right_prev = n_right;
+        n_top_prev = n_top;
+        unit_v_prev = unit_v;
+
+        if (!closed)
+        {
+            // Terminate open paths with caps.
+            if (i == 0)
+                volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]);
+
+            // We don't use 'else' because both cases are true if we have only one line.
+            if (i + 1 == lines.size())
+                volume.push_quad(idx_b[BOTTOM], idx_b[LEFT], idx_b[TOP], idx_b[RIGHT]);
+        }
+
+        // Add quads for a straight hollow tube-like segment.
+        // bottom-right face
+        volume.push_quad(idx_a[BOTTOM], idx_b[BOTTOM], idx_b[RIGHT], idx_a[RIGHT]);
+        // top-right face
+        volume.push_quad(idx_a[RIGHT], idx_b[RIGHT], idx_b[TOP], idx_a[TOP]);
+        // top-left face
+        volume.push_quad(idx_a[TOP], idx_b[TOP], idx_b[LEFT], idx_a[LEFT]);
+        // bottom-left face
+        volume.push_quad(idx_a[LEFT], idx_b[LEFT], idx_b[BOTTOM], idx_a[BOTTOM]);
+    }
+
+#undef LEFT
+#undef RIGHT
+#undef TOP
+#undef BOTTOM
+}
+
+static void point_to_indexed_vertex_array(const Point3& point,
+    double width,
+    double height,
+    GLIndexedVertexArray& volume)
+{
+    // builds a double piramid, with vertices on the local axes, around the point
+
+    Pointf3 center = Pointf3::new_unscale(point);
+
+    double scale_factor = 1.0;
+    double w = scale_factor * width;
+    double h = scale_factor * height;
+
+    // new vertices ids
+    int idx_last = int(volume.vertices_and_normals_interleaved.size() / 6);
+    int idxs[6];
+    for (int i = 0; i < 6; ++i)
+    {
+        idxs[i] = idx_last + i;
+    }
+
+    Vectorf3 displacement_x(w, 0.0, 0.0);
+    Vectorf3 displacement_y(0.0, w, 0.0);
+    Vectorf3 displacement_z(0.0, 0.0, h);
+
+    Vectorf3 unit_x(1.0, 0.0, 0.0);
+    Vectorf3 unit_y(0.0, 1.0, 0.0);
+    Vectorf3 unit_z(0.0, 0.0, 1.0);
+
+    // vertices
+    volume.push_geometry(center - displacement_x, -unit_x); // idxs[0]
+    volume.push_geometry(center + displacement_x, unit_x);  // idxs[1]
+    volume.push_geometry(center - displacement_y, -unit_y); // idxs[2]
+    volume.push_geometry(center + displacement_y, unit_y);  // idxs[3]
+    volume.push_geometry(center - displacement_z, -unit_z); // idxs[4]
+    volume.push_geometry(center + displacement_z, unit_z);  // idxs[5]
+
+    // top piramid faces
+    volume.push_triangle(idxs[0], idxs[2], idxs[5]);
+    volume.push_triangle(idxs[2], idxs[1], idxs[5]);
+    volume.push_triangle(idxs[1], idxs[3], idxs[5]);
+    volume.push_triangle(idxs[3], idxs[0], idxs[5]);
+
+    // bottom piramid faces
+    volume.push_triangle(idxs[2], idxs[0], idxs[4]);
+    volume.push_triangle(idxs[1], idxs[2], idxs[4]);
+    volume.push_triangle(idxs[3], idxs[1], idxs[4]);
+    volume.push_triangle(idxs[0], idxs[3], idxs[4]);
+}
+
 static void thick_lines_to_verts(
     const Lines                 &lines, 
     const std::vector<double>   &widths,
@@ -616,6 +987,32 @@ 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,
+    const std::vector<double>& widths,
+    const std::vector<double>& heights,
+    bool closed,
+    GLVolume& volume)
+{
+    thick_lines_to_indexed_vertex_array(lines, widths, heights, closed, volume.indexed_vertex_array);
+}
+
+static void thick_point_to_verts(const Point3& point,
+    double width,
+    double height,
+    GLVolume& volume)
+{
+    point_to_indexed_vertex_array(point, width, height, volume.indexed_vertex_array);
+}
+
+// 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)
+{
+    Lines               lines = extrusion_path.polyline.lines();
+    std::vector<double> widths(lines.size(), extrusion_path.width);
+    std::vector<double> heights(lines.size(), extrusion_path.height);
+    thick_lines_to_verts(lines, widths, heights, false, print_z, volume);
+}
+
 // 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)
 {
@@ -699,6 +1096,231 @@ static void extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, fl
     }
 }
 
+static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume)
+{
+    Lines3 lines = polyline.lines();
+    std::vector<double> widths(lines.size(), width);
+    std::vector<double> heights(lines.size(), height);
+    thick_lines_to_verts(lines, widths, heights, false, volume);
+}
+
+static void point3_to_verts(const Point3& point, double width, double height, GLVolume& volume)
+{
+    thick_point_to_verts(point, width, height, volume);
+}
+
+_3DScene::GCodePreviewData::FirstVolume::FirstVolume(_3DScene::GCodePreviewData::EType type, unsigned int flag, unsigned int id)
+    : type(type)
+    , flag(flag)
+    , id(id)
+{
+}
+
+void _3DScene::GCodePreviewData::reset()
+{
+    first_volumes.clear();
+}
+
+_3DScene::GCodePreviewData _3DScene::s_gcode_preview_data;
+_3DScene::LegendTexture _3DScene::s_legend_texture;
+
+const unsigned char _3DScene::LegendTexture::Squares_Border_Color[3] = { 64, 64, 64 };
+const unsigned char _3DScene::LegendTexture::Background_Color[3] = { 9, 91, 134 };
+const unsigned char _3DScene::LegendTexture::Opacity = 255;
+
+_3DScene::LegendTexture::LegendTexture()
+    : m_tex_id(0)
+    , m_tex_width(0)
+    , m_tex_height(0)
+{
+}
+
+_3DScene::LegendTexture::~LegendTexture()
+{
+    _destroy_texture();
+}
+
+bool _3DScene::LegendTexture::generate_texture(const Print& print, const std::vector<float>& tool_colors)
+{
+    _destroy_texture();
+
+    // collects items to render
+    const std::string& title = print.gcode_preview.get_legend_title();
+    const GCodeAnalyzer::PreviewData::LegendItemsList& items = print.gcode_preview.get_legend_items(tool_colors);
+
+    unsigned int items_count = (unsigned int)items.size();
+    if (items_count == 0)
+        // nothing to render, return
+        return false;
+
+    wxMemoryDC memDC;
+    // select default font
+    memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
+
+    // calculates texture size
+    wxCoord w, h;
+    memDC.GetTextExtent(title, &w, &h);
+    unsigned int title_width = (unsigned int)w;
+    unsigned int title_height = (unsigned int)h;
+
+    unsigned int max_text_width = 0;
+    unsigned int max_text_height = 0;
+    for (const GCodeAnalyzer::PreviewData::LegendItem& item : items)
+    {
+        memDC.GetTextExtent(item.text, &w, &h);
+        max_text_width = std::max(max_text_width, (unsigned int)w);
+        max_text_height = std::max(max_text_height, (unsigned int)h);
+    }
+
+    m_tex_width = std::max(2 * Px_Border + title_width, 2 * (Px_Border + Px_Square_Contour) + Px_Square + Px_Text_Offset + max_text_width);
+    m_tex_height = 2 * (Px_Border + Px_Square_Contour) + title_height + Px_Title_Offset + items_count * Px_Square;
+    if (items_count > 1)
+        m_tex_height += (items_count - 1) * Px_Square_Contour;
+
+    // generates bitmap
+    wxBitmap bitmap(m_tex_width, m_tex_height);
+
+#if defined(__APPLE__) || defined(_MSC_VER)
+    bitmap.UseAlpha();
+#endif
+
+    memDC.SelectObject(bitmap);
+    memDC.SetBackground(wxBrush(wxColour(Background_Color[0], Background_Color[1], Background_Color[2])));
+    memDC.Clear();
+
+    memDC.SetTextForeground(*wxWHITE);
+
+    // draw title
+    unsigned int title_x = Px_Border;
+    unsigned int title_y = Px_Border;
+    memDC.DrawText(title, title_x, title_y);
+
+    // draw icons contours as background
+    unsigned int squares_contour_x = Px_Border;
+    unsigned int squares_contour_y = Px_Border + title_height + Px_Title_Offset;
+    unsigned int squares_contour_width = Px_Square + 2 * Px_Square_Contour;
+    unsigned int squares_contour_height = items_count * Px_Square + 2 * Px_Square_Contour;
+    if (items_count > 1)
+        squares_contour_height += (items_count - 1) * Px_Square_Contour;
+
+    wxColour color(Squares_Border_Color[0], Squares_Border_Color[1], Squares_Border_Color[2]);
+    wxPen pen(color);
+    wxBrush brush(color);
+    memDC.SetPen(pen);
+    memDC.SetBrush(brush);
+    memDC.DrawRectangle(wxRect(squares_contour_x, squares_contour_y, squares_contour_width, squares_contour_height));
+
+    // draw items (colored icon + text)
+    unsigned int icon_x = squares_contour_x + Px_Square_Contour;
+    unsigned int icon_x_inner = icon_x + 1;
+    unsigned int icon_y = squares_contour_y + Px_Square_Contour;
+    unsigned int icon_y_step = Px_Square + Px_Square_Contour;
+
+    unsigned int text_x = icon_x + Px_Square + Px_Text_Offset;
+    unsigned int text_y_offset = (Px_Square - max_text_height) / 2;
+
+    unsigned int px_inner_square = Px_Square - 2;
+
+    for (const GCodeAnalyzer::PreviewData::LegendItem& item : items)
+    {
+        // draw darker icon perimeter
+        const std::vector<unsigned char>& item_color_bytes = item.color.as_bytes();
+        wxImage::HSVValue dark_hsv = wxImage::RGBtoHSV(wxImage::RGBValue(item_color_bytes[0], item_color_bytes[1], item_color_bytes[2]));
+        dark_hsv.value *= 0.75;
+        wxImage::RGBValue dark_rgb = wxImage::HSVtoRGB(dark_hsv);
+        color.Set(dark_rgb.red, dark_rgb.green, dark_rgb.blue, item_color_bytes[3]);
+        pen.SetColour(color);
+        brush.SetColour(color);
+        memDC.SetPen(pen);
+        memDC.SetBrush(brush);
+        memDC.DrawRectangle(wxRect(icon_x, icon_y, Px_Square, Px_Square));
+
+        // draw icon interior
+        color.Set(item_color_bytes[0], item_color_bytes[1], item_color_bytes[2], item_color_bytes[3]);
+        pen.SetColour(color);
+        brush.SetColour(color);
+        memDC.SetPen(pen);
+        memDC.SetBrush(brush);
+        memDC.DrawRectangle(wxRect(icon_x_inner, icon_y + 1, px_inner_square, px_inner_square));
+
+        // draw text
+        memDC.DrawText(item.text, text_x, icon_y + text_y_offset);
+
+        // update y
+        icon_y += icon_y_step;
+    }
+
+    memDC.SelectObject(wxNullBitmap);
+
+    return _create_texture(print, bitmap);
+}
+
+unsigned int _3DScene::LegendTexture::get_texture_id() const
+{
+    return m_tex_id;
+}
+
+unsigned int _3DScene::LegendTexture::get_texture_width() const
+{
+    return m_tex_width;
+}
+
+unsigned int _3DScene::LegendTexture::get_texture_height() const
+{
+    return m_tex_height;
+}
+
+void _3DScene::LegendTexture::reset_texture()
+{
+    _destroy_texture();
+}
+
+bool _3DScene::LegendTexture::_create_texture(const Print& print, const wxBitmap& bitmap)
+{
+    if ((m_tex_width == 0) || (m_tex_height == 0))
+        return false;
+
+    wxImage image = bitmap.ConvertToImage();
+    image.SetMaskColour(Background_Color[0], Background_Color[1], Background_Color[2]);
+
+    // prepare buffer
+    std::vector<unsigned char> buffer(4 * m_tex_width * m_tex_height, 0);
+    for (unsigned int h = 0; h < m_tex_height; ++h)
+    {
+        unsigned int hh = h * m_tex_width;
+        for (unsigned int w = 0; w < m_tex_width; ++w)
+        {
+            unsigned char* px_ptr = buffer.data() + 4 * (hh + w);
+            *px_ptr++ = image.GetRed(w, h);
+            *px_ptr++ = image.GetGreen(w, h);
+            *px_ptr++ = image.GetBlue(w, h);
+            *px_ptr++ = image.IsTransparent(w, h) ? 0 : Opacity;
+        }
+    }
+
+    // sends buffer to gpu
+    ::glGenTextures(1, &m_tex_id);
+    ::glBindTexture(GL_TEXTURE_2D, m_tex_id);
+    ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_tex_width, (GLsizei)m_tex_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const GLvoid*)buffer.data());
+    ::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);
+    ::glBindTexture(GL_TEXTURE_2D, 0);
+
+    return true;
+}
+
+void _3DScene::LegendTexture::_destroy_texture()
+{
+    if (m_tex_id > 0)
+    {
+        ::glDeleteTextures(1, &m_tex_id);
+        m_tex_id = 0;
+        m_tex_height = 0;
+        m_tex_width = 0;
+    }
+}
+
 void _3DScene::_glew_init()
 { 
     glewInit();
@@ -731,6 +1353,59 @@ static inline std::vector<float> parse_colors(const std::vector<std::string> &sc
     return output;
 }
 
+void _3DScene::load_gcode_preview(const Print* print, GLVolumeCollection* volumes, const std::vector<std::string>& str_tool_colors, bool use_VBOs)
+{
+    if ((print == nullptr) || (volumes == nullptr))
+        return;
+
+    if (volumes->empty())
+    {
+        std::vector<float> tool_colors = parse_colors(str_tool_colors);
+
+        s_gcode_preview_data.reset();
+
+        _load_gcode_extrusion_paths(*print, *volumes, tool_colors, use_VBOs);
+        _load_gcode_travel_paths(*print, *volumes, tool_colors, use_VBOs);
+        _load_gcode_retractions(*print, *volumes, use_VBOs);
+        _load_gcode_unretractions(*print, *volumes, use_VBOs);
+
+        if (volumes->empty())
+        {
+            reset_legend_texture();
+            volumes->set_render_interleaved_only_volumes(GLVolumeCollection::RenderInterleavedOnlyVolumes(false, 0.0f));
+        }
+        else
+        {
+            _generate_legend_texture(*print, tool_colors);
+
+            _load_shells(*print, *volumes, use_VBOs);
+            volumes->set_render_interleaved_only_volumes(GLVolumeCollection::RenderInterleavedOnlyVolumes(true, 0.25f));
+        }
+    }
+
+    _update_gcode_volumes_visibility(*print, *volumes);
+}
+
+unsigned int _3DScene::get_legend_texture_id()
+{
+    return s_legend_texture.get_texture_id();
+}
+
+unsigned int _3DScene::get_legend_texture_width()
+{
+    return s_legend_texture.get_texture_width();
+}
+
+unsigned int _3DScene::get_legend_texture_height()
+{
+    return s_legend_texture.get_texture_height();
+}
+
+void _3DScene::reset_legend_texture()
+{
+    s_legend_texture.reset_texture();
+}
+
 // Create 3D thick extrusion lines for a skirt and brim.
 // Adds a new Slic3r::GUI::3DScene::Volume to volumes.
 void _3DScene::_load_print_toolpaths(
@@ -739,7 +1414,7 @@ void _3DScene::_load_print_toolpaths(
     const std::vector<std::string>  &tool_colors,
     bool                             use_VBOs)
 {
-    if (! print->has_skirt() && print->config.brim_width.value == 0)
+    if (!print->has_skirt() && print->config.brim_width.value == 0)
         return;
     
     const float color[] = { 0.5f, 1.0f, 0.5f, 1.f }; // greenish
@@ -1088,4 +1763,559 @@ void _3DScene::_load_wipe_tower_toolpaths(
     BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; 
 }
 
+void _3DScene::_load_gcode_extrusion_paths(const Print& print, 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(GCodeAnalyzer::PreviewData::Extrusion::EViewType type, const ExtrusionPath& path)
+        {
+            switch (type)
+            {
+            case GCodeAnalyzer::PreviewData::Extrusion::FeatureType:
+                return (float)path.role();
+            case GCodeAnalyzer::PreviewData::Extrusion::Height:
+                return path.height;
+            case GCodeAnalyzer::PreviewData::Extrusion::Width:
+                return path.width;
+            case GCodeAnalyzer::PreviewData::Extrusion::Feedrate:
+                return path.feedrate;
+            case GCodeAnalyzer::PreviewData::Extrusion::Tool:
+                return (float)path.extruder_id;
+            }
+
+            return 0.0f;
+        }
+
+        static const GCodeAnalyzer::PreviewData::Color& path_color(const GCodeAnalyzer::PreviewData& data, const std::vector<float>& tool_colors, float value)
+        {
+            switch (data.extrusion.view_type)
+            {
+            case GCodeAnalyzer::PreviewData::Extrusion::FeatureType:
+                return data.get_extrusion_role_color((ExtrusionRole)(int)value);
+            case GCodeAnalyzer::PreviewData::Extrusion::Height:
+                return data.get_extrusion_height_color(value);
+            case GCodeAnalyzer::PreviewData::Extrusion::Width:
+                return data.get_extrusion_width_color(value);
+            case GCodeAnalyzer::PreviewData::Extrusion::Feedrate:
+                return data.get_extrusion_feedrate_color(value);
+            case GCodeAnalyzer::PreviewData::Extrusion::Tool:
+                {
+                    static GCodeAnalyzer::PreviewData::Color color;
+                    ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + (unsigned int)value * 4), 4 * sizeof(float));
+                    return color;
+                }
+            }
+
+            return GCodeAnalyzer::PreviewData::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 GCodeAnalyzer::PreviewData::Extrusion::Layer& layer : print.gcode_preview.extrusion.layers)
+    {
+        for (const ExtrusionPath& path : layer.paths)
+        {
+            ExtrusionRole role = path.role();
+            float path_filter = Helper::path_filter(print.gcode_preview.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_data.first_volumes.emplace_back(GCodePreviewData::Extrusion, (unsigned int)filter.role, (unsigned int)volumes.volumes.size());
+
+        GLVolume* volume = new GLVolume(Helper::path_color(print.gcode_preview, 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_data.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 GCodeAnalyzer::PreviewData::Extrusion::Layer& layer : print.gcode_preview.extrusion.layers)
+    {
+        for (const ExtrusionPath& path : layer.paths)
+        {
+            float path_filter = Helper::path_filter(print.gcode_preview.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 Print& print, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs)
+{
+    size_t initial_volumes_count = volumes.volumes.size();
+    s_gcode_preview_data.first_volumes.emplace_back(GCodePreviewData::Travel, 0, (unsigned int)initial_volumes_count);
+
+    bool res = true;
+    switch (print.gcode_preview.extrusion.view_type)
+    {
+    case GCodeAnalyzer::PreviewData::Extrusion::Feedrate:
+        {
+            res = _travel_paths_by_feedrate(print, volumes);
+            break;
+        }
+    case GCodeAnalyzer::PreviewData::Extrusion::Tool:
+        {
+            res = _travel_paths_by_tool(print, volumes, tool_colors);
+            break;
+        }
+    default:
+        {
+            res = _travel_paths_by_type(print, 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 Print& print, GLVolumeCollection& volumes)
+{
+    // Helper structure for types
+    struct Type
+    {
+        GCodeAnalyzer::PreviewData::Travel::EType value;
+        GLVolume* volume;
+
+        explicit Type(GCodeAnalyzer::PreviewData::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 GCodeAnalyzer::PreviewData::Travel::Polyline& polyline : print.gcode_preview.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(print.gcode_preview.travel.type_colors[type.value].rgba);
+        if (volume == nullptr)
+            return false;
+        else
+        {
+            type.volume = volume;
+            volumes.volumes.emplace_back(volume);
+        }
+    }
+
+    // populates volumes
+    for (const GCodeAnalyzer::PreviewData::Travel::Polyline& polyline : print.gcode_preview.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().max.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, print.gcode_preview.travel.width, print.gcode_preview.travel.height, *type->volume);
+        }
+    }
+
+    return true;
+}
+
+bool _3DScene::_travel_paths_by_feedrate(const Print& print, 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 GCodeAnalyzer::PreviewData::Travel::Polyline& polyline : print.gcode_preview.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(print.gcode_preview.get_extrusion_feedrate_color(feedrate.value).rgba);
+        if (volume == nullptr)
+            return false;
+        else
+        {
+            feedrate.volume = volume;
+            volumes.volumes.emplace_back(volume);
+        }
+    }
+
+    // populates volumes
+    for (const GCodeAnalyzer::PreviewData::Travel::Polyline& polyline : print.gcode_preview.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().max.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, print.gcode_preview.travel.width, print.gcode_preview.travel.height, *feedrate->volume);
+        }
+    }
+
+    return true;
+}
+
+bool _3DScene::_travel_paths_by_tool(const Print& print, 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 GCodeAnalyzer::PreviewData::Travel::Polyline& polyline : print.gcode_preview.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 GCodeAnalyzer::PreviewData::Travel::Polyline& polyline : print.gcode_preview.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().max.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, print.gcode_preview.travel.width, print.gcode_preview.travel.height, *tool->volume);
+        }
+    }
+
+    return true;
+}
+
+void _3DScene::_load_gcode_retractions(const Print& print, GLVolumeCollection& volumes, bool use_VBOs)
+{
+    s_gcode_preview_data.first_volumes.emplace_back(GCodePreviewData::Retraction, 0, (unsigned int)volumes.volumes.size());
+
+    // nothing to render, return
+    if (print.gcode_preview.retraction.positions.empty())
+        return;
+
+    GLVolume* volume = new GLVolume(print.gcode_preview.retraction.color.rgba);
+    if (volume != nullptr)
+    {
+        volumes.volumes.emplace_back(volume);
+
+        for (const GCodeAnalyzer::PreviewData::Retraction::Position& position : print.gcode_preview.retraction.positions)
+        {
+            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 Print& print, GLVolumeCollection& volumes, bool use_VBOs)
+{
+    s_gcode_preview_data.first_volumes.emplace_back(GCodePreviewData::Unretraction, 0, (unsigned int)volumes.volumes.size());
+
+    // nothing to render, return
+    if (print.gcode_preview.unretraction.positions.empty())
+        return;
+
+    GLVolume* volume = new GLVolume(print.gcode_preview.unretraction.color.rgba);
+    if (volume != nullptr)
+    {
+        volumes.volumes.emplace_back(volume);
+
+        for (const GCodeAnalyzer::PreviewData::Retraction::Position& position : print.gcode_preview.unretraction.positions)
+        {
+            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 Print& print, GLVolumeCollection& volumes)
+{
+    unsigned int size = (unsigned int)s_gcode_preview_data.first_volumes.size();
+    for (unsigned int i = 0; i < size; ++i)
+    {
+        std::vector<GLVolume*>::iterator begin = volumes.volumes.begin() + s_gcode_preview_data.first_volumes[i].id;
+        std::vector<GLVolume*>::iterator end = (i + 1 < size) ? volumes.volumes.begin() + s_gcode_preview_data.first_volumes[i + 1].id : volumes.volumes.end();
+
+        for (std::vector<GLVolume*>::iterator it = begin; it != end; ++it)
+        {
+            GLVolume* volume = *it;
+
+            switch (s_gcode_preview_data.first_volumes[i].type)
+            {
+            case GCodePreviewData::Extrusion:
+                {
+                    volume->is_active = print.gcode_preview.extrusion.is_role_flag_set((ExtrusionRole)s_gcode_preview_data.first_volumes[i].flag);
+                    break;
+                }
+            case GCodePreviewData::Travel:
+                {
+                    volume->is_active = print.gcode_preview.travel.is_visible;
+                    break;
+                }
+            case GCodePreviewData::Retraction:
+                {
+                    volume->is_active = print.gcode_preview.retraction.is_visible;
+                    break;
+                }
+            case GCodePreviewData::Unretraction:
+                {
+                    volume->is_active = print.gcode_preview.unretraction.is_visible;
+                    break;
+                }
+            case GCodePreviewData::Shell:
+                {
+                    volume->is_active = print.gcode_preview.shell.is_visible;
+                    break;
+                }
+            default:
+                {
+                    volume->is_active = false;
+                    break;
+                }
+            }
+        }
+    }
+}
+
+void _3DScene::_generate_legend_texture(const Print& print, const std::vector<float>& tool_colors)
+{
+    s_legend_texture.generate_texture(print, 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_data.first_volumes.emplace_back(GCodePreviewData::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)
+        volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, config.wipe_tower_per_color_wipe * (extruders_count - 1), max_z, use_VBOs);
+}
+
 }
diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp
index 27eeb7ca8..44e5dbea3 100644
--- a/xs/src/slic3r/GUI/3DScene.hpp
+++ b/xs/src/slic3r/GUI/3DScene.hpp
@@ -7,6 +7,8 @@
 #include "../../libslic3r/TriangleMesh.hpp"
 #include "../../libslic3r/Utils.hpp"
 
+class wxBitmap;
+
 namespace Slic3r {
 
 class Print;
@@ -106,6 +108,10 @@ public:
         push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
     }
 
+    inline void push_geometry(const Pointf3& p, const Vectorf3& n) {
+        push_geometry(p.x, p.y, p.z, n.x, n.y, n.z);
+    }
+
     inline void push_triangle(int idx1, int idx2, int idx3) {
         if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity())
             this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3));
@@ -207,6 +213,7 @@ public:
         select_group_id(-1),
         drag_group_id(-1),
         selected(false),
+        is_active(true),
         hover(false),
         tverts_range(0, size_t(-1)),
         qverts_range(0, size_t(-1))
@@ -243,6 +250,8 @@ public:
     int                 drag_group_id;
     // Is this object selected?
     bool                selected;
+    // Whether or not this volume is active for rendering
+    bool                is_active;
     // Boolean: Is mouse over this object?
     bool                hover;
 
@@ -258,6 +267,7 @@ public:
     // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer.
     std::vector<size_t>         offsets;
 
+
     int                 object_idx() const { return this->composite_id / 1000000; }
     int                 volume_idx() const { return (this->composite_id / 1000) % 1000; }
     int                 instance_idx() const { return this->composite_id % 1000; }
@@ -299,6 +309,28 @@ public:
 
 class GLVolumeCollection
 {
+public:
+    struct RenderInterleavedOnlyVolumes
+    {
+        bool enabled;
+        float alpha; // [0..1]
+
+        RenderInterleavedOnlyVolumes()
+            : enabled(false)
+            , alpha(0.0f)
+        {
+        }
+
+        RenderInterleavedOnlyVolumes(bool enabled, float alpha)
+            : enabled(enabled)
+            , alpha(alpha)
+        {
+        }
+    };
+
+private:
+    RenderInterleavedOnlyVolumes _render_interleaved_only_volumes;
+
 public:
     std::vector<GLVolume*> volumes;
     
@@ -334,6 +366,8 @@ public:
     bool empty() const { return volumes.empty(); }
     void set_range(double low, double high) { for (GLVolume *vol : this->volumes) vol->set_range(low, high); }
 
+    void set_render_interleaved_only_volumes(const RenderInterleavedOnlyVolumes& render_interleaved_only_volumes) { _render_interleaved_only_volumes = render_interleaved_only_volumes; }
+
 private:
     GLVolumeCollection(const GLVolumeCollection &other);
     GLVolumeCollection& operator=(const GLVolumeCollection &);
@@ -341,9 +375,79 @@ private:
 
 class _3DScene
 {
+    struct GCodePreviewData
+    {
+        enum EType
+        {
+            Extrusion,
+            Travel,
+            Retraction,
+            Unretraction,
+            Shell,
+            Num_Geometry_Types
+        };
+
+        struct FirstVolume
+        {
+            EType type;
+            unsigned int flag;
+            unsigned int id;
+
+            FirstVolume(EType type, unsigned int flag, unsigned int id);
+        };
+
+        std::vector<FirstVolume> first_volumes;
+
+        void reset();
+    };
+
+    static GCodePreviewData s_gcode_preview_data;
+
+    class LegendTexture
+    {
+        static const unsigned int Px_Title_Offset = 5;
+        static const unsigned int Px_Text_Offset = 5;
+        static const unsigned int Px_Square = 20;
+        static const unsigned int Px_Square_Contour = 1;
+        static const unsigned int Px_Border = Px_Square / 2;
+        static const unsigned char Squares_Border_Color[3];
+        static const unsigned char Background_Color[3];
+        static const unsigned char Opacity;
+
+        unsigned int m_tex_id;
+        unsigned int m_tex_width;
+        unsigned int m_tex_height;
+
+    public:
+        LegendTexture();
+        ~LegendTexture();
+        
+        bool generate_texture(const Print& print, const std::vector<float>& tool_colors);
+
+        unsigned int get_texture_id() const;
+        unsigned int get_texture_width() const;
+        unsigned int get_texture_height() const;
+
+        void reset_texture();
+
+    private:
+        bool _create_texture(const Print& print, const wxBitmap& bitmap);
+        void _destroy_texture();
+    };
+
+    static LegendTexture s_legend_texture;
+
 public:
     static void _glew_init();
 
+    static void load_gcode_preview(const Print* print, GLVolumeCollection* volumes, const std::vector<std::string>& str_tool_colors, bool use_VBOs);
+
+    static unsigned int get_legend_texture_id();
+    static unsigned int get_legend_texture_width();
+    static unsigned int get_legend_texture_height();
+
+    static void reset_legend_texture();
+
     static void _load_print_toolpaths(
         const Print                     *print,
         GLVolumeCollection              *volumes,
@@ -356,12 +460,30 @@ public:
         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 Print& print, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs);
+    // generates gcode travel paths geometry
+    static void _load_gcode_travel_paths(const Print& print, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs);
+    static bool _travel_paths_by_type(const Print& print, GLVolumeCollection& volumes);
+    static bool _travel_paths_by_feedrate(const Print& print, GLVolumeCollection& volumes);
+    static bool _travel_paths_by_tool(const Print& print, GLVolumeCollection& volumes, const std::vector<float>& tool_colors);
+    // generates gcode retractions geometry
+    static void _load_gcode_retractions(const Print& print, GLVolumeCollection& volumes, bool use_VBOs);
+    // generates gcode unretractions geometry
+    static void _load_gcode_unretractions(const Print& print, GLVolumeCollection& volumes, bool use_VBOs);
+    // sets gcode geometry visibility according to user selection
+    static void _update_gcode_volumes_visibility(const Print& print, GLVolumeCollection& volumes);
+    // generates the legend texture in dependence of the current shown view type
+    static void _generate_legend_texture(const Print& print, const std::vector<float>& tool_colors);
+    // generates objects and wipe tower geometry
+    static void _load_shells(const Print& print, GLVolumeCollection& volumes, bool use_VBOs);
 };
 
 }
diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp
index 8db0508f1..809ee8508 100644
--- a/xs/src/slic3r/GUI/GUI.cpp
+++ b/xs/src/slic3r/GUI/GUI.cpp
@@ -5,6 +5,9 @@
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/filesystem.hpp>
 
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/classification.hpp>
+
 #if __APPLE__
 #import <IOKit/pwr_mgt/IOPMLib.h>
 #elif _WIN32
@@ -20,6 +23,9 @@
 #include <wx/notebook.h>
 #include <wx/panel.h>
 #include <wx/sizer.h>
+#include <wx/combo.h>
+
+#include "wxExtensions.hpp"
 
 namespace Slic3r { namespace GUI {
 
@@ -183,4 +189,49 @@ void create_preset_tab(const char *name)
     g_wxTabPanel->AddPage(panel, name);
 }
 
+void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value)
+{
+    if (comboCtrl == nullptr)
+        return;
+
+    wxCheckListBoxComboPopup* popup = new wxCheckListBoxComboPopup;
+    if (popup != nullptr)
+    {
+        comboCtrl->SetPopupControl(popup);
+        popup->SetStringValue(text);
+        popup->Connect(wxID_ANY, wxEVT_CHECKLISTBOX, wxCommandEventHandler(wxCheckListBoxComboPopup::OnCheckListBox), nullptr, popup);
+        popup->Connect(wxID_ANY, wxEVT_LISTBOX, wxCommandEventHandler(wxCheckListBoxComboPopup::OnListBoxSelection), nullptr, popup);
+
+        std::vector<std::string> items_str;
+        boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off);
+
+        for (const std::string& item : items_str)
+        {
+            popup->Append(item);
+        }
+
+        for (unsigned int i = 0; i < popup->GetCount(); ++i)
+        {
+            popup->Check(i, initial_value);
+        }
+    }
+}
+
+int combochecklist_get_flags(wxComboCtrl* comboCtrl)
+{
+    int flags = 0;
+
+    wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup);
+    if (popup != nullptr)
+    {
+        for (unsigned int i = 0; i < popup->GetCount(); ++i)
+        {
+            if (popup->IsChecked(i))
+                flags |= 1 << i;
+        }
+    }
+
+    return flags;
+}
+
 } }
diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp
index 3634e0bc8..f15ba15d6 100644
--- a/xs/src/slic3r/GUI/GUI.hpp
+++ b/xs/src/slic3r/GUI/GUI.hpp
@@ -8,6 +8,7 @@ class wxApp;
 class wxFrame;
 class wxMenuBar;
 class wxNotebook;
+class wxComboCtrl;
 
 namespace Slic3r { namespace GUI {
 
@@ -27,6 +28,16 @@ void add_debug_menu(wxMenuBar *menu);
 // add it at the end of the tab panel.
 void create_preset_tab(const char *name);
 
-} }
+// Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items.
+// Items are all initialized to the given value.
+// Items must be separated by '|', for example "Item1|Item2|Item3", and so on.
+void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value);
+
+// Returns the current state of the items listed in the wxCheckListBoxComboPopup contained in the given wxComboCtrl,
+// encoded inside an int.
+int combochecklist_get_flags(wxComboCtrl* comboCtrl);
+
+}
+}
 
 #endif
diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp
new file mode 100644
index 000000000..b5f0595f8
--- /dev/null
+++ b/xs/src/slic3r/GUI/wxExtensions.cpp
@@ -0,0 +1,68 @@
+#include "wxExtensions.hpp"
+
+const unsigned int wxCheckListBoxComboPopup::Height = 210;
+
+bool wxCheckListBoxComboPopup::Create(wxWindow* parent)
+{
+    return wxCheckListBox::Create(parent, wxID_HIGHEST + 1, wxPoint(0, 0));
+}
+
+wxWindow* wxCheckListBoxComboPopup::GetControl()
+{
+    return this;
+}
+
+void wxCheckListBoxComboPopup::SetStringValue(const wxString& value)
+{
+    m_text = value;
+}
+
+wxString wxCheckListBoxComboPopup::GetStringValue() const
+{
+    return m_text;
+}
+
+wxSize wxCheckListBoxComboPopup::GetAdjustedSize(int minWidth, int prefHeight, int maxHeight)
+{
+    // matches owner wxComboCtrl's width
+
+    wxComboCtrl* cmb = GetComboCtrl();
+    if (cmb != nullptr)
+    {
+        wxSize size = GetComboCtrl()->GetSize();
+        size.SetHeight(Height);
+        return size;
+    }
+    else
+        return wxSize(200, Height);
+}
+
+void wxCheckListBoxComboPopup::OnCheckListBox(wxCommandEvent& evt)
+{
+    // forwards the checklistbox event to the owner wxComboCtrl
+
+    wxComboCtrl* cmb = GetComboCtrl();
+    if (cmb != nullptr)
+    {
+        wxCommandEvent event(wxEVT_CHECKLISTBOX, cmb->GetId());
+        event.SetEventObject(cmb);
+        cmb->ProcessWindowEvent(event);
+    }
+}
+
+void wxCheckListBoxComboPopup::OnListBoxSelection(wxCommandEvent& evt)
+{
+    // transforms list box item selection event into checklistbox item toggle event 
+
+    int selId = GetSelection();
+    if (selId != wxNOT_FOUND)
+    {
+        Check((unsigned int)selId, !IsChecked((unsigned int)selId));
+        SetSelection(wxNOT_FOUND);
+
+        wxCommandEvent event(wxEVT_CHECKLISTBOX, GetId());
+        event.SetInt(selId);
+        event.SetEventObject(this);
+        ProcessEvent(event);
+    }
+}
diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp
new file mode 100644
index 000000000..8e62f766c
--- /dev/null
+++ b/xs/src/slic3r/GUI/wxExtensions.hpp
@@ -0,0 +1,24 @@
+#ifndef slic3r_GUI_wxExtensions_hpp_
+#define slic3r_GUI_wxExtensions_hpp_
+
+#include <wx/checklst.h>
+#include <wx/combo.h>
+
+class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
+{
+    static const unsigned int Height;
+
+    wxString m_text;
+
+public:
+    virtual bool Create(wxWindow* parent);
+    virtual wxWindow* GetControl();
+    virtual void SetStringValue(const wxString& value);
+    virtual wxString GetStringValue() const;
+    virtual wxSize GetAdjustedSize(int minWidth, int prefHeight, int maxHeight);
+
+    void OnCheckListBox(wxCommandEvent& evt);
+    void OnListBoxSelection(wxCommandEvent& evt);
+};
+
+#endif // slic3r_GUI_wxExtensions_hpp_
diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp
index d6b55dbf1..3b2c47e26 100644
--- a/xs/xsp/GUI.xsp
+++ b/xs/xsp/GUI.xsp
@@ -37,3 +37,10 @@ void add_debug_menu(SV *ui)
 
 void create_preset_tab(const char *name)
     %code%{ Slic3r::GUI::create_preset_tab(name); %};
+
+void create_combochecklist(SV *ui, std::string text, std::string items, bool initial_value)
+    %code%{ Slic3r::GUI::create_combochecklist((wxComboCtrl*)wxPli_sv_2_object(aTHX_ ui, "Wx::ComboCtrl"), text, items, initial_value); %};
+        
+int combochecklist_get_flags(SV *ui)
+    %code%{ RETVAL=Slic3r::GUI::combochecklist_get_flags((wxComboCtrl*)wxPli_sv_2_object(aTHX_ ui, "Wx::ComboCtrl")); %};
+    
\ No newline at end of file
diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp
index 13f0d42f2..7520ce6c2 100644
--- a/xs/xsp/GUI_3DScene.xsp
+++ b/xs/xsp/GUI_3DScene.xsp
@@ -141,6 +141,32 @@ _glew_init()
     CODE:
         _3DScene::_glew_init();
 
+unsigned int
+get_legend_texture_id()
+    CODE:
+        RETVAL = _3DScene::get_legend_texture_id();
+    OUTPUT:
+        RETVAL
+
+unsigned int
+get_legend_texture_width()
+    CODE:
+        RETVAL = _3DScene::get_legend_texture_width();
+    OUTPUT:
+        RETVAL
+
+unsigned int
+get_legend_texture_height()
+    CODE:
+        RETVAL = _3DScene::get_legend_texture_height();
+    OUTPUT:
+        RETVAL
+             
+void
+reset_legend_texture()
+    CODE:
+        _3DScene::reset_legend_texture();
+             
 void
 _load_print_toolpaths(print, volumes, tool_colors, use_VBOs)
         Print               *print;
@@ -168,4 +194,13 @@ _load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs)
     CODE:
         _3DScene::_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs != 0);
 
+void 
+load_gcode_preview(print, volumes, str_tool_colors, use_VBOs)
+        Print                    *print;
+        GLVolumeCollection       *volumes;
+        std::vector<std::string> str_tool_colors;
+        int                      use_VBOs;
+    CODE:
+        _3DScene::load_gcode_preview(print, volumes, str_tool_colors, use_VBOs != 0);
+        
 %}
diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp
index cbc04a804..5749c20db 100644
--- a/xs/xsp/Print.xsp
+++ b/xs/xsp/Print.xsp
@@ -165,6 +165,15 @@ _constant()
     size_t object_count()
         %code%{ RETVAL = THIS->objects.size(); %};
 
+    void clear_gcode_preview_data();
+    void set_gcode_preview_type(unsigned char type);
+    void set_gcode_preview_extrusion_flags(unsigned int flags);
+    void set_gcode_preview_travel_visible(bool visible);
+    void set_gcode_preview_retractions_visible(bool visible);
+    void set_gcode_preview_unretractions_visible(bool visible);
+    void set_gcode_preview_shells_visible(bool visible);
+    void set_gcode_extrusion_paths_colors(std::vector<std::string> colors);
+        
     PrintRegionPtrs* regions()
         %code%{ RETVAL = &THIS->regions; %};
     Ref<PrintRegion> get_region(int idx);