diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index b834bc202..e9668225f 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -41,6 +41,7 @@ our $PROCESS_COMPLETED_EVENT = Wx::NewEventType;
 my $PreventListEvents = 0;
 our $appController;
 
+# XXX: VK: done, except callback handling and timer
 sub new {
     my ($class, $parent, %params) = @_;
     my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
@@ -813,12 +814,14 @@ sub new {
     return $self;
 }
 
+# XXX: VK: WIP
 # sets the callback
 sub on_select_preset {
     my ($self, $cb) = @_;
     $self->{on_select_preset} = $cb;
 }
 
+# XXX: merged with on_select_preset
 # Called from the platter combo boxes selecting the active print, filament or printer.
 sub _on_select_preset {
 	my ($self, $group, $choice, $idx) = @_;
@@ -855,6 +858,7 @@ sub _on_select_preset {
 	$self->on_config_change(wxTheApp->{preset_bundle}->full_config);
 }
 
+# XXX: VK: done
 sub on_layer_editing_toggled {
     my ($self, $new_state) = @_;
     Slic3r::GUI::_3DScene::enable_layers_editing($self->{canvas3D}, $new_state);
@@ -873,11 +877,13 @@ sub on_layer_editing_toggled {
     $self->{canvas3D}->Update;
 }
 
-sub GetFrame {   # XXX: main_frame in C++ Plater
+# XXX: VK: done (Plater::priv::main_frame)
+sub GetFrame {
     my ($self) = @_;
     return &Wx::GetTopLevelParent($self);
 }
 
+# XXX: not done
 # Called after the Preferences dialog is closed and the program settings are saved.
 # Update the UI based on the current preferences.
 sub update_ui_from_settings
@@ -889,6 +895,7 @@ sub update_ui_from_settings
     }
 }
 
+# XXX: VK: done
 # Update preset combo boxes (Print settings, Filament, Material, Printer) from their respective tabs.
 # Called by 
 #       Slic3r::GUI::Tab::Print::_on_presets_changed
@@ -934,12 +941,14 @@ sub update_presets {
     wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config});
 }
 
+# XXX: VK: done, in on_action_add()
 sub add {
     my ($self) = @_;
     my @input_files = wxTheApp->open_model($self);
     $self->load_files(\@input_files);
 }
 
+# XXX: VK: done
 sub load_files {
     my ($self, $input_files) = @_;
 
@@ -1030,6 +1039,7 @@ sub load_files {
     return @obj_idx;
 }
 
+# XXX: VK: done, except a few todos
 sub load_model_objects {
     my ($self, @model_objects) = @_;
     
@@ -1112,6 +1122,7 @@ sub load_model_objects {
     return @obj_idx;
 }
 
+# XXX: Removed
 sub bed_centerf {
     my ($self) = @_;
     
@@ -1120,6 +1131,7 @@ sub bed_centerf {
     return Slic3r::Pointf->new(unscale($bed_center->x), unscale($bed_center->y)); #)
 }
 
+# XXX: VK: done
 sub remove {
     my ($self, $obj_idx) = @_;
     
@@ -1146,6 +1158,7 @@ sub remove {
     $self->update;
 }
 
+# XXX: VK: done
 sub reset {
     my ($self) = @_;
     
@@ -1166,6 +1179,7 @@ sub reset {
     $self->update;
 }
 
+# XXX: not done
 sub increase {
     my ($self, $copies) = @_;
     $copies //= 1;
@@ -1197,6 +1211,7 @@ sub increase {
     $self->schedule_background_process;
 }
 
+# XXX: not done
 sub decrease {
     my ($self, $copies_asked) = @_;
     my $copies = $copies_asked // 1;
@@ -1224,6 +1239,7 @@ sub decrease {
     $self->update;
 }
 
+# XXX: not done
 sub set_number_of_copies {
     my ($self) = @_;
     # get current number of copies
@@ -1242,6 +1258,7 @@ sub set_number_of_copies {
     }
 }
 
+# XXX: not done (?)
 sub _get_number_from_user {     # XXX: Enrico
     my ($self, $title, $prompt_message, $error_message, $default, $only_positive) = @_;
     for (;;) {
@@ -1264,6 +1281,7 @@ sub _get_number_from_user {     # XXX: Enrico
     }
 }
 
+# XXX: not done
 sub rotate {
     my ($self, $angle, $axis, $relative_key, $axis_x, $axis_y, $axis_z) = @_;
     $relative_key //= 'absolute'; # relative or absolute coordinates
@@ -1334,6 +1352,7 @@ sub rotate {
     $self->update;
 }
 
+# XXX: not done
 sub mirror {
     my ($self, $axis) = @_;
     
@@ -1363,6 +1382,7 @@ sub mirror {
     $self->update;
 }
 
+# XXX: not done
 sub changescale {
     my ($self, $axis, $tosize) = @_;
     
@@ -1437,6 +1457,7 @@ sub changescale {
     $self->update;
 }
 
+# XXX: not done
 sub arrange {
     my ($self) = @_;
     
@@ -1454,6 +1475,7 @@ sub arrange {
     $self->update(0);
 }
 
+# XXX: not done
 sub split_object {
     my $self = shift;
     
@@ -1485,6 +1507,7 @@ sub split_object {
     }
 }
 
+# XXX: not done
 # Trigger $self->async_apply_config() after 500ms.
 # The call is delayed to avoid restarting the background processing during typing into an edit field.
 sub schedule_background_process {
@@ -1492,6 +1515,7 @@ sub schedule_background_process {
     $self->{apply_config_timer}->Start(0.5 * 1000, 1);  # 1 = one shot, every half a second.
 }
 
+# XXX: not done
 # Executed asynchronously by a timer every PROCESS_DELAY (0.5 second).
 # The timer is started by schedule_background_process(), 
 sub async_apply_config {
@@ -1526,6 +1550,7 @@ sub async_apply_config {
     }
 }
 
+# XXX: not done
 # Background processing is started either by the "Slice now" button, by the "Export G-code button" or by async_apply_config().
 sub start_background_process {
     my ($self) = @_;
@@ -1546,6 +1571,7 @@ sub start_background_process {
     $self->{background_slicing_process}->start;
 }
 
+# XXX: not done
 # Stop the background processing
 sub stop_background_process {
     my ($self) = @_;
@@ -1554,6 +1580,7 @@ sub stop_background_process {
 #    $self->{preview3D}->reload_print if $self->{preview3D};
 }
 
+# XXX: not done
 # Called by the "Slice now" button, which is visible only if the background processing is disabled.
 sub reslice {
     # explicitly cancel a previous thread and start a new one.
@@ -1574,6 +1601,7 @@ sub reslice {
     }
 }
 
+# XXX: VK: done
 sub export_gcode {
     my ($self, $output_file) = @_;
     
@@ -1654,6 +1682,7 @@ sub export_gcode {
     return $self->{export_gcode_output_file};
 }
 
+# XXX: not done
 # This message should be called by the background process synchronously.
 sub on_update_print_preview {
     my ($self) = @_;
@@ -1666,6 +1695,7 @@ sub on_update_print_preview {
     Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1);
 }
 
+# XXX: not done
 # This gets called also if we have no threads.
 sub on_progress_event {
     my ($self, $percent, $message) = @_;
@@ -1676,6 +1706,7 @@ sub on_progress_event {
     $self->statusbar->SetStatusText("$message...");
 }
 
+# XXX: not done
 # Called when the G-code export finishes, either successfully or with an error.
 # This gets called also if we don't have threads.
 sub on_process_completed {
@@ -1743,6 +1774,7 @@ sub on_process_completed {
 #    $self->{preview3D}->reload_print if $self->{preview3D};
 }
 
+# XXX: partially done in the Sidebar
 # Fill in the "Sliced info" box with the result of the G-code generator.
 sub print_info_box_show {
     my ($self, $show) = @_;
@@ -1819,6 +1851,7 @@ sub print_info_box_show {
     $panel->Refresh;
 }
 
+# XXX: not done - to be removed
 sub do_print {
     my ($self) = @_;
     
@@ -1832,6 +1865,7 @@ sub do_print {
     $printer_panel->load_print_job($self->{print_file}, $filament_stats);
 }
 
+# XXX: VK: done
 sub export_stl {
     my ($self) = @_;
     return if !@{$self->{objects}};
@@ -1842,6 +1876,7 @@ sub export_stl {
     $self->statusbar->SetStatusText(L("STL file exported to ").$output_file);
 }
 
+# XXX: not done
 sub reload_from_disk {
     my ($self) = @_;
     
@@ -1873,6 +1908,7 @@ sub reload_from_disk {
     $self->remove($obj_idx);
 }
 
+# XXX: VK: done
 sub export_object_stl {
     my ($self) = @_;
     my ($obj_idx, $object) = $self->selected_object;
@@ -1884,6 +1920,7 @@ sub export_object_stl {
     $self->statusbar->SetStatusText(L("STL file exported to ").$output_file);
 }
 
+# XXX: not done
 sub fix_through_netfabb {
     my ($self) = @_;
     my ($obj_idx, $object) = $self->selected_object;
@@ -1912,6 +1949,7 @@ sub fix_through_netfabb {
     $self->remove($obj_idx);
 }
 
+# XXX: VK: done
 sub export_amf {
     my ($self) = @_;
     return if !@{$self->{objects}};
@@ -1928,6 +1966,7 @@ sub export_amf {
     }
 }
 
+# XXX: VK: done
 sub export_3mf {
     my ($self) = @_;
     return if !@{$self->{objects}};
@@ -1944,6 +1983,7 @@ sub export_3mf {
     }
 }
 
+# XXX: VK: done
 # Ask user to select an output file for a given file format (STl, AMF, 3MF).
 # Propose a default file name based on the 'output_filename_format' configuration value.
 sub _get_export_file {
@@ -1993,6 +2033,7 @@ sub _get_export_file {
 #    $self->{objects}[$obj_idx]->thumbnail(undef);
 #}
 
+# XXX: VK: done
 # this method gets called whenever print center is changed or the objects' bounding box changes
 # (i.e. when an object is added/removed/moved/rotated/scaled)
 sub update {
@@ -2016,6 +2057,7 @@ sub update {
     $self->Thaw;
 }
 
+# XXX: done in sidebar?
 # When a printer technology is changed, the UI needs to be updated to show/hide needed preset combo boxes.
 sub show_preset_comboboxes{
     my ($self, $showSLA) = @_; #if showSLA is oposite value to "ptFFF"
@@ -2034,6 +2076,7 @@ sub show_preset_comboboxes{
     $self->Layout;
 }
 
+# XXX: not done
 # When a number of extruders changes, the UI needs to be updated to show a single filament selection combo box per extruder.
 # Also the wxTheApp->{preset_bundle}->filament_presets needs to be resized accordingly
 # and some reasonable default has to be selected for the additional extruders.
@@ -2078,6 +2121,7 @@ sub on_extruders_change {
     $self->Layout;
 }
 
+# XXX: not done
 sub on_config_change {
     my ($self, $config) = @_;
     
@@ -2144,6 +2188,7 @@ sub on_config_change {
     $self->schedule_background_process;
 }
 
+# XXX: not done
 sub item_changed_selection {
     my ($self, $obj_idx) = @_;
 
@@ -2159,6 +2204,7 @@ sub item_changed_selection {
     }
 }
 
+# XXX: VK: done
 sub collect_selections {
     my ($self) = @_;
     my $selections = [];
@@ -2168,6 +2214,7 @@ sub collect_selections {
     return $selections;
 }
 
+# XXX: not done
 # Called when clicked on the filament preset combo box.
 # When clicked on the icon, show the color picker.
 sub filament_color_box_lmouse_down
@@ -2221,6 +2268,7 @@ sub filament_color_box_lmouse_down
 #	}
 #}
 
+# XXX: not done
 sub changed_object_settings {
     my ($self, $obj_idx, $parts_changed, $part_settings_changed) = @_;
     
@@ -2246,6 +2294,7 @@ sub changed_object_settings {
     }
 }
 
+# XXX: VK: done
 # Called to update various buttons depending on whether there are any objects or
 # whether background processing (export of a G-code, sending to Octoprint, forced background re-slicing) is active.
 sub object_list_changed {
@@ -2276,6 +2325,7 @@ sub object_list_changed {
         for grep $self->{"btn_$_"}, qw(reslice export_gcode print send_gcode);
 }
 
+# XXX: VK: WIP
 # Selection of an active 3D object changed.
 sub selection_changed {
     my ($self) = @_;
@@ -2393,6 +2443,7 @@ sub selection_changed {
     $self->{right_panel}->Thaw;
 }
 
+# XXX: VK: done
 sub select_object {
     my ($self, $obj_idx, $child) = @_;
 
@@ -2413,6 +2464,7 @@ sub select_object {
     $self->selection_changed(1);
 }
 
+# XXX: not done
 sub select_object_from_cpp {
     my ($self, $obj_idx, $vol_idx) = @_;
     
@@ -2457,16 +2509,19 @@ sub select_object_from_cpp {
     $self->selection_changed(1);
 }
 
+# XXX: VK: done
 sub selected_object {
     my ($self) = @_;
     my $obj_idx = first { $self->{objects}[$_]->selected } 0..$#{ $self->{objects} };
     return defined $obj_idx ? ($obj_idx, $self->{objects}[$obj_idx]) : undef;
 }
 
+# XXX: VK: done
 sub statusbar {
     return $_[0]->GetFrame->{statusbar};
 }
 
+# XXX: not done, to be removed (?)
 sub object_menu {
     my ($self) = @_;
     
@@ -2577,6 +2632,7 @@ sub object_menu {
     return $menu;
 }
 
+# XXX: not done
 # Set a camera direction, zoom to all objects.
 sub select_view {
     my ($self, $direction) = @_;
@@ -2594,6 +2650,8 @@ sub select_view {
     }
 }
 
+
+# XXX: VK: done, in PlaterDropTarget
 package Slic3r::GUI::Plater::DropTarget;
 use Wx::DND;
 use base 'Wx::FileDropTarget';
diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp
index ee66fd293..90817fadc 100644
--- a/src/libslic3r/Format/3mf.cpp
+++ b/src/libslic3r/Format/3mf.cpp
@@ -1350,7 +1350,7 @@ namespace Slic3r {
             return false;
         }
 
-        m_curr_config.volume_id = object->second.volumes.size();
+        m_curr_config.volume_id = (int)object->second.volumes.size();
 
         unsigned int first_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, FIRST_TRIANGLE_ID_ATTR);
         unsigned int last_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, LAST_TRIANGLE_ID_ATTR);
@@ -1408,7 +1408,7 @@ namespace Slic3r {
             return false;
         }
 
-        unsigned int geo_tri_count = geometry.triangles.size() / 3;
+        unsigned int geo_tri_count = (unsigned int)geometry.triangles.size() / 3;
 
         for (const ObjectMetadata::VolumeMetadata& volume_data : volumes)
         {
@@ -1429,7 +1429,7 @@ namespace Slic3r {
 
             unsigned int src_start_id = volume_data.first_triangle_id * 3;
 
-            for (size_t i = 0; i < triangles_count; ++i)
+            for (unsigned int i = 0; i < triangles_count; ++i)
             {
                 unsigned int ii = i * 3;
                 stl_facet& facet = stl.facet_start[i];
diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp
index 68c747cda..96befdabf 100644
--- a/src/libslic3r/Format/AMF.cpp
+++ b/src/libslic3r/Format/AMF.cpp
@@ -911,7 +911,7 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c
             if (volume->is_modifier())
                 stream << "        <metadata type=\"slic3r.modifier\">1</metadata>\n";
             stream << "        <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n";
-            for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) {
+            for (int i = 0; i < (int)volume->mesh.stl.stats.number_of_facets; ++i) {
                 stream << "        <triangle>\n";
                 for (int j = 0; j < 3; ++j)
                 stream << "          <v" << j + 1 << ">" << volume->mesh.stl.v_indices[i].vertex[j] + vertices_offset << "</v" << j + 1 << ">\n";
diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp
index d86016361..b68726a37 100644
--- a/src/libslic3r/Format/PRUS.cpp
+++ b/src/libslic3r/Format/PRUS.cpp
@@ -37,7 +37,7 @@ static_assert(sizeof(StlHeader) == 84, "StlHeader size not correct");
 class LineReader
 {
 public:
-    LineReader(std::vector<char> &data) : m_buffer(data), m_pos(0), m_len(data.size()) {}
+    LineReader(std::vector<char> &data) : m_buffer(data), m_pos(0), m_len((int)data.size()) {}
 
     const char* next_line() {
         // Skip empty lines.
@@ -154,7 +154,7 @@ static void extract_model_from_archive(
 #if ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM
             instance_offset = Vec3d((double)(position[0] - zero[0]), (double)(position[1] - zero[1]), (double)(position[2] - zero[2]));
             // CHECK_ME -> Is the following correct ?
-            trafo[2][3] = position[2] / instance_scaling_factor(2);
+            trafo[2][3] = position[2] / (float)instance_scaling_factor(2);
 #else
             instance_offset(0) = position[0] - zero[0];
             instance_offset(1) = position[1] - zero[1];
@@ -291,8 +291,8 @@ static void extract_model_from_archive(
         if (! facets.empty() && solid_name.empty()) {
             stl_file &stl = mesh.stl;
             stl.stats.type = inmemory;
-            stl.stats.number_of_facets = facets.size();
-            stl.stats.original_num_facets = facets.size();
+            stl.stats.number_of_facets = (uint32_t)facets.size();
+            stl.stats.original_num_facets = (int)facets.size();
             stl_allocate(&stl);
             memcpy((void*)stl.facet_start, facets.data(), facets.size() * 50);
             stl_get_size(&stl);
diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp
index 51d5b1a06..e3cd67e7c 100644
--- a/src/libslic3r/GCode/Analyzer.cpp
+++ b/src/libslic3r/GCode/Analyzer.cpp
@@ -698,7 +698,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
 
             // update current values
             data = move.data;
-            z = move.start_position.z();
+            z = (float)move.start_position.z();
             volumetric_rate = move.data.feedrate * (float)move.data.mm3_per_mm;
             height_range.update_from(move.data.height);
             width_range.update_from(move.data.width);
diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp
index f97265ee3..4bfd5d63f 100644
--- a/src/libslic3r/GCodeTimeEstimator.cpp
+++ b/src/libslic3r/GCodeTimeEstimator.cpp
@@ -484,14 +484,14 @@ namespace Slic3r {
     {
         _state.filament_load_times.clear();
         for (double t : filament_load_times)
-            _state.filament_load_times.push_back(t);
+            _state.filament_load_times.push_back((float)t);
     }
 
     void GCodeTimeEstimator::set_filament_unload_times(const std::vector<double> &filament_unload_times)
     {
         _state.filament_unload_times.clear();
         for (double t : filament_unload_times)
-            _state.filament_unload_times.push_back(t);
+            _state.filament_unload_times.push_back((float)t);
     }
 
     float GCodeTimeEstimator::get_filament_load_time(unsigned int id_extruder)
@@ -731,7 +731,7 @@ namespace Slic3r {
 #endif // ENABLE_MOVE_STATS
         }
 
-        _last_st_synchronized_block_id = _blocks.size() - 1;
+        _last_st_synchronized_block_id = (int)_blocks.size() - 1;
         // The additional time has been consumed (added to the total time), reset it to zero.
         set_additional_time(0.);
     }
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index aad94ee3c..88e4c551a 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -707,16 +707,23 @@ void ModelObject::center_around_origin()
         if (v->is_model_part())
 			bb.merge(v->mesh.bounding_box());
     
+#if ENABLE_EXTENDED_SELECTION
+    // Shift is the vector from the center of the bounding box to the origin
+    Vec3d shift = -bb.center();
+#else
     // Shift is the vector from the center of the bottom face of the bounding box to the origin
     Vec3d shift = -bb.center();
     shift(2) = -bb.min(2);
+#endif // ENABLE_EXTENDED_SELECTION
 
     this->translate(shift);
     this->origin_translation += shift;
 
 #if ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM
+#if !ENABLE_EXTENDED_SELECTION
     // set z to zero, translation in z has already been done within the mesh
     shift(2) = 0.0;
+#endif // !ENABLE_EXTENDED_SELECTION
 #endif // ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM
 
     if (!this->instances.empty()) {
diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp
index 87cdc3777..100178108 100644
--- a/src/libslic3r/Point.hpp
+++ b/src/libslic3r/Point.hpp
@@ -56,10 +56,10 @@ inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); }
 inline Vec2f   to_2d(const Vec3f   &pt3) { return Vec2f  (pt3(0), pt3(1)); }
 inline Vec2d   to_2d(const Vec3d   &pt3) { return Vec2d  (pt3(0), pt3(1)); }
 
-// inline Vec3crd to_3d(const Vec2crd &pt2, coord_t z) { return Vec3crd(pt2(0), pt2(1), z); }
-// inline Vec3i64 to_3d(const Vec2i64 &pt2, int64_t z) { return Vec3i64(pt2(0), pt2(1), z); }
-// inline Vec3f   to_3d(const Vec2f   &pt2, float z)   { return Vec3f  (pt2(0), pt2(1), z); }
-// inline Vec3d   to_3d(const Vec2d   &pt2, double z)  { return Vec3d  (pt2(0), pt2(1), z); }
+inline Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); }
+inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); }
+inline Vec3i64 to_3d(const Vec2i64 &v, float z) { return Vec3i64(v(0), v(1), z); }
+inline Vec3crd to_3d(const Vec3crd &p, coord_t z) { return Vec3crd(p(0), p(1), z); }
 
 inline Vec2d   unscale(coord_t x, coord_t y) { return Vec2d(unscale<double>(x), unscale<double>(y)); }
 inline Vec2d   unscale(const Vec2crd &pt) { return Vec2d(unscale<double>(pt(0)), unscale<double>(pt(1))); }
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
index 7f88110bc..a646ab8fd 100644
--- a/src/libslic3r/Print.hpp
+++ b/src/libslic3r/Print.hpp
@@ -419,6 +419,7 @@ public:
     const PrintObject*          get_object(int idx) const { return m_objects[idx]; }
     const PrintRegionPtrs&      regions() const { return m_regions; }
     const PlaceholderParser&    placeholder_parser() const { return m_placeholder_parser; }
+    PlaceholderParser&          placeholder_parser() { return m_placeholder_parser; }
 
     // Returns extruder this eec should be printed with, according to PrintRegion config:
     static int get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion &region);
diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp
index 9b9067af3..77802d811 100644
--- a/src/libslic3r/Technologies.hpp
+++ b/src/libslic3r/Technologies.hpp
@@ -5,13 +5,15 @@
 #define ENABLE_1_42_0 1
 
 // Add z coordinate to model instances' offset
-// Add x and y rotation components to model instances' offset
+// Add x and y rotation components to model instances' rotation
 // Add scaling factors for all the three axes to model instances
 #define ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM (1 && ENABLE_1_42_0)
 // Add double click on gizmo grabbers to reset transformation components to their default value
 #define ENABLE_GIZMOS_RESET (1 && ENABLE_1_42_0)
 // Uses a unique opengl context
 #define ENABLE_USE_UNIQUE_GLCONTEXT (1 && ENABLE_1_42_0)
+// New selections
+#define ENABLE_EXTENDED_SELECTION (0 && ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM)
 
 #endif // _technologies_h_
 
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 54c90c20e..4e9edfbdc 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -39,6 +39,8 @@ add_library(libslic3r_gui STATIC
     ${LIBDIR}/slic3r/GUI/GUI_PreviewIface.hpp
     ${LIBDIR}/slic3r/GUI/GUI_App.cpp
     ${LIBDIR}/slic3r/GUI/GUI_App.hpp
+    ${LIBDIR}/slic3r/GUI/GUI_Utils.cpp
+    ${LIBDIR}/slic3r/GUI/GUI_Utils.hpp
     ${LIBDIR}/slic3r/GUI/MainFrame.cpp
     ${LIBDIR}/slic3r/GUI/MainFrame.hpp
     ${LIBDIR}/slic3r/GUI/Plater.cpp
diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp
index 44a068303..2ae900e00 100644
--- a/src/slic3r/GUI/3DScene.cpp
+++ b/src/slic3r/GUI/3DScene.cpp
@@ -36,7 +36,7 @@ void GLIndexedVertexArray::load_mesh_flat_shading(const TriangleMesh &mesh)
 
     this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count());
     
-    for (int i = 0; i < mesh.stl.stats.number_of_facets; ++ i) {
+    for (int i = 0; i < (int)mesh.stl.stats.number_of_facets; ++i) {
         const stl_facet &facet = mesh.stl.facet_start[i];
         for (int j = 0; j < 3; ++ j)
             this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2));
@@ -52,7 +52,7 @@ void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh &mesh)
     this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count());
 
     unsigned int vertices_count = 0;
-    for (int i = 0; i < mesh.stl.stats.number_of_facets; ++i) {
+    for (int i = 0; i < (int)mesh.stl.stats.number_of_facets; ++i) {
         const stl_facet &facet = mesh.stl.facet_start[i];
         for (int j = 0; j < 3; ++j)
             this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2));
@@ -209,8 +209,10 @@ GLVolume::GLVolume(float r, float g, float b, float a)
     , m_transformed_convex_hull_bounding_box_dirty(true)
     , m_convex_hull(nullptr)
     , composite_id(-1)
+#if !ENABLE_EXTENDED_SELECTION
     , select_group_id(-1)
     , drag_group_id(-1)
+#endif // !ENABLE_EXTENDED_SELECTION
     , extruder_id(0)
     , selected(false)
     , is_active(true)
@@ -242,7 +244,7 @@ void GLVolume::set_render_color(float r, float g, float b, float a)
 void GLVolume::set_render_color(const float* rgba, unsigned int size)
 {
     size = std::min((unsigned int)4, size);
-    for (int i = 0; i < size; ++i)
+    for (unsigned int i = 0; i < size; ++i)
     {
         render_color[i] = rgba[i];
     }
@@ -311,6 +313,13 @@ void GLVolume::set_offset(const Vec3d& offset)
 }
 
 #if ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM
+#if ENABLE_EXTENDED_SELECTION
+const Vec3d& GLVolume::get_scaling_factor() const
+{
+    return m_scaling_factor;
+}
+#endif // ENABLE_EXTENDED_SELECTION
+
 void GLVolume::set_scaling_factor(const Vec3d& scaling_factor)
 {
     if (m_scaling_factor != scaling_factor)
@@ -339,6 +348,7 @@ void GLVolume::set_convex_hull(const TriangleMesh& convex_hull)
     m_convex_hull = &convex_hull;
 }
 
+#if !ENABLE_EXTENDED_SELECTION
 void GLVolume::set_select_group_id(const std::string& select_by)
 {
     if (select_by == "object")
@@ -356,6 +366,7 @@ void GLVolume::set_drag_group_id(const std::string& drag_by)
     else if (drag_by == "instance")
         drag_group_id = object_idx() * 1000 + instance_idx();
 }
+#endif // !ENABLE_EXTENDED_SELECTION
 
 const Transform3f& GLVolume::world_matrix() const
 {
@@ -682,6 +693,14 @@ void GLVolume::generate_layer_height_texture(const PrintObject *print_object, bo
 #define LAYER_HEIGHT_TEXTURE_WIDTH  1024
 #define LAYER_HEIGHT_TEXTURE_HEIGHT 1024
 
+#if ENABLE_EXTENDED_SELECTION
+std::vector<int> GLVolumeCollection::load_object(
+    const ModelObject       *model_object,
+    int                      obj_idx,
+    const std::vector<int>  &instance_idxs,
+    const std::string       &color_by,
+    bool                     use_VBOs)
+#else
 std::vector<int> GLVolumeCollection::load_object(
     const ModelObject       *model_object, 
     int                      obj_idx,
@@ -690,6 +709,7 @@ std::vector<int> GLVolumeCollection::load_object(
     const std::string       &select_by,
     const std::string       &drag_by,
     bool                     use_VBOs)
+#endif // ENABLE_EXTENDED_SELECTION
 {
     static float colors[4][4] = {
         { 1.0f, 1.0f, 0.0f, 1.f }, 
@@ -740,8 +760,10 @@ std::vector<int> GLVolumeCollection::load_object(
             v.bounding_box = v.indexed_vertex_array.bounding_box();
             v.indexed_vertex_array.finalize_geometry(use_VBOs);
             v.composite_id = obj_idx * 1000000 + volume_idx * 1000 + instance_idx;
+#if !ENABLE_EXTENDED_SELECTION
             v.set_select_group_id(select_by);
             v.set_drag_group_id(drag_by);
+#endif // !ENABLE_EXTENDED_SELECTION
             if (model_volume->is_model_part())
             {
                 v.set_convex_hull(model_volume->get_convex_hull());
@@ -789,9 +811,9 @@ int GLVolumeCollection::load_wipe_tower_preview(
         // edge has y=0 and centerline of the back edge has y=depth:
         Pointf3s points;
         std::vector<Vec3crd> facets;
-        float out_points_idx[][3] = {{0, -depth, 0}, {0, 0, 0}, {38.453, 0, 0}, {61.547, 0, 0}, {100, 0, 0}, {100, -depth, 0}, {55.7735, -10, 0}, {44.2265, 10, 0},
-                                     {38.453, 0, 1}, {0, 0, 1}, {0, -depth, 1}, {100, -depth, 1}, {100, 0, 1}, {61.547, 0, 1}, {55.7735, -10, 1}, {44.2265, 10, 1}};
-        int out_facets_idx[][3] = {{0, 1, 2}, {3, 4, 5}, {6, 5, 0}, {3, 5, 6}, {6, 2, 7}, {6, 0, 2}, {8, 9, 10}, {11, 12, 13}, {10, 11, 14}, {14, 11, 13}, {15, 8, 14},
+        float out_points_idx[][3] = { { 0, -depth, 0 }, { 0, 0, 0 }, { 38.453f, 0, 0 }, { 61.547f, 0, 0 }, { 100.0f, 0, 0 }, { 100.0f, -depth, 0 }, { 55.7735f, -10.0f, 0 }, { 44.2265f, 10.0f, 0 },
+        { 38.453f, 0, 1 }, { 0, 0, 1 }, { 0, -depth, 1 }, { 100.0f, -depth, 1 }, { 100.0f, 0, 1 }, { 61.547f, 0, 1 }, { 55.7735f, -10.0f, 1 }, { 44.2265f, 10.0f, 1 } };
+        int out_facets_idx[][3] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 5, 0 }, { 3, 5, 6 }, { 6, 2, 7 }, { 6, 0, 2 }, { 8, 9, 10 }, { 11, 12, 13 }, { 10, 11, 14 }, { 14, 11, 13 }, { 15, 8, 14 },
                                    {8, 10, 14}, {3, 12, 4}, {3, 13, 12}, {6, 13, 3}, {6, 14, 13}, {7, 14, 6}, {7, 15, 14}, {2, 15, 7}, {2, 8, 15}, {1, 8, 2}, {1, 9, 8},
                                    {0, 9, 1}, {0, 10, 9}, {5, 10, 0}, {5, 11, 10}, {4, 11, 5}, {4, 12, 11}};
         for (int i=0;i<16;++i)
@@ -834,8 +856,10 @@ int GLVolumeCollection::load_wipe_tower_preview(
     v.bounding_box = v.indexed_vertex_array.bounding_box();
     v.indexed_vertex_array.finalize_geometry(use_VBOs);
     v.composite_id = obj_idx * 1000000;
+#if !ENABLE_EXTENDED_SELECTION
     v.select_group_id = obj_idx * 1000000;
     v.drag_group_id = obj_idx * 1000;
+#endif // !ENABLE_EXTENDED_SELECTION
     v.is_wipe_tower = true;
     v.shader_outside_printer_detection_enabled = ! size_unknown;
     return int(this->volumes.size() - 1);
@@ -1017,7 +1041,7 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
             continue;
 
         int extruder_id = volume->extruder_id - 1;
-        if ((extruder_id < 0) || ((unsigned int)colors.size() <= extruder_id))
+        if ((extruder_id < 0) || ((int)colors.size() <= extruder_id))
             extruder_id = 0;
 
         const Color& color = colors[extruder_id];
@@ -1031,6 +1055,7 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
     }
 }
 
+#if !ENABLE_EXTENDED_SELECTION
 void GLVolumeCollection::set_select_by(const std::string& select_by)
 {
     for (GLVolume *vol : this->volumes)
@@ -1048,6 +1073,7 @@ void GLVolumeCollection::set_drag_by(const std::string& drag_by)
             vol->set_drag_group_id(drag_by);
     }
 }
+#endif // !ENABLE_EXTENDED_SELECTION
 
 std::vector<double> GLVolumeCollection::get_current_print_zs(bool active_only) const
 {
@@ -1820,6 +1846,7 @@ void _3DScene::reset_volumes(wxGLCanvas* canvas)
     s_canvas_mgr.reset_volumes(canvas);
 }
 
+#if !ENABLE_EXTENDED_SELECTION
 void _3DScene::deselect_volumes(wxGLCanvas* canvas)
 {
     s_canvas_mgr.deselect_volumes(canvas);
@@ -1834,6 +1861,7 @@ void _3DScene::update_volumes_selection(wxGLCanvas* canvas, const std::vector<in
 {
     s_canvas_mgr.update_volumes_selection(canvas, selections);
 }
+#endif // !ENABLE_EXTENDED_SELECTION
 
 int _3DScene::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config)
 {
@@ -1850,10 +1878,12 @@ bool _3DScene::move_volume_down(wxGLCanvas* canvas, unsigned int id)
     return s_canvas_mgr.move_volume_down(canvas, id);
 }
 
+#if !ENABLE_EXTENDED_SELECTION
 void _3DScene::set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections)
 {
     s_canvas_mgr.set_objects_selections(canvas, selections);
 }
+#endif // !ENABLE_EXTENDED_SELECTION
 
 void _3DScene::set_config(wxGLCanvas* canvas, DynamicPrintConfig* config)
 {
@@ -1900,6 +1930,7 @@ void _3DScene::set_color_by(wxGLCanvas* canvas, const std::string& value)
     s_canvas_mgr.set_color_by(canvas, value);
 }
 
+#if !ENABLE_EXTENDED_SELECTION
 void _3DScene::set_select_by(wxGLCanvas* canvas, const std::string& value)
 {
     s_canvas_mgr.set_select_by(canvas, value);
@@ -1914,6 +1945,7 @@ std::string _3DScene::get_select_by(wxGLCanvas* canvas)
 {
     return s_canvas_mgr.get_select_by(canvas);
 }
+#endif // !ENABLE_EXTENDED_SELECTION
 
 bool _3DScene::is_layers_editing_enabled(wxGLCanvas* canvas)
 {
diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp
index 971083ab6..09f425ba0 100644
--- a/src/slic3r/GUI/3DScene.hpp
+++ b/src/slic3r/GUI/3DScene.hpp
@@ -292,10 +292,12 @@ public:
     float               render_color[4];
     // An ID containing the object ID, volume ID and instance ID.
     int                 composite_id;
+#if !ENABLE_EXTENDED_SELECTION
     // An ID for group selection. It may be the same for all meshes of all object instances, or for just a single object instance.
     int                 select_group_id;
     // An ID for group dragging. It may be the same for all meshes of all object instances, or for just a single object instance.
     int                 drag_group_id;
+#endif // !ENABLE_EXTENDED_SELECTION
     // An ID containing the extruder ID (used to select color).
     int                 extruder_id;
     // Is this object selected?
@@ -338,6 +340,9 @@ public:
     const Vec3d& get_rotation() const;
     void set_rotation(const Vec3d& rotation);
 
+#if ENABLE_EXTENDED_SELECTION
+    const Vec3d& get_scaling_factor() const;
+#endif // ENABLE_EXTENDED_SELECTION
     void set_scaling_factor(const Vec3d& scaling_factor);
 #else
     double get_rotation() const;
@@ -351,8 +356,10 @@ public:
 
     void set_convex_hull(const TriangleMesh& convex_hull);
 
+#if !ENABLE_EXTENDED_SELECTION
     void set_select_group_id(const std::string& select_by);
     void set_drag_group_id(const std::string& drag_by);
+#endif // !ENABLE_EXTENDED_SELECTION
 
     int                 object_idx() const { return this->composite_id / 1000000; }
     int                 volume_idx() const { return (this->composite_id / 1000) % 1000; }
@@ -410,6 +417,10 @@ public:
     void reset_layer_height_texture_data() { layer_height_texture_data.reset(); }
 };
 
+#if ENABLE_EXTENDED_SELECTION
+typedef std::vector<GLVolume*> GLVolumePtrs;
+#endif // ENABLE_EXTENDED_SELECTION
+
 class GLVolumeCollection
 {
     // min and max vertex of the print box volume
@@ -417,11 +428,23 @@ class GLVolumeCollection
     float print_box_max[3];
 
 public:
+#if ENABLE_EXTENDED_SELECTION
+    GLVolumePtrs volumes;
+#else
     std::vector<GLVolume*> volumes;
-    
+#endif // ENABLE_EXTENDED_SELECTION
+
     GLVolumeCollection() {};
     ~GLVolumeCollection() { clear(); };
 
+#if ENABLE_EXTENDED_SELECTION
+    std::vector<int> load_object(
+        const ModelObject       *model_object,
+        int                      obj_idx,
+        const std::vector<int>  &instance_idxs,
+        const std::string       &color_by,
+        bool                     use_VBOs);
+#else
     std::vector<int> load_object(
         const ModelObject       *model_object,
         int                      obj_idx,
@@ -430,6 +453,7 @@ public:
         const std::string       &select_by,
         const std::string       &drag_by,
         bool                     use_VBOs);
+#endif // ENABLE_EXTENDED_SELECTION
 
     int load_wipe_tower_preview(
         int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width);
@@ -463,8 +487,10 @@ public:
 
     void update_colors_by_extruder(const DynamicPrintConfig* config);
 
+#if !ENABLE_EXTENDED_SELECTION
     void set_select_by(const std::string& select_by);
     void set_drag_by(const std::string& drag_by);
+#endif // !ENABLE_EXTENDED_SELECTION
 
     // Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection
     std::vector<double> get_current_print_zs(bool active_only) const;
@@ -493,14 +519,18 @@ public:
 
     static unsigned int get_volumes_count(wxGLCanvas* canvas);
     static void reset_volumes(wxGLCanvas* canvas);
+#if !ENABLE_EXTENDED_SELECTION
     static void deselect_volumes(wxGLCanvas* canvas);
     static void select_volume(wxGLCanvas* canvas, unsigned int id);
     static void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections);
+#endif // !ENABLE_EXTENDED_SELECTION
     static int check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config);
     static bool move_volume_up(wxGLCanvas* canvas, unsigned int id);
     static bool move_volume_down(wxGLCanvas* canvas, unsigned int id);
 
+#if !ENABLE_EXTENDED_SELECTION
     static void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections);
+#endif // !ENABLE_EXTENDED_SELECTION
 
     static void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config);
     static void set_print(wxGLCanvas* canvas, Print* print);
@@ -516,10 +546,12 @@ public:
     static void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons);
 
     static void set_color_by(wxGLCanvas* canvas, const std::string& value);
+#if !ENABLE_EXTENDED_SELECTION
     static void set_select_by(wxGLCanvas* canvas, const std::string& value);
     static void set_drag_by(wxGLCanvas* canvas, const std::string& value);
 
     static std::string get_select_by(wxGLCanvas* canvas);
+#endif // !ENABLE_EXTENDED_SELECTION
 
     static bool is_layers_editing_enabled(wxGLCanvas* canvas);
     static bool is_layers_editing_allowed(wxGLCanvas* canvas);
diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp
index 7c019599a..407da7ae3 100644
--- a/src/slic3r/GUI/BedShapeDialog.cpp
+++ b/src/slic3r/GUI/BedShapeDialog.cpp
@@ -292,7 +292,7 @@ void BedShapePanel::update_shape()
 void BedShapePanel::load_stl()
 {
 	auto dialog = new wxFileDialog(this, _(L("Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):")), "", "",
-		MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+		file_wildcards[FT_MODEL], wxFD_OPEN | wxFD_FILE_MUST_EXIST);
 	if (dialog->ShowModal() != wxID_OK) {
 		dialog->Destroy();
 		return;
diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp
index 02fc9b8b6..5dbc65877 100644
--- a/src/slic3r/GUI/Field.cpp
+++ b/src/slic3r/GUI/Field.cpp
@@ -611,8 +611,9 @@ void ColourPicker::BUILD()
 	if (m_opt.width >= 0) size.SetWidth(m_opt.width);
 
 	wxString clr(static_cast<const ConfigOptionStrings*>(m_opt.default_value)->get_at(m_opt_idx));
+	// FIXME: verify clr is valid, otherwise this causes an assert
 	auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size);
-		
+
 	// 	// recast as a wxWindow to fit the calling convention
 	window = dynamic_cast<wxWindow*>(temp);
 
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 7eccf3af7..116d8cb3b 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -414,7 +414,7 @@ void GLCanvas3D::Bed::_calc_gridlines(const ExPolygon& poly, const BoundingBox&
     }
 
     // clip with a slightly grown expolygon because our lines lay on the contours and may get erroneously clipped
-    Lines gridlines = to_lines(intersection_pl(axes_lines, offset(poly, SCALED_EPSILON)));
+    Lines gridlines = to_lines(intersection_pl(axes_lines, offset(poly, (float)SCALED_EPSILON)));
 
     // append bed contours
     Lines contour_lines = to_lines(poly);
@@ -644,7 +644,7 @@ bool GLCanvas3D::CuttingPlane::set(float z, const ExPolygons& polygons)
     m_z = z;
 
     // grow slices in order to display them better
-    ExPolygons expolygons = offset_ex(polygons, scale_(0.1));
+    ExPolygons expolygons = offset_ex(polygons, (float)scale_(0.1));
     Lines lines = to_lines(expolygons);
     return m_lines.set_from_lines(lines, m_z);
 }
@@ -1089,10 +1089,14 @@ const Vec3d GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX)
 GLCanvas3D::Mouse::Drag::Drag()
     : start_position_2D(Invalid_2D_Point)
     , start_position_3D(Invalid_3D_Point)
+#if !ENABLE_EXTENDED_SELECTION
     , volume_center_offset(0, 0, 0)
     , move_with_shift(false)
+#endif // !ENABLE_EXTENDED_SELECTION
     , move_volume_idx(-1)
+#if !ENABLE_EXTENDED_SELECTION
     , gizmo_volume_idx(-1)
+#endif // !ENABLE_EXTENDED_SELECTION
 {
 }
 
@@ -1125,6 +1129,421 @@ bool GLCanvas3D::Mouse::is_start_position_3D_defined() const
     return (drag.start_position_3D != Drag::Invalid_3D_Point);
 }
 
+#if ENABLE_EXTENDED_SELECTION
+GLCanvas3D::Selection::VolumeCache::VolumeCache()
+    : position(Vec3d::Zero())
+    , rotation(Vec3d::Zero())
+    , scaling_factor(Vec3d::Ones())
+{
+    m_rotation_matrix = Transform3d::Identity();
+}
+
+GLCanvas3D::Selection::VolumeCache::VolumeCache(const Vec3d& position, const Vec3d& rotation, const Vec3d& scaling_factor)
+    : position(position)
+    , rotation(rotation)
+    , scaling_factor(scaling_factor)
+{
+    m_rotation_matrix = Transform3d::Identity();
+    m_rotation_matrix.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ()));
+    m_rotation_matrix.rotate(Eigen::AngleAxisd(rotation(1), Vec3d::UnitY()));
+    m_rotation_matrix.rotate(Eigen::AngleAxisd(rotation(0), Vec3d::UnitX()));
+}
+
+GLCanvas3D::Selection::Selection()
+    : m_volumes(nullptr)
+    , m_model(nullptr)
+    , m_mode(Instance)
+    , m_valid(false)
+    , m_bounding_box_dirty(true)
+{
+}
+
+void GLCanvas3D::Selection::set_volumes(GLVolumePtrs* volumes)
+{
+    m_volumes = volumes;
+    update_valid();
+}
+
+void GLCanvas3D::Selection::set_model(Model* model)
+{
+    m_model = model;
+    update_valid();
+}
+
+void GLCanvas3D::Selection::add(unsigned int volume_idx, bool as_single_selection)
+{
+    if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx))
+        return;
+
+    // resets the current list if needed
+    const GLVolume* volume = (*m_volumes)[volume_idx];
+    if (as_single_selection || volume->is_wipe_tower || is_wipe_tower() || volume->is_modifier || is_modifier())
+        clear();
+
+    switch (m_mode)
+    {
+    case Volume:
+    {
+        add_volume(volume_idx);
+        break;
+    }
+    case Instance:
+    {
+        add_instance(volume_idx);
+        break;
+    }
+    case Object:
+    {
+        add_object(volume_idx);
+        break;
+    }
+    }
+
+    m_bounding_box_dirty = true;
+}
+
+void GLCanvas3D::Selection::remove(unsigned int volume_idx)
+{
+    if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx))
+        return;
+
+    switch (m_mode)
+    {
+    case Volume:
+    {
+        remove_volume(volume_idx);
+        break;
+    }
+    case Instance:
+    {
+        remove_instance(volume_idx);
+        break;
+    }
+    case Object:
+    {
+        remove_object(volume_idx);
+        break;
+    }
+    }
+
+    m_bounding_box_dirty = true;
+}
+
+void GLCanvas3D::Selection::clear()
+{
+    if (!m_valid)
+        return;
+
+    for (unsigned int i : m_list)
+    {
+        (*m_volumes)[i]->selected = false;
+    }
+
+    m_list.clear();
+    m_bounding_box_dirty = true;
+}
+
+bool GLCanvas3D::Selection::is_single_full_instance(int& object_idx_out, int& instance_idx_out) const
+{
+    if (!m_valid || is_empty() || is_wipe_tower())
+    {
+        object_idx_out = -1;
+        instance_idx_out = -1;
+        return false;
+    }
+
+    const GLVolume* first = (*m_volumes)[*m_list.begin()];
+    int object_idx = first->object_idx();
+    int instance_idx = first->instance_idx();
+    unsigned int count = 0;
+
+    for (unsigned int i : m_list)
+    {
+        const GLVolume* v = (*m_volumes)[i];
+        if ((v->object_idx() != object_idx) || (v->instance_idx() != instance_idx))
+            return false;
+        else
+            ++count;
+    }
+
+    bool res = (count == (unsigned int)m_model->objects[object_idx]->volumes.size());
+    object_idx_out = res ? object_idx : -1;
+    instance_idx_out = res ? instance_idx : -1;
+    return res;
+}
+
+bool GLCanvas3D::Selection::is_single_full_object(int& object_idx_out) const
+{
+    if (!m_valid || is_empty() || is_wipe_tower())
+    {
+        object_idx_out = -1;
+        return false;
+    }
+
+    int object_idx = (*m_volumes)[*m_list.begin()]->object_idx();
+    unsigned int count = 0;
+
+    for (unsigned int i : m_list)
+    {
+        const GLVolume* v = (*m_volumes)[i];
+        if (v->object_idx() != object_idx)
+            return false;
+        else
+            ++count;
+    }
+
+    bool res = (count == (unsigned int)m_model->objects[object_idx]->volumes.size() * (unsigned int)m_model->objects[object_idx]->instances.size());
+    object_idx_out = res ? object_idx : -1;
+    return res;
+}
+
+bool GLCanvas3D::Selection::is_from_single_instance(int& object_idx_out, int& instance_idx_out) const
+{
+    if (!m_valid || is_empty() || is_wipe_tower())
+    {
+        object_idx_out = -1;
+        instance_idx_out = -1;
+        return false;
+    }
+
+    const GLVolume* first = (*m_volumes)[*m_list.begin()];
+    int object_idx = first->object_idx();
+    int instance_idx = first->instance_idx();
+
+    for (unsigned int i : m_list)
+    {
+        const GLVolume* v = (*m_volumes)[i];
+        if ((v->object_idx() != object_idx) || (v->instance_idx() != instance_idx))
+            return false;
+    }
+
+    object_idx_out = object_idx;
+    instance_idx_out = instance_idx;
+    return true;
+}
+
+bool GLCanvas3D::Selection::is_from_single_object(int& object_idx_out) const
+{
+    if (!m_valid || is_empty() || is_wipe_tower())
+    {
+        object_idx_out = -1;
+        return false;
+    }
+
+    int object_idx = (*m_volumes)[*m_list.begin()]->object_idx();
+
+    for (unsigned int i : m_list)
+    {
+        const GLVolume* v = (*m_volumes)[i];
+        if (v->object_idx() != object_idx)
+            return false;
+    }
+
+    object_idx_out = object_idx;
+    return true;
+}
+
+const GLVolume* GLCanvas3D::Selection::get_volume(unsigned int volume_idx) const
+{
+    if (!m_valid)
+        return nullptr;
+
+    return (volume_idx < (unsigned int)m_volumes->size()) ? (*m_volumes)[volume_idx] : nullptr;
+}
+
+const BoundingBoxf3& GLCanvas3D::Selection::get_bounding_box() const
+{
+    if (m_bounding_box_dirty)
+        calc_bounding_box();
+
+    return m_bounding_box;
+}
+
+void GLCanvas3D::Selection::start_dragging()
+{
+    if (!m_valid)
+        return;
+
+    set_caches();
+}
+
+void GLCanvas3D::Selection::translate(const Vec3d& displacement)
+{
+    if (!m_valid)
+        return;
+
+    for (unsigned int i : m_list)
+    {
+        (*m_volumes)[i]->set_offset(m_cache.volumes_data[i].position + displacement);
+    }
+
+    m_bounding_box_dirty = true;
+}
+
+void GLCanvas3D::Selection::render() const
+{
+    if (is_empty())
+        return;
+
+    float color[3] = { 1.0f, 1.0f, 1.0f };
+    render_bounding_box(get_bounding_box(), color);
+}
+
+void GLCanvas3D::Selection::update_valid()
+{
+    m_valid = (m_volumes != nullptr) && (m_model != nullptr);
+}
+
+void GLCanvas3D::Selection::set_caches()
+{
+    m_cache.volumes_data.clear();
+    for (unsigned int i : m_list)
+    {
+        const GLVolume* v = (*m_volumes)[i];
+        m_cache.volumes_data.emplace(i, VolumeCache(v->get_offset(), v->get_rotation(), v->get_scaling_factor()));
+    }
+    m_cache.dragging_center = get_bounding_box().center();
+}
+
+void GLCanvas3D::Selection::add_volume(unsigned int volume_idx)
+{
+    // check if the given idx is already selected
+    if (m_list.find(volume_idx) != m_list.end())
+        return;
+
+    m_list.insert(volume_idx);
+    (*m_volumes)[volume_idx]->selected = true;
+}
+
+void GLCanvas3D::Selection::add_instance(unsigned int volume_idx)
+{
+    GLVolume* volume = (*m_volumes)[volume_idx];
+    int object_idx = volume->object_idx();
+    int instance_idx = volume->instance_idx();
+
+    for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
+    {
+        GLVolume* v = (*m_volumes)[i];
+        if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx))
+            add_volume(i);
+    }
+}
+
+void GLCanvas3D::Selection::add_object(unsigned int volume_idx)
+{
+    GLVolume* volume = (*m_volumes)[volume_idx];
+    int object_idx = volume->object_idx();
+
+    for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
+    {
+        GLVolume* v = (*m_volumes)[i];
+        if (v->object_idx() == object_idx)
+            add_volume(i);
+    }
+}
+
+void GLCanvas3D::Selection::remove_volume(unsigned int volume_idx)
+{
+    IndicesList::iterator v_it = m_list.find(volume_idx);
+    if (v_it == m_list.end())
+        return;
+
+    m_list.erase(v_it);
+
+    (*m_volumes)[volume_idx]->selected = false;
+}
+
+void GLCanvas3D::Selection::remove_instance(unsigned int volume_idx)
+{
+    GLVolume* volume = (*m_volumes)[volume_idx];
+    int object_idx = volume->object_idx();
+    int instance_idx = volume->instance_idx();
+
+    for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
+    {
+        GLVolume* v = (*m_volumes)[i];
+        if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx))
+            remove_volume(i);
+    }
+}
+
+void GLCanvas3D::Selection::remove_object(unsigned int volume_idx)
+{
+    GLVolume* volume = (*m_volumes)[volume_idx];
+    int object_idx = volume->object_idx();
+
+    for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
+    {
+        GLVolume* v = (*m_volumes)[i];
+        if (v->object_idx() == object_idx)
+            remove_volume(i);
+    }
+}
+
+void GLCanvas3D::Selection::calc_bounding_box() const
+{
+    m_bounding_box = BoundingBoxf3();
+    if (m_valid)
+    {
+        for (unsigned int i : m_list)
+        {
+            m_bounding_box.merge((*m_volumes)[i]->transformed_bounding_box());
+        }
+    }
+    m_bounding_box_dirty = false;
+}
+
+void GLCanvas3D::Selection::render_bounding_box(const BoundingBoxf3& box, float* color) const
+{
+    if (color == nullptr)
+        return;
+
+    Vec3f b_min = box.min.cast<float>();
+    Vec3f b_max = box.max.cast<float>();
+    Vec3f size = 0.25f * box.size().cast<float>();
+
+    ::glEnable(GL_DEPTH_TEST);
+
+    ::glColor3fv(color);
+
+    ::glBegin(GL_LINES);
+
+    ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_min(2));
+    ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_min(2));
+    ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1), b_min(2) + size(2));
+
+    ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_min(2));
+    ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_min(2));
+    ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1), b_min(2) + size(2));
+
+    ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_min(2));
+    ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_min(2));
+    ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1), b_min(2) + size(2));
+
+    ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_min(2));
+    ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_min(2));
+    ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1), b_min(2) + size(2));
+
+    ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_max(2));
+    ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_max(2));
+    ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1), b_max(2) - size(2));
+
+    ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_max(2));
+    ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_max(2));
+    ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1), b_max(2) - size(2));
+
+    ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_max(2));
+    ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_max(2));
+    ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1), b_max(2) - size(2));
+
+    ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_max(2));
+    ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_max(2));
+    ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1), b_max(2) - size(2));
+
+    ::glEnd();
+}
+#endif // ENABLE_EXTENDED_SELECTION
+
 const float GLCanvas3D::Gizmos::OverlayTexturesScale = 0.75f;
 const float GLCanvas3D::Gizmos::OverlayOffsetX = 10.0f * OverlayTexturesScale;
 const float GLCanvas3D::Gizmos::OverlayGapY = 5.0f * OverlayTexturesScale;
@@ -1223,7 +1642,11 @@ void GLCanvas3D::Gizmos::set_enabled(bool enable)
     m_enabled = enable;
 }
 
+#if ENABLE_EXTENDED_SELECTION
+void GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const GLCanvas3D::Selection& selection)
+#else
 void GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos)
+#endif // ENABLE_EXTENDED_SELECTION
 {
     if (!m_enabled)
         return;
@@ -1240,7 +1663,12 @@ void GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Vec2
         float half_tex_size = 0.5f * tex_size;
 
         // we currently use circular icons for gizmo, so we check the radius
+#if ENABLE_EXTENDED_SELECTION
+        bool no_wipe_tower = selection.is_wipe_tower() && !it->second->get_accept_wipe_tower();
+        if (!no_wipe_tower && (it->second->get_state() != GLGizmoBase::On))
+#else
         if (it->second->get_state() != GLGizmoBase::On)
+#endif // ENABLE_EXTENDED_SELECTION
         {
             bool inside = (mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size;
             it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off);
@@ -1249,7 +1677,11 @@ void GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Vec2
     }
 }
 
+#if ENABLE_EXTENDED_SELECTION
+void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const GLCanvas3D::Selection& selection)
+#else
 void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos)
+#endif // ENABLE_EXTENDED_SELECTION
 {
     if (!m_enabled)
         return;
@@ -1266,7 +1698,12 @@ void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec
         float half_tex_size = 0.5f * tex_size;
 
         // we currently use circular icons for gizmo, so we check the radius
+#if ENABLE_EXTENDED_SELECTION
+        bool no_wipe_tower = selection.is_wipe_tower() && !it->second->get_accept_wipe_tower();
+        if (!no_wipe_tower && ((mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size))
+#else
         if ((mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size)
+#endif // ENABLE_EXTENDED_SELECTION
         {
             if ((it->second->get_state() == GLGizmoBase::On))
             {
@@ -1315,6 +1752,28 @@ void GLCanvas3D::Gizmos::set_hover_id(int id)
     }
 }
 
+#if ENABLE_EXTENDED_SELECTION
+void GLCanvas3D::Gizmos::enable_grabber(EType type, unsigned int id)
+{
+    if (!m_enabled)
+        return;
+
+    GizmosMap::const_iterator it = m_gizmos.find(type);
+    if (it != m_gizmos.end())
+        it->second->enable_grabber(id);
+}
+
+void GLCanvas3D::Gizmos::disable_grabber(EType type, unsigned int id)
+{
+    if (!m_enabled)
+        return;
+
+    GizmosMap::const_iterator it = m_gizmos.find(type);
+    if (it != m_gizmos.end())
+        it->second->disable_grabber(id);
+}
+#endif // ENABLE_EXTENDED_SELECTION
+
 bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const
 {
     if (!m_enabled)
@@ -1406,6 +1865,16 @@ void GLCanvas3D::Gizmos::stop_dragging()
         curr->stop_dragging();
 }
 
+#if ENABLE_EXTENDED_SELECTION
+Vec3d GLCanvas3D::Gizmos::get_displacement() const
+{
+    if (!m_enabled)
+        return Vec3d::Zero();
+
+    GizmosMap::const_iterator it = m_gizmos.find(Move);
+    return (it != m_gizmos.end()) ? reinterpret_cast<GLGizmoMove3D*>(it->second)->get_displacement() : Vec3d::Zero();
+}
+#else
 Vec3d GLCanvas3D::Gizmos::get_position() const
 {
     if (!m_enabled)
@@ -1424,6 +1893,7 @@ void GLCanvas3D::Gizmos::set_position(const Vec3d& position)
     if (it != m_gizmos.end())
         reinterpret_cast<GLGizmoMove3D*>(it->second)->set_position(position);
 }
+#endif // ENABLE_EXTENDED_SELECTION
 
 #if ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM
 Vec3d GLCanvas3D::Gizmos::get_scale() const
@@ -1620,6 +2090,12 @@ float GLCanvas3D::Gizmos::_get_total_overlay_height() const
     return height;
 }
 
+GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const
+{
+    GizmosMap::const_iterator it = m_gizmos.find(m_current);
+    return (it != m_gizmos.end()) ? it->second : nullptr;
+}
+
 const unsigned char GLCanvas3D::WarningTexture::Background_Color[3] = { 9, 91, 134 };
 const unsigned char GLCanvas3D::WarningTexture::Opacity = 255;
 
@@ -1933,12 +2409,6 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const
     }
 }
 
-GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const
-{
-    GizmosMap::const_iterator it = m_gizmos.find(m_current);
-    return (it != m_gizmos.end()) ? it->second : nullptr;
-}
-
 wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, ObjectSelectEvent);
 wxDEFINE_EVENT(EVT_GLCANVAS_VIEWPORT_CHANGED, SimpleEvent);
 wxDEFINE_EVENT(EVT_GLCANVAS_DOUBLE_CLICK, SimpleEvent);
@@ -1980,9 +2450,14 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
     , m_shader_enabled(false)
     , m_dynamic_background_enabled(false)
     , m_multisample_allowed(false)
+#if ENABLE_EXTENDED_SELECTION
+    , m_regenerate_volumes(true)
+#endif // ENABLE_EXTENDED_SELECTION
     , m_color_by("volume")
+#if !ENABLE_EXTENDED_SELECTION
     , m_select_by("object")
     , m_drag_by("instance")
+#endif // !ENABLE_EXTENDED_SELECTION
     , m_reload_delayed(false)
 {
     if (m_canvas != nullptr)
@@ -1992,6 +2467,10 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
 #endif // !ENABLE_USE_UNIQUE_GLCONTEXT
         m_timer = new wxTimer(m_canvas);
     }
+
+#if ENABLE_EXTENDED_SELECTION
+    m_selection.set_volumes(&m_volumes.volumes);
+#endif // ENABLE_EXTENDED_SELECTION
 }
 
 GLCanvas3D::~GLCanvas3D()
@@ -2132,6 +2611,9 @@ void GLCanvas3D::reset_volumes()
 
         m_volumes.release_geometry();
         m_volumes.clear();
+#if ENABLE_EXTENDED_SELECTION
+        m_selection.clear();
+#endif // ENABLE_EXTENDED_SELECTION
         m_dirty = true;
     }
 
@@ -2139,6 +2621,7 @@ void GLCanvas3D::reset_volumes()
     _reset_warning_texture();
 }
 
+#if !ENABLE_EXTENDED_SELECTION
 void GLCanvas3D::deselect_volumes()
 {
     for (GLVolume* vol : m_volumes.volumes)
@@ -2178,6 +2661,7 @@ void GLCanvas3D::update_volumes_selection(const std::vector<int>& selections)
         }
     }
 }
+#endif // !ENABLE_EXTENDED_SELECTION
 
 int GLCanvas3D::check_volumes_outside_state(const DynamicPrintConfig* config) const
 {
@@ -2192,8 +2676,10 @@ bool GLCanvas3D::move_volume_up(unsigned int id)
     {
         std::swap(m_volumes.volumes[id - 1], m_volumes.volumes[id]);
         std::swap(m_volumes.volumes[id - 1]->composite_id, m_volumes.volumes[id]->composite_id);
+#if !ENABLE_EXTENDED_SELECTION
         std::swap(m_volumes.volumes[id - 1]->select_group_id, m_volumes.volumes[id]->select_group_id);
         std::swap(m_volumes.volumes[id - 1]->drag_group_id, m_volumes.volumes[id]->drag_group_id);
+#endif // !ENABLE_EXTENDED_SELECTION
         return true;
     }
 
@@ -2206,18 +2692,22 @@ bool GLCanvas3D::move_volume_down(unsigned int id)
     {
         std::swap(m_volumes.volumes[id + 1], m_volumes.volumes[id]);
         std::swap(m_volumes.volumes[id + 1]->composite_id, m_volumes.volumes[id]->composite_id);
+#if !ENABLE_EXTENDED_SELECTION
         std::swap(m_volumes.volumes[id + 1]->select_group_id, m_volumes.volumes[id]->select_group_id);
         std::swap(m_volumes.volumes[id + 1]->drag_group_id, m_volumes.volumes[id]->drag_group_id);
+#endif // !ENABLE_EXTENDED_SELECTION
         return true;
     }
 
     return false;
 }
 
+#if !ENABLE_EXTENDED_SELECTION
 void GLCanvas3D::set_objects_selections(const std::vector<int>& selections)
 {
     m_objects_selections = selections;
 }
+#endif // !ENABLE_EXTENDED_SELECTION
 
 void GLCanvas3D::set_config(DynamicPrintConfig* config)
 {
@@ -2232,6 +2722,9 @@ void GLCanvas3D::set_print(Print* print)
 void GLCanvas3D::set_model(Model* model)
 {
     m_model = model;
+#if ENABLE_EXTENDED_SELECTION
+    m_selection.set_model(m_model);
+#endif // ENABLE_EXTENDED_SELECTION
 }
 
 void GLCanvas3D::set_bed_shape(const Pointfs& shape)
@@ -2289,6 +2782,7 @@ void GLCanvas3D::set_color_by(const std::string& value)
     m_color_by = value;
 }
 
+#if !ENABLE_EXTENDED_SELECTION
 void GLCanvas3D::set_select_by(const std::string& value)
 {
     m_select_by = value;
@@ -2310,6 +2804,7 @@ const std::string& GLCanvas3D::get_drag_by() const
 {
     return m_drag_by;
 }
+#endif // !ENABLE_EXTENDED_SELECTION
 
 float GLCanvas3D::get_camera_zoom() const
 {
@@ -2365,6 +2860,9 @@ void GLCanvas3D::enable_legend_texture(bool enable)
 void GLCanvas3D::enable_picking(bool enable)
 {
     m_picking_enabled = enable;
+#if ENABLE_EXTENDED_SELECTION
+    m_selection.set_mode(Selection::Instance);
+#endif // ENABLE_EXTENDED_SELECTION
 }
 
 void GLCanvas3D::enable_moving(bool enable)
@@ -2478,6 +2976,32 @@ void GLCanvas3D::update_gizmos_data()
     if (!m_gizmos.is_enabled())
         return;
 
+#if ENABLE_EXTENDED_SELECTION
+    if (m_gizmos.get_current_type() == Gizmos::Move)
+    {
+        if (m_selection.is_wipe_tower())
+            m_gizmos.disable_grabber(Gizmos::Move, 2);
+        else
+            m_gizmos.enable_grabber(Gizmos::Move, 2);
+    }
+
+    int object_idx = -1;
+    int instance_idx = -1;
+    if (m_selection.is_single_full_instance(object_idx, instance_idx))
+    {
+        ModelObject* model_object = m_model->objects[object_idx];
+        ModelInstance* model_instance = model_object->instances[instance_idx];
+        m_gizmos.set_scale(model_instance->get_scaling_factor());
+        m_gizmos.set_rotation(model_instance->get_rotation());
+        m_gizmos.set_flattening_data(model_object);
+    }
+    else
+    {
+        m_gizmos.set_scale(Vec3d::Ones());
+        m_gizmos.set_rotation(Vec3d::Zero());
+        m_gizmos.set_flattening_data(nullptr);
+    }
+#else
     int id = _get_first_selected_object_id();
     if ((id != -1) && (m_model != nullptr))
     {
@@ -2512,6 +3036,7 @@ void GLCanvas3D::update_gizmos_data()
 #endif // ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM
         m_gizmos.set_flattening_data(nullptr);
     }
+#endif // ENABLE_EXTENDED_SELECTION
 }
 
 void GLCanvas3D::render()
@@ -2556,6 +3081,9 @@ void GLCanvas3D::render()
         _render_axes(false);
     }
     _render_objects();
+#if ENABLE_EXTENDED_SELECTION
+    _render_selection();
+#endif // ENABLE_EXTENDED_SELECTION
     if (!is_custom_bed) // textured bed needs to be rendered after objects
     {
         _render_axes(true);
@@ -2594,7 +3122,11 @@ std::vector<int> GLCanvas3D::load_object(const ModelObject& model_object, int ob
             instance_idxs.push_back(i);
         }
     }
+#if ENABLE_EXTENDED_SELECTION
+    return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_use_VBOs && m_initialized);
+#else
     return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_select_by, m_drag_by, m_use_VBOs && m_initialized);
+#endif // ENABLE_EXTENDED_SELECTION
 }
 
 std::vector<int> GLCanvas3D::load_object(const Model& model, int obj_idx)
@@ -2630,7 +3162,9 @@ void GLCanvas3D::reload_scene(bool force)
     if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr))
         return;
 
+#if !ENABLE_EXTENDED_SELECTION
     reset_volumes();
+#endif // !ENABLE_EXTENDED_SELECTION
 
 #if !ENABLE_USE_UNIQUE_GLCONTEXT
     // ensures this canvas is current
@@ -2638,6 +3172,11 @@ void GLCanvas3D::reload_scene(bool force)
         return;
 #endif // !ENABLE_USE_UNIQUE_GLCONTEXT
 
+#if ENABLE_EXTENDED_SELECTION
+    if (m_regenerate_volumes)
+        reset_volumes();
+#endif // ENABLE_EXTENDED_SELECTION
+
     set_bed_shape(dynamic_cast<const ConfigOptionPoints*>(m_config->option("bed_shape"))->values);
 
     if (!m_canvas->IsShown() && !force)
@@ -2648,6 +3187,17 @@ void GLCanvas3D::reload_scene(bool force)
 
     m_reload_delayed = false;
 
+#if ENABLE_EXTENDED_SELECTION
+    if (m_regenerate_volumes)
+    {
+        for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx)
+        {
+            load_object(*m_model, obj_idx);
+        }
+    }
+
+    update_gizmos_data();
+#else
     m_objects_volumes_idxs.clear();
 
     for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx)
@@ -2661,36 +3211,44 @@ void GLCanvas3D::reload_scene(bool force)
     // 2nd call to restore selection, if any
     if (!m_objects_selections.empty())
         update_gizmos_data();
+#endif // ENABLE_EXTENDED_SELECTION
 
-    if (m_config->has("nozzle_diameter"))
+#if ENABLE_EXTENDED_SELECTION
+    if (m_regenerate_volumes)
     {
-        // Should the wipe tower be visualized ?
-        unsigned int extruders_count = (unsigned int)dynamic_cast<const ConfigOptionFloats*>(m_config->option("nozzle_diameter"))->values.size();
-
-        bool semm = dynamic_cast<const ConfigOptionBool*>(m_config->option("single_extruder_multi_material"))->value;
-        bool wt = dynamic_cast<const ConfigOptionBool*>(m_config->option("wipe_tower"))->value;
-        bool co = dynamic_cast<const ConfigOptionBool*>(m_config->option("complete_objects"))->value;
-
-        if ((extruders_count > 1) && semm && wt && !co)
+#endif // ENABLE_EXTENDED_SELECTION
+        if (m_config->has("nozzle_diameter"))
         {
-            // Height of a print (Show at least a slab)
-            double height = std::max(m_model->bounding_box().max(2), 10.0);
+            // Should the wipe tower be visualized ?
+            unsigned int extruders_count = (unsigned int)dynamic_cast<const ConfigOptionFloats*>(m_config->option("nozzle_diameter"))->values.size();
 
-            float x = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_x"))->value;
-            float y = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_y"))->value;
-            float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_width"))->value;
-            float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
+            bool semm = dynamic_cast<const ConfigOptionBool*>(m_config->option("single_extruder_multi_material"))->value;
+            bool wt = dynamic_cast<const ConfigOptionBool*>(m_config->option("wipe_tower"))->value;
+            bool co = dynamic_cast<const ConfigOptionBool*>(m_config->option("complete_objects"))->value;
 
-            float depth = m_print->get_wipe_tower_depth();
-            if (!m_print->is_step_done(psWipeTower))
-                depth = (900.f/w) * (float)(extruders_count - 1) ;
+            if ((extruders_count > 1) && semm && wt && !co)
+            {
+                // Height of a print (Show at least a slab)
+                double height = std::max(m_model->bounding_box().max(2), 10.0);
 
-            m_volumes.load_wipe_tower_preview(1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower),
-                                              m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f);
+                float x = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_x"))->value;
+                float y = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_y"))->value;
+                float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_width"))->value;
+                float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
+
+                float depth = m_print->get_wipe_tower_depth();
+                if (!m_print->is_step_done(psWipeTower))
+                    depth = (900.f/w) * (float)(extruders_count - 1) ;
+
+                m_volumes.load_wipe_tower_preview(1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower),
+                                                  m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f);
+            }
         }
-    }
 
-    update_volumes_colors_by_extruder();
+        update_volumes_colors_by_extruder();
+#if ENABLE_EXTENDED_SELECTION
+    }
+#endif // ENABLE_EXTENDED_SELECTION
 
     // checks for geometry outside the print volume to render it accordingly
     if (!m_volumes.empty())
@@ -2718,6 +3276,11 @@ void GLCanvas3D::reload_scene(bool force)
         _reset_warning_texture();
         post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false));
     }
+
+#if ENABLE_EXTENDED_SELECTION
+    // restore to default value
+    m_regenerate_volumes = true;
+#endif // ENABLE_EXTENDED_SELECTION
 }
 
 void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors)
@@ -2913,7 +3476,12 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
     // Performs layers editing updates, if enabled
     if (is_layers_editing_enabled())
     {
+#if ENABLE_EXTENDED_SELECTION
+        int object_idx_selected = -1;
+        m_selection.is_from_single_object(object_idx_selected);
+#else
         int object_idx_selected = _get_first_selected_object_id();
+#endif // ENABLE_EXTENDED_SELECTION
         if (object_idx_selected != -1)
         {
             // A volume is selected. Test, whether hovering over a layer thickness bar.
@@ -2957,8 +3525,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 {
     Point pos(evt.GetX(), evt.GetY());
 
+#if ENABLE_EXTENDED_SELECTION
+    int selected_object_idx = -1;
+    m_selection.is_from_single_object(selected_object_idx);
+    int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1;
+#else
     int selected_object_idx = _get_first_selected_object_id();
     int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1;
+#endif // ENABLE_EXTENDED_SELECTION
     m_layers_editing.last_object_id = layer_editing_object_idx;
     bool gizmos_overlay_contains_mouse = m_gizmos.overlay_contains_mouse(*this, m_mouse.position);
     int toolbar_contains_mouse = m_toolbar.contains_mouse(m_mouse.position);
@@ -3026,7 +3600,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
     {
         // If user pressed left or right button we first check whether this happened
         // on a volume or not.
+#if !ENABLE_EXTENDED_SELECTION
         int volume_idx = m_hover_volume_id;
+#endif // !ENABLE_EXTENDED_SELECTION
         m_layers_editing.state = LayersEditing::Unknown;
         if ((layer_editing_object_idx != -1) && m_layers_editing.bar_rect_contains(*this, pos(0), pos(1)))
         {
@@ -3049,17 +3625,35 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 m_dirty = true;
             }
         }
+#if ENABLE_EXTENDED_SELECTION
+        else if (!m_selection.is_empty() && gizmos_overlay_contains_mouse)
+        {
+            m_gizmos.update_on_off_state(*this, m_mouse.position, m_selection);
+            update_gizmos_data();
+            m_dirty = true;
+        }
+#else
         else if ((selected_object_idx != -1) && gizmos_overlay_contains_mouse)
         {
             update_gizmos_data();
             m_gizmos.update_on_off_state(*this, m_mouse.position);
             m_dirty = true;
         }
+#endif // ENABLE_EXTENDED_SELECTION
+#if ENABLE_EXTENDED_SELECTION
+        else if (!m_selection.is_empty() && m_gizmos.grabber_contains_mouse())
+#else
         else if ((selected_object_idx != -1) && m_gizmos.grabber_contains_mouse())
+#endif // ENABLE_EXTENDED_SELECTION
         {
             update_gizmos_data();
+#if ENABLE_EXTENDED_SELECTION
+            m_selection.start_dragging();
+            m_gizmos.start_dragging(m_selection.get_bounding_box());
+#else
             m_gizmos.start_dragging(_selected_volumes_bounding_box());
             m_mouse.drag.gizmo_volume_idx = _get_first_selected_volume_id(selected_object_idx);
+#endif // ENABLE_EXTENDED_SELECTION
 
             if (m_gizmos.get_current_type() == Gizmos::Flatten) {
 #if ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM
@@ -3089,8 +3683,25 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             // Don't deselect a volume if layer editing is enabled. We want the object to stay selected
             // during the scene manipulation.
 
+#if ENABLE_EXTENDED_SELECTION
+            if (m_picking_enabled && ((m_hover_volume_id != -1) || !is_layers_editing_enabled()))
+#else
             if (m_picking_enabled && ((volume_idx != -1) || !is_layers_editing_enabled()))
+#endif // ENABLE_EXTENDED_SELECTION
             {
+#if ENABLE_EXTENDED_SELECTION
+                if (m_hover_volume_id != -1)
+                {
+                    if (evt.ControlDown())
+                        m_selection.remove(m_hover_volume_id);
+                    else
+                        m_selection.add(m_hover_volume_id, !evt.ShiftDown());
+
+                    update_gizmos_data();
+                    wxGetApp().obj_manipul()->update_settings_value(m_selection);
+                    m_dirty = true;
+                }
+#else
                 if (volume_idx != -1)
                 {
                     deselect_volumes();
@@ -3108,32 +3719,61 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                     update_gizmos_data();
                     m_dirty = true;
                 }
+#endif // ENABLE_EXTENDED_SELECTION
             }
 
             // propagate event through callback
+#if ENABLE_EXTENDED_SELECTION
+            if (m_picking_enabled && (m_hover_volume_id != -1))
+            {
+                int object_idx = -1;
+                m_selection.is_from_single_object(object_idx);
+                _on_select(m_hover_volume_id, object_idx);
+            }
+#else
             if (m_picking_enabled && (volume_idx != -1))
                 _on_select(volume_idx, selected_object_idx);
+#endif // ENABLE_EXTENDED_SELECTION
 
+#if ENABLE_EXTENDED_SELECTION
+            if (m_hover_volume_id != -1)
+#else
             if (volume_idx != -1)
+#endif // ENABLE_EXTENDED_SELECTION
             {
+#if ENABLE_EXTENDED_SELECTION
+                if (evt.LeftDown() && m_moving_enabled && (m_mouse.drag.move_volume_idx == -1))
+#else
                 if (evt.LeftDown() && m_moving_enabled)
+#endif // ENABLE_EXTENDED_SELECTION
                 {
                     // The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate pos x, y,
                     // an converts the screen space coordinate to unscaled object space.
-                    Vec3d pos3d = (volume_idx == -1) ? Vec3d(DBL_MAX, DBL_MAX, DBL_MAX) : _mouse_to_3d(pos);
+                    Vec3d pos3d = _mouse_to_3d(pos);
 
                     // Only accept the initial position, if it is inside the volume bounding box.
+#if ENABLE_EXTENDED_SELECTION
+                    BoundingBoxf3 volume_bbox = m_volumes.volumes[m_hover_volume_id]->transformed_bounding_box();
+#else
                     BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box();
+#endif // ENABLE_EXTENDED_SELECTION
                     volume_bbox.offset(1.0);
                     if (volume_bbox.contains(pos3d))
                     {
                         // The dragging operation is initiated.
+#if ENABLE_EXTENDED_SELECTION
+                        m_mouse.drag.move_volume_idx = m_hover_volume_id;
+                        m_selection.start_dragging();
+#else
                         m_mouse.drag.move_with_shift = evt.ShiftDown();
                         m_mouse.drag.move_volume_idx = volume_idx;
+#endif // ENABLE_EXTENDED_SELECTION
                         m_mouse.drag.start_position_3D = pos3d;
+#if !ENABLE_EXTENDED_SELECTION
                         // Remember the shift to to the object center.The object center will later be used
                         // to limit the object placement close to the bed.
                         m_mouse.drag.volume_center_offset = volume_bbox.center() - pos3d;
+#endif // !ENABLE_EXTENDED_SELECTION
                     }
                 }
                 else if (evt.RightDown())
@@ -3145,7 +3785,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                     if (m_hover_volume_id != -1)
                     {
                         // if right clicking on volume, propagate event through callback (shows context menu)
+#if ENABLE_EXTENDED_SELECTION
+                        if (m_volumes.volumes[m_hover_volume_id]->hover)
+#else
                         if (m_volumes.volumes[volume_idx]->hover)
+#endif // ENABLE_EXTENDED_SELECTION
                             post_event(Vec2dEvent(EVT_GLCANVAS_RIGHT_CLICK, pos.cast<double>()));
                     }
                 }
@@ -3161,6 +3805,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         float z1 = 1.0f;
         Vec3d cur_pos = Linef3(_mouse_to_3d(pos, &z0), _mouse_to_3d(pos, &z1)).intersect_plane(m_mouse.drag.start_position_3D(2));
 
+#if !ENABLE_EXTENDED_SELECTION
         // Clip the new position, so the object center remains close to the bed.
         cur_pos += m_mouse.drag.volume_center_offset;
         Point cur_pos2(scale_(cur_pos(0)), scale_(cur_pos(1)));
@@ -3171,9 +3816,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             cur_pos(1) = unscale<double>(ip(1));
         }
         cur_pos -= m_mouse.drag.volume_center_offset;
+#endif // !ENABLE_EXTENDED_SELECTION
 
         // Calculate the translation vector.
-        Vec3d vector = cur_pos - m_mouse.drag.start_position_3D;
+        Vec3d displacement = cur_pos - m_mouse.drag.start_position_3D;
+#if ENABLE_EXTENDED_SELECTION
+        m_selection.translate(displacement);
+        wxGetApp().obj_manipul()->update_settings_value(m_selection);
+#else
         // Get the volume being dragged.
         GLVolume* volume = m_volumes.volumes[m_mouse.drag.move_volume_idx];
         // Get all volumes belonging to the same group, if any.
@@ -3196,11 +3846,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         // Apply new temporary volume origin and ignore Z.
         for (GLVolume* v : volumes)
         {
-            v->set_offset(v->get_offset() + Vec3d(vector(0), vector(1), 0.0));
+            v->set_offset(v->get_offset() + Vec3d(displacement(0), displacement(1), 0.0));
         }
 
         wxGetApp().obj_manipul()->update_position_values(volume->get_offset());
         m_mouse.drag.start_position_3D = cur_pos;
+#endif // ENABLE_EXTENDED_SELECTION
 
         m_dirty = true;
     }
@@ -3212,6 +3863,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         m_mouse.dragging = true;
         m_gizmos.update(mouse_ray(pos));
 
+#if !ENABLE_EXTENDED_SELECTION
         std::vector<GLVolume*> volumes;
         if (m_mouse.drag.gizmo_volume_idx != -1)
         {
@@ -3228,12 +3880,17 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 }
             }
         }
+#endif // !ENABLE_EXTENDED_SELECTION
 
         switch (m_gizmos.get_current_type())
         {
         case Gizmos::Move:
         {
             // Apply new temporary offset
+#if ENABLE_EXTENDED_SELECTION
+            m_selection.translate(m_gizmos.get_displacement());
+            wxGetApp().obj_manipul()->update_settings_value(m_selection);
+#else
             GLVolume* volume = m_volumes.volumes[m_mouse.drag.gizmo_volume_idx];
             Vec3d offset = m_gizmos.get_position() - volume->get_offset();
             for (GLVolume* v : volumes)
@@ -3241,11 +3898,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 v->set_offset(v->get_offset() + offset);
             }
             wxGetApp().obj_manipul()->update_position_values(volume->get_offset());
+#endif // ENABLE_EXTENDED_SELECTION
             break;
         }
         case Gizmos::Scale:
         {
 #if ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM
+#if ENABLE_EXTENDED_SELECTION
+#else
             // Apply new temporary scale factors
             const Vec3d& scale = m_gizmos.get_scale();
             for (GLVolume* v : volumes)
@@ -3253,6 +3913,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 v->set_scaling_factor(scale);
             }
             wxGetApp().obj_manipul()->update_scale_values(scale);
+#endif // ENABLE_EXTENDED_SELECTION
 #else
             // Apply new temporary scale factor
             float scale_factor = m_gizmos.get_scale();
@@ -3267,6 +3928,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         case Gizmos::Rotate:
         {
 #if ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM
+#if ENABLE_EXTENDED_SELECTION
+#else
             // Apply new temporary rotation
             const Vec3d& rotation = m_gizmos.get_rotation();
             for (GLVolume* v : volumes)
@@ -3274,6 +3937,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 v->set_rotation(rotation);
             }
             wxGetApp().obj_manipul()->update_rotation_value(rotation);
+#endif // ENABLE_EXTENDED_SELECTION
 #else
             // Apply new temporary angle_z
             float angle_z = m_gizmos.get_angle_z();
@@ -3289,6 +3953,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             break;
         }
 
+#if ENABLE_EXTENDED_SELECTION
+#else
         if (!volumes.empty())
         {
             BoundingBoxf3 bb;
@@ -3304,6 +3970,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             m_on_update_geometry_info_callback.call(size(0), size(1), size(2), m_gizmos.get_scale());
 #endif // ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM
         }
+#endif // ENABLE_EXTENDED_SELECTION
 
         m_dirty = true;
     }
@@ -3362,6 +4029,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         }
         else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging)
         {
+#if ENABLE_EXTENDED_SELECTION
+            m_regenerate_volumes = false;
+            _on_move();
+            wxGetApp().obj_manipul()->update_settings_value(m_selection);
+#else
             // get all volumes belonging to the same group, if any
             std::vector<int> volume_idxs;
             int vol_id = m_mouse.drag.move_volume_idx;
@@ -3382,6 +4054,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             // force re-selection of the wipe tower, if needed
             if ((volume_idxs.size() == 1) && m_volumes.volumes[volume_idxs[0]]->is_wipe_tower)
                 select_volume(volume_idxs[0]);
+#endif // ENABLE_EXTENDED_SELECTION
         }
         else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled())
         {
@@ -3392,7 +4065,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             if (m_picking_enabled && !m_toolbar_action_running)
 #endif // ENABLE_GIZMOS_RESET
             {
+#if ENABLE_EXTENDED_SELECTION
+                m_selection.clear();
+                wxGetApp().obj_manipul()->update_settings_value(m_selection);
+#else
                 deselect_volumes();
+#endif // ENABLE_EXTENDED_SELECTION
                 _on_select(-1, -1);
                 update_gizmos_data();
             }
@@ -3407,6 +4085,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             {
             case Gizmos::Move:
             {
+#if ENABLE_EXTENDED_SELECTION
+                m_regenerate_volumes = false;
+                _on_move();
+#else
                 // get all volumes belonging to the same group, if any
                 std::vector<int> volume_idxs;
                 int vol_id = m_mouse.drag.gizmo_volume_idx;
@@ -3423,6 +4105,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 }
 
                 _on_move(volume_idxs);
+#endif // ENABLE_EXTENDED_SELECTION
 
                 break;
             }
@@ -3448,11 +4131,17 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 break;
             }
             m_gizmos.stop_dragging();
+#if ENABLE_EXTENDED_SELECTION
+            wxGetApp().obj_manipul()->update_settings_value(m_selection);
+#else
             wxGetApp().obj_manipul()->update_values();
+#endif // ENABLE_EXTENDED_SELECTION
         }
 
         m_mouse.drag.move_volume_idx = -1;
+#if !ENABLE_EXTENDED_SELECTION
         m_mouse.drag.gizmo_volume_idx = -1;
+#endif // !ENABLE_EXTENDED_SELECTION
         m_mouse.set_start_position_3D_as_invalid();
         m_mouse.set_start_position_2D_as_invalid();
         m_mouse.dragging = false;
@@ -3656,6 +4345,7 @@ bool GLCanvas3D::_init_toolbar()
     if (!m_toolbar.add_separator())
         return false;
 
+#if !ENABLE_EXTENDED_SELECTION
     item.name = "selectbyparts";
     item.tooltip = GUI::L_str("Select by parts");
     item.sprite_id = 10;
@@ -3663,6 +4353,7 @@ bool GLCanvas3D::_init_toolbar()
     item.action_event = EVT_GLTOOLBAR_SELECTBYPARTS;
     if (!m_toolbar.add_item(item))
         return false;
+#endif // !ENABLE_EXTENDED_SELECTION
 
     enable_toolbar_item("add", true);
 
@@ -3758,6 +4449,7 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box() const
     return bb;
 }
 
+#if !ENABLE_EXTENDED_SELECTION
 BoundingBoxf3 GLCanvas3D::_selected_volumes_bounding_box() const
 {
     BoundingBoxf3 bb;
@@ -3802,6 +4494,7 @@ BoundingBoxf3 GLCanvas3D::_selected_volumes_bounding_box() const
 
     return bb;
 }
+#endif // !ENABLE_EXTENDED_SELECTION
 
 void GLCanvas3D::_zoom_to_bounding_box(const BoundingBoxf3& bbox)
 {
@@ -3893,7 +4586,11 @@ void GLCanvas3D::_mark_volumes_for_layer_height() const
 
     for (GLVolume* vol : m_volumes.volumes)
     {
+#if ENABLE_EXTENDED_SELECTION
+        int object_id = vol->object_idx();
+#else
         int object_id = int(vol->select_group_id / 1000000);
+#endif // ENABLE_EXTENDED_SELECTION
         int shader_id = m_layers_editing.get_shader_program_id();
 
         if (is_layers_editing_enabled() && (shader_id != -1) && vol->selected &&
@@ -3949,16 +4646,22 @@ void GLCanvas3D::_picking_pass() const
         ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
         _render_volumes(true);
+#if ENABLE_EXTENDED_SELECTION
+        m_gizmos.render_current_gizmo_for_picking_pass(m_selection.get_bounding_box());
+#else
         m_gizmos.render_current_gizmo_for_picking_pass(_selected_volumes_bounding_box());
+#endif // ENABLE_EXTENDED_SELECTION
 
         if (m_multisample_allowed)
             ::glEnable(GL_MULTISAMPLE);
 
         int volume_id = -1;
+#if !ENABLE_EXTENDED_SELECTION
         for (GLVolume* vol : m_volumes.volumes)
         {
             vol->hover = false;
         }
+#endif // !ENABLE_EXTENDED_SELECTION
 
         GLubyte color[4] = { 0, 0, 0, 0 };
         const Size& cnv_size = get_canvas_size();
@@ -3972,6 +4675,7 @@ void GLCanvas3D::_picking_pass() const
         if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size()))
         {
             m_hover_volume_id = volume_id;
+#if !ENABLE_EXTENDED_SELECTION
             m_volumes.volumes[volume_id]->hover = true;
             int group_id = m_volumes.volumes[volume_id]->select_group_id;
             if (group_id != -1)
@@ -3982,6 +4686,7 @@ void GLCanvas3D::_picking_pass() const
                         vol->hover = true;
                 }
             }
+#endif // !ENABLE_EXTENDED_SELECTION
             m_gizmos.set_hover_id(-1);
         }
         else
@@ -3990,9 +4695,18 @@ void GLCanvas3D::_picking_pass() const
             m_gizmos.set_hover_id(inside ? (254 - (int)color[2]) : -1);
         }
 
+#if ENABLE_EXTENDED_SELECTION
+        _update_volumes_hover_state();
+#endif // ENABLE_EXTENDED_SELECTION
+
         // updates gizmos overlay
+#if ENABLE_EXTENDED_SELECTION
+        if (!m_selection.is_empty())
+            m_gizmos.update_hover_state(*this, pos, m_selection);
+#else
         if (_get_first_selected_object_id() != -1)
             m_gizmos.update_hover_state(*this, pos);
+#endif // ENABLE_EXTENDED_SELECTION
         else
             m_gizmos.reset_all_states();
 
@@ -4092,6 +4806,13 @@ void GLCanvas3D::_render_objects() const
     ::glDisable(GL_LIGHTING);
 }
 
+#if ENABLE_EXTENDED_SELECTION
+void GLCanvas3D::_render_selection() const
+{
+    m_selection.render();
+}
+#endif // ENABLE_EXTENDED_SELECTION
+
 void GLCanvas3D::_render_cutting_plane() const
 {
     m_cutting_plane.render(volumes_bounding_box());
@@ -4134,7 +4855,11 @@ void GLCanvas3D::_render_layer_editing_overlay() const
 
     // If the active object was not allocated at the Print, go away.This should only be a momentary case between an object addition / deletion
     // and an update by Platter::async_apply_config.
+#if ENABLE_EXTENDED_SELECTION
+    int object_idx = volume->object_idx();
+#else
     int object_idx = int(volume->select_group_id / 1000000);
+#endif // ENABLE_EXTENDED_SELECTION
     if ((int)m_print->objects().size() < object_idx)
         return;
 
@@ -4194,7 +4919,11 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const
 
 void GLCanvas3D::_render_current_gizmo() const
 {
+#if ENABLE_EXTENDED_SELECTION
+    m_gizmos.render_current_gizmo(m_selection.get_bounding_box());
+#else
     m_gizmos.render_current_gizmo(_selected_volumes_bounding_box());
+#endif // ENABLE_EXTENDED_SELECTION
 }
 
 void GLCanvas3D::_render_gizmos_overlay() const
@@ -4208,6 +4937,43 @@ void GLCanvas3D::_render_toolbar() const
     m_toolbar.render();
 }
 
+#if ENABLE_EXTENDED_SELECTION
+void GLCanvas3D::_update_volumes_hover_state() const
+{
+    for (GLVolume* v : m_volumes.volumes)
+    {
+        v->hover = false;
+    }
+
+    if (m_hover_volume_id == -1)
+        return;
+
+    GLVolume* volume = m_volumes.volumes[m_hover_volume_id];
+
+    switch (m_selection.get_mode())
+    {
+    case Selection::Volume:
+    {
+        volume->hover = true;
+        break;
+    }
+    case Selection::Instance:
+    {
+        int object_idx = volume->object_idx();
+        int instance_idx = volume->instance_idx();
+
+        for (GLVolume* v : m_volumes.volumes)
+        {
+            if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx))
+                v->hover = true;
+        }
+
+        break;
+    }
+    }
+}
+#endif // ENABLE_EXTENDED_SELECTION
+
 float GLCanvas3D::_get_layers_editing_cursor_z_relative() const
 {
     return m_layers_editing.get_cursor_z_relative(*this);
@@ -4312,6 +5078,7 @@ void GLCanvas3D::_stop_timer()
         m_timer->Stop();
 }
 
+#if !ENABLE_EXTENDED_SELECTION
 int GLCanvas3D::_get_first_selected_object_id() const
 {
     if (m_print != nullptr)
@@ -4345,6 +5112,7 @@ int GLCanvas3D::_get_first_selected_volume_id(int object_id) const
 
     return -1;
 }
+#endif // !ENABLE_EXTENDED_SELECTION
 
 void GLCanvas3D::_load_print_toolpaths()
 {
@@ -4473,7 +5241,11 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
     tbb::parallel_for(
         tbb::blocked_range<size_t>(0, ctxt.layers.size(), grain_size),
         [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) {
+#if ENABLE_EXTENDED_SELECTION
+        GLVolumePtrs vols;
+#else
         std::vector<GLVolume*> vols;
+#endif // ENABLE_EXTENDED_SELECTION
         if (ctxt.color_by_tool()) {
             for (size_t i = 0; i < ctxt.number_tools(); ++i)
                 vols.emplace_back(new_volume(ctxt.color_tool(i)));
@@ -4632,7 +5404,11 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_
         tbb::blocked_range<size_t>(0, n_items, grain_size),
         [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) {
         // Bounding box of this slab of a wipe tower.
+#if ENABLE_EXTENDED_SELECTION
+        GLVolumePtrs vols;
+#else
         std::vector<GLVolume*> vols;
+#endif // ENABLE_EXTENDED_SELECTION
         if (ctxt.color_by_tool()) {
             for (size_t i = 0; i < ctxt.number_tools(); ++i)
                 vols.emplace_back(new_volume(ctxt.color_tool(i)));
@@ -4858,9 +5634,15 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
             m_gcode_preview_volume_index.first_volumes.pop_back();
             if (initial_volumes_count != m_volumes.volumes.size())
             {
+#if ENABLE_EXTENDED_SELECTION
+                GLVolumePtrs::iterator begin = m_volumes.volumes.begin() + initial_volumes_count;
+                GLVolumePtrs::iterator end = m_volumes.volumes.end();
+                for (GLVolumePtrs::iterator it = begin; it < end; ++it)
+#else
                 std::vector<GLVolume*>::iterator begin = m_volumes.volumes.begin() + initial_volumes_count;
                 std::vector<GLVolume*>::iterator end = m_volumes.volumes.end();
                 for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it)
+#endif // ENABLE_EXTENDED_SELECTION
                 {
                     GLVolume* volume = *it;
                     delete volume;
@@ -4931,9 +5713,15 @@ void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data,
         // an error occourred - restore to previous state and return
         if (initial_volumes_count != m_volumes.volumes.size())
         {
+#if ENABLE_EXTENDED_SELECTION
+            GLVolumePtrs::iterator begin = m_volumes.volumes.begin() + initial_volumes_count;
+            GLVolumePtrs::iterator end = m_volumes.volumes.end();
+            for (GLVolumePtrs::iterator it = begin; it < end; ++it)
+#else
             std::vector<GLVolume*>::iterator begin = m_volumes.volumes.begin() + initial_volumes_count;
             std::vector<GLVolume*>::iterator end = m_volumes.volumes.end();
             for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it)
+#endif // ENABLE_EXTENDED_SELECTION
             {
                 GLVolume* volume = *it;
                 delete volume;
@@ -5237,7 +6025,11 @@ void GLCanvas3D::_load_shells()
             instance_ids[i] = i;
         }
 
+#if ENABLE_EXTENDED_SELECTION
+        m_volumes.load_object(model_obj, object_id, instance_ids, "object", m_use_VBOs && m_initialized);
+#else
         m_volumes.load_object(model_obj, object_id, instance_ids, "object", "object", "object", m_use_VBOs && m_initialized);
+#endif // ENABLE_EXTENDED_SELECTION
 
         ++object_id;
     }
@@ -5260,10 +6052,17 @@ void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& previe
     unsigned int size = (unsigned int)m_gcode_preview_volume_index.first_volumes.size();
     for (unsigned int i = 0; i < size; ++i)
     {
+#if ENABLE_EXTENDED_SELECTION
+        GLVolumePtrs::iterator begin = m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i].id;
+        GLVolumePtrs::iterator end = (i + 1 < size) ? m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i + 1].id : m_volumes.volumes.end();
+
+        for (GLVolumePtrs::iterator it = begin; it != end; ++it)
+#else
         std::vector<GLVolume*>::iterator begin = m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i].id;
         std::vector<GLVolume*>::iterator end = (i + 1 < size) ? m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i + 1].id : m_volumes.volumes.end();
 
         for (std::vector<GLVolume*>::iterator it = begin; it != end; ++it)
+#endif // ENABLE_EXTENDED_SELECTION
         {
             GLVolume* volume = *it;
 
@@ -5352,6 +6151,54 @@ void GLCanvas3D::_show_warning_texture_if_needed()
     }
 }
 
+#if ENABLE_EXTENDED_SELECTION
+void GLCanvas3D::_on_move()
+{
+    if (m_model == nullptr)
+        return;
+
+    std::set<std::string> done;  // prevent moving instances twice
+    bool object_moved = false;
+    Vec3d wipe_tower_origin = Vec3d::Zero();
+    const Selection::IndicesList& selection = m_selection.get_volume_idxs();
+
+    for (unsigned int i : selection)
+    {
+        const GLVolume* v = m_volumes.volumes[i];
+        int object_idx = v->object_idx();
+        int instance_idx = v->instance_idx();
+
+        // prevent moving instances twice
+        char done_id[64];
+        ::sprintf(done_id, "%d_%d", object_idx, instance_idx);
+        if (done.find(done_id) != done.end())
+            continue;
+
+        done.insert(done_id);
+
+        if (object_idx < 1000)
+        {
+            // Move a regular object.
+            ModelObject* model_object = m_model->objects[object_idx];
+            if (model_object != nullptr)
+            {
+                model_object->instances[instance_idx]->set_offset(v->get_offset());
+                model_object->invalidate_bounding_box();
+                object_moved = true;
+            }
+        }
+        else if (object_idx == 1000)
+            // Move a wipe tower proxy.
+            wipe_tower_origin = v->get_offset();
+    }
+
+    if (object_moved)
+        post_event(SimpleEvent(EVT_GLCANVAS_WIPETOWER_MOVED));
+
+    if (wipe_tower_origin != Vec3d::Zero())
+        post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin)));
+}
+#else
 void GLCanvas3D::_on_move(const std::vector<int>& volume_idxs)
 {
     if (m_model == nullptr)
@@ -5402,9 +6249,13 @@ void GLCanvas3D::_on_move(const std::vector<int>& volume_idxs)
     if (wipe_tower_origin != Vec3d::Zero())
         post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin)));
 }
+#endif // ENABLE_EXTENDED_SELECTION
 
 void GLCanvas3D::_on_select(int volume_idx, int object_idx)
 {
+#if ENABLE_EXTENDED_SELECTION
+    post_event(ObjectSelectEvent(object_idx, -1));
+#else
     int vol_id = -1;
     int obj_id = -1;
 
@@ -5433,6 +6284,7 @@ void GLCanvas3D::_on_select(int volume_idx, int object_idx)
 
     post_event(ObjectSelectEvent(obj_id, vol_id));
     wxGetApp().obj_list()->select_current_volume(obj_id, vol_id);
+#endif // !ENABLE_EXTENDED_SELECTION
 }
 
 std::vector<float> GLCanvas3D::_parse_colors(const std::vector<std::string>& colors)
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index 59893368e..d900a2621 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -346,11 +346,15 @@ class GLCanvas3D
 
             Point start_position_2D;
             Vec3d start_position_3D;
+#if !ENABLE_EXTENDED_SELECTION
             Vec3d volume_center_offset;
 
             bool move_with_shift;
+#endif // !ENABLE_EXTENDED_SELECTION
             int move_volume_idx;
+#if !ENABLE_EXTENDED_SELECTION
             int gizmo_volume_idx;
+#endif // !ENABLE_EXTENDED_SELECTION
 
         public:
             Drag();
@@ -372,6 +376,104 @@ class GLCanvas3D
         bool is_start_position_3D_defined() const;
     };
 
+#if ENABLE_EXTENDED_SELECTION
+public:
+    class Selection
+    {
+    public:
+        typedef std::set<unsigned int> IndicesList;
+
+        enum EMode : unsigned char
+        {
+            Volume,
+            Instance,
+            Object
+        };
+
+    private:
+        struct VolumeCache
+        {
+        private:
+            Transform3d m_rotation_matrix;
+
+        public:
+            Vec3d position;
+            Vec3d rotation;
+            Vec3d scaling_factor;
+
+            VolumeCache();
+            VolumeCache(const Vec3d& position, const Vec3d& rotation, const Vec3d& scaling_factor);
+
+            const Transform3d& get_rotation_matrix() const { return m_rotation_matrix; }
+        };
+
+        typedef std::map<unsigned int, VolumeCache> VolumesCache;
+
+        struct Cache
+        {
+            VolumesCache volumes_data;
+            Vec3d dragging_center;
+        };
+
+        GLVolumePtrs* m_volumes;
+        Model* m_model;
+
+        bool m_valid;
+        EMode m_mode;
+        IndicesList m_list;
+        Cache m_cache;
+        mutable BoundingBoxf3 m_bounding_box;
+        mutable bool m_bounding_box_dirty;
+
+    public:
+        Selection();
+
+        void set_volumes(GLVolumePtrs* volumes);
+        void set_model(Model* model);
+
+        EMode get_mode() const { return m_mode; }
+        void set_mode(EMode mode) { m_mode = mode; }
+
+        void add(unsigned int volume_idx, bool as_single_selection = true);
+        void remove(unsigned int volume_idx);
+        void clear();
+
+        bool is_empty() const { return m_list.empty(); }
+        bool is_wipe_tower() const { return m_valid && (m_list.size() == 1) && (*m_volumes)[*m_list.begin()]->is_wipe_tower; }
+        bool is_modifier() const { return m_valid && (m_list.size() == 1) && (*m_volumes)[*m_list.begin()]->is_modifier; }
+        bool is_single_full_instance(int& object_idx_out, int& instance_idx_out) const;
+        bool is_single_full_object(int& object_idx_out) const;
+        bool is_from_single_instance(int& object_idx_out, int& instance_idx_out) const;
+        bool is_from_single_object(int& object_idx_out) const;
+
+        const IndicesList& get_volume_idxs() const { return m_list; }
+        const GLVolume* get_volume(unsigned int volume_idx) const;
+
+        unsigned int volumes_count() const { return (unsigned int)m_list.size(); }
+        const BoundingBoxf3& get_bounding_box() const;
+
+        void start_dragging();
+
+        void translate(const Vec3d& displacement);
+
+        void render() const;
+
+    private:
+        void update_valid();
+        void set_caches();
+        void add_volume(unsigned int volume_idx);
+        void add_instance(unsigned int volume_idx);
+        void add_object(unsigned int volume_idx);
+        void remove_volume(unsigned int volume_idx);
+        void remove_instance(unsigned int volume_idx);
+        void remove_object(unsigned int volume_idx);
+        void calc_bounding_box() const;
+        void render_bounding_box(const BoundingBoxf3& box, float* color) const;
+    };
+
+private:
+#endif // ENABLE_EXTENDED_SELECTION
+
     class Gizmos
     {
         static const float OverlayTexturesScale;
@@ -404,11 +506,20 @@ class GLCanvas3D
         bool is_enabled() const;
         void set_enabled(bool enable);
 
+#if ENABLE_EXTENDED_SELECTION
+        void update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection);
+        void update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection);
+#else
         void update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos);
         void update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos);
+#endif // ENABLE_EXTENDED_SELECTION
         void reset_all_states();
 
         void set_hover_id(int id);
+#if ENABLE_EXTENDED_SELECTION
+        void enable_grabber(EType type, unsigned int id);
+        void disable_grabber(EType type, unsigned int id);
+#endif // ENABLE_EXTENDED_SELECTION
 
         bool overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const;
         bool grabber_contains_mouse() const;
@@ -425,8 +536,12 @@ class GLCanvas3D
         void start_dragging(const BoundingBoxf3& box);
         void stop_dragging();
 
+#if ENABLE_EXTENDED_SELECTION
+        Vec3d get_displacement() const;
+#else
         Vec3d get_position() const;
         void set_position(const Vec3d& position);
+#endif // ENABLE_EXTENDED_SELECTION
 
 #if ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM
         Vec3d get_scale() const;
@@ -517,6 +632,9 @@ class GLCanvas3D
     mutable GLToolbar m_toolbar;
 
     mutable GLVolumeCollection m_volumes;
+#if ENABLE_EXTENDED_SELECTION
+    Selection m_selection;
+#endif // ENABLE_EXTENDED_SELECTION
     DynamicPrintConfig* m_config;
     Print* m_print;
     Model* m_model;
@@ -535,14 +653,21 @@ class GLCanvas3D
     bool m_shader_enabled;
     bool m_dynamic_background_enabled;
     bool m_multisample_allowed;
+#if ENABLE_EXTENDED_SELECTION
+    bool m_regenerate_volumes;
+#endif // ENABLE_EXTENDED_SELECTION
 
     std::string m_color_by;
+#if !ENABLE_EXTENDED_SELECTION
     std::string m_select_by;
     std::string m_drag_by;
+#endif // !ENABLE_EXTENDED_SELECTION
 
     bool m_reload_delayed;
+#if !ENABLE_EXTENDED_SELECTION
     std::vector<std::vector<int>> m_objects_volumes_idxs;
     std::vector<int> m_objects_selections;
+#endif // !ENABLE_EXTENDED_SELECTION
 
     GCodePreviewVolumeIndex m_gcode_preview_volume_index;
 
@@ -568,19 +693,27 @@ public:
 
     unsigned int get_volumes_count() const;
     void reset_volumes();
+#if !ENABLE_EXTENDED_SELECTION
     void deselect_volumes();
     void select_volume(unsigned int id);
     void update_volumes_selection(const std::vector<int>& selections);
+#endif // !ENABLE_EXTENDED_SELECTION
     int check_volumes_outside_state(const DynamicPrintConfig* config) const;
     bool move_volume_up(unsigned int id);
     bool move_volume_down(unsigned int id);
 
+#if !ENABLE_EXTENDED_SELECTION
     void set_objects_selections(const std::vector<int>& selections);
+#endif // !ENABLE_EXTENDED_SELECTION
 
     void set_config(DynamicPrintConfig* config);
     void set_print(Print* print);
     void set_model(Model* model);
 
+#if ENABLE_EXTENDED_SELECTION
+    const Selection& get_selection() const { return m_selection; }
+#endif // ENABLE_EXTENDED_SELECTION
+
     // Set the bed shape to a single closed 2D polygon(array of two element arrays),
     // triangulate the bed and store the triangles into m_bed.m_triangles,
     // fills the m_bed.m_grid_lines and sets m_bed.m_origin.
@@ -594,11 +727,13 @@ public:
     void set_cutting_plane(float z, const ExPolygons& polygons);
 
     void set_color_by(const std::string& value);
+#if !ENABLE_EXTENDED_SELECTION
     void set_select_by(const std::string& value);
     void set_drag_by(const std::string& value);
 
     const std::string& get_select_by() const;
     const std::string& get_drag_by() const;
+#endif // !ENABLE_EXTENDED_SELECTION
 
     float get_camera_zoom() const;
 
@@ -680,7 +815,9 @@ private:
     void _resize(unsigned int w, unsigned int h);
 
     BoundingBoxf3 _max_bounding_box() const;
+#if !ENABLE_EXTENDED_SELECTION
     BoundingBoxf3 _selected_volumes_bounding_box() const;
+#endif // !ENABLE_EXTENDED_SELECTION
 
     void _zoom_to_bounding_box(const BoundingBoxf3& bbox);
     float _get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const;
@@ -694,6 +831,9 @@ private:
     void _render_bed(float theta) const;
     void _render_axes(bool depth_test) const;
     void _render_objects() const;
+#if ENABLE_EXTENDED_SELECTION
+    void _render_selection() const;
+#endif // ENABLE_EXTENDED_SELECTION
     void _render_cutting_plane() const;
     void _render_warning_texture() const;
     void _render_legend_texture() const;
@@ -703,6 +843,10 @@ private:
     void _render_gizmos_overlay() const;
     void _render_toolbar() const;
 
+#if ENABLE_EXTENDED_SELECTION
+    void _update_volumes_hover_state() const;
+#endif // ENABLE_EXTENDED_SELECTION
+
     float _get_layers_editing_cursor_z_relative() const;
     void _perform_layer_editing_action(wxMouseEvent* evt = nullptr);
 
@@ -719,8 +863,10 @@ private:
     void _start_timer();
     void _stop_timer();
 
+#if !ENABLE_EXTENDED_SELECTION
     int _get_first_selected_object_id() const;
     int _get_first_selected_volume_id(int object_id) const;
+#endif // !ENABLE_EXTENDED_SELECTION
 
     // Create 3D thick extrusion lines for a skirt and brim.
     // Adds a new Slic3r::GUI::3DScene::Volume to volumes.
@@ -750,7 +896,11 @@ private:
     void _update_toolpath_volumes_outside_state();
     void _show_warning_texture_if_needed();
 
+#if ENABLE_EXTENDED_SELECTION
+    void _on_move();
+#else
     void _on_move(const std::vector<int>& volume_idxs);
+#endif // ENABLE_EXTENDED_SELECTION
     void _on_select(int volume_idx, int object_idx);
 
     // generates the legend texture in dependence of the current shown view type
diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp
index 8720caba5..87909110e 100644
--- a/src/slic3r/GUI/GLCanvas3DManager.cpp
+++ b/src/slic3r/GUI/GLCanvas3DManager.cpp
@@ -246,6 +246,7 @@ void GLCanvas3DManager::reset_volumes(wxGLCanvas* canvas)
         it->second->reset_volumes();
 }
 
+#if !ENABLE_EXTENDED_SELECTION
 void GLCanvas3DManager::deselect_volumes(wxGLCanvas* canvas)
 {
     CanvasesMap::iterator it = _get_canvas(canvas);
@@ -266,6 +267,7 @@ void GLCanvas3DManager::update_volumes_selection(wxGLCanvas* canvas, const std::
     if (it != m_canvases.end())
         it->second->update_volumes_selection(selections);
 }
+#endif // !ENABLE_EXTENDED_SELECTION
 
 int GLCanvas3DManager::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const
 {
@@ -285,12 +287,14 @@ bool GLCanvas3DManager::move_volume_down(wxGLCanvas* canvas, unsigned int id)
     return (it != m_canvases.end()) ? it->second->move_volume_down(id) : false;
 }
 
+#if !ENABLE_EXTENDED_SELECTION
 void GLCanvas3DManager::set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections)
 {
     CanvasesMap::iterator it = _get_canvas(canvas);
     if (it != m_canvases.end())
         it->second->set_objects_selections(selections);
 }
+#endif // !ENABLE_EXTENDED_SELECTION
 
 void GLCanvas3DManager::set_config(wxGLCanvas* canvas, DynamicPrintConfig* config)
 {
@@ -354,6 +358,7 @@ void GLCanvas3DManager::set_color_by(wxGLCanvas* canvas, const std::string& valu
         it->second->set_color_by(value);
 }
 
+#if !ENABLE_EXTENDED_SELECTION
 void GLCanvas3DManager::set_select_by(wxGLCanvas* canvas, const std::string& value)
 {
     CanvasesMap::iterator it = _get_canvas(canvas);
@@ -373,6 +378,7 @@ std::string GLCanvas3DManager::get_select_by(wxGLCanvas* canvas) const
     CanvasesMap::const_iterator it = _get_canvas(canvas);
     return (it != m_canvases.end()) ? it->second->get_select_by() : "";
 }
+#endif // !ENABLE_EXTENDED_SELECTION
 
 bool GLCanvas3DManager::is_layers_editing_enabled(wxGLCanvas* canvas) const
 {
diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp
index 960bb6d2a..ca882d4ae 100644
--- a/src/slic3r/GUI/GLCanvas3DManager.hpp
+++ b/src/slic3r/GUI/GLCanvas3DManager.hpp
@@ -87,14 +87,18 @@ public:
 
     unsigned int get_volumes_count(wxGLCanvas* canvas) const;
     void reset_volumes(wxGLCanvas* canvas);
+#if !ENABLE_EXTENDED_SELECTION
     void deselect_volumes(wxGLCanvas* canvas);
     void select_volume(wxGLCanvas* canvas, unsigned int id);
     void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections);
+#endif // !ENABLE_EXTENDED_SELECTION
     int check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const;
     bool move_volume_up(wxGLCanvas* canvas, unsigned int id);
     bool move_volume_down(wxGLCanvas* canvas, unsigned int id);
 
+#if !ENABLE_EXTENDED_SELECTION
     void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections);
+#endif // !ENABLE_EXTENDED_SELECTION
 
     void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config);
     void set_print(wxGLCanvas* canvas, Print* print);
@@ -110,10 +114,12 @@ public:
     void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons);
 
     void set_color_by(wxGLCanvas* canvas, const std::string& value);
+#if !ENABLE_EXTENDED_SELECTION
     void set_select_by(wxGLCanvas* canvas, const std::string& value);
     void set_drag_by(wxGLCanvas* canvas, const std::string& value);
 
     std::string get_select_by(wxGLCanvas* canvas) const;
+#endif // !ENABLE_EXTENDED_SELECTION
 
     bool is_layers_editing_enabled(wxGLCanvas* canvas) const;
     bool is_layers_editing_allowed(wxGLCanvas* canvas) const;
diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp
index 55729ac87..72bf18de9 100644
--- a/src/slic3r/GUI/GLGizmo.cpp
+++ b/src/slic3r/GUI/GLGizmo.cpp
@@ -216,6 +216,9 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent)
     : m_parent(parent)
     , m_group_id(-1)
     , m_state(Off)
+#if ENABLE_EXTENDED_SELECTION
+    , m_accept_wipe_tower(false)
+#endif // ENABLE_EXTENDED_SELECTION
     , m_hover_id(-1)
     , m_dragging(false)
 {
@@ -1050,7 +1053,11 @@ const double GLGizmoMove3D::Offset = 10.0;
 
 GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent)
     : GLGizmoBase(parent)
+#if ENABLE_EXTENDED_SELECTION
+    , m_displacement(Vec3d::Zero())
+#else
     , m_position(Vec3d::Zero())
+#endif // ENABLE_EXTENDED_SELECTION
     , m_starting_drag_position(Vec3d::Zero())
     , m_starting_box_center(Vec3d::Zero())
     , m_starting_box_bottom_center(Vec3d::Zero())
@@ -1078,6 +1085,10 @@ bool GLGizmoMove3D::on_init()
         m_grabbers.push_back(Grabber());
     }
 
+#if ENABLE_EXTENDED_SELECTION
+    m_accept_wipe_tower = true;
+#endif // ENABLE_EXTENDED_SELECTION
+
     return true;
 }
 
@@ -1085,6 +1096,9 @@ void GLGizmoMove3D::on_start_dragging(const BoundingBoxf3& box)
 {
     if (m_hover_id != -1)
     {
+#if ENABLE_EXTENDED_SELECTION
+        m_displacement = Vec3d::Zero();
+#endif // ENABLE_EXTENDED_SELECTION
         m_starting_drag_position = m_grabbers[m_hover_id].center;
         m_starting_box_center = box.center();
         m_starting_box_bottom_center = box.center();
@@ -1094,22 +1108,40 @@ void GLGizmoMove3D::on_start_dragging(const BoundingBoxf3& box)
 
 void GLGizmoMove3D::on_update(const Linef3& mouse_ray)
 {
+#if ENABLE_EXTENDED_SELECTION
+    if (m_hover_id == 0)
+        m_displacement(0) = calc_projection(X, 1, mouse_ray) - (m_starting_drag_position(0) - m_starting_box_center(0));
+    else if (m_hover_id == 1)
+        m_displacement(1) = calc_projection(Y, 2, mouse_ray) - (m_starting_drag_position(1) - m_starting_box_center(1));
+    else if (m_hover_id == 2)
+        m_displacement(2) = calc_projection(Z, 1, mouse_ray) - (m_starting_drag_position(2) - m_starting_box_bottom_center(2));
+#else
     if (m_hover_id == 0)
         m_position(0) = 2.0 * m_starting_box_center(0) + calc_projection(X, 1, mouse_ray) - m_starting_drag_position(0);
     else if (m_hover_id == 1)
         m_position(1) = 2.0 * m_starting_box_center(1) + calc_projection(Y, 2, mouse_ray) - m_starting_drag_position(1);
     else if (m_hover_id == 2)
         m_position(2) = 2.0 * m_starting_box_bottom_center(2) + calc_projection(Z, 1, mouse_ray) - m_starting_drag_position(2);
+#endif // ENABLE_EXTENDED_SELECTION
 }
 
 void GLGizmoMove3D::on_render(const BoundingBoxf3& box) const
 {
+#if ENABLE_EXTENDED_SELECTION
+    if (m_grabbers[0].dragging)
+        set_tooltip("X: " + format(m_displacement(0), 2));
+    else if (m_grabbers[1].dragging)
+        set_tooltip("Y: " + format(m_displacement(1), 2));
+    else if (m_grabbers[2].dragging)
+        set_tooltip("Z: " + format(m_displacement(2), 2));
+#else
     if (m_grabbers[0].dragging)
         set_tooltip("X: " + format(m_position(0), 2));
     else if (m_grabbers[1].dragging)
         set_tooltip("Y: " + format(m_position(1), 2));
     else if (m_grabbers[2].dragging)
         set_tooltip("Z: " + format(m_position(2), 2));
+#endif // ENABLE_EXTENDED_SELECTION
 
     ::glEnable(GL_DEPTH_TEST);
 
diff --git a/src/slic3r/GUI/GLGizmo.hpp b/src/slic3r/GUI/GLGizmo.hpp
index ac85caa18..0ad40a8b3 100644
--- a/src/slic3r/GUI/GLGizmo.hpp
+++ b/src/slic3r/GUI/GLGizmo.hpp
@@ -56,6 +56,9 @@ protected:
 
     int m_group_id;
     EState m_state;
+#if ENABLE_EXTENDED_SELECTION
+    bool m_accept_wipe_tower;
+#endif // ENABLE_EXTENDED_SELECTION
     // textures are assumed to be square and all with the same size in pixels, no internal check is done
     GLTexture m_textures[Num_States];
     int m_hover_id;
@@ -77,6 +80,11 @@ public:
     EState get_state() const { return m_state; }
     void set_state(EState state) { m_state = state; on_set_state(); }
 
+#if ENABLE_EXTENDED_SELECTION
+    bool get_accept_wipe_tower() { return m_accept_wipe_tower; }
+    void set_accept_wipe_tower(bool accept) { m_accept_wipe_tower = accept; }
+#endif // ENABLE_EXTENDED_SELECTION
+
     unsigned int get_texture_id() const { return m_textures[m_state].get_id(); }
     int get_textures_size() const { return m_textures[Off].get_width(); }
 
@@ -318,7 +326,11 @@ class GLGizmoMove3D : public GLGizmoBase
 {
     static const double Offset;
 
+#if ENABLE_EXTENDED_SELECTION
+    Vec3d m_displacement;
+#else
     Vec3d m_position;
+#endif // ENABLE_EXTENDED_SELECTION
     Vec3d m_starting_drag_position;
     Vec3d m_starting_box_center;
     Vec3d m_starting_box_bottom_center;
@@ -326,8 +338,12 @@ class GLGizmoMove3D : public GLGizmoBase
 public:
     explicit GLGizmoMove3D(GLCanvas3D& parent);
 
+#if ENABLE_EXTENDED_SELECTION
+    const Vec3d& get_displacement() const { return m_displacement; }
+#else
     const Vec3d& get_position() const { return m_position; }
     void set_position(const Vec3d& position) { m_position = position; }
+#endif // ENABLE_EXTENDED_SELECTION
 
 protected:
     virtual bool on_init();
diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp
index e2995f7c3..623e02a05 100644
--- a/src/slic3r/GUI/GLShader.cpp
+++ b/src/slic3r/GUI/GLShader.cpp
@@ -135,7 +135,7 @@ bool GLShader::load_from_file(const char* fragment_shader_filename, const char*
         return false;
 
     vs.seekg(0, vs.end);
-    int file_length = vs.tellg();
+    int file_length = (int)vs.tellg();
     vs.seekg(0, vs.beg);
     std::string vertex_shader(file_length, '\0');
     vs.read(const_cast<char*>(vertex_shader.data()), file_length);
@@ -149,7 +149,7 @@ bool GLShader::load_from_file(const char* fragment_shader_filename, const char*
         return false;
 
     fs.seekg(0, fs.end);
-    file_length = fs.tellg();
+    file_length = (int)fs.tellg();
     fs.seekg(0, fs.beg);
     std::string fragment_shader(file_length, '\0');
     fs.read(const_cast<char*>(fragment_shader.data()), file_length);
diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp
index f2d882e22..479238368 100644
--- a/src/slic3r/GUI/GLToolbar.cpp
+++ b/src/slic3r/GUI/GLToolbar.cpp
@@ -26,7 +26,9 @@ wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT, SimpleEvent);
 wxDEFINE_EVENT(EVT_GLTOOLBAR_CUT, SimpleEvent);
 wxDEFINE_EVENT(EVT_GLTOOLBAR_SETTINGS, SimpleEvent);
 wxDEFINE_EVENT(EVT_GLTOOLBAR_LAYERSEDITING, SimpleEvent);
+#if !ENABLE_EXTENDED_SELECTION
 wxDEFINE_EVENT(EVT_GLTOOLBAR_SELECTBYPARTS, SimpleEvent);
+#endif // !ENABLE_EXTENDED_SELECTION
 
 
 GLToolbarItem::Data::Data()
diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp
index 43097c007..dc4d10f37 100644
--- a/src/slic3r/GUI/GLToolbar.hpp
+++ b/src/slic3r/GUI/GLToolbar.hpp
@@ -26,7 +26,9 @@ wxDECLARE_EVENT(EVT_GLTOOLBAR_SPLIT, SimpleEvent);
 wxDECLARE_EVENT(EVT_GLTOOLBAR_CUT, SimpleEvent);
 wxDECLARE_EVENT(EVT_GLTOOLBAR_SETTINGS, SimpleEvent);
 wxDECLARE_EVENT(EVT_GLTOOLBAR_LAYERSEDITING, SimpleEvent);
+#if !ENABLE_EXTENDED_SELECTION
 wxDECLARE_EVENT(EVT_GLTOOLBAR_SELECTBYPARTS, SimpleEvent);
+#endif // !ENABLE_EXTENDED_SELECTION
 
 class GLToolbarItem
 {
diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp
index e06f6b721..0e23585f7 100644
--- a/src/slic3r/GUI/GUI.cpp
+++ b/src/slic3r/GUI/GUI.cpp
@@ -347,53 +347,6 @@ std::string into_u8(const wxString &str)
 	return std::string(buffer_utf8.data());
 }
 
-wxWindow* export_option_creator(wxWindow* parent)
-{
-    wxPanel* panel = new wxPanel(parent, -1);
-    wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
-    wxCheckBox* cbox = new wxCheckBox(panel, wxID_HIGHEST + 1, L("Export print config"));
-    cbox->SetValue(true);
-    sizer->AddSpacer(5);
-    sizer->Add(cbox, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5);
-    panel->SetSizer(sizer);
-    sizer->SetSizeHints(panel);
-    return panel;
-}
-
-void add_export_option(wxFileDialog* dlg, const std::string& format)
-{
-    if ((dlg != nullptr) && (format == "AMF") || (format == "3MF"))
-    {
-        if (dlg->SupportsExtraControl())
-            dlg->SetExtraControlCreator(export_option_creator);
-    }
-}
-
-int get_export_option(wxFileDialog* dlg)
-{
-    if (dlg != nullptr)
-    {
-        wxWindow* wnd = dlg->GetExtraControl();
-        if (wnd != nullptr)
-        {
-            wxPanel* panel = dynamic_cast<wxPanel*>(wnd);
-            if (panel != nullptr)
-            {
-                wxWindow* child = panel->FindWindow(wxID_HIGHEST + 1);
-                if (child != nullptr)
-                {
-                    wxCheckBox* cbox = dynamic_cast<wxCheckBox*>(child);
-                    if (cbox != nullptr)
-                        return cbox->IsChecked() ? 1 : 0;
-                }
-            }
-        }
-    }
-
-    return 0;
-
-}
-
 bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height)
 {
 	const auto idx = wxDisplay::GetFromWindow(window);
diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp
index 1f2107d59..3203fd8b2 100644
--- a/src/slic3r/GUI/GUI.hpp
+++ b/src/slic3r/GUI/GUI.hpp
@@ -102,9 +102,6 @@ std::string	into_u8(const wxString &str);
 
 // Callback to trigger a configuration update timer on the Plater.
 static PerlCallback g_on_request_update_callback;
- 
-void add_export_option(wxFileDialog* dlg, const std::string& format);
-int get_export_option(wxFileDialog* dlg);
 
 // Returns the dimensions of the screen on which the main frame is displayed
 bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height);
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 50de1cb4a..3bf068b56 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -34,6 +34,21 @@
 namespace Slic3r {
 namespace GUI {
 
+
+const wxString file_wildcards[FT_SIZE] = {
+    /* FT_STL */   "STL files (*.stl)|*.stl;*.STL",
+    /* FT_OBJ */   "OBJ files (*.obj)|*.obj;*.OBJ",
+    /* FT_AMF */   "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML",
+    /* FT_3MF */   "3MF files (*.3mf)|*.3mf;*.3MF;",
+    /* FT_PRUSA */ "Prusa Control files (*.prusa)|*.prusa;*.PRUSA",
+    /* FT_GCODE */ "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC",
+    /* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA",
+
+    /* FT_INI */   "INI files *.ini|*.ini;*.INI",
+    /* FT_SVG */   "SVG files *.svg|*.svg;*.SVG",
+};
+
+
 static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); }
 
 IMPLEMENT_APP(GUI_App)
@@ -291,7 +306,7 @@ void GUI_App::open_model(wxWindow *parent, wxArrayString& input_files)
     auto dialog = new wxFileDialog(parent ? parent : GetTopWindow(),
         _(L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):")),
         app_config->get_last_dir(), "",
-        MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
+        file_wildcards[FT_MODEL], wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
     if (dialog->ShowModal() != wxID_OK) {
         dialog->Destroy();
         return;
diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp
index 1e4d13004..451b89147 100644
--- a/src/slic3r/GUI/GUI_App.hpp
+++ b/src/slic3r/GUI/GUI_App.hpp
@@ -25,26 +25,24 @@ class ModelObject;
 
 namespace GUI
 {
-// Map from an file_type name to full file wildcard name.
-const std::map<const std::string, const std::string> FILE_WILDCARDS{
-    std::make_pair("known", "Known files (*.stl, *.obj, *.amf, *.xml, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.prusa;*.PRUSA"),
-    std::make_pair("stl",   "STL files (*.stl)|*.stl;*.STL"),
-    std::make_pair("obj",   "OBJ files (*.obj)|*.obj;*.OBJ"),
-    std::make_pair("amf",   "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML"),
-    std::make_pair("3mf",   "3MF files (*.3mf)|*.3mf;*.3MF;"),
-    std::make_pair("prusa", "Prusa Control files (*.prusa)|*.prusa;*.PRUSA"),
-    std::make_pair("ini",   "INI files *.ini|*.ini;*.INI"),
-    std::make_pair("gcode", "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC"),
-    std::make_pair("svg",   "SVG files *.svg|*.svg;*.SVG")
+
+enum FileType
+{
+    FT_STL,
+    FT_OBJ,
+    FT_AMF,
+    FT_3MF,
+    FT_PRUSA,
+    FT_GCODE,
+    FT_MODEL,
+
+    FT_INI,
+    FT_SVG,
+
+    FT_SIZE,
 };
 
-const std::string MODEL_WILDCARD{ FILE_WILDCARDS.at("known") + std::string("|") +
-    FILE_WILDCARDS.at("stl") + std::string("|") +
-    FILE_WILDCARDS.at("obj") + std::string("|") +
-    FILE_WILDCARDS.at("amf") + std::string("|") +
-    FILE_WILDCARDS.at("3mf") + std::string("|") +
-    FILE_WILDCARDS.at("prusa") };
-
+extern const wxString file_wildcards[FT_SIZE];
 
 enum ConfigMenuIDs {
     ConfigMenuWizard,
diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp
index 589f4ce8b..bad81a22e 100644
--- a/src/slic3r/GUI/GUI_ObjectList.cpp
+++ b/src/slic3r/GUI/GUI_ObjectList.cpp
@@ -510,7 +510,7 @@ void ObjectList::menu_item_add_generic(wxMenuItem* &menu, int id) {
         sub_menu->Append(new wxMenuItem(sub_menu, ++id, _(item)));
 
 #ifndef __WXMSW__
-    sub_menu->Bind(wxEVT_MENU, [sub_menu](wxEvent &event) {
+    sub_menu->Bind(wxEVT_MENU, [this, sub_menu](wxEvent &event) {
         load_lambda(sub_menu->GetLabel(event.GetId()).ToStdString());
     });
 #endif //no __WXMSW__
@@ -632,7 +632,7 @@ wxMenu* ObjectList::create_add_settings_popupmenu(bool is_part)
         menu->Append(menu_item);
     }
 #ifndef __WXMSW__
-    menu->Bind(wxEVT_MENU, [menu, is_part](wxEvent &event) {
+    menu->Bind(wxEVT_MENU, [this, menu, is_part](wxEvent &event) {
         get_settings_choice(menu, event.GetId(), is_part);
     });
 #endif //no __WXMSW__
@@ -1032,7 +1032,9 @@ void ObjectList::add_object_to_list(size_t obj_idx)
     auto model_object = (*m_objects)[obj_idx];
     wxString item_name = model_object->name;
     auto item = m_objects_model->Add(item_name, model_object->instances.size());
+#if !ENABLE_EXTENDED_SELECTION
     Select(item);
+#endif // !ENABLE_EXTENDED_SELECTION
 
     // Add error icon if detected auto-repaire
     auto stats = model_object->volumes[0]->mesh.stl.stats;
diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
index c37d1a1a7..afdc60806 100644
--- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp
+++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
@@ -262,6 +262,51 @@ void ObjectManipulation::update_settings_list()
     parent->GetParent()->Layout();
 }
 
+#if ENABLE_EXTENDED_SELECTION
+void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& selection)
+{
+    int object_idx = -1;
+    int instance_idx = -1;
+    if (selection.is_single_full_instance(object_idx, instance_idx))
+    {
+        // all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first
+        const GLCanvas3D::Selection::IndicesList& idxs = selection.get_volume_idxs();
+        update_position_values(selection.get_volume(*idxs.begin())->get_offset());
+        m_og->enable();
+    }
+    else if (selection.is_wipe_tower())
+    {
+        // the selection contains a single volume
+        const GLCanvas3D::Selection::IndicesList& idxs = selection.get_volume_idxs();
+        update_position_values(selection.get_volume(*idxs.begin())->get_offset());
+        m_og->enable();
+    }
+    else if (selection.is_modifier())
+    {
+        // the selection contains a single volume
+        const GLCanvas3D::Selection::IndicesList& idxs = selection.get_volume_idxs();
+        update_position_values(selection.get_volume(*idxs.begin())->get_offset());
+        m_og->enable();
+    }
+    else
+        reset_settings_value();
+}
+
+void ObjectManipulation::reset_settings_value()
+{
+    m_og->set_value("position_x", 0);
+    m_og->set_value("position_y", 0);
+    m_og->set_value("position_z", 0);
+    m_og->set_value("scale_x", 0);
+    m_og->set_value("scale_y", 0);
+    m_og->set_value("scale_z", 0);
+    m_og->set_value("rotation_x", 0);
+    m_og->set_value("rotation_y", 0);
+    m_og->set_value("rotation_z", 0);
+    m_og->disable();
+}
+#endif // ENABLE_EXTENDED_SELECTION
+
 void ObjectManipulation::update_values()
 {
     int selection = ol_selection();
diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp
index 19683362f..2a9ec004e 100644
--- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp
+++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp
@@ -6,6 +6,9 @@
 #include <wx/panel.h>
 
 #include "Preset.hpp"
+#if ENABLE_EXTENDED_SELECTION
+#include "GLCanvas3D.hpp"
+#endif // ENABLE_EXTENDED_SELECTION
 
 class wxBoxSizer;
 
@@ -44,6 +47,11 @@ public:
     int ol_selection();
     void update_settings_list();
 
+#if ENABLE_EXTENDED_SELECTION
+    void update_settings_value(const GLCanvas3D::Selection& selection);
+    void reset_settings_value();
+#endif // ENABLE_EXTENDED_SELECTION
+
     void update_values();
     // update position values displacements or "gizmos"
     void update_position_values();
diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp
new file mode 100644
index 000000000..b8cd60944
--- /dev/null
+++ b/src/slic3r/GUI/GUI_Utils.cpp
@@ -0,0 +1,54 @@
+#include "GUI_Utils.hpp"
+
+#include <wx/sizer.h>
+#include <wx/panel.h>
+#include <wx/checkbox.h>
+
+
+namespace Slic3r {
+namespace GUI {
+
+
+CheckboxFileDialog::CheckboxFileDialog(wxWindow *parent,
+	const wxString &checkbox_label,
+    bool checkbox_value,
+    const wxString &message,
+    const wxString &default_dir,
+    const wxString &default_file,
+    const wxString &wildcard,
+    long style,
+    const wxPoint &pos,
+    const wxSize &size,
+    const wxString &name
+)
+    : wxFileDialog(parent, message, default_dir, default_file, wildcard, style, pos, size, name)
+    , cbox(nullptr)
+{
+	if (checkbox_label.IsEmpty()) {
+		return;
+	}
+
+	extra_control_creator = [this, checkbox_label](wxWindow *parent) -> wxWindow* {
+		wxPanel* panel = new wxPanel(parent, -1);
+	    wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
+	    this->cbox = new wxCheckBox(panel, wxID_HIGHEST + 1, checkbox_label);
+	    this->cbox->SetValue(true);
+	    sizer->AddSpacer(5);
+	    sizer->Add(this->cbox, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5);
+	    panel->SetSizer(sizer);
+	    sizer->SetSizeHints(panel);
+
+	    return panel;
+	};
+
+    SetExtraControlCreator(*extra_control_creator.target<ExtraControlCreatorFunction>());
+}
+
+bool CheckboxFileDialog::get_checkbox_value() const
+{
+	return this->cbox != nullptr ? cbox->IsChecked() : false;
+}
+
+
+}
+}
diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp
new file mode 100644
index 000000000..bf2ee961f
--- /dev/null
+++ b/src/slic3r/GUI/GUI_Utils.hpp
@@ -0,0 +1,41 @@
+#ifndef slic3r_GUI_Utils_hpp_
+#define slic3r_GUI_Utils_hpp_
+
+#include <functional>
+
+#include <wx/filedlg.h>
+
+class wxCheckBox;
+
+
+namespace Slic3r {
+namespace GUI {
+
+
+class CheckboxFileDialog : public wxFileDialog
+{
+public:
+    CheckboxFileDialog(wxWindow *parent,
+        const wxString &checkbox_label,
+        bool checkbox_value,
+        const wxString &message = wxFileSelectorPromptStr,
+        const wxString &default_dir = wxEmptyString,
+        const wxString &default_file = wxEmptyString,
+        const wxString &wildcard = wxFileSelectorDefaultWildcardStr,
+        long style = wxFD_DEFAULT_STYLE,
+        const wxPoint &pos = wxDefaultPosition,
+        const wxSize &size = wxDefaultSize,
+        const wxString &name = wxFileDialogNameStr
+    );
+
+    bool get_checkbox_value() const;
+
+private:
+    std::function<wxWindow*(wxWindow*)> extra_control_creator;
+    wxCheckBox *cbox;
+};
+
+
+}}
+
+#endif
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index 1210d5faa..8df8e338a 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -441,7 +441,7 @@ void MainFrame::quick_slice(const int qs){
     if (!(qs & qsReslice)) {
         auto dlg = new wxFileDialog(this, _(L("Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):")),
             wxGetApp().app_config->get_last_dir(), "",
-            MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+            file_wildcards[FT_MODEL], wxFD_OPEN | wxFD_FILE_MUST_EXIST);
         if (dlg->ShowModal() != wxID_OK) {
             dlg->Destroy();
             return;
@@ -500,7 +500,7 @@ void MainFrame::quick_slice(const int qs){
 //         output_file = ~s / \.[gG][cC][oO][dD][eE]$ / .svg /;
         auto dlg = new wxFileDialog(this, _(L("Save ")) + (qs & qsExportSVG ? _(L("SVG")) : _(L("G-code"))) + _(L(" file as:")),
             wxGetApp().app_config->get_last_output_dir(get_dir_name(output_file)), get_base_name(input_file), 
-            qs & qsExportSVG ? FILE_WILDCARDS.at("svg") : FILE_WILDCARDS.at("gcode"),
+            qs & qsExportSVG ? file_wildcards[FT_SVG] : file_wildcards[FT_GCODE],
             wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
         if (dlg->ShowModal() != wxID_OK) {
             dlg->Destroy();
@@ -568,7 +568,7 @@ void MainFrame::repair_stl()
     {
         auto dlg = new wxFileDialog(this, _(L("Select the STL file to repair:")),
             wxGetApp().app_config->get_last_dir(), "",
-            FILE_WILDCARDS.at("stl"), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+            file_wildcards[FT_STL], wxFD_OPEN | wxFD_FILE_MUST_EXIST);
         if (dlg->ShowModal() != wxID_OK) {
             dlg->Destroy();
             return;
@@ -582,7 +582,7 @@ void MainFrame::repair_stl()
 //         output_file = ~s / \.[sS][tT][lL]$ / _fixed.obj / ;
         auto dlg = new wxFileDialog( this, L("Save OBJ file (less prone to coordinate errors than STL) as:"), 
                                         get_dir_name(output_file), get_base_name(output_file), 
-                                        FILE_WILDCARDS.at("obj"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
+                                        file_wildcards[FT_OBJ], wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
         if (dlg->ShowModal() != wxID_OK) {
             dlg->Destroy();
             return /*undef*/;
@@ -612,7 +612,7 @@ void MainFrame::export_config()
     auto dlg = new wxFileDialog(this, _(L("Save configuration as:")),
         !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
         !m_last_config.IsEmpty() ? get_base_name(m_last_config) : "config.ini",
-        FILE_WILDCARDS.at("ini"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
+        file_wildcards[FT_INI], wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
     wxString file;
     if (dlg->ShowModal() == wxID_OK)
         file = dlg->GetPath();
@@ -664,7 +664,7 @@ void MainFrame::export_configbundle()
     auto dlg = new wxFileDialog(this, _(L("Save presets bundle as:")),
         !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
         "Slic3r_config_bundle.ini",
-        FILE_WILDCARDS.at("ini"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
+        file_wildcards[FT_INI], wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
     wxString file;
     if (dlg->ShowModal() == wxID_OK)
         file = dlg->GetPath();
@@ -688,7 +688,7 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re
     if (file.IsEmpty()) {
         auto dlg = new wxFileDialog(this, _(L("Select configuration to load:")),
             !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
-            "config.ini", FILE_WILDCARDS.at("ini"), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+            "config.ini", file_wildcards[FT_INI], wxFD_OPEN | wxFD_FILE_MUST_EXIST);
         if (dlg->ShowModal() != wxID_OK)
             return;
         file = dlg->GetPath();
@@ -755,7 +755,7 @@ void MainFrame::on_presets_changed(SimpleEvent &event)
 
     // Update preset combo boxes(Print settings, Filament, Material, Printer) from their respective tabs.
     auto presets = tab->get_presets();
-    if (presets) {
+    if (m_plater != nullptr && presets != nullptr) {
         auto reload_dependent_tabs = tab->get_dependent_tabs();
 
         // FIXME: The preset type really should be a property of Tab instead
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 2a185205e..23e26a73f 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -5,6 +5,7 @@
 #include <vector>
 #include <string>
 #include <regex>
+#include <boost/optional.hpp>
 #include <boost/filesystem/path.hpp>
 
 #include <wx/sizer.h>
@@ -25,10 +26,14 @@
 #include "libslic3r/GCode/PreviewData.hpp"
 #include "libslic3r/Utils.hpp"
 #include "libslic3r/Polygon.hpp"
+#include "libslic3r/Format/STL.hpp"
+#include "libslic3r/Format/AMF.hpp"
+#include "libslic3r/Format/3mf.hpp"
 #include "GUI.hpp"
 #include "GUI_App.hpp"
 #include "GUI_ObjectList.hpp"
 #include "GUI_ObjectManipulation.hpp"
+#include "GUI_Utils.hpp"
 #include "MainFrame.hpp"
 #include "3DScene.hpp"
 #include "GLCanvas3D.hpp"
@@ -43,6 +48,7 @@
 #include <wx/glcanvas.h>    // Needs to be last because reasons :-/
 #include "WipeTowerDialog.hpp"
 
+using boost::optional;
 namespace fs = boost::filesystem;
 using Slic3r::_3DScene;
 using Slic3r::Preset;
@@ -340,7 +346,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) :
 
 struct Sidebar::priv
 {
-    // Sidebar *q;      // PIMPL back pointer ("Q-Pointer")
+    Plater *plater;
 
     wxScrolledWindow *scrolled;
 
@@ -363,6 +369,8 @@ struct Sidebar::priv
     // wxButton *btn_print;  // XXX: remove
     wxButton *btn_send_gcode;
 
+    priv(Plater *plater) : plater(plater) {}
+
     bool show_manifold_warning_icon = false;
     bool show_print_info = false;
 };
@@ -370,8 +378,8 @@ struct Sidebar::priv
 
 // Sidebar / public
 
-Sidebar::Sidebar(wxWindow *parent)
-    : wxPanel(parent), p(new priv)
+Sidebar::Sidebar(Plater *parent)
+    : wxPanel(parent), p(new priv(parent))
 {
     p->scrolled = new wxScrolledWindow(this);
 
@@ -460,6 +468,11 @@ Sidebar::Sidebar(wxWindow *parent)
     sizer->Add(p->scrolled, 1, wxEXPAND | wxTOP, 5);
     sizer->Add(btns_sizer, 0, wxEXPAND | wxLEFT, 20);
     SetSizer(sizer);
+
+    // Events
+    p->btn_export_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(); });
+    p->btn_reslice->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->reslice(); });
+    p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); });
 }
 
 Sidebar::~Sidebar() {}
@@ -558,6 +571,13 @@ void Sidebar::show_buttons(const bool show)
     }
 }
 
+void Sidebar::enable_buttons(bool enable)
+{
+    p->btn_reslice->Enable(enable);
+    p->btn_export_gcode->Enable(enable);
+    p->btn_send_gcode->Enable(enable);
+}
+
 // Plater::Object
 
 struct PlaterObject
@@ -579,13 +599,28 @@ public:
 
 private:
     Plater *plater;
+
+    static const std::regex pattern_drop;
 };
 
+const std::regex PlaterDropTarget::pattern_drop("[.](stl|obj|amf|3mf|prusa)$", std::regex::icase);
+
 bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames)
 {
-    // TODO
-    // return false;
-    throw 0;
+    std::vector<fs::path> paths;
+
+    for (const auto &filename : filenames) {
+        fs::path path(filename);
+
+        if (std::regex_match(path.string(), pattern_drop)) {
+            paths.push_back(std::move(path));
+        } else {
+            return false;
+        }
+    }
+
+    plater->load_files(paths);
+    return true;
 }
 
 // Plater / private
@@ -603,8 +638,8 @@ struct Plater::priv
     Slic3r::GCodePreviewData gcode_preview_data;
     std::vector<PlaterObject> objects;
 
-    std::string export_gcode_output_file;
-    std::string send_gcode_file;
+    fs::path export_gcode_output_file;
+    fs::path send_gcode_file;
 
     // GUI elements
     wxNotebook *notebook;
@@ -613,14 +648,15 @@ struct Plater::priv
     Preview *preview;
     BackgroundSlicingProcess background_process;
 
-    static const int gl_attrs[];
     static const std::regex pattern_bundle;
     static const std::regex pattern_3mf;
     static const std::regex pattern_zip_amf;
 
     priv(Plater *q, MainFrame *main_frame);
 
+#if !ENABLE_EXTENDED_SELECTION
     std::vector<int> collect_selections();
+#endif // !ENABLE_EXTENDED_SELECTION
     void update(bool force_autocenter = false);
     void update_ui_from_settings();
     ProgressStatusBar* statusbar();
@@ -629,19 +665,47 @@ struct Plater::priv
     BoundingBox scaled_bed_shape_bb() const;
     std::vector<size_t> load_files(const std::vector<fs::path> &input_files);
     std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects);
+    std::unique_ptr<CheckboxFileDialog> get_export_file(GUI::FileType file_type);
 
-    void on_notebook_changed(wxBookCtrlEvent &);
-    void on_select_preset(wxCommandEvent &);
-    void on_update_print_preview(wxCommandEvent &);
-    void on_process_completed(wxCommandEvent &);
+    void select_object(optional<size_t> obj_idx);
+    optional<size_t> selected_object() const;
+    void selection_changed();
+    void object_list_changed();
+
+    void remove(size_t obj_idx);
+    void reset();
+    void increase(size_t num = 1);
+    void decrease(size_t num = 1);
+
+    void on_notebook_changed(wxBookCtrlEvent&);
+    void on_select_preset(wxCommandEvent&);
+    void on_update_print_preview(wxCommandEvent&);
+    void on_process_completed(wxCommandEvent&);
     void on_layer_editing_toggled(bool enable);
-    void on_action_add(SimpleEvent&);
 
-    void on_viewport_changed(SimpleEvent& evt);
+
+    void on_action_add(SimpleEvent&);
+    void on_action_arrange(SimpleEvent&);
+    void on_action_more(SimpleEvent&);
+    void on_action_fewer(SimpleEvent&);
+    void on_action_split(SimpleEvent&);
+    void on_action_cut(SimpleEvent&);
+    void on_action_settings(SimpleEvent&);
+    void on_action_layersediting(SimpleEvent&);
+    void on_action_selectbyparts(SimpleEvent&);
+
+    void on_viewport_changed(SimpleEvent&);
+    void on_right_click(Vec2dEvent&);
+    void on_model_update(SimpleEvent&);
+    void on_remove_object(SimpleEvent&);
+    void on_arrange(SimpleEvent&);
+    void on_scale_uniformly(SimpleEvent&);
+    void on_instance_moves(SimpleEvent&);
+    void on_wipetower_moved(Vec3dEvent&);
+    void on_enable_action_buttons(Event<bool>&);
+    void on_update_geometry(Vec3dsEvent<2>&);
 };
 
-// TODO: multisample, see 3DScene.pm
-const int Plater::priv::gl_attrs[] = {WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24, 0};
 const std::regex Plater::priv::pattern_bundle("[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)$", std::regex::icase);
 const std::regex Plater::priv::pattern_3mf("[.]3mf$", std::regex::icase);
 const std::regex Plater::priv::pattern_zip_amf("[.]zip[.]amf$", std::regex::icase);
@@ -674,8 +738,10 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) :
     _3DScene::enable_picking(canvas3D, true);
     _3DScene::enable_moving(canvas3D, true);
     // XXX: more config from 3D.pm
+#if !ENABLE_EXTENDED_SELECTION
     _3DScene::set_select_by(canvas3D, "object");
     _3DScene::set_drag_by(canvas3D, "instance");
+#endif // !ENABLE_EXTENDED_SELECTION
     _3DScene::set_model(canvas3D, &model);
     _3DScene::set_print(canvas3D, &print);
     _3DScene::set_config(canvas3D, config);
@@ -714,18 +780,35 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) :
     // Preset change event
     sidebar->Bind(wxEVT_COMBOBOX, &priv::on_select_preset, this);
 
-    // Sidebar button events
-    sidebar->p->btn_export_gcode->Bind(wxEVT_BUTTON, [q](wxCommandEvent&) { q->export_gcode(""); });
-    sidebar->p->btn_reslice->Bind(wxEVT_BUTTON, [q](wxCommandEvent&) { q->reslice(); });
-    sidebar->p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
-        this->send_gcode_file = this->q->export_gcode("");
-    });
-
     // 3DScene events:
-    // TODO: more
-    canvas3D->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
+    canvas3D->Bind(EVT_GLCANVAS_OBJECT_SELECT, [](ObjectSelectEvent&) { /*TODO*/ });
     canvas3D->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this);
+    // canvas3D->Bind(EVT_GLCANVAS_DOUBLE_CLICK, [](SimpleEvent&) { });  // XXX: remove?
+    canvas3D->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this);
+    canvas3D->Bind(EVT_GLCANVAS_MODEL_UPDATE, &priv::on_model_update, this);
+    canvas3D->Bind(EVT_GLCANVAS_REMOVE_OBJECT, &priv::on_remove_object, this);
+    canvas3D->Bind(EVT_GLCANVAS_ARRANGE, &priv::on_arrange, this);
+    canvas3D->Bind(EVT_GLCANVAS_ROTATE_OBJECT, [this](Event<int> &evt) { /*TODO: call rotate */ });
+    canvas3D->Bind(EVT_GLCANVAS_SCALE_UNIFORMLY, &priv::on_scale_uniformly, this);
+    canvas3D->Bind(EVT_GLCANVAS_INCREASE_OBJECTS, [this](Event<int> &evt) { evt.data == 1 ? increase() : decrease(); });
+    canvas3D->Bind(EVT_GLCANVAS_INSTANCE_MOVES, &priv::on_instance_moves, this);
+    canvas3D->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this);
+    canvas3D->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, &priv::on_enable_action_buttons, this);
+    canvas3D->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this);
+    // 3DScene/Toolbar:
+    canvas3D->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
+    canvas3D->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); } );
+    canvas3D->Bind(EVT_GLTOOLBAR_DELETE_ALL, [this](SimpleEvent&) { reset(); });
+    canvas3D->Bind(EVT_GLTOOLBAR_ARRANGE, &priv::on_action_arrange, this);
+    canvas3D->Bind(EVT_GLTOOLBAR_MORE, &priv::on_action_more, this);
+    canvas3D->Bind(EVT_GLTOOLBAR_FEWER, &priv::on_action_fewer, this);
+    canvas3D->Bind(EVT_GLTOOLBAR_SPLIT, &priv::on_action_split, this);
+    canvas3D->Bind(EVT_GLTOOLBAR_CUT, &priv::on_action_cut, this);
+    canvas3D->Bind(EVT_GLTOOLBAR_SETTINGS, &priv::on_action_settings, this);
+    canvas3D->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this);
+    canvas3D->Bind(EVT_GLTOOLBAR_SELECTBYPARTS, &priv::on_action_selectbyparts, this);
 
+    // Preview events:
     preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this);
 
     q->Bind(EVT_SLICING_COMPLETED, &priv::on_update_print_preview, this);
@@ -738,6 +821,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) :
     q->Layout();
 }
 
+#if !ENABLE_EXTENDED_SELECTION
 std::vector<int> Plater::priv::collect_selections()
 {
     std::vector<int> res;
@@ -746,6 +830,7 @@ std::vector<int> Plater::priv::collect_selections()
     }
     return res;
 }
+#endif // !ENABLE_EXTENDED_SELECTION
 
 void Plater::priv::update(bool force_autocenter)
 {
@@ -761,8 +846,10 @@ void Plater::priv::update(bool force_autocenter)
     // stop_background_process();   // TODO
     print.reload_model_instances();
 
+#if !ENABLE_EXTENDED_SELECTION
     const auto selections = collect_selections();
     _3DScene::set_objects_selections(canvas3D, selections);
+#endif // !ENABLE_EXTENDED_SELECTION
     _3DScene::reload_scene(canvas3D, false);
     preview->reset_gcode_preview_data();
     preview->reload_print();
@@ -805,7 +892,7 @@ BoundingBox Plater::priv::scaled_bed_shape_bb() const
 
 std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path> &input_files)
 {
-    if (input_files.size() < 1) { return std::vector<size_t>(); }
+    if (input_files.empty()) { return std::vector<size_t>(); }
 
     auto *nozzle_dmrs = config->opt<ConfigOptionFloats>("nozzle_diameter");
 
@@ -878,9 +965,6 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path> &input_
         }
 
         if (one_by_one) {
-            // TODO:
-            // push @obj_idx, $self->load_model_objects(@{$model->objects});
-            // obj_idx.push_back(load_model_objects(model.objects);
             auto loaded_idxs = load_model_objects(model.objects);
             obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
         } else {
@@ -901,9 +985,6 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path> &input_
             new_model->convert_multipart_object(nozzle_dmrs->values.size());
         }
 
-        // TODO:
-        // push @obj_idx, $self->load_model_objects(@{$new_model->objects});
-        // obj_idx.push_back(load_model_objects(new_model->objects);
         auto loaded_idxs = load_model_objects(model.objects);
         obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
     }
@@ -914,18 +995,11 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path> &input_
     return obj_idxs;
 }
 
-
-// TODO: move to Point.hpp
-Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); }
-Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); }
-Vec3i64 to_3d(const Vec2i64 &v, float z) { return Vec3i64(v(0), v(1), z); }
-Vec3crd to_3d(const Point &p, coord_t z) { return Vec3crd(p(0), p(1), z); }
-
-std::vector<size_t>  Plater::priv::load_model_objects(const ModelObjectPtrs &model_objects)
+std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &model_objects)
 {
     const BoundingBoxf bed_shape = bed_shape_bb();
-    const Vec3d bed_center = to_3d(bed_shape.center().cast<double>(), 0.0);
-    const Vec3d bed_size = to_3d(bed_shape.size().cast<double>(), 1.0);
+    const Vec3d bed_center = Slic3r::to_3d(bed_shape.center().cast<double>(), 0.0);
+    const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast<double>(), 1.0);
 
     bool need_arrange = false;
     bool scaled_down = false;
@@ -937,7 +1011,7 @@ std::vector<size_t>  Plater::priv::load_model_objects(const ModelObjectPtrs &mod
         objects.emplace_back(std::move(object_name));
         obj_idxs.push_back(objects.size() - 1);
 
-        if (model_object->instances.size() == 0) {
+        if (model_object->instances.empty()) {
             // if object has no defined position(s) we need to rearrange everything after loading
             need_arrange = true;
 
@@ -993,12 +1067,229 @@ std::vector<size_t>  Plater::priv::load_model_objects(const ModelObjectPtrs &mod
     return obj_idxs;
 }
 
+std::unique_ptr<CheckboxFileDialog> Plater::priv::get_export_file(GUI::FileType file_type)
+{
+    wxString wildcard;
+    switch (file_type) {
+        case FT_STL:
+        case FT_AMF:
+        case FT_3MF:
+            wildcard = file_wildcards[FT_STL];
+        break;
+
+        default:
+            wildcard = file_wildcards[FT_MODEL];
+        break;
+    }
+
+    fs::path output_file(print.output_filepath(std::string()));
+
+    switch (file_type) {
+        case FT_STL: output_file.replace_extension("stl"); break;
+        case FT_AMF: output_file.replace_extension("zip.amf"); break;   // XXX: Problem on OS X with double extension?
+        case FT_3MF: output_file.replace_extension("3mf"); break;
+        default: break;
+    }
+
+    wxGetApp().preset_bundle->export_selections(print.placeholder_parser());
+
+    auto dlg = Slic3r::make_unique<CheckboxFileDialog>(q,
+        _(L("Export print config")),
+        true,
+        _(L("Save file as:")),
+        output_file.parent_path().string(),
+        output_file.filename().string(),
+        wildcard,
+        wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+    );
+
+    if (dlg->ShowModal() != wxID_OK) {
+        return nullptr;
+    }
+
+    fs::path path(dlg->GetPath());
+    wxGetApp().app_config->update_last_output_dir(path.parent_path().string());
+    export_gcode_output_file = path;
+
+    return dlg;
+}
+
+void Plater::priv::select_object(optional<size_t> obj_idx)
+{
+    for (auto &obj : objects) {
+        obj.selected = false;
+    }
+
+    if (obj_idx) {
+        objects[*obj_idx].selected = true;
+    }
+
+    selection_changed();
+}
+
+optional<size_t> Plater::priv::selected_object() const
+{
+    for (size_t i = 0; i < objects.size(); i++) {
+        if (objects[i].selected) { return i; }
+    }
+
+    return boost::none;
+}
+
+void Plater::priv::selection_changed()
+{
+    // TODO
+
+    const auto obj_idx = selected_object();
+    const bool have_sel = !!obj_idx;
+    const bool layers_height_allowed = config->opt<ConfigOptionBool>("variable_layer_height")->value;
+
+    wxWindowUpdateLocker freeze_guard(sidebar);
+
+    _3DScene::enable_toolbar_item(canvas3D, "delete", have_sel);
+    _3DScene::enable_toolbar_item(canvas3D, "more", have_sel);
+    _3DScene::enable_toolbar_item(canvas3D, "fewer", have_sel);
+    _3DScene::enable_toolbar_item(canvas3D, "split", have_sel);
+    _3DScene::enable_toolbar_item(canvas3D, "cut", have_sel);
+    _3DScene::enable_toolbar_item(canvas3D, "settings", have_sel);
+
+    _3DScene::enable_toolbar_item(canvas3D, "layersediting", layers_height_allowed);
+
+    bool can_select_by_parts = false;
+
+    if (have_sel) {
+        const auto *model_object = model.objects[*obj_idx];
+        // XXX: ?
+        can_select_by_parts = *obj_idx < 1000 && model_object->volumes.size() > 1;
+        _3DScene::enable_toolbar_item(canvas3D, "fewer", model_object->instances.size() > 1);
+    }
+
+    if (can_select_by_parts) {
+        // first disable to let the item in the toolbar to switch to the unpressed state   // XXX: ?
+        _3DScene::enable_toolbar_item(canvas3D, "selectbyparts", false);
+        _3DScene::enable_toolbar_item(canvas3D, "selectbyparts", true);
+    } else {
+        _3DScene::enable_toolbar_item(canvas3D, "selectbyparts", false);
+        _3DScene::set_select_by(canvas3D, "object");
+    }
+
+    if (have_sel) {
+        const auto *model_object = model.objects[*obj_idx];
+        // FIXME print_info runs model fixing in two rounds, it is very slow, it should not be performed here!
+        // # $model_object->print_info;
+
+        // my $model_instance = $model_object->instances->[0];
+        const auto *model_instance = model_object->instances[0];
+        // TODO
+        // $self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", @{$model_object->instance_bounding_box(0)->size}));
+        // $self->{object_info_materials}->SetLabel($model_object->materials_count);
+
+        // if (my $stats = $model_object->mesh_stats) {
+        //     $self->{object_info_volume}->SetLabel(sprintf('%.2f', $stats->{volume} * ($model_instance->scaling_factor**3)));
+        //     $self->{object_info_facets}->SetLabel(sprintf(L('%d (%d shells)'), $model_object->facets_count, $stats->{number_of_parts}));
+        //     if (my $errors = sum(@$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)})) {
+        //         $self->{object_info_manifold}->SetLabel(sprintf(L("Auto-repaired (%d errors)"), $errors));
+        //         #$self->{object_info_manifold_warning_icon}->Show;
+        //         $self->{"object_info_manifold_warning_icon_show"}->(1);
+
+        //         # we don't show normals_fixed because we never provide normals
+        //         # to admesh, so it generates normals for all facets
+        //         my $message = sprintf L('%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges'),
+        //             @$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)};
+        //         $self->{object_info_manifold}->SetToolTipString($message);
+        //         $self->{object_info_manifold_warning_icon}->SetToolTipString($message);
+        //     } else {
+        //         $self->{object_info_manifold}->SetLabel(L("Yes"));
+        //         #$self->{object_info_manifold_warning_icon}->Hide;
+        //         $self->{"object_info_manifold_warning_icon_show"}->(0);
+        //         $self->{object_info_manifold}->SetToolTipString("");
+        //         $self->{object_info_manifold_warning_icon}->SetToolTipString("");
+        //     }
+        // } else {
+        //     $self->{object_info_facets}->SetLabel($object->facets);
+        // }
+    } else {
+        // $self->{"object_info_$_"}->SetLabel("") for qw(size volume facets materials manifold);
+        // $self->{"object_info_manifold_warning_icon_show"}->(0);
+        // $self->{object_info_manifold}->SetToolTipString("");
+        // $self->{object_info_manifold_warning_icon}->SetToolTipString("");
+    }
+
+    q->Layout();
+}
+
+void Plater::priv::object_list_changed()
+{
+    // Enable/disable buttons depending on whether there are any objects on the platter.
+    const bool have_objects = !objects.empty();
+
+    _3DScene::enable_toolbar_item(canvas3D, "deleteall", have_objects);
+    _3DScene::enable_toolbar_item(canvas3D, "arrange", have_objects);
+
+    const bool export_in_progress = !(export_gcode_output_file.empty() && send_gcode_file.empty());
+    // XXX: is this right?
+    const bool model_fits = _3DScene::check_volumes_outside_state(canvas3D, config) == ModelInstance::PVS_Inside;
+
+    sidebar->enable_buttons(have_objects && !export_in_progress && model_fits);
+}
+
+void Plater::priv::remove(size_t obj_idx)
+{
+    // $self->stop_background_process;   // TODO
+
+    // Prevent toolpaths preview from rendering while we modify the Print object
+    preview->set_enabled(false);
+
+    objects.erase(objects.begin() + obj_idx);
+    model.delete_object(obj_idx);
+    print.delete_object(obj_idx);
+    // Delete object from Sidebar list
+    sidebar->obj_list()->delete_object_from_list();
+
+    object_list_changed();
+
+    select_object(boost::none);
+    update();
+}
+
+void Plater::priv::reset()
+{
+    // $self->stop_background_process;   // TODO
+
+    // Prevent toolpaths preview from rendering while we modify the Print object
+    preview->set_enabled(false);
+
+    objects.clear();
+    model.clear_objects();
+    print.clear_objects();
+
+    // Delete all objects from list on c++ side
+    sidebar->obj_list()->delete_all_objects_from_list();
+    object_list_changed();
+
+    select_object(boost::none);
+    update();
+}
+
+void Plater::priv::increase(size_t num)
+{
+    // TODO
+}
+
+void Plater::priv::decrease(size_t num)
+{
+    // TODO
+}
+
+
 void Plater::priv::on_notebook_changed(wxBookCtrlEvent&)
 {
     const auto current_id = notebook->GetCurrentPage()->GetId();
     if (current_id == canvas3D->GetId()) {
         if (_3DScene::is_reload_delayed(canvas3D)) {
+#if !ENABLE_EXTENDED_SELECTION
             _3DScene::set_objects_selections(canvas3D, collect_selections());
+#endif // !ENABLE_EXTENDED_SELECTION
             _3DScene::reload_scene(canvas3D, true);
         }
         // sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably)
@@ -1071,6 +1362,47 @@ void Plater::priv::on_action_add(SimpleEvent&)
     load_files(input_paths);
 }
 
+void Plater::priv::on_action_arrange(SimpleEvent&)
+{
+    // TODO
+}
+
+void Plater::priv::on_action_more(SimpleEvent&)
+{
+    // TODO
+}
+
+void Plater::priv::on_action_fewer(SimpleEvent&)
+{
+    // TODO
+}
+
+void Plater::priv::on_action_split(SimpleEvent&)
+{
+    // TODO
+}
+
+void Plater::priv::on_action_cut(SimpleEvent&)
+{
+    // TODO
+}
+
+void Plater::priv::on_action_settings(SimpleEvent&)
+{
+    // TODO
+}
+
+void Plater::priv::on_action_layersediting(SimpleEvent&)
+{
+    // TODO
+}
+
+void Plater::priv::on_action_selectbyparts(SimpleEvent&)
+{
+    // TODO
+}
+
+
 void Plater::priv::on_viewport_changed(SimpleEvent& evt)
 {
     wxObject* o = evt.GetEventObject();
@@ -1080,6 +1412,53 @@ void Plater::priv::on_viewport_changed(SimpleEvent& evt)
         preview->set_viewport_from_scene(canvas3D);
 }
 
+void Plater::priv::on_right_click(Vec2dEvent&)
+{
+    // TODO
+}
+
+void Plater::priv::on_model_update(SimpleEvent&)
+{
+    // TODO
+}
+
+void Plater::priv::on_remove_object(SimpleEvent&)
+{
+    // TODO
+}
+
+void Plater::priv::on_arrange(SimpleEvent&)
+{
+    // TODO
+}
+
+void Plater::priv::on_scale_uniformly(SimpleEvent&)
+{
+    // TODO
+}
+
+void Plater::priv::on_instance_moves(SimpleEvent&)
+{
+    // TODO
+}
+
+void Plater::priv::on_wipetower_moved(Vec3dEvent&)
+{
+    // TODO
+}
+
+void Plater::priv::on_enable_action_buttons(Event<bool>&)
+{
+    // TODO
+}
+
+void Plater::priv::on_update_geometry(Vec3dsEvent<2>&)
+{
+    // TODO
+}
+
+
+
 // Plater / Public
 
 Plater::Plater(wxWindow *parent, MainFrame *main_frame)
@@ -1096,64 +1475,136 @@ Plater::~Plater()
 Sidebar& Plater::sidebar() { return *p->sidebar; }
 Model&  Plater::model()  { return p->model; }
 
-std::string Plater::export_gcode(const std::string &output_path)
+void Plater::update(bool force_autocenter) { p->update(force_autocenter); }
+void Plater::remove(size_t obj_idx) { p->remove(obj_idx); }
+
+void Plater::remove_selected()
 {
-    if (p->objects.size() == 0) { return ""; }
+    const auto selected = p->selected_object();
+    if (selected) {
+        remove(*selected);
+    }
+}
+
+void Plater::load_files(const std::vector<fs::path> &input_files) { p->load_files(input_files); }
+
+fs::path Plater::export_gcode(const fs::path &output_path)
+{
+    if (p->objects.empty()) { return ""; }
 
     if (! p->export_gcode_output_file.empty()) {
         GUI::show_error(this, _(L("Another export job is currently running.")));
         return "";
     }
 
-    // wxTheApp->{preset_bundle}->full_config->validate;   // FIXME
-    const std::string err = p->print.validate();
+    std::string err = wxGetApp().preset_bundle->full_config().validate();
+    if (err.empty()) {
+        err = p->print.validate();
+    }
     if (! err.empty()) {
         // The config is not valid
         GUI::show_error(this, _(err));
-        return "";
+        return fs::path();
     }
 
     // Copy the names of active presets into the placeholder parser.
-    // wxTheApp->{preset_bundle}->export_selections_pp($self->{print}->placeholder_parser);   // FIXME
+    wxGetApp().preset_bundle->export_selections(p->print.placeholder_parser());
 
     // select output file
     if (! output_path.empty()) {
-        p->export_gcode_output_file = p->print.output_filepath(output_path);
+        p->export_gcode_output_file = fs::path(p->print.output_filepath(output_path.string()));
         // FIXME: ^ errors to handle?
     } else {
-        // FIXME:
-        std::string default_output_file;  // FIXME: tmp
-        // my $default_output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') };
-        // Slic3r::GUI::catch_error($self) and return;
+
+        // XXX: take output path from CLI opts? Ancient Slic3r versions used to do that...
 
         // If possible, remove accents from accented latin characters.
         // This function is useful for generating file names to be processed by legacy firmwares.
-        default_output_file = Slic3r::fold_utf8_to_ascii(default_output_file);
+        auto default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(
+            p->print.output_filepath(output_path.string())
+            // FIXME: ^ errors to handle?
+        ));
+        auto start_dir = wxGetApp().app_config->get_last_output_dir(default_output_file.parent_path().string());
         wxFileDialog dlg(this, _(L("Save G-code file as:")),
-            wxEmptyString,
-            wxEmptyString,
-            Slic3r::GUI::FILE_WILDCARDS.at("gcode"),
+            start_dir,
+            default_output_file.filename().string(),
+            GUI::file_wildcards[FT_GCODE],
             wxFD_SAVE | wxFD_OVERWRITE_PROMPT
         );
-        // FIXME: ^ defaultDir:
-        // wxTheApp->{app_config}->get_last_output_dir(dirname($default_output_file)),
-        // FIXME: ^ defaultFile:
-        // basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
 
         if (dlg.ShowModal() != wxID_OK) { return ""; }
-        auto path = dlg.GetPath();
-        // wxTheApp->{app_config}->update_last_output_dir(dirname($path));   // FIXME
+        fs::path path(dlg.GetPath());
+        wxGetApp().app_config->update_last_output_dir(path.parent_path().string());
         p->export_gcode_output_file = path;
     }
 
     return p->export_gcode_output_file;
 }
 
+void Plater::export_stl()
+{
+    if (p->objects.empty()) { return; }
+
+    auto dialog = p->get_export_file(FT_STL);
+    if (! dialog) { return; }
+
+    // Store a binary STL
+    wxString path = dialog->GetPath();
+    auto path_cstr = path.c_str();
+    auto mesh = p->model.mesh();
+    Slic3r::store_stl(path_cstr, &mesh, true);
+    p->statusbar()->set_status_text(wxString::Format(_(L("STL file exported to %s")), path));
+}
+
+void Plater::export_amf()
+{
+    if (p->objects.empty()) { return; }
+
+    auto dialog = p->get_export_file(FT_AMF);
+    if (! dialog) { return; }
+
+    wxString path = dialog->GetPath();
+    auto path_cstr = path.c_str();
+
+    if (Slic3r::store_amf(path_cstr, &p->model, &p->print, dialog->get_checkbox_value())) {
+        // Success
+        p->statusbar()->set_status_text(wxString::Format(_(L("AMF file exported to %s")), path));
+    } else {
+        // Failure
+        p->statusbar()->set_status_text(wxString::Format(_(L("Error exporting AMF file %s")), path));
+    }
+}
+
+void Plater::export_3mf()
+{
+    if (p->objects.empty()) { return; }
+
+    auto dialog = p->get_export_file(FT_3MF);
+    if (! dialog) { return; }
+
+    wxString path = dialog->GetPath();
+    auto path_cstr = path.c_str();
+
+    if (Slic3r::store_3mf(path_cstr, &p->model, &p->print, dialog->get_checkbox_value())) {
+        // Success
+        p->statusbar()->set_status_text(wxString::Format(_(L("3MF file exported to %s")), path));
+    } else {
+        // Failure
+        p->statusbar()->set_status_text(wxString::Format(_(L("Error exporting 3MF file %s")), path));
+    }
+}
+
+
 void Plater::reslice()
 {
     // TODO
 }
 
+void Plater::send_gcode()
+{
+    p->send_gcode_file = export_gcode();
+}
+
 void Plater::changed_object_settings(int obj_idx)
 {
     if (obj_idx < 0)
@@ -1163,7 +1614,6 @@ void Plater::changed_object_settings(int obj_idx)
     if (list == nullptr)
         return;
 
-
     if (list->is_parts_changed()) {
         // recenter and re - align to Z = 0
         auto model_object = p->model.objects[list->get_sel_obj_id()];
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index c05b454ff..0592579db 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -2,11 +2,16 @@
 #define slic3r_Plater_hpp_
 
 #include <memory>
+#include <vector>
+#include <boost/filesystem/path.hpp>
 
 #include <wx/panel.h>
 
 #include "Preset.hpp"
 
+class wxButton;
+
+
 namespace Slic3r {
 
 class Model;
@@ -20,10 +25,12 @@ class ObjectList;
 
 using t_optgroups = std::vector <std::shared_ptr<ConfigOptionsGroup>>;
 
+class Plater;
+
 class Sidebar : public wxPanel
 {
 public:
-    Sidebar(wxWindow *parent);
+    Sidebar(Plater *parent);
     Sidebar(Sidebar &&) = delete;
     Sidebar(const Sidebar &) = delete;
     Sidebar &operator=(Sidebar &&) = delete;
@@ -41,12 +48,11 @@ public:
     int                     get_ol_selection();
     void                    show_info_sizers(const bool show);
     void                    show_buttons(const bool show);
+    void                    enable_buttons(bool enable);
 
 private:
     struct priv;
     std::unique_ptr<priv> p;
-
-    friend class Plater;    // XXX: better encapsulation?
 };
 
 
@@ -63,11 +69,20 @@ public:
     Sidebar& sidebar();
     Model&  model();
 
-    // TODO: use fs::path
-    // Note: empty string means request default path
-    std::string export_gcode(const std::string &output_path);
+    void update(bool force_autocenter = false);
+    void remove(size_t obj_idx);
+    void remove_selected();
+
+    void load_files(const std::vector<boost::filesystem::path> &input_files);
+
+    // Note: empty path means "use the default"
+    boost::filesystem::path export_gcode(const boost::filesystem::path &output_path = boost::filesystem::path());
+    void export_stl();
+    void export_amf();
+    void export_3mf();
     void reslice();
     void changed_object_settings(int obj_idx);
+    void send_gcode();
 private:
     struct priv;
     std::unique_ptr<priv> p;