diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp
index b86ec5055..d6de6ce6a 100644
--- a/src/admesh/connect.cpp
+++ b/src/admesh/connect.cpp
@@ -430,6 +430,8 @@ private:
 // floats of the first edge matches all six floats of the second edge.
 void stl_check_facets_exact(stl_file *stl)
 {
+	assert(stl->facet_start.size() == stl->neighbors_start.size());
+
   	stl->stats.connected_edges         = 0;
   	stl->stats.connected_facets_1_edge = 0;
   	stl->stats.connected_facets_2_edge = 0;
diff --git a/src/admesh/stl.h b/src/admesh/stl.h
index 2ac6c7fd2..9b1146f8d 100644
--- a/src/admesh/stl.h
+++ b/src/admesh/stl.h
@@ -127,6 +127,10 @@ struct stl_file {
 		this->stats.reset();
 	}
 
+	size_t memsize() const {
+		return sizeof(*this) + sizeof(stl_facet) * facet_start.size() + sizeof(stl_neighbors) * neighbors_start.size();
+	}
+
 	std::vector<stl_facet>     		facet_start;
 	std::vector<stl_neighbors> 		neighbors_start;
 	// Statistics
@@ -139,6 +143,10 @@ struct indexed_triangle_set
 
 	void clear() { indices.clear(); vertices.clear(); }
 
+	size_t memsize() const {
+		return sizeof(*this) + sizeof(stl_triangle_vertex_indices) * indices.size() + sizeof(stl_vertex) * vertices.size();
+	}
+
 	std::vector<stl_triangle_vertex_indices> 	indices;
 	std::vector<stl_vertex>       				vertices;
 	//FIXME add normals once we get rid of the stl_file from TriangleMesh completely.
diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp
index 844287efb..7d96f6cf3 100644
--- a/src/libslic3r/Config.hpp
+++ b/src/libslic3r/Config.hpp
@@ -93,7 +93,7 @@ enum ConfigOptionMode {
     comExpert
 };
 
-enum PrinterTechnology
+enum PrinterTechnology : unsigned char
 {
     // Fused Filament Fabrication
     ptFFF,
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 37a14d42c..81880831f 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -169,6 +169,9 @@ static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2
 
 std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const
 {
+    if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
+        throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
+
     std::string gcode;
 
     // Toolchangeresult.gcode assumes the wipe tower corner is at the origin
@@ -182,8 +185,11 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
         end_pos = Eigen::Rotation2Df(alpha) * end_pos;
         end_pos += m_wipe_tower_pos;
     }
-    std::string tcr_rotated_gcode = tcr.priming ? tcr.gcode : rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha);
-    
+
+    Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
+    float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
+
+    std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
 
     // Disable linear advance for the wipe tower operations.
     gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n"));
@@ -285,17 +291,21 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
 
 // This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode
 // Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate)
-std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gcode_original, const Vec2f& start_pos, const Vec2f& translation, float angle) const
+std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const
 {
-    std::istringstream gcode_str(gcode_original);
+    Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast<float>();
+
+    std::istringstream gcode_str(tcr.gcode);
     std::string gcode_out;
     std::string line;
-    Vec2f pos = start_pos;
-    Vec2f transformed_pos;
+    Vec2f pos = tcr.start_pos;
+    Vec2f transformed_pos = pos;
     Vec2f old_pos(-1000.1f, -1000.1f);
 
     while (gcode_str) {
         std::getline(gcode_str, line);  // we read the gcode line by line
+
+        // All G1 commands should be translated and rotated
         if (line.find("G1 ") == 0) {
             std::ostringstream line_out;
             std::istringstream line_str(line);
@@ -317,17 +327,34 @@ std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gco
 
             if (transformed_pos != old_pos) {
                 line = line_out.str();
-                char buf[2048] = "G1";
+                std::ostringstream oss;
+                oss << std::fixed << std::setprecision(3) << "G1 ";
                 if (transformed_pos.x() != old_pos.x())
-                    sprintf(buf + strlen(buf), " X%.3f", transformed_pos.x());
+                    oss << " X" << transformed_pos.x() - extruder_offset.x();
                 if (transformed_pos.y() != old_pos.y())
-                    sprintf(buf + strlen(buf), " Y%.3f", transformed_pos.y());
+                    oss << " Y" << transformed_pos.y() - extruder_offset.y();
 
-                line.replace(line.find("G1 "), 3, buf);
+                line.replace(line.find("G1 "), 3, oss.str());
                 old_pos = transformed_pos;
             }
         }
+
         gcode_out += line + "\n";
+
+        // If this was a toolchange command, we should change current extruder offset
+        if (line == "[toolchange_gcode]") {
+            extruder_offset = m_extruder_offsets[tcr.new_tool].cast<float>();
+
+            // If the extruder offset changed, add an extra move so everything is continuous
+            if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast<float>()) {
+                std::ostringstream oss;
+                oss << std::fixed << std::setprecision(3)
+                    << "G1 X" << transformed_pos.x() - extruder_offset.x()
+                    << " Y"   << transformed_pos.y() - extruder_offset.y()
+                    << "\n";
+                gcode_out += oss.str();
+            }
+        }
     }
     return gcode_out;
 }
@@ -2784,7 +2811,17 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
     // if we are running a single-extruder setup, just set the extruder and return nothing
     if (!m_writer.multiple_extruders) {
         m_placeholder_parser.set("current_extruder", extruder_id);
-        return m_writer.toolchange(extruder_id);
+        
+        std::string gcode;
+        // Append the filament start G-code.
+        const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id);
+        if (! start_filament_gcode.empty()) {
+            // Process the start_filament_gcode for the filament.
+            gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id);
+            check_add_eol(gcode);
+        }
+        gcode += m_writer.toolchange(extruder_id);
+        return gcode;
     }
     
     // prepend retraction on the current extruder
diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp
index 4b81b42aa..f2a67f600 100644
--- a/src/libslic3r/GCode.hpp
+++ b/src/libslic3r/GCode.hpp
@@ -90,6 +90,7 @@ public:
         m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)),
         m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)),
         m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)),
+        m_extruder_offsets(print_config.extruder_offset.values),
         m_priming(priming),
         m_tool_changes(tool_changes),
         m_final_purge(final_purge),
@@ -107,14 +108,16 @@ private:
     WipeTowerIntegration& operator=(const WipeTowerIntegration&);
     std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const;
 
-    // Postprocesses gcode: rotates and moves all G1 extrusions and returns result
-    std::string rotate_wipe_tower_moves(const std::string& gcode_original, const Vec2f& start_pos, const Vec2f& translation, float angle) const;
+    // Postprocesses gcode: rotates and moves G1 extrusions and returns result
+    std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
 
     // Left / right edges of the wipe tower, for the planning of wipe moves.
     const float                                                  m_left;
     const float                                                  m_right;
     const Vec2f                                                  m_wipe_tower_pos;
     const float                                                  m_wipe_tower_rotation;
+    const std::vector<Vec2d>                                     m_extruder_offsets;
+
     // Reference to cached values at the Printer class.
     const std::vector<WipeTower::ToolChangeResult>              &m_priming;
     const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes;
diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp
index 37e4040d1..354ec6d9e 100644
--- a/src/libslic3r/GCode/WipeTower.cpp
+++ b/src/libslic3r/GCode/WipeTower.cpp
@@ -553,7 +553,7 @@ std::vector<WipeTower::ToolChangeResult> WipeTower::prime(
         result.elapsed_time = writer.elapsed_time();
         result.extrusions 	= writer.extrusions();
         result.start_pos  	= writer.start_pos_rotated();
-        result.end_pos 	  	= writer.pos_rotated();
+        result.end_pos 	  	= writer.pos();
 
         results.push_back(std::move(result));
 
@@ -643,7 +643,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(unsigned int tool, bool last_
                           m_is_first_layer ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature);
         toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials.
         toolchange_Load(writer, cleaning_box);
-        writer.travel(writer.x(),writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
+        writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
         toolchange_Wipe(writer, cleaning_box, wipe_volume);     // Wipe the newly loaded filament until the end of the assigned wipe area.
         ++ m_num_tool_changes;
     } else
diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp
index c18a502b1..badb3e8b2 100644
--- a/src/libslic3r/GCode/WipeTower.hpp
+++ b/src/libslic3r/GCode/WipeTower.hpp
@@ -139,13 +139,15 @@ public:
 
         m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter
 
-        std::stringstream stream{m_semm ? ramming_parameters : std::string()};
-        float speed = 0.f;
-        stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator;
-        m_filpar[idx].ramming_line_width_multiplicator /= 100;
-        m_filpar[idx].ramming_step_multiplicator /= 100;
-        while (stream >> speed)
-            m_filpar[idx].ramming_speed.push_back(speed);
+        if (m_semm) {
+            std::stringstream stream{ramming_parameters};
+            float speed = 0.f;
+            stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator;
+            m_filpar[idx].ramming_line_width_multiplicator /= 100;
+            m_filpar[idx].ramming_step_multiplicator /= 100;
+            while (stream >> speed)
+                m_filpar[idx].ramming_speed.push_back(speed);
+        }
 
         m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later
 	}
@@ -241,8 +243,8 @@ public:
         int                 cooling_moves = 0;
         float               cooling_initial_speed = 0.f;
         float               cooling_final_speed = 0.f;
-        float               ramming_line_width_multiplicator = 0.f;
-        float               ramming_step_multiplicator = 0.f;
+        float               ramming_line_width_multiplicator = 1.f;
+        float               ramming_step_multiplicator = 1.f;
         float               max_e_speed = std::numeric_limits<float>::max();
         std::vector<float>  ramming_speed;
         float               nozzle_diameter;
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index cdea83fb7..ecfb6697a 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -1918,6 +1918,31 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO
     return false;
 }
 
+extern bool model_has_multi_part_objects(const Model &model)
+{
+    for (const ModelObject *model_object : model.objects)
+    	if (model_object->volumes.size() != 1 || ! model_object->volumes.front()->is_model_part())
+    		return true;
+    return false;
+}
+
+extern bool model_has_advanced_features(const Model &model)
+{
+	auto config_is_advanced = [](const DynamicPrintConfig &config) {
+        return ! (config.empty() || (config.size() == 1 && config.cbegin()->first == "extruder"));
+	};
+    for (const ModelObject *model_object : model.objects) {
+        // Is there more than one instance or advanced config data?
+        if (model_object->instances.size() > 1 || config_is_advanced(model_object->config))
+        	return true;
+        // Is there any modifier or advanced config data?
+        for (const ModelVolume* model_volume : model_object->volumes)
+            if (! model_volume->is_model_part() || config_is_advanced(model_volume->config))
+            	return true;
+    }
+    return false;
+}
+
 #ifndef NDEBUG
 // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
 void check_model_ids_validity(const Model &model)
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index 008699818..4bd2c7473 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -18,6 +18,15 @@
 #include <utility>
 #include <vector>
 
+namespace cereal {
+	class BinaryInputArchive;
+	class BinaryOutputArchive;
+	template <class T> void load_optional(BinaryInputArchive &ar, std::shared_ptr<const T> &ptr);
+	template <class T> void save_optional(BinaryOutputArchive &ar, const std::shared_ptr<const T> &ptr);
+	template <class T> void load_by_value(BinaryInputArchive &ar, T &obj);
+	template <class T> void save_by_value(BinaryOutputArchive &ar, const T &obj);
+}
+
 namespace Slic3r {
 
 class Model;
@@ -25,6 +34,7 @@ class ModelInstance;
 class ModelMaterial;
 class ModelObject;
 class ModelVolume;
+class ModelWipeTower;
 class Print;
 class SLAPrint;
 
@@ -60,6 +70,21 @@ private:
 	}
 };
 
+namespace Internal {
+	template<typename T>
+	class StaticSerializationWrapper
+	{
+	public:
+		StaticSerializationWrapper(T &wrap) : wrapped(wrap) {}
+	private:
+		friend class cereal::access;
+		friend class UndoRedo::StackImpl;
+		template<class Archive> void load(Archive &ar) { cereal::load_by_value(ar, wrapped); }
+		template<class Archive> void save(Archive &ar) const { cereal::save_by_value(ar, wrapped); }
+		T&	wrapped;
+	};
+}
+
 typedef std::string t_model_material_id;
 typedef std::string t_model_material_attribute;
 typedef std::map<t_model_material_attribute, std::string> t_model_material_attributes;
@@ -134,7 +159,8 @@ private:
 	ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); }
 	template<class Archive> void serialize(Archive &ar) { 
 		assert(this->id().invalid()); assert(this->config.id().invalid());
-		ar(attributes, config);
+		Internal::StaticSerializationWrapper<ModelConfig> config_wrapper(config);
+		ar(attributes, config_wrapper);
 		// assert(this->id().valid()); assert(this->config.id().valid());
 	}
 
@@ -343,7 +369,8 @@ private:
 	}
 	template<class Archive> void serialize(Archive &ar) {
 		ar(cereal::base_class<ObjectBase>(this));
-		ar(name, input_file, instances, volumes, config, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation,
+		Internal::StaticSerializationWrapper<ModelConfig> config_wrapper(config);
+		ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation,
 			m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid);
 	}
 };
@@ -527,8 +554,25 @@ private:
 	ModelVolume() : ObjectBase(-1), config(-1), object(nullptr) {
 		assert(this->id().invalid()); assert(this->config.id().invalid());
 	}
-	template<class Archive> void serialize(Archive &ar) {
-		ar(name, config, m_mesh, m_type, m_material_id, m_convex_hull, m_transformation, m_is_splittable);
+	template<class Archive> void load(Archive &ar) {
+		bool has_convex_hull;
+		ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
+		cereal::load_by_value(ar, config);
+		assert(m_mesh);
+		if (has_convex_hull) {
+			cereal::load_optional(ar, m_convex_hull);
+			if (! m_convex_hull && ! m_mesh->empty())
+				// The convex hull was released from the Undo / Redo stack to conserve memory. Recalculate it.
+				this->calculate_convex_hull();
+		} else
+			m_convex_hull.reset();
+	}
+	template<class Archive> void save(Archive &ar) const {
+		bool has_convex_hull = m_convex_hull.get() != nullptr;
+		ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
+		cereal::save_by_value(ar, config);
+		if (has_convex_hull)
+			cereal::save_optional(ar, m_convex_hull);
 	}
 };
 
@@ -641,6 +685,35 @@ private:
 	}
 };
 
+class ModelWipeTower final : public ObjectBase
+{
+public:
+	Vec2d		position;
+	double 		rotation;
+
+private:
+	friend class cereal::access;
+	friend class UndoRedo::StackImpl;
+	friend class Model;
+
+    // Constructors to be only called by derived classes.
+    // Default constructor to assign a unique ID.
+    explicit ModelWipeTower() {}
+    // Constructor with ignored int parameter to assign an invalid ID, to be replaced
+    // by an existing ID copied from elsewhere.
+    explicit ModelWipeTower(int) : ObjectBase(-1) {}
+    // Copy constructor copies the ID.
+	explicit ModelWipeTower(const ModelWipeTower &cfg) = default;
+
+	// Disabled methods.
+	ModelWipeTower(ModelWipeTower &&rhs) = delete;
+	ModelWipeTower& operator=(const ModelWipeTower &rhs) = delete;
+    ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete;
+
+    // For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object.
+    template<typename Archive> void serialize(Archive &ar) { ar(position, rotation); }
+};
+
 // The print bed content.
 // Description of a triangular model with multiple materials, multiple instances with various affine transformations
 // and with multiple modifier meshes.
@@ -656,6 +729,8 @@ public:
     ModelMaterialMap    materials;
     // Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation).
     ModelObjectPtrs     objects;
+    // Wipe tower object.
+    ModelWipeTower	    wipe_tower;
     
     // Default constructor assigns a new ID to the model.
     Model() { assert(this->id().valid()); }
@@ -733,7 +808,8 @@ private:
 	friend class cereal::access;
 	friend class UndoRedo::StackImpl;
 	template<class Archive> void serialize(Archive &ar) {
-		ar(materials, objects);
+		Internal::StaticSerializationWrapper<ModelWipeTower> wipe_tower_wrapper(wipe_tower);
+		ar(materials, objects, wipe_tower_wrapper);
 	}
 };
 
@@ -752,6 +828,12 @@ extern bool model_object_list_extended(const Model &model_old, const Model &mode
 // than the old ModelObject.
 extern bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type);
 
+// If the model has multi-part objects, then it is currently not supported by the SLA mode.
+// Either the model cannot be loaded, or a SLA printer has to be activated.
+extern bool model_has_multi_part_objects(const Model &model);
+// If the model has advanced features, then it cannot be processed in simple mode.
+extern bool model_has_advanced_features(const Model &model);
+
 #ifndef NDEBUG
 // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
 void check_model_ids_validity(const Model &model);
@@ -760,4 +842,10 @@ void check_model_ids_equal(const Model &model1, const Model &model2);
 
 } // namespace Slic3r
 
+namespace cereal
+{
+	template <class Archive> struct specialize<Archive, Slic3r::ModelVolume, cereal::specialization::member_load_save> {};
+	template <class Archive> struct specialize<Archive, Slic3r::ModelConfig, cereal::specialization::member_serialize> {};
+}
+
 #endif /* slic3r_Model_hpp_ */
diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp
index fbfff90fb..c2fcb11bd 100644
--- a/src/libslic3r/TriangleMesh.cpp
+++ b/src/libslic3r/TriangleMesh.cpp
@@ -607,6 +607,40 @@ void TriangleMesh::require_shared_vertices()
     BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end";
 }
 
+size_t TriangleMesh::memsize() const
+{
+	size_t memsize = 8 + this->stl.memsize() + this->its.memsize();
+	return memsize;
+}
+
+// Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released.
+size_t TriangleMesh::release_optional()
+{
+	size_t memsize_released = sizeof(stl_neighbors) * this->stl.neighbors_start.size() + this->its.memsize();
+	// The indexed triangle set may be recalculated using the stl_generate_shared_vertices() function.
+	this->its.clear();
+	// The neighbors structure may be recalculated using the stl_check_facets_exact() function.
+	this->stl.neighbors_start.clear();
+	return memsize_released;
+}
+
+// Restore optional data possibly released by release_optional().
+void TriangleMesh::restore_optional()
+{
+	if (! this->stl.facet_start.empty()) {
+		// Save the old stats before calling stl_check_faces_exact, as it may modify the statistics.
+		stl_stats stats = this->stl.stats;
+		if (this->stl.neighbors_start.empty()) {
+			stl_reallocate(&this->stl);
+			stl_check_facets_exact(&this->stl);
+		}
+		if (this->its.vertices.empty())
+			stl_generate_shared_vertices(&this->stl, this->its);
+		// Restore the old statistics.
+		this->stl.stats = stats;
+	}
+}
+
 void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callback_type throw_on_cancel)
 {
     mesh = _mesh;
diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp
index 5dd2597a5..81390b79b 100644
--- a/src/libslic3r/TriangleMesh.hpp
+++ b/src/libslic3r/TriangleMesh.hpp
@@ -67,6 +67,12 @@ public:
     size_t facets_count() const { return this->stl.stats.number_of_facets; }
     bool   empty() const { return this->facets_count() == 0; }
     bool is_splittable() const;
+    // Estimate of the memory occupied by this structure, important for keeping an eye on the Undo / Redo stack allocation.
+    size_t memsize() const;
+    // Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released.
+    size_t release_optional();
+	// Restore optional data possibly released by release_optional().
+	void restore_optional();
 
     stl_file stl;
     indexed_triangle_set its;
diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp
index 3b30e981c..09e24a475 100644
--- a/src/libslic3r/Utils.hpp
+++ b/src/libslic3r/Utils.hpp
@@ -21,6 +21,8 @@ extern std::string format_memsize_MB(size_t n);
 // The string is non-empty only if the loglevel >= info (3).
 extern std::string log_memory_info();
 extern void disable_multi_threading();
+// Returns the size of physical memory (RAM) in bytes.
+extern size_t total_physical_memory();
 
 // Set a path with GUI resource files.
 void set_var_dir(const std::string &path);
diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp
index 519763731..f9a044338 100644
--- a/src/libslic3r/utils.cpp
+++ b/src/libslic3r/utils.cpp
@@ -7,10 +7,15 @@
 #include <stdio.h>
 
 #ifdef WIN32
-#include <windows.h>
-#include <psapi.h>
+	#include <windows.h>
+	#include <psapi.h>
 #else
-#include <unistd.h>
+	#include <unistd.h>
+	#include <sys/types.h>
+	#include <sys/param.h>
+	#ifdef BSD
+		#include <sys/sysctl.h>
+	#endif
 #endif
 
 #include <boost/log/core.hpp>
@@ -467,4 +472,75 @@ std::string log_memory_info()
 }
 #endif
 
+// Returns the size of physical memory (RAM) in bytes.
+// http://nadeausoftware.com/articles/2012/09/c_c_tip_how_get_physical_memory_size_system
+size_t total_physical_memory()
+{
+#if defined(_WIN32) && (defined(__CYGWIN__) || defined(__CYGWIN32__))
+	// Cygwin under Windows. ------------------------------------
+	// New 64-bit MEMORYSTATUSEX isn't available.  Use old 32.bit
+	MEMORYSTATUS status;
+	status.dwLength = sizeof(status);
+	GlobalMemoryStatus( &status );
+	return (size_t)status.dwTotalPhys;
+#elif defined(_WIN32)
+	// Windows. -------------------------------------------------
+	// Use new 64-bit MEMORYSTATUSEX, not old 32-bit MEMORYSTATUS
+	MEMORYSTATUSEX status;
+	status.dwLength = sizeof(status);
+	GlobalMemoryStatusEx( &status );
+	return (size_t)status.ullTotalPhys;
+#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
+	// UNIX variants. -------------------------------------------
+	// Prefer sysctl() over sysconf() except sysctl() HW_REALMEM and HW_PHYSMEM
+
+#if defined(CTL_HW) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM64))
+	int mib[2];
+	mib[0] = CTL_HW;
+#if defined(HW_MEMSIZE)
+	mib[1] = HW_MEMSIZE;            // OSX. ---------------------
+#elif defined(HW_PHYSMEM64)
+	mib[1] = HW_PHYSMEM64;          // NetBSD, OpenBSD. ---------
+#endif
+	int64_t size = 0;               // 64-bit
+	size_t len = sizeof( size );
+	if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 )
+		return (size_t)size;
+	return 0L;			// Failed?
+
+#elif defined(_SC_AIX_REALMEM)
+	// AIX. -----------------------------------------------------
+	return (size_t)sysconf( _SC_AIX_REALMEM ) * (size_t)1024L;
+
+#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
+	// FreeBSD, Linux, OpenBSD, and Solaris. --------------------
+	return (size_t)sysconf( _SC_PHYS_PAGES ) *
+		(size_t)sysconf( _SC_PAGESIZE );
+
+#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGE_SIZE)
+	// Legacy. --------------------------------------------------
+	return (size_t)sysconf( _SC_PHYS_PAGES ) *
+		(size_t)sysconf( _SC_PAGE_SIZE );
+
+#elif defined(CTL_HW) && (defined(HW_PHYSMEM) || defined(HW_REALMEM))
+	// DragonFly BSD, FreeBSD, NetBSD, OpenBSD, and OSX. --------
+	int mib[2];
+	mib[0] = CTL_HW;
+#if defined(HW_REALMEM)
+	mib[1] = HW_REALMEM;		// FreeBSD. -----------------
+#elif defined(HW_PYSMEM)
+	mib[1] = HW_PHYSMEM;		// Others. ------------------
+#endif
+	unsigned int size = 0;		// 32-bit
+	size_t len = sizeof( size );
+	if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 )
+		return (size_t)size;
+	return 0L;			// Failed?
+#endif // sysctl and sysconf variants
+
+#else
+	return 0L;			// Unknown OS.
+#endif
+}
+
 }; // namespace Slic3r
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index c68e89778..3bbda795d 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -1202,7 +1202,8 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar
     , m_bed(bed)
     , m_camera(camera)
     , m_view_toolbar(view_toolbar)
-    , m_toolbar(GLToolbar::Normal, "Top")
+    , m_main_toolbar(GLToolbar::Normal, "Top")
+    , m_undoredo_toolbar(GLToolbar::Normal, "Top")
     , m_gizmos(*this)
     , m_use_clipping_planes(false)
     , m_sidebar_field("")
@@ -1309,7 +1310,7 @@ bool GLCanvas3D::init()
         return false;
     }
 
-    if (m_toolbar.is_enabled() && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs"))
+    if (m_main_toolbar.is_enabled() && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs"))
     {
         std::cout << "Unable to initialize variable_layer_height shader: please, check that the files variable_layer_height.vs and variable_layer_height.fs are available" << std::endl;
         return false;
@@ -1323,7 +1324,7 @@ bool GLCanvas3D::init()
     if (m_gizmos.is_enabled() && !m_gizmos.init())
         std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl;
 
-    if (!_init_toolbar())
+    if (!_init_toolbars())
         return false;
 
     if (m_selection.is_enabled() && !m_selection.init())
@@ -1514,9 +1515,14 @@ void GLCanvas3D::enable_selection(bool enable)
     m_selection.set_enabled(enable);
 }
 
-void GLCanvas3D::enable_toolbar(bool enable)
+void GLCanvas3D::enable_main_toolbar(bool enable)
 {
-    m_toolbar.set_enabled(enable);
+    m_main_toolbar.set_enabled(enable);
+}
+
+void GLCanvas3D::enable_undoredo_toolbar(bool enable)
+{
+    m_undoredo_toolbar.set_enabled(enable);
 }
 
 void GLCanvas3D::enable_dynamic_background(bool enable)
@@ -1852,6 +1858,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
     PrinterTechnology printer_technology        = m_process->current_printer_technology();
     int               volume_idx_wipe_tower_old = -1;
 
+    if (printer_technology == ptSLA)
+    	// Always do the full refresh in SLA mode to show / hide SLA support structures when an object is moved outside / inside the build volume.
+    	m_regenerate_volumes = true;
+
     if (m_regenerate_volumes)
     {
         // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed).
@@ -1884,7 +1894,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
                             state.step[istep].state = PrintStateBase::INVALID;
                         else
     						for (const ModelInstance *model_instance : print_object->model_object()->instances)
-                                aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id());
+    							// Only the instances, which are currently printable, will have the SLA support structures kept.
+    							// The instances outside the print bed will have the GLVolumes of their support structures released.
+    							if (model_instance->is_printable())
+                                	aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id());
                     }
 				}
 				sla_support_state.emplace_back(state);
@@ -2036,7 +2049,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
 
 			// Shift-up all volumes of the object so that it has the right elevation with respect to the print bed
 			for (GLVolume* volume : m_volumes.volumes)
-				volume->set_sla_shift_z(shift_zs[volume->object_idx()]);
+				if (volume->object_idx() < m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable())
+					volume->set_sla_shift_z(shift_zs[volume->object_idx()]);
         }
 
         if (printer_technology == ptFFF && m_config->has("nozzle_diameter"))
@@ -2192,7 +2206,8 @@ void GLCanvas3D::load_sla_preview()
     if ((m_canvas != nullptr) && (print != nullptr))
     {
         _set_current();
-        _load_sla_shells();
+        // Reload the SLA support structures into GLVolumes.
+		this->reload_scene(true, true);
         _update_sla_shells_outside_state();
         _show_warning_texture_if_needed(WarningTexture::SlaSupportsOutside);
     }
@@ -2295,7 +2310,8 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
     if (!m_initialized)
         return;
 
-    m_dirty |= m_toolbar.update_items_state();
+    m_dirty |= m_main_toolbar.update_items_state();
+    m_dirty |= m_undoredo_toolbar.update_items_state();
     m_dirty |= m_view_toolbar.update_items_state();
 
     if (!m_dirty)
@@ -2674,7 +2690,15 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 #endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
 	}
 
-    if (m_toolbar.on_mouse(evt, *this))
+    if (m_main_toolbar.on_mouse(evt, *this))
+    {
+        if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
+            mouse_up_cleanup();
+        m_mouse.set_start_position_3D_as_invalid();
+        return;
+    }
+
+    if (m_undoredo_toolbar.on_mouse(evt, *this))
     {
         if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
             mouse_up_cleanup();
@@ -3015,7 +3039,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             tooltip = m_gizmos.get_tooltip();
 
         if (tooltip.empty())
-            tooltip = m_toolbar.get_tooltip();
+            tooltip = m_main_toolbar.get_tooltip();
+
+        if (tooltip.empty())
+            tooltip = m_undoredo_toolbar.get_tooltip();
 
         if (tooltip.empty())
             tooltip = m_view_toolbar.get_tooltip();
@@ -3468,16 +3495,19 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x)
     ImGuiWrapper* imgui = wxGetApp().imgui();
 
     const float x = pos_x * (float)get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width();
-    imgui->set_next_window_pos(x, m_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f);
+    imgui->set_next_window_pos(x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f);
     imgui->set_next_window_bg_alpha(0.5f);
     imgui->begin(wxString::Format(_(L("%s Stack")), stack_name),
                  ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
 
     int hovered = m_imgui_undo_redo_hovered_pos;
     int selected = -1;
-    const float em = static_cast<float>(wxGetApp().em_unit());
+    float em = static_cast<float>(wxGetApp().em_unit());
+#if ENABLE_RETINA_GL
+	em *= m_retina_helper->get_scale_factor();
+#endif
 
-    if (imgui->undo_redo_list(ImVec2(12 * em, 20 * em), is_undo, &string_getter, hovered, selected))
+    if (imgui->undo_redo_list(ImVec2(18 * em, 26 * em), is_undo, &string_getter, hovered, selected))
         m_imgui_undo_redo_hovered_pos = hovered;
     else
         m_imgui_undo_redo_hovered_pos = -1;
@@ -3490,9 +3520,20 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x)
     imgui->end();
 }
 
-bool GLCanvas3D::_init_toolbar()
+bool GLCanvas3D::_init_toolbars()
 {
-    if (!m_toolbar.is_enabled())
+    if (!_init_main_toolbar())
+        return false;
+
+    if (!_init_undoredo_toolbar())
+        return false;
+
+    return true;
+}
+
+bool GLCanvas3D::_init_main_toolbar()
+{
+    if (!m_main_toolbar.is_enabled())
         return true;
 
     BackgroundTexture::Metadata background_data;
@@ -3502,19 +3543,19 @@ bool GLCanvas3D::_init_toolbar()
     background_data.right = 16;
     background_data.bottom = 16;
 
-    if (!m_toolbar.init(background_data))
+    if (!m_main_toolbar.init(background_data))
     {
         // unable to init the toolbar texture, disable it
-        m_toolbar.set_enabled(false);
+        m_main_toolbar.set_enabled(false);
         return true;
     }
 
-//    m_toolbar.set_layout_type(GLToolbar::Layout::Vertical);
-    m_toolbar.set_layout_type(GLToolbar::Layout::Horizontal);
-    m_toolbar.set_layout_orientation(GLToolbar::Layout::Top);
-    m_toolbar.set_border(5.0f);
-    m_toolbar.set_separator_size(5);
-    m_toolbar.set_gap_size(2);
+//    m_main_toolbar.set_layout_type(GLToolbar::Layout::Vertical);
+    m_main_toolbar.set_layout_type(GLToolbar::Layout::Horizontal);
+    m_main_toolbar.set_layout_orientation(GLToolbar::Layout::Top);
+    m_main_toolbar.set_border(5.0f);
+    m_main_toolbar.set_separator_size(5);
+    m_main_toolbar.set_gap_size(2);
 
     GLToolbarItem::Data item;
 
@@ -3523,7 +3564,7 @@ bool GLCanvas3D::_init_toolbar()
     item.tooltip = _utf8(L("Add...")) + " [" + GUI::shortkey_ctrl_prefix() + "I]";
     item.sprite_id = 0;
     item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); };
-    if (!m_toolbar.add_item(item))
+    if (!m_main_toolbar.add_item(item))
         return false;
 
     item.name = "delete";
@@ -3532,7 +3573,7 @@ bool GLCanvas3D::_init_toolbar()
     item.sprite_id = 1;
     item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); };
     item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete(); };
-    if (!m_toolbar.add_item(item))
+    if (!m_main_toolbar.add_item(item))
         return false;
 
     item.name = "deleteall";
@@ -3541,7 +3582,7 @@ bool GLCanvas3D::_init_toolbar()
     item.sprite_id = 2;
     item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); };
     item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); };
-    if (!m_toolbar.add_item(item))
+    if (!m_main_toolbar.add_item(item))
         return false;
 
     item.name = "arrange";
@@ -3550,10 +3591,10 @@ bool GLCanvas3D::_init_toolbar()
     item.sprite_id = 3;
     item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); };
     item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
-    if (!m_toolbar.add_item(item))
+    if (!m_main_toolbar.add_item(item))
         return false;
 
-    if (!m_toolbar.add_separator())
+    if (!m_main_toolbar.add_separator())
         return false;
 
     item.name = "copy";
@@ -3562,7 +3603,7 @@ bool GLCanvas3D::_init_toolbar()
     item.sprite_id = 4;
     item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); };
     item.enabling_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); };
-    if (!m_toolbar.add_item(item))
+    if (!m_main_toolbar.add_item(item))
         return false;
 
     item.name = "paste";
@@ -3571,10 +3612,10 @@ bool GLCanvas3D::_init_toolbar()
     item.sprite_id = 5;
     item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); };
     item.enabling_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); };
-    if (!m_toolbar.add_item(item))
+    if (!m_main_toolbar.add_item(item))
         return false;
 
-    if (!m_toolbar.add_separator())
+    if (!m_main_toolbar.add_separator())
         return false;
 
     item.name = "more";
@@ -3585,7 +3626,7 @@ bool GLCanvas3D::_init_toolbar()
     item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; };
     item.enabling_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); };
 
-    if (!m_toolbar.add_item(item))
+    if (!m_main_toolbar.add_item(item))
         return false;
 
     item.name = "fewer";
@@ -3595,10 +3636,10 @@ bool GLCanvas3D::_init_toolbar()
     item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); };
     item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; };
     item.enabling_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); };
-    if (!m_toolbar.add_item(item))
+    if (!m_main_toolbar.add_item(item))
         return false;
 
-    if (!m_toolbar.add_separator())
+    if (!m_main_toolbar.add_separator())
         return false;
 
     item.name = "splitobjects";
@@ -3608,7 +3649,7 @@ bool GLCanvas3D::_init_toolbar()
     item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); };
     item.visibility_callback = GLToolbarItem::Default_Visibility_Callback;
     item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); };
-    if (!m_toolbar.add_item(item))
+    if (!m_main_toolbar.add_item(item))
         return false;
 
     item.name = "splitvolumes";
@@ -3618,10 +3659,10 @@ bool GLCanvas3D::_init_toolbar()
     item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); };
     item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; };
     item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); };
-    if (!m_toolbar.add_item(item))
+    if (!m_main_toolbar.add_item(item))
         return false;
 
-    if (!m_toolbar.add_separator())
+    if (!m_main_toolbar.add_separator())
         return false;
 
     item.name = "layersediting";
@@ -3632,16 +3673,44 @@ bool GLCanvas3D::_init_toolbar()
     item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); };
     item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; };
     item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); };
-    if (!m_toolbar.add_item(item))
+    if (!m_main_toolbar.add_item(item))
         return false;
 
-    if (!m_toolbar.add_separator())
-        return false;
+    return true;
+}
+
+bool GLCanvas3D::_init_undoredo_toolbar()
+{
+    if (!m_undoredo_toolbar.is_enabled())
+        return true;
+
+    BackgroundTexture::Metadata background_data;
+    background_data.filename = "toolbar_background.png";
+    background_data.left = 16;
+    background_data.top = 16;
+    background_data.right = 16;
+    background_data.bottom = 16;
+
+    if (!m_undoredo_toolbar.init(background_data))
+    {
+        // unable to init the toolbar texture, disable it
+        m_undoredo_toolbar.set_enabled(false);
+        return true;
+    }
+
+//    m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Vertical);
+    m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Horizontal);
+    m_undoredo_toolbar.set_layout_orientation(GLToolbar::Layout::Top);
+    m_undoredo_toolbar.set_border(5.0f);
+    m_undoredo_toolbar.set_separator_size(5);
+    m_undoredo_toolbar.set_gap_size(2);
+
+    GLToolbarItem::Data item;
 
     item.name = "undo";
     item.icon_filename = "undo_toolbar.svg";
     item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]";
-    item.sprite_id = 11;
+    item.sprite_id = 0;
     item.left.toggable = false;
     item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); };
     item.right.toggable = true;
@@ -3649,18 +3718,18 @@ bool GLCanvas3D::_init_toolbar()
     item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(true, 0.5f * (left + right)); };
     item.visibility_callback = []()->bool { return true; };
     item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_undo(); };
-    if (!m_toolbar.add_item(item))
+    if (!m_undoredo_toolbar.add_item(item))
         return false;
 
     item.name = "redo";
     item.icon_filename = "redo_toolbar.svg";
     item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]";
-    item.sprite_id = 12;
+    item.sprite_id = 1;
     item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); };
     item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; };
     item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(false, 0.5f * (left + right)); };
     item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_redo(); };
-    if (!m_toolbar.add_item(item))
+    if (!m_undoredo_toolbar.add_item(item))
         return false;
 
     return true;
@@ -3991,7 +4060,8 @@ void GLCanvas3D::_render_overlays() const
     _render_gizmos_overlay();
     _render_warning_texture();
     _render_legend_texture();
-    _render_toolbar();
+    _render_main_toolbar();
+    _render_undoredo_toolbar();
     _render_view_toolbar();
 
     if ((m_layers_editing.last_object_id >= 0) && (m_layers_editing.object_max_z() > 0.0f))
@@ -4081,64 +4151,57 @@ void GLCanvas3D::_render_gizmos_overlay() const
     m_gizmos.render_overlay();
 }
 
-void GLCanvas3D::_render_toolbar() const
+void GLCanvas3D::_render_main_toolbar() const
 {
+    if (!m_main_toolbar.is_enabled())
+        return;
+
 #if ENABLE_RETINA_GL
-//     m_toolbar.set_scale(m_retina_helper->get_scale_factor());
+//     m_main_toolbar.set_scale(m_retina_helper->get_scale_factor());
     const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(true);
-    m_toolbar.set_scale(scale); //! #ys_FIXME_experiment
+    m_main_toolbar.set_scale(scale); //! #ys_FIXME_experiment
 #else
-//     m_toolbar.set_scale(m_canvas->GetContentScaleFactor());
-//     m_toolbar.set_scale(wxGetApp().em_unit()*0.1f);
+//     m_main_toolbar.set_scale(m_canvas->GetContentScaleFactor());
+//     m_main_toolbar.set_scale(wxGetApp().em_unit()*0.1f);
     const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(true));
-    m_toolbar.set_icons_size(size); //! #ys_FIXME_experiment
+    m_main_toolbar.set_icons_size(size); //! #ys_FIXME_experiment
 #endif // ENABLE_RETINA_GL
 
     Size cnv_size = get_canvas_size();
     float zoom = (float)m_camera.get_zoom();
     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
 
-    GLToolbar::Layout::EOrientation orientation = m_toolbar.get_layout_orientation();
+    float top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
+    float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width()) * inv_zoom;
 
-    float top = 0.0f;
-    float left = 0.0f;
-    switch (m_toolbar.get_layout_type())
-    {
-    default:
-    case GLToolbar::Layout::Horizontal:
-    {
-        // centers the toolbar on the top edge of the 3d scene
-        if (orientation == GLToolbar::Layout::Top)
-        {
-            top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
-            left = -0.5f * m_toolbar.get_width() * inv_zoom;
-        }
-        else
-        {
-            top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom;
-            left = -0.5f * m_toolbar.get_width() * inv_zoom;
-        }
-        break;
-    }
-    case GLToolbar::Layout::Vertical:
-    {
-        // centers the toolbar on the right edge of the 3d scene
-        if (orientation == GLToolbar::Layout::Left)
-        {
-            top = 0.5f * m_toolbar.get_height() * inv_zoom;
-            left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom;
-        }
-        else
-        {
-            top = 0.5f * m_toolbar.get_height() * inv_zoom;
-            left = (0.5f * (float)cnv_size.get_width() - m_toolbar.get_width()) * inv_zoom;
-        }
-        break;
-    }
-    }
-    m_toolbar.set_position(top, left);
+    m_main_toolbar.set_position(top, left);
+    m_main_toolbar.render(*this);
+}
 
-    m_toolbar.render(*this);
+void GLCanvas3D::_render_undoredo_toolbar() const
+{
+    if (!m_undoredo_toolbar.is_enabled())
+        return;
+
+#if ENABLE_RETINA_GL
+//     m_undoredo_toolbar.set_scale(m_retina_helper->get_scale_factor());
+    const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(true);
+    m_undoredo_toolbar.set_scale(scale); //! #ys_FIXME_experiment
+#else
+//     m_undoredo_toolbar.set_scale(m_canvas->GetContentScaleFactor());
+//     m_undoredo_toolbar.set_scale(wxGetApp().em_unit()*0.1f);
+    const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(true));
+    m_undoredo_toolbar.set_icons_size(size); //! #ys_FIXME_experiment
+#endif // ENABLE_RETINA_GL
+
+    Size cnv_size = get_canvas_size();
+    float zoom = (float)m_camera.get_zoom();
+    float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+
+    float top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
+    float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width())) * inv_zoom;
+    m_undoredo_toolbar.set_position(top, left);
+    m_undoredo_toolbar.render(*this);
 }
 
 void GLCanvas3D::_render_view_toolbar() const
@@ -5395,56 +5458,6 @@ void GLCanvas3D::_load_fff_shells()
     }
 }
 
-void GLCanvas3D::_load_sla_shells()
-{
-    //FIXME use reload_scene
-#if 1
-    const SLAPrint* print = this->sla_print();
-    if (print->objects().empty())
-        // nothing to render, return
-        return;
-
-    auto add_volume = [this](const SLAPrintObject &object, int volume_id, const SLAPrintObject::Instance& instance,
-        const TriangleMesh &mesh, const float color[4], bool outside_printer_detection_enabled) {
-        m_volumes.volumes.emplace_back(new GLVolume(color));
-        GLVolume& v = *m_volumes.volumes.back();
-        v.indexed_vertex_array.load_mesh(mesh);
-        v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled;
-        v.composite_id.volume_id = volume_id;
-        v.set_instance_offset(unscale(instance.shift(0), instance.shift(1), 0));
-        v.set_instance_rotation(Vec3d(0.0, 0.0, (double)instance.rotation));
-        v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.);
-        v.set_convex_hull(mesh.convex_hull_3d());
-    };
-
-    // adds objects' volumes 
-    for (const SLAPrintObject* obj : print->objects())
-        if (obj->is_step_done(slaposSliceSupports)) {
-            unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size();
-            for (const SLAPrintObject::Instance& instance : obj->instances()) {
-                add_volume(*obj, 0, instance, obj->transformed_mesh(), GLVolume::MODEL_COLOR[0], true);
-                // Set the extruder_id and volume_id to achieve the same color as in the 3D scene when
-                // through the update_volumes_colors_by_extruder() call.
-                m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id();
-                if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree))
-                    add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true);
-                if (obj->is_step_done(slaposBasePool) && obj->has_mesh(slaposBasePool))
-                    add_volume(*obj, -int(slaposBasePool), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false);
-            }
-            double shift_z = obj->get_current_elevation();
-            for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) {
-                GLVolume& v = *m_volumes.volumes[i];
-                // apply shift z
-                v.set_sla_shift_z(shift_z);
-            }
-        }
-
-    update_volumes_colors_by_extruder();
-#else
-    this->reload_scene(true, true);
-#endif
-}
-
 void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data)
 {
     unsigned int size = (unsigned int)m_gcode_preview_volume_index.first_volumes.size();
@@ -5660,14 +5673,14 @@ void GLCanvas3D::_update_selection_from_hover()
 
 bool GLCanvas3D::_deactivate_undo_redo_toolbar_items()
 {
-    if (m_toolbar.is_item_pressed("undo"))
+    if (m_undoredo_toolbar.is_item_pressed("undo"))
     {
-        m_toolbar.force_right_action(m_toolbar.get_item_id("undo"), *this);
+        m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("undo"), *this);
         return true;
     }
-    else if (m_toolbar.is_item_pressed("redo"))
+    else if (m_undoredo_toolbar.is_item_pressed("redo"))
     {
-        m_toolbar.force_right_action(m_toolbar.get_item_id("redo"), *this);
+        m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("redo"), *this);
         return true;
     }
 
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index fdf7cf38e..a946cd95e 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -437,7 +437,8 @@ private:
     Shader m_shader;
     Mouse m_mouse;
     mutable GLGizmosManager m_gizmos;
-    mutable GLToolbar m_toolbar;
+    mutable GLToolbar m_main_toolbar;
+    mutable GLToolbar m_undoredo_toolbar;
     ClippingPlane m_clipping_planes[2];
     mutable ClippingPlane m_camera_clipping_plane;
     bool m_use_clipping_planes;
@@ -550,7 +551,8 @@ public:
     void enable_moving(bool enable);
     void enable_gizmos(bool enable);
     void enable_selection(bool enable);
-    void enable_toolbar(bool enable);
+    void enable_main_toolbar(bool enable);
+    void enable_undoredo_toolbar(bool enable);
     void enable_dynamic_background(bool enable);
     void allow_multisample(bool allow);
 
@@ -662,10 +664,16 @@ public:
     void start_keeping_dirty() { m_keep_dirty = true; }
     void stop_keeping_dirty() { m_keep_dirty = false; }
 
+    unsigned int get_main_toolbar_item_id(const std::string& name) const { return m_main_toolbar.get_item_id(name); }
+    void force_main_toolbar_left_action(unsigned int item_id) { m_main_toolbar.force_left_action(item_id, *this); }
+    void force_main_toolbar_right_action(unsigned int item_id) { m_main_toolbar.force_right_action(item_id, *this); }
+
 private:
     bool _is_shown_on_screen() const;
 
-    bool _init_toolbar();
+    bool _init_toolbars();
+    bool _init_main_toolbar();
+    bool _init_undoredo_toolbar();
 
     bool _set_current();
     void _resize(unsigned int w, unsigned int h);
@@ -692,7 +700,8 @@ private:
     void _render_volumes_for_picking() const;
     void _render_current_gizmo() const;
     void _render_gizmos_overlay() const;
-    void _render_toolbar() const;
+    void _render_main_toolbar() const;
+    void _render_undoredo_toolbar() const;
     void _render_view_toolbar() const;
 #if ENABLE_SHOW_CAMERA_TARGET
     void _render_camera_target() const;
@@ -739,8 +748,6 @@ private:
     void _load_gcode_unretractions(const GCodePreviewData& preview_data);
     // generates objects and wipe tower geometry
     void _load_fff_shells();
-    // generates objects geometry for sla
-    void _load_sla_shells();
     // sets gcode geometry visibility according to user selection
     void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data);
     void _update_toolpath_volumes_outside_state();
diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp
index 0002eda2d..cdd80ed17 100644
--- a/src/slic3r/GUI/GLToolbar.cpp
+++ b/src/slic3r/GUI/GLToolbar.cpp
@@ -403,6 +403,9 @@ void GLToolbar::render(const GLCanvas3D& parent) const
 
 bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
 {
+    if (!m_enabled)
+        return false;
+
     Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY());
     bool processed = false;
 
@@ -1009,9 +1012,6 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const
         float bg_right = right;
         float bg_top = top;
         float bg_bottom = bottom;
-        float bg_width = right - left;
-        float bg_height = top - bottom;
-        float bg_min_size = std::min(bg_width, bg_height);
 
         float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width;
         float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width;
@@ -1139,9 +1139,6 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const
         float bg_right = right;
         float bg_top = top;
         float bg_bottom = bottom;
-        float bg_width = right - left;
-        float bg_height = top - bottom;
-        float bg_min_size = std::min(bg_width, bg_height);
 
         float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width;
         float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width;
diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp
index 9a641c7c0..8f41ed5a3 100644
--- a/src/slic3r/GUI/GUI.cpp
+++ b/src/slic3r/GUI/GUI.cpp
@@ -135,8 +135,7 @@ void config_wizard(int reason)
 
 	wxGetApp().load_current_presets();
 
-    if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA &&
-        wxGetApp().obj_list()->has_multi_part_objects())
+    if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA && model_has_multi_part_objects(wxGetApp().model()))
     {
         show_info(nullptr,
             _(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" +
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 8a376c3a3..02290e83d 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -534,7 +534,7 @@ void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_max
     });
 }
 
-void GUI_App::load_project(wxWindow *parent, wxString& input_file)
+void GUI_App::load_project(wxWindow *parent, wxString& input_file) const
 {
     input_file.Clear();
     wxFileDialog dialog(parent ? parent : GetTopWindow(),
@@ -546,7 +546,7 @@ void GUI_App::load_project(wxWindow *parent, wxString& input_file)
         input_file = dialog.GetPath();
 }
 
-void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files)
+void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const
 {
     input_files.Clear();
     wxFileDialog dialog(parent ? parent : GetTopWindow(),
@@ -854,7 +854,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
 
 // This is called when closing the application, when loading a config file or when starting the config wizard
 // to notify the user whether he is aware that some preset changes will be lost.
-bool GUI_App::check_unsaved_changes()
+bool GUI_App::check_unsaved_changes(const wxString &header)
 {
     wxString dirty;
     PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
@@ -868,8 +868,12 @@ bool GUI_App::check_unsaved_changes()
         // No changes, the application may close or reload presets.
         return true;
     // Ask the user.
+    wxString message;
+    if (! header.empty())
+    	message = header + "\n\n";
+    message += _(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?"));
     wxMessageDialog dialog(mainframe,
-        _(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?")),
+        message,
         wxString(SLIC3R_APP_NAME) + " - " + _(L("Unsaved Presets")),
         wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
     return dialog.ShowModal() == wxID_YES;
@@ -944,9 +948,9 @@ Plater* GUI_App::plater()
     return plater_;
 }
 
-ModelObjectPtrs* GUI_App::model_objects()
+Model& GUI_App::model()
 {
-    return &plater_->model().objects;
+    return plater_->model();
 }
 
 wxNotebook* GUI_App::tab_panel() const
diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp
index e69503ff8..8d0bcfe2f 100644
--- a/src/slic3r/GUI/GUI_App.hpp
+++ b/src/slic3r/GUI/GUI_App.hpp
@@ -121,8 +121,8 @@ public:
     void            recreate_GUI();
     void            system_info();
     void            keyboard_shortcuts();
-    void            load_project(wxWindow *parent, wxString& input_file);
-    void            import_model(wxWindow *parent, wxArrayString& input_files);
+    void            load_project(wxWindow *parent, wxString& input_file) const;
+    void            import_model(wxWindow *parent, wxArrayString& input_files) const;
     static bool     catch_error(std::function<void()> cb, const std::string& err);
 
     void            persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false);
@@ -139,7 +139,7 @@ public:
     void            update_mode();
 
     void            add_config_menu(wxMenuBar *menu);
-    bool            check_unsaved_changes();
+    bool            check_unsaved_changes(const wxString &header = wxString());
     bool            checked_tab(Tab* tab);
     void            load_current_presets();
 
@@ -158,7 +158,7 @@ public:
     ObjectList*         obj_list();
     ObjectLayers*       obj_layers();
     Plater*             plater();
-    std::vector<ModelObject*> *model_objects();
+    Model&      		model();
 
     AppConfig*      app_config{ nullptr };
     PresetBundle*   preset_bundle{ nullptr };
diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp
index 9e681257c..f7abb4128 100644
--- a/src/slic3r/GUI/GUI_ObjectList.cpp
+++ b/src/slic3r/GUI/GUI_ObjectList.cpp
@@ -71,9 +71,6 @@ static void take_snapshot(const wxString& snapshot_name)
     wxGetApp().plater()->take_snapshot(snapshot_name);
 }
 
-static void suppress_snapshots(){ wxGetApp().plater()->suppress_snapshots(); }
-static void allow_snapshots()   { wxGetApp().plater()->allow_snapshots(); }
-
 ObjectList::ObjectList(wxWindow* parent) :
     wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE),
     m_parent(parent)
@@ -2406,8 +2403,7 @@ void ObjectList::remove()
 
     wxDataViewItem  parent = wxDataViewItem(0);
 
-    take_snapshot(_(L("Delete Selected")));
-    suppress_snapshots();
+    Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete Selected")));
 
     for (auto& item : sels)
     {
@@ -2429,8 +2425,6 @@ void ObjectList::remove()
 
     if (parent)
         select_item(parent);
-
-    allow_snapshots();
 }
 
 void ObjectList::del_layer_range(const t_layer_height_range& range)
@@ -2505,8 +2499,7 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre
             
             t_layer_height_range new_range = { midl_layer, next_range.second };
 
-            take_snapshot(_(L("Add New Layers Range")));
-            suppress_snapshots();
+            Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add New Layers Range")));
 
             // create new 2 layers instead of deleted one
 
@@ -2521,7 +2514,6 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre
             new_range = { current_range.second, midl_layer };
             ranges[new_range] = get_default_layer_config(obj_idx);
             add_layer_item(new_range, layers_item, layer_idx);
-            allow_snapshots();
         }
         else
         {
@@ -2611,7 +2603,7 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay
 
 void ObjectList::init_objects()
 {
-    m_objects = wxGetApp().model_objects();
+    m_objects = &wxGetApp().model().objects;
 }
 
 bool ObjectList::multiple_selection() const 
@@ -3088,19 +3080,6 @@ void ObjectList::last_volume_is_deleted(const int obj_idx)
     volume->config.set_key_value("extruder", new ConfigOptionInt(0));
 }
 
-bool ObjectList::has_multi_part_objects()
-{
-    if (!m_objects_model->IsEmpty()) {
-        wxDataViewItemArray items;
-        m_objects_model->GetChildren(wxDataViewItem(0), items);
-
-        for (auto& item : items)
-            if (m_objects_model->GetItemByType(item, itVolume))
-                return true;
-    }
-    return false;
-}
-
 /* #lm_FIXME_delete_after_testing
 void ObjectList::update_settings_items()
 {
@@ -3531,7 +3510,7 @@ void ObjectList::update_after_undo_redo()
     m_prevent_list_events = true;
     m_prevent_canvas_selection_update = true;
 
-    suppress_snapshots();
+    Plater::SuppressSnapshots suppress(wxGetApp().plater());
 
     // Unselect all objects before deleting them, so that no change of selection is emitted during deletion.
     this->UnselectAll();
@@ -3543,8 +3522,6 @@ void ObjectList::update_after_undo_redo()
         ++obj_idx;
     }
 
-    allow_snapshots();
-
 #ifndef __WXOSX__ 
     selection_changed();
 #endif /* __WXOSX__ */
diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp
index 2d7e9f5f1..5f5d5d625 100644
--- a/src/slic3r/GUI/GUI_ObjectList.hpp
+++ b/src/slic3r/GUI/GUI_ObjectList.hpp
@@ -317,7 +317,6 @@ public:
     void change_part_type();
 
     void last_volume_is_deleted(const int obj_idx);
-    bool has_multi_part_objects();
     void update_settings_items();
     void update_and_show_object_settings_item();
     void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections);
diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
index fcb047139..0a2877d92 100644
--- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp
+++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
@@ -25,7 +25,7 @@ static double get_volume_min_z(const GLVolume* volume)
     const Transform3f& world_matrix = volume->world_matrix().cast<float>();
 
     // need to get the ModelVolume pointer
-    const ModelObject* mo = wxGetApp().model_objects()->at(volume->composite_id.object_id);
+    const ModelObject* mo = wxGetApp().model().objects[volume->composite_id.object_id];
     const ModelVolume* mv = mo->volumes[volume->composite_id.volume_id];
     const TriangleMesh& hull = mv->get_convex_hull();
 
@@ -199,18 +199,17 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
     m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off.png");
     m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png");
 
-	for (const std::string axis : { "x", "y", "z" }) {
-        const std::string label = boost::algorithm::to_upper_copy(axis);
-        def.set_default_value(new ConfigOptionString{ "   " + label });
-        Option option = Option(def, axis + "_axis_legend");
-
-        unsigned int axis_idx = (axis[0] - 'x'); // 0, 1 or 2
+    static const char axes[] = { 'X', 'Y', 'Z' };
+    for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) {
+        const char label = axes[axis_idx];
+        def.set_default_value(new ConfigOptionString{ std::string("   ") + label });
+        Option option(def, std::string() + label + "_axis_legend");
 
         // We will add a button to toggle mirroring to each axis:
-        auto mirror_button = [this, mirror_btn_width, axis_idx, &label](wxWindow* parent) {
+        auto mirror_button = [this, mirror_btn_width, axis_idx, label](wxWindow* parent) {
             wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width);
             auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off.png", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW);
-            btn->SetToolTip(wxString::Format(_(L("Toggle %s axis mirroring")), label));
+            btn->SetToolTip(wxString::Format(_(L("Toggle %c axis mirroring")), (int)label));
 
             m_mirror_buttons[axis_idx].first = btn;
             m_mirror_buttons[axis_idx].second = mbShown;
@@ -245,7 +244,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
                 canvas->do_mirror(L("Set Mirror"));
                 UpdateAndShow(true);
             });
-        return sizer;
+
+            return sizer;
         };
 
         option.side_widget = mirror_button;
@@ -466,7 +466,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
 			m_new_scale    = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.;
 		} else {
 			m_new_rotation = volume->get_instance_rotation() * (180. / M_PI);
-			m_new_size     = volume->get_instance_transformation().get_scaling_factor().cwiseProduct((*wxGetApp().model_objects())[volume->object_idx()]->raw_mesh_bounding_box().size());
+			m_new_size     = volume->get_instance_transformation().get_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size());
 			m_new_scale    = volume->get_instance_scaling_factor() * 100.;
 		}
 
@@ -779,7 +779,7 @@ void ObjectManipulation::change_size_value(int axis, double value)
     else if (selection.is_single_full_instance())
 		ref_size = m_world_coordinates ? 
             selection.get_unscaled_instance_bounding_box().size() :
-            (*wxGetApp().model_objects())[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size();
+            wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size();
 
     this->do_scale(axis, 100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2)));
 
@@ -902,7 +902,7 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value)
                 return;
             }
             // Bake the rotation into the meshes of the object.
-            (*wxGetApp().model_objects())[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id);
+            wxGetApp().model().objects[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id);
             // Update the 3D scene, selections etc.
             wxGetApp().plater()->update();
             // Recalculate cached values at this panel, refresh the screen.
diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp
index 0ba447c1b..36354ab24 100644
--- a/src/slic3r/GUI/GUI_Preview.cpp
+++ b/src/slic3r/GUI/GUI_Preview.cpp
@@ -63,7 +63,8 @@ bool View3D::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_
     m_canvas->set_config(config);
     m_canvas->enable_gizmos(true);
     m_canvas->enable_selection(true);
-    m_canvas->enable_toolbar(true);
+    m_canvas->enable_main_toolbar(true);
+    m_canvas->enable_undoredo_toolbar(true);
 
     wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
     main_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp
index cb996a104..5a42cbd31 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp
@@ -29,7 +29,7 @@ void GLGizmoFlatten::on_set_state()
     // m_model_object pointer can be invalid (for instance because of undo/redo action),
     // we should recover it from the object id
     m_model_object = nullptr;
-    for (const auto mo : *wxGetApp().model_objects()) {
+    for (const auto mo : wxGetApp().model().objects) {
         if (mo->id() == m_model_object_id) {
             m_model_object = mo;
             break;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
index 833ba3ee2..8027906a9 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
@@ -1049,7 +1049,7 @@ void GLGizmoSlaSupports::on_set_state()
     // we should recover it from the object id
     const ModelObject* old_model_object = m_model_object;
     m_model_object = nullptr;
-    for (const auto mo : *wxGetApp().model_objects()) {
+    for (const auto mo : wxGetApp().model().objects) {
         if (mo->id() == m_current_mesh_object_id) {
             m_model_object = mo;
             break;
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 52795fa41..1ecf0eb61 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -1255,13 +1255,9 @@ const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)",
 
 bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames)
 {
-	plater->take_snapshot(_(L("Load Files")));
-
     std::vector<fs::path> paths;
-
     for (const auto &filename : filenames) {
         fs::path path(into_path(filename));
-
         if (std::regex_match(path.string(), pattern_drop)) {
             paths.push_back(std::move(path));
         } else {
@@ -1269,6 +1265,23 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi
         }
     }
 
+	wxString snapshot_label;
+	assert(! paths.empty());
+	if (paths.size() == 1) {
+		snapshot_label = _(L("Load File"));
+		snapshot_label += ": ";
+		snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
+	} else {
+		snapshot_label = _(L("Load Files"));
+		snapshot_label += ": ";
+		snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
+		for (size_t i = 1; i < paths.size(); ++ i) {
+			snapshot_label += ", ";
+			snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str());
+		}
+	}
+	Plater::TakeSnapshot snapshot(plater, snapshot_label);
+
     // FIXME: when drag and drop is done on a .3mf or a .amf file we should clear the plater for consistence with the open project command
     // (the following call to plater->load_files() will load the config data, if present)
 
@@ -1736,7 +1749,7 @@ struct Plater::priv
     priv(Plater *q, MainFrame *main_frame);
     ~priv();
 
-    void update(bool force_full_scene_refresh = false);
+    void update(bool force_full_scene_refresh = false, bool force_background_processing_update = false);
     void select_view(const std::string& direction);
     void select_view_3D(const std::string& name);
     void select_next_view_3D();
@@ -1772,19 +1785,13 @@ struct Plater::priv
     void split_volume();
     void scale_selection_to_fit_print_volume();
 
-	void take_snapshot(const std::string& snapshot_name)
-	{
-        if (this->m_prevent_snapshots > 0) 
-            return;
-        assert(this->m_prevent_snapshots >= 0);
-        this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager());
-    }
+	void take_snapshot(const std::string& snapshot_name);
 	void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); }
     int  get_active_snapshot_index();
     void undo();
     void redo();
-    void undo_to(size_t time_to_load);
-    void redo_to(size_t time_to_load);
+    void undo_redo_to(size_t time_to_load);
+
     void suppress_snapshots()   { this->m_prevent_snapshots++; }
     void allow_snapshots()      { this->m_prevent_snapshots--; }
 
@@ -1878,10 +1885,13 @@ private:
 
     void update_fff_scene();
     void update_sla_scene();
-    void update_after_undo_redo();
+	void undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator it_snapshot);
+    void update_after_undo_redo(bool temp_snapshot_was_taken = false);
 
     // path to project file stored with no extension
     wxString m_project_filename;
+    std::string m_last_fff_printer_profile_name;
+    std::string m_last_sla_printer_profile_name;
 };
 
 const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase);
@@ -2032,7 +2042,7 @@ Plater::priv::~priv()
         delete config;
 }
 
-void Plater::priv::update(bool force_full_scene_refresh)
+void Plater::priv::update(bool force_full_scene_refresh, bool force_background_processing_update)
 {
     // the following line, when enabled, causes flickering on NVIDIA graphics cards
 //    wxWindowUpdateLocker freeze_guard(q);
@@ -2045,7 +2055,7 @@ void Plater::priv::update(bool force_full_scene_refresh)
     }
 
     unsigned int update_status = 0;
-    if (this->printer_technology == ptSLA)
+    if (this->printer_technology == ptSLA || force_background_processing_update)
         // Update the SLAPrint from the current Model, so that the reload_scene()
         // pulls the correct data.
         update_status = this->update_background_process(false);
@@ -2231,66 +2241,22 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
                     }
                 }
             }
-            else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf))
-            {
-                bool advanced = false;
-                for (const ModelObject* model_object : model.objects)
+            else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) {
+                wxMessageDialog dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?\n")),
+                    _(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO);
+                if (dlg.ShowModal() == wxID_YES)
                 {
-                    // is there more than one instance ?
-                    if (model_object->instances.size() > 1)
-                    {
-                        advanced = true;
-                        break;
-                    }
-
-                    // is there any advanced config data ?
-                    auto opt_keys = model_object->config.keys();
-                    if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder")))
-                    {
-                        advanced = true;
-                        break;
-                    }
-
-                    // is there any modifier ?
-                    for (const ModelVolume* model_volume : model_object->volumes)
-                    {
-                        if (!model_volume->is_model_part())
-                        {
-                            advanced = true;
-                            break;
-                        }
-
-                        // is there any advanced config data ?
-                        opt_keys = model_volume->config.keys();
-                        if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder")))
-                        {
-                            advanced = true;
-                            break;
-                        }
-                    }
-
-                    if (advanced)
-                        break;
-                }
-
-                if (advanced)
-                {
-                    wxMessageDialog dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?\n")),
-                        _(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO);
-                    if (dlg.ShowModal() == wxID_YES)
-                    {
-                        Slic3r::GUI::wxGetApp().save_mode(comAdvanced);
-                        view3D->set_as_dirty();
-                    }
-                    else
-                        return obj_idxs;
+                    Slic3r::GUI::wxGetApp().save_mode(comAdvanced);
+                    view3D->set_as_dirty();
                 }
+                else
+                    return obj_idxs;
             }
 
-                for (ModelObject* model_object : model.objects) {
-                    model_object->center_around_origin(false);
-                    model_object->ensure_on_bed();
-                }
+            for (ModelObject* model_object : model.objects) {
+                model_object->center_around_origin(false);
+                model_object->ensure_on_bed();
+            }
 
             // check multi-part object adding for the SLA-printing
             if (printer_technology == ptSLA)
@@ -2603,7 +2569,10 @@ void Plater::priv::remove(size_t obj_idx)
 
 void Plater::priv::delete_object_from_model(size_t obj_idx)
 {
-    this->take_snapshot(_(L("Delete Object")));
+	wxString snapshot_label = _(L("Delete Object"));
+	if (! model.objects[obj_idx]->name.empty())
+		snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str());
+	Plater::TakeSnapshot snapshot(q, snapshot_label);
     model.delete_object(obj_idx);
     update();
     object_list_changed();
@@ -2611,7 +2580,7 @@ void Plater::priv::delete_object_from_model(size_t obj_idx)
 
 void Plater::priv::reset()
 {
-	this->take_snapshot(_(L("Reset Project")));
+	Plater::TakeSnapshot snapshot(q, _(L("Reset Project")));
 
     set_project_filename(wxEmptyString);
 
@@ -2804,7 +2773,7 @@ void Plater::priv::split_object()
         Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part.")));
     else
     {
-        this->take_snapshot(_(L("Split to Objects")));
+		Plater::TakeSnapshot snapshot(q, _(L("Split to Objects")));
 
         unsigned int counter = 1;
         for (ModelObject* m : new_objects)
@@ -3044,7 +3013,7 @@ void Plater::priv::update_sla_scene()
 
 void Plater::priv::reload_from_disk()
 {
-    this->take_snapshot(_(L("Reload from Disk")));
+	Plater::TakeSnapshot snapshot(q, _(L("Reload from Disk")));
 
     const auto &selection = get_selection();
     const auto obj_orig_idx = selection.get_object_idx();
@@ -3080,7 +3049,7 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* =
     if (obj_idx < 0)
         return;
 
-    this->take_snapshot(_(L("Fix Throught NetFabb")));
+	Plater::TakeSnapshot snapshot(q, _(L("Fix Throught NetFabb")));
 
     fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx);
     this->update();
@@ -3744,48 +3713,152 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const
 
 int Plater::priv::get_active_snapshot_index()
 {
-    const size_t& active_snapshot_time = this->undo_redo_stack.active_snapshot_time();
+    const size_t active_snapshot_time = this->undo_redo_stack.active_snapshot_time();
     const std::vector<UndoRedo::Snapshot>& ss_stack = this->undo_redo_stack.snapshots();
     const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time));
     return it - ss_stack.begin();
 }
 
+void Plater::priv::take_snapshot(const std::string& snapshot_name)
+{
+    if (this->m_prevent_snapshots > 0) 
+        return;
+    assert(this->m_prevent_snapshots >= 0);
+    unsigned int flags = 0;
+    if (this->view3D->is_layers_editing_enabled())
+    	flags |= UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE;
+    //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config.
+    // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config.
+    if (this->printer_technology == ptFFF) {
+        const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
+        model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y"));
+        model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle");
+    }
+    this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology, flags);
+    this->undo_redo_stack.release_least_recently_used();
+    // Save the last active preset name of a particular printer technology.
+    ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name();
+	BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack.memsize()) << log_memory_info();
+}
+
 void Plater::priv::undo()
 {
-    if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager()))
-        this->update_after_undo_redo();
+	const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack.snapshots();
+	auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack.active_snapshot_time()));
+	if (-- it_current != snapshots.begin())
+		this->undo_redo_to(it_current);
 }
 
 void Plater::priv::redo()
 { 
-    if (this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager()))
-        this->update_after_undo_redo();
+	const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack.snapshots();
+	auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack.active_snapshot_time()));
+	if (++ it_current != snapshots.end())
+		this->undo_redo_to(it_current);
 }
 
-void Plater::priv::undo_to(size_t time_to_load)
+void Plater::priv::undo_redo_to(size_t time_to_load)
 {
-    if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), time_to_load))
-        this->update_after_undo_redo();
+	const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack.snapshots();
+	auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(time_to_load));
+	assert(it_current != snapshots.end());
+	this->undo_redo_to(it_current);
 }
 
-void Plater::priv::redo_to(size_t time_to_load)
-{ 
-    if (this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), time_to_load))
-        this->update_after_undo_redo();
-}
-
-void Plater::priv::update_after_undo_redo()
+void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator it_snapshot)
 {
-    this->view3D->get_canvas3d()->get_selection().clear();
-	this->update(false); // update volumes from the deserializd model
+	bool 				temp_snapshot_was_taken 	= this->undo_redo_stack.temp_snapshot_active();
+	PrinterTechnology 	new_printer_technology 		= it_snapshot->printer_technology;
+	bool 				printer_technology_changed 	= this->printer_technology != new_printer_technology;
+	if (printer_technology_changed) {
+		// Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type.
+		std::string s_pt = (it_snapshot->printer_technology == ptFFF) ? "FFF" : "SLA";
+		if (! wxGetApp().check_unsaved_changes(from_u8((boost::format(_utf8(
+			L("%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."))) % s_pt).str())))
+			// Don't switch the profiles.
+			return;
+	}
+    // Save the last active preset name of a particular printer technology.
+    ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name();
+    //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config.
+    // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config.
+    if (this->printer_technology == ptFFF) {
+        const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
+                model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y"));
+                model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle");
+    }
+    // Flags made of Snapshot::Flags enum values.
+    unsigned int new_flags = it_snapshot->flags;
+	unsigned int top_snapshot_flags = 0;
+    if (this->view3D->is_layers_editing_enabled())
+    	top_snapshot_flags |= UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE;
+	bool   		 new_variable_layer_editing_active = (new_flags & UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE) != 0;
+	// Disable layer editing before the Undo / Redo jump.
+    if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled())
+        view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting"));
+    // Do the jump in time.
+    if (it_snapshot->timestamp < this->undo_redo_stack.active_snapshot_time() ?
+		this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology, top_snapshot_flags, it_snapshot->timestamp) :
+		this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), it_snapshot->timestamp)) {
+		if (printer_technology_changed) {
+			// Switch to the other printer technology. Switch to the last printer active for that particular technology.
+		    AppConfig *app_config = wxGetApp().app_config;
+    		app_config->set("presets", "printer", (new_printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name);
+			wxGetApp().preset_bundle->load_presets(*app_config);
+        	// Load the currently selected preset into the GUI, update the preset selection box.
+        	// This also switches the printer technology based on the printer technology of the active printer profile.
+        	wxGetApp().load_current_presets();
+        }
+        //FIXME updating the Print config from the Wipe tower config values at the ModelWipeTower.
+        // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config.
+        if (this->printer_technology == ptFFF) {
+            const DynamicPrintConfig &current_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
+            Vec2d 					  current_position(current_config.opt_float("wipe_tower_x"), current_config.opt_float("wipe_tower_y"));
+            double 					  current_rotation = current_config.opt_float("wipe_tower_rotation_angle");
+            if (current_position != model.wipe_tower.position || current_rotation != model.wipe_tower.rotation) {
+                DynamicPrintConfig new_config;
+                new_config.set_key_value("wipe_tower_x", new ConfigOptionFloat(model.wipe_tower.position.x()));
+                new_config.set_key_value("wipe_tower_y", new ConfigOptionFloat(model.wipe_tower.position.y()));
+                new_config.set_key_value("wipe_tower_rotation_angle", new ConfigOptionFloat(model.wipe_tower.rotation));
+                Tab *tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT);
+                tab_print->load_config(new_config);
+                tab_print->update_dirty();
+            }
+        }
+        this->update_after_undo_redo(temp_snapshot_was_taken);
+		// Enable layer editing after the Undo / Redo jump.
+		if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active)
+            view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting"));
+    }
+}
+
+void Plater::priv::update_after_undo_redo(bool /* temp_snapshot_was_taken */)
+{
+	this->view3D->get_canvas3d()->get_selection().clear();
+	// Update volumes from the deserializd model, always stop / update the background processing (for both the SLA and FFF technologies).
+	this->update(false, true);
+	// Release old snapshots if the memory allocated is excessive. This may remove the top most snapshot if jumping to the very first snapshot.
+	//if (temp_snapshot_was_taken)
+	// Release the old snapshots always, as it may have happened, that some of the triangle meshes got deserialized from the snapshot, while some
+	// triangle meshes may have gotten released from the scene or the background processing, therefore now being calculated into the Undo / Redo stack size.
+		this->undo_redo_stack.release_least_recently_used();
 	//YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time)
     this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack.selection_deserialized().mode), this->undo_redo_stack.selection_deserialized().volumes_and_instances);
     this->view3D->get_canvas3d()->get_gizmos_manager().update_after_undo_redo();
 
     wxGetApp().obj_list()->update_after_undo_redo();
 
+    if (wxGetApp().get_mode() == comSimple && model_has_advanced_features(this->model)) {
+    	// If the user jumped to a snapshot that require user interface with advanced features, switch to the advanced mode without asking.
+    	// There is a little risk of surprising the user, as he already must have had the advanced or expert mode active for such a snapshot to be taken.
+        Slic3r::GUI::wxGetApp().save_mode(comAdvanced);
+        view3D->set_as_dirty();
+    }
+
 	//FIXME what about the state of the manipulators?
 	//FIXME what about the focus? Cursor in the side panel?
+
+    BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot reloaded. Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack.memsize()) << log_memory_info();
 }
 
 void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const
@@ -3825,10 +3898,12 @@ void Plater::new_project()
 
 void Plater::load_project()
 {
-	this->take_snapshot(_(L("Load Project")));
-
+    // Ask user for a project file name.
     wxString input_file;
     wxGetApp().load_project(this, input_file);
+    // Take the Undo / Redo snapshot.
+	Plater::TakeSnapshot snapshot(this, _(L("Load Project")) + ": " + wxString::FromUTF8(into_path(input_file).stem().string().c_str()));
+    // And finally load the new project.
     load_project(input_file);
 }
 
@@ -3852,13 +3927,28 @@ void Plater::add_model()
     if (input_files.empty())
         return;
 
-    this->take_snapshot(_(L("Add object(s)")));
+    std::vector<fs::path> paths;
+    for (const auto &file : input_files)
+        paths.push_back(into_path(file));
 
-    std::vector<fs::path> input_paths;
-    for (const auto &file : input_files) {
-        input_paths.push_back(into_path(file));
-    }
-    load_files(input_paths, true, false);
+	wxString snapshot_label;
+	assert(! paths.empty());
+	if (paths.size() == 1) {
+		snapshot_label = _(L("Import Object"));
+		snapshot_label += ": ";
+		snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
+	} else {
+		snapshot_label = _(L("Import Objects"));
+		snapshot_label += ": ";
+		snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
+		for (size_t i = 1; i < paths.size(); ++ i) {
+			snapshot_label += ", ";
+			snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str());
+		}
+	}
+
+	Plater::TakeSnapshot snapshot(this, snapshot_label);
+    load_files(paths, true, false);
 }
 
 void Plater::extract_config_from_project()
@@ -3911,17 +4001,15 @@ void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_mo
 
 void Plater::remove_selected()
 {
-	this->take_snapshot(_(L("Delete Selected Objects")));
-    this->suppress_snapshots();
+	Plater::TakeSnapshot snapshot(this, _(L("Delete Selected Objects")));
     this->p->view3D->delete_selected();
-    this->allow_snapshots();
 }
 
 void Plater::increase_instances(size_t num)
 {
     if (! can_increase_instances()) { return; }
 
-	this->take_snapshot(_(L("Increase Instances")));
+	Plater::TakeSnapshot snapshot(this, _(L("Increase Instances")));
 
     int obj_idx = p->get_selected_object_idx();
 
@@ -3957,7 +4045,7 @@ void Plater::decrease_instances(size_t num)
 {
     if (! can_decrease_instances()) { return; }
 
-	this->take_snapshot(_(L("Decrease Instances")));
+	Plater::TakeSnapshot snapshot(this, _(L("Decrease Instances")));
 
     int obj_idx = p->get_selected_object_idx();
 
@@ -3993,16 +4081,13 @@ void Plater::set_number_of_copies(/*size_t num*/)
     if (num < 0)
         return;
 
-    this->take_snapshot(wxString::Format(_(L("Set numbers of copies to %d")), num));
-    this->suppress_snapshots();
+	Plater::TakeSnapshot snapshot(this, wxString::Format(_(L("Set numbers of copies to %d")), num));
 
     int diff = (int)num - (int)model_object->instances.size();
     if (diff > 0)
         increase_instances(diff);
     else if (diff < 0)
         decrease_instances(-diff);
-
-    this->allow_snapshots();
 }
 
 bool Plater::is_selection_empty() const
@@ -4026,7 +4111,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe
         return;
     }
 
-    this->take_snapshot(_(L("Gizmo-Cut")));
+	Plater::TakeSnapshot snapshot(this, _(L("Cut by Plane")));
 
     wxBusyCursor wait;
     const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower);
@@ -4331,7 +4416,7 @@ void Plater::undo_to(int selection)
     }
     
     const int idx = p->get_active_snapshot_index() - selection - 1;
-    p->undo_to(p->undo_redo_stack.snapshots()[idx].timestamp);
+    p->undo_redo_to(p->undo_redo_stack.snapshots()[idx].timestamp);
 }
 void Plater::redo_to(int selection)
 {
@@ -4341,7 +4426,7 @@ void Plater::redo_to(int selection)
     }
     
     const int idx = p->get_active_snapshot_index() + selection + 1;
-    p->redo_to(p->undo_redo_stack.snapshots()[idx].timestamp);
+    p->undo_redo_to(p->undo_redo_stack.snapshots()[idx].timestamp);
 }
 bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** out_text)
 {
@@ -4650,15 +4735,9 @@ bool Plater::can_copy_to_clipboard() const
     return true;
 }
 
-bool Plater::can_undo() const
-{
-    return p->undo_redo_stack.has_undo_snapshot();
-}
-
-bool Plater::can_redo() const
-{
-    return p->undo_redo_stack.has_redo_snapshot();
-}
+bool Plater::can_undo() const { return p->undo_redo_stack.has_undo_snapshot(); }
+bool Plater::can_redo() const { return p->undo_redo_stack.has_redo_snapshot(); }
+const UndoRedo::Stack& Plater::undo_redo_stack() const { return p->undo_redo_stack; }
 
 SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() :
     m_was_running(wxGetApp().plater()->is_background_process_running())
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index e73f88ef3..d7e91f516 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -27,6 +27,11 @@ class ModelObject;
 class Print;
 class SLAPrint;
 
+namespace UndoRedo {
+	class Stack;
+	struct Snapshot;	
+};
+
 namespace GUI {
 
 class MainFrame;
@@ -186,13 +191,12 @@ public:
 
     void take_snapshot(const std::string &snapshot_name);
     void take_snapshot(const wxString &snapshot_name);
-    void suppress_snapshots();
-    void allow_snapshots();
     void undo();
     void redo();
     void undo_to(int selection);
     void redo_to(int selection);
     bool undo_redo_string_getter(const bool is_undo, int idx, const char** out_text);
+    const Slic3r::UndoRedo::Stack& undo_redo_stack() const;
 
     void on_extruders_change(int extruders_count);
     void on_config_change(const DynamicPrintConfig &config);
@@ -235,10 +239,46 @@ public:
 
     const Camera& get_camera() const;
 
+	// ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
+	class SuppressSnapshots
+	{
+	public:
+		SuppressSnapshots(Plater *plater) : m_plater(plater)
+		{
+			m_plater->suppress_snapshots();
+		}
+		~SuppressSnapshots()
+		{
+			m_plater->allow_snapshots();
+		}
+	private:
+		Plater *m_plater;
+	};
+
+	// ROII wrapper for taking an Undo / Redo snapshot while disabling the snapshot taking by the methods called from inside this snapshot.
+	class TakeSnapshot
+	{
+	public:
+		TakeSnapshot(Plater *plater, const wxString &snapshot_name) : m_plater(plater)
+		{
+			m_plater->take_snapshot(snapshot_name);
+			m_plater->suppress_snapshots();
+		}
+		~TakeSnapshot()
+		{
+			m_plater->allow_snapshots();
+		}
+	private:
+		Plater *m_plater;
+	};
+
 private:
     struct priv;
     std::unique_ptr<priv> p;
 
+    void suppress_snapshots();
+    void allow_snapshots();
+
     friend class SuppressBackgroundProcessingUpdate;
 };
 
diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp
index bd19c38c3..96a3e9a81 100644
--- a/src/slic3r/GUI/SysInfoDialog.cpp
+++ b/src/slic3r/GUI/SysInfoDialog.cpp
@@ -2,6 +2,7 @@
 #include "I18N.hpp"
 #include "3DScene.hpp"
 #include "GUI.hpp"
+#include "../Utils/UndoRedo.hpp"
 
 #include <string>
 
@@ -10,6 +11,14 @@
 #include "GUI_App.hpp"
 #include "wxExtensions.hpp"
 
+#ifdef _WIN32
+	// The standard Windows includes.
+	#define WIN32_LEAN_AND_MEAN
+	#define NOMINMAX
+	#include <Windows.h>
+	#include <psapi.h>
+#endif /* _WIN32 */
+
 namespace Slic3r { 
 namespace GUI {
 
@@ -36,10 +45,37 @@ std::string get_main_info(bool format_as_html)
         "System Version:      "
 #endif
         << b_end << wxPlatformInfo::Get().GetOperatingSystemDescription() << line_end;
+    out << b_start << "Total RAM size [MB]: "  << b_end << Slic3r::format_memsize_MB(Slic3r::total_physical_memory());
 
     return out.str();
 }
 
+std::string get_mem_info(bool format_as_html)
+{
+    std::stringstream out;
+
+    std::string b_start  = format_as_html ? "<b>"  : "";
+    std::string b_end    = format_as_html ? "</b>" : "";
+    std::string line_end = format_as_html ? "<br>" : "\n";
+
+    const Slic3r::UndoRedo::Stack &stack = wxGetApp().plater()->undo_redo_stack();
+    out << b_start << "RAM size reserved for the Undo / Redo stack [MB]: "  << b_end << Slic3r::format_memsize_MB(stack.get_memory_limit()) << line_end;
+    out << b_start << "RAM size occupied by the Undo / Redo stack  [MB]: "  << b_end << Slic3r::format_memsize_MB(stack.memsize()) << line_end << line_end;
+
+#ifdef _WIN32
+   	HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ::GetCurrentProcessId());
+    if (hProcess != nullptr) {
+        PROCESS_MEMORY_COUNTERS_EX pmc;
+        if (GetProcessMemoryInfo(hProcess, (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc)))
+			out << b_start << "WorkingSet     [MB]: "   << b_end << format_memsize_MB(pmc.WorkingSetSize) << line_end
+				<< b_start << "PrivateBytes   [MB]: " << b_end << format_memsize_MB(pmc.PrivateUsage) << line_end
+				<< b_start << "Pagefile(peak) [MB]: " << b_end << format_memsize_MB(pmc.PagefileUsage) << "(" << format_memsize_MB(pmc.PeakPagefileUsage) << ")" << line_end;
+        CloseHandle(hProcess);
+    }
+#endif
+    return out.str();
+}
+
 SysInfoDialog::SysInfoDialog()
     : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("System Information")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
 {
@@ -111,7 +147,7 @@ SysInfoDialog::SysInfoDialog()
             "</font>"
             "</body>"
             "</html>", bgr_clr_str, text_clr_str, text_clr_str,
-            _3DScene::get_gl_info(true, true));
+            get_mem_info(true) + "<br>" + _3DScene::get_gl_info(true, true));
         m_opengl_info_html->SetPage(text);
         main_sizer->Add(m_opengl_info_html, 1, wxEXPAND | wxBOTTOM, 15);
     }
diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp
index 7ab564beb..b52f9dd46 100644
--- a/src/slic3r/GUI/Tab.cpp
+++ b/src/slic3r/GUI/Tab.cpp
@@ -2904,7 +2904,7 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr
 // Because of we can't to print the multi-part objects with SLA technology.
 bool Tab::may_switch_to_SLA_preset()
 {
-    if (wxGetApp().obj_list()->has_multi_part_objects())
+    if (model_has_multi_part_objects(wxGetApp().model()))
     {
         show_info( parent(), 
                     _(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" +
diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp
index 69d60603e..a8f9cc134 100644
--- a/src/slic3r/Utils/UndoRedo.cpp
+++ b/src/slic3r/Utils/UndoRedo.cpp
@@ -17,20 +17,29 @@
 #define CEREAL_FUTURE_EXPERIMENTAL
 #include <cereal/archives/adapters.hpp>
 
+#include <libslic3r/ObjectID.hpp>
+#include <libslic3r/Utils.hpp>
+
 #include <boost/foreach.hpp>
 
 #ifndef NDEBUG
 // #define SLIC3R_UNDOREDO_DEBUG
 #endif /* NDEBUG */
+#if 0
+	// Stop at a fraction of the normal Undo / Redo stack size.
+	#define UNDO_REDO_DEBUG_LOW_MEM_FACTOR 10000
+#else
+	#define UNDO_REDO_DEBUG_LOW_MEM_FACTOR 1
+#endif
 
 namespace Slic3r {
 namespace UndoRedo {
 
-static std::string topmost_snapsnot_name = "@@@ Topmost @@@";
+static std::string topmost_snapshot_name = "@@@ Topmost @@@";
 
 bool Snapshot::is_topmost() const
 {
-	return this->name == topmost_snapsnot_name;
+	return this->name == topmost_snapshot_name;
 }
 
 // Time interval, start is closed, end is open.
@@ -51,9 +60,12 @@ public:
 	bool    operator<(const Interval &rhs) const { return (m_begin < rhs.m_begin) || (m_begin == rhs.m_begin && m_end < rhs.m_end); }
 	bool 	operator==(const Interval &rhs) const { return m_begin == rhs.m_begin && m_end == rhs.m_end; }
 
+	void 	trim_begin(size_t new_begin)  { m_begin = std::max(m_begin, new_begin); }
 	void    trim_end(size_t new_end) { m_end = std::min(m_end, new_end); }
 	void 	extend_end(size_t new_end) { assert(new_end >= m_end); m_end = new_end; }
 
+	size_t 	memsize() const { return sizeof(this); }
+
 private:
 	size_t 	m_begin;
 	size_t 	m_end;
@@ -68,13 +80,27 @@ public:
 	// Is the object captured by this history mutable or immutable?
 	virtual bool is_mutable() const = 0;
 	virtual bool is_immutable() const = 0;
+	// The object is optional, it may be released if the Undo / Redo stack memory grows over the limits.
+	virtual bool is_optional() const { return false; }
+	// If it is an immutable object, return its pointer. There is a map assigning a temporary ObjectID to the immutable object pointer.
 	virtual const void* immutable_object_ptr() const { return nullptr; }
 
 	// If the history is empty, the ObjectHistory object could be released.
 	virtual bool empty() = 0;
 
+	// Release all data before the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
+	// Return the amount of memory released.
+	virtual size_t release_before_timestamp(size_t timestamp) = 0;
 	// Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
-	virtual void release_after_timestamp(size_t timestamp) = 0;
+	// Return the amount of memory released.
+	virtual size_t release_after_timestamp(size_t timestamp) = 0;
+	// Release all optional data of this history.
+	virtual size_t release_optional() = 0;
+	// Restore optional data possibly released by release_optional.
+	virtual void   restore_optional() = 0;
+
+	// Estimated size in memory, to be used to drop least recently used snapshots.
+	virtual size_t memsize() const = 0;
 
 #ifdef SLIC3R_UNDOREDO_DEBUG
 	// Human readable debug information.
@@ -94,21 +120,54 @@ public:
 	// If the history is empty, the ObjectHistory object could be released.
 	bool empty() override { return m_history.empty(); }
 
-	// Release all data after the given timestamp. The shared pointer is NOT released.
-	void release_after_timestamp(size_t timestamp) override {
-		assert(! m_history.empty());
-		assert(this->valid());
-		// it points to an interval which either starts with timestamp, or follows the timestamp.
-		auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp));
-		if (it != m_history.begin()) {
-			auto it_prev = it;
-			-- it_prev;
-			assert(it_prev->begin() < timestamp);
-			// Trim the last interval with timestamp.
-			it_prev->trim_end(timestamp);
+	// Release all data before the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
+	size_t release_before_timestamp(size_t timestamp) override {
+		size_t mem_released = 0;
+		if (! m_history.empty()) {
+			assert(this->valid());
+			// it points to an interval which either starts with timestamp, or follows the timestamp.
+			auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp));
+			// Find the first iterator with begin() < timestamp.
+			if (it == m_history.end())
+				-- it;
+			while (it != m_history.begin() && it->begin() >= timestamp)
+				-- it;
+			if (it->begin() < timestamp && it->end() > timestamp) {
+				it->trim_begin(timestamp);
+				if (it != m_history.begin())
+					-- it;
+			}
+			if (it->end() <= timestamp) {
+				auto it_end = ++ it;
+				for (it = m_history.begin(); it != it_end; ++ it)
+					mem_released += it->memsize();
+				m_history.erase(m_history.begin(), it_end);
+			}
+			assert(this->valid());
 		}
-		m_history.erase(it, m_history.end());
-		assert(this->valid());
+		return mem_released;
+	}
+
+	// Release all data after the given timestamp. The shared pointer is NOT released.
+	size_t release_after_timestamp(size_t timestamp) override {
+		size_t mem_released = 0;
+		if (! m_history.empty()) {
+			assert(this->valid());
+			// it points to an interval which either starts with timestamp, or follows the timestamp.
+			auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp));
+			if (it != m_history.begin()) {
+				auto it_prev = it;
+				-- it_prev;
+				assert(it_prev->begin() < timestamp);
+				// Trim the last interval with timestamp.
+				it_prev->trim_end(timestamp);
+			}
+			for (auto it2 = it; it2 != m_history.end(); ++ it2)
+				mem_released += it2->memsize();
+			m_history.erase(it, m_history.end());
+			assert(this->valid());
+		}
+		return mem_released;
 	}
 
 protected:
@@ -129,13 +188,27 @@ template<typename T>
 class ImmutableObjectHistory : public ObjectHistory<Interval>
 {
 public:
-	ImmutableObjectHistory(std::shared_ptr<const T>	shared_object) : m_shared_object(shared_object) {}
+	ImmutableObjectHistory(std::shared_ptr<const T>	shared_object, bool optional) : m_shared_object(shared_object), m_optional(optional) {}
 	~ImmutableObjectHistory() override {}
 
 	bool is_mutable() const override { return false; }
 	bool is_immutable() const override { return true; }
+	bool is_optional() const override { return m_optional; }
+	// If it is an immutable object, return its pointer. There is a map assigning a temporary ObjectID to the immutable object pointer.
 	const void* immutable_object_ptr() const { return (const void*)m_shared_object.get(); }
 
+	// Estimated size in memory, to be used to drop least recently used snapshots.
+	size_t memsize() const override {
+		size_t memsize = sizeof(*this);
+		if (this->is_serialized())
+			memsize += m_serialized.size();
+		else if (m_shared_object.use_count() == 1)
+			// Only count the shared object's memsize into the total Undo / Redo stack memsize if it is referenced from the Undo / Redo stack only.
+			memsize += m_shared_object->memsize();
+		memsize += m_history.size() * sizeof(Interval);
+		return memsize;
+	}
+
 	void save(size_t active_snapshot_time, size_t current_time) {
 		assert(m_history.empty() || m_history.back().end() <= active_snapshot_time || 
 			// The snapshot of an immutable object may have already been taken from another mutable object.
@@ -158,6 +231,37 @@ public:
 		return timestamp >= it->begin() && timestamp < it->end();
 	}
 
+	// Release all optional data of this history.
+	size_t release_optional() override {
+		size_t mem_released = 0;
+		if (m_optional) {
+			bool released = false;
+			if (this->is_serialized()) {
+				mem_released += m_serialized.size();
+				m_serialized.clear();
+				released = true;
+			} else if (m_shared_object.use_count() == 1) {
+				mem_released += m_shared_object->memsize();
+				m_shared_object.reset();
+				released = true;
+			}
+			if (released) {
+				mem_released += m_history.size() * sizeof(Interval);
+				m_history.clear();
+			}
+		} else if (m_shared_object.use_count() == 1) {
+			// The object is in memory, but it is not shared with the scene. Let the object decide whether there is any optional data to release.
+			const_cast<T*>(m_shared_object.get())->release_optional();
+		}
+		return mem_released;
+	}
+
+	// Restore optional data possibly released by this->release_optional().
+	void restore_optional() override {
+		if (m_shared_object.use_count() == 1)
+			const_cast<T*>(m_shared_object.get())->restore_optional();
+	}
+
 	bool 						is_serialized() const { return m_shared_object.get() == nullptr; }
 	const std::string&			serialized_data() const { return m_serialized; }
 	std::shared_ptr<const T>& 	shared_ptr(StackImpl &stack);
@@ -182,6 +286,8 @@ private:
 	// Either the source object is held by a shared pointer and the m_serialized field is empty,
 	// or the shared pointer is null and the object is being serialized into m_serialized.
 	std::shared_ptr<const T>	m_shared_object;
+	// If this object is optional, then it may be deleted from the Undo / Redo stack and recalculated from other data (for example mesh convex hull).
+	bool 						m_optional;
 	std::string 				m_serialized;
 };
 
@@ -228,7 +334,8 @@ public:
 	const Interval& interval() const { return m_interval; }
 	size_t		begin() const { return m_interval.begin(); }
 	size_t		end()   const { return m_interval.end(); }
-	void 		trim_end(size_t timestamp) { m_interval.trim_end(timestamp); }
+	void 		trim_begin(size_t timestamp) { m_interval.trim_begin(timestamp); }
+	void 		trim_end  (size_t timestamp) { m_interval.trim_end(timestamp); }
 	void 		extend_end(size_t timestamp) { m_interval.extend_end(timestamp); }
 
 	bool		operator<(const MutableHistoryInterval& rhs) const { return m_interval < rhs.m_interval; }
@@ -238,6 +345,13 @@ public:
 	size_t  	size() const { return m_data->size; }
 	size_t		refcnt() const { return m_data->refcnt; }
 	bool		matches(const std::string& data) { return m_data->matches(data); }
+	size_t 		memsize() const { 
+		return m_data->refcnt == 1 ?
+			// Count just the size of the snapshot data.
+			m_data->size :
+			// Count the size of the snapshot data divided by the number of references, rounded up.
+			(m_data->size + m_data->refcnt - 1) / m_data->refcnt;
+	}
 
 private:
 	MutableHistoryInterval(const MutableHistoryInterval &rhs);
@@ -268,6 +382,15 @@ public:
 	bool is_mutable() const override { return true; }
 	bool is_immutable() const override { return false; }
 
+	// Estimated size in memory, to be used to drop least recently used snapshots.
+	size_t memsize() const override {
+		size_t memsize = sizeof(*this);
+		memsize += m_history.size() * sizeof(MutableHistoryInterval);
+		for (const MutableHistoryInterval &interval : m_history)
+			memsize += interval.memsize();
+		return memsize;
+	}
+
 	void save(size_t active_snapshot_time, size_t current_time, const std::string &data) {
 		assert(m_history.empty() || m_history.back().end() <= active_snapshot_time);
 		if (m_history.empty() || m_history.back().end() < active_snapshot_time) {
@@ -300,6 +423,11 @@ public:
 		return std::string(it->data(), it->data() + it->size());
 	}
 
+	// Currently all mutable snapshots are mandatory.
+	size_t release_optional() override { return 0; }
+	// Currently there is no way to release optional data from the mutable objects.
+	void   restore_optional() override {}
+
 #ifdef SLIC3R_UNDOREDO_DEBUG
 	std::string format() override {
 		std::string out = typeid(T).name();
@@ -354,30 +482,43 @@ class StackImpl
 {
 public:
 	// Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning.
-	StackImpl() : m_active_snapshot_time(0), m_current_time(0) {}
+	// Initially enable Undo / Redo stack to occupy maximum 10% of the total system physical memory.
+	StackImpl() : m_memory_limit(std::min(Slic3r::total_physical_memory() / 10, size_t(1 * 16384 * 65536 / UNDO_REDO_DEBUG_LOW_MEM_FACTOR))), m_active_snapshot_time(0), m_current_time(0) {}
+
+	void set_memory_limit(size_t memsize) { m_memory_limit = memsize; }
+	size_t get_memory_limit() const { return m_memory_limit; }
+
+	size_t memsize() const {
+		size_t memsize = 0;
+		for (const auto &object : m_objects)
+			memsize += object.second->memsize();
+		return memsize;
+	}
 
     // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
-    void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos);
+    void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags);
     void load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos);
 
 	bool has_undo_snapshot() const;
 	bool has_redo_snapshot() const;
-    bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t jump_to_time);
-    bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t jump_to_time);
+    bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t jump_to_time);
+    bool redo(Slic3r::Model &model, Slic3r::GUI::GLGizmosManager &gizmos, size_t jump_to_time);
+	void release_least_recently_used();
 
 	// Snapshot history (names with timestamps).
 	const std::vector<Snapshot>& 	snapshots() const { return m_snapshots; }
 	// Timestamp of the active snapshot.
 	size_t 							active_snapshot_time() const { return m_active_snapshot_time; }
+	bool 							temp_snapshot_active() const { return m_snapshots.back().timestamp == m_active_snapshot_time && ! m_snapshots.back().is_topmost_captured(); }
 
-	const Selection& selection_deserialized() const { return m_selection; }
+	const Selection& 				selection_deserialized() const { return m_selection; }
 
 //protected:
-	template<typename T, typename T_AS> ObjectID save_mutable_object(const T &object);
-	template<typename T> ObjectID save_immutable_object(std::shared_ptr<const T> &object);
+	template<typename T> ObjectID save_mutable_object(const T &object);
+	template<typename T> ObjectID save_immutable_object(std::shared_ptr<const T> &object, bool optional);
 	template<typename T> T* load_mutable_object(const Slic3r::ObjectID id);
-	template<typename T> std::shared_ptr<const T> load_immutable_object(const Slic3r::ObjectID id);
-	template<typename T, typename T_AS> void load_mutable_object(const Slic3r::ObjectID id, T &target);
+	template<typename T> std::shared_ptr<const T> load_immutable_object(const Slic3r::ObjectID id, bool optional);
+	template<typename T> void load_mutable_object(const Slic3r::ObjectID id, T &target);
 
 #ifdef SLIC3R_UNDOREDO_DEBUG
 	std::string format() const {
@@ -394,6 +535,7 @@ public:
 		if (m_active_snapshot_time > m_snapshots.back().timestamp)
 			out += ">>>\n";
 		out += "Current time: " + std::to_string(m_current_time) + "\n";
+		out += "Total memory occupied: " + std::to_string(this->memsize()) + "\n";
 		return out;
 	}
 	void print() const {
@@ -406,9 +548,10 @@ public:
 #ifndef NDEBUG
 	bool valid() const {
 		assert(! m_snapshots.empty());
+		assert(m_snapshots.back().is_topmost());
 		auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
 		assert(it != m_snapshots.begin() && it != m_snapshots.end() && it->timestamp == m_active_snapshot_time);
-		assert(m_active_snapshot_time < m_snapshots.back().timestamp || m_snapshots.back().is_topmost());
+		assert(m_active_snapshot_time <= m_snapshots.back().timestamp);
 		for (auto it = m_objects.begin(); it != m_objects.end(); ++ it)
 			assert(it->second->valid());
 		return true;
@@ -430,6 +573,9 @@ private:
 	}
 	void 							collect_garbage();
 
+	// Maximum memory allowed to be occupied by the Undo / Redo stack. If the limit is exceeded,
+	// least recently used snapshots will be released.
+	size_t 													m_memory_limit;
 	// Each individual object (Model, ModelObject, ModelInstance, ModelVolume, Selection, TriangleMesh)
 	// is stored with its own history, referenced by the ObjectID. Immutable objects do not provide
 	// their own IDs, therefore there are temporary IDs generated for them and stored to m_shared_ptr_to_object_id.
@@ -455,7 +601,6 @@ class ModelObject;
 class ModelVolume;
 class ModelInstance;
 class ModelMaterial;
-class ModelConfig;
 class DynamicPrintConfig;
 class TriangleMesh;
 
@@ -470,14 +615,13 @@ namespace cereal
 	template <class Archive> struct specialize<Archive, Slic3r::ModelVolume*, cereal::specialization::non_member_load_save> {};
 	template <class Archive> struct specialize<Archive, Slic3r::ModelInstance*, cereal::specialization::non_member_load_save> {};
 	template <class Archive> struct specialize<Archive, Slic3r::ModelMaterial*, cereal::specialization::non_member_load_save> {};
-	template <class Archive> struct specialize<Archive, Slic3r::ModelConfig, cereal::specialization::non_member_load_save> {};
 	template <class Archive> struct specialize<Archive, std::shared_ptr<Slic3r::TriangleMesh>, cereal::specialization::non_member_load_save> {};
 
 	// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
 	// store just the ObjectID to this stream.
 	template <class T> void save(BinaryOutputArchive& ar, T* const& ptr)
 	{
-		ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_mutable_object<T, T>(*ptr));
+		ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_mutable_object<T>(*ptr));
 	}
 
 	// Load ObjectBase derived class from the Undo / Redo stack as a separate object
@@ -509,26 +653,29 @@ namespace cereal
 
 	// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
 	// store just the ObjectID to this stream.
-	void save(BinaryOutputArchive& ar, const Slic3r::ModelConfig &cfg)
+	template<class T> void save_by_value(BinaryOutputArchive& ar, const T &cfg)
 	{
-		ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_mutable_object<Slic3r::ModelConfig, Slic3r::DynamicPrintConfig>(cfg));
+		ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_mutable_object<T>(cfg));
 	}
-
 	// Load ObjectBase derived class from the Undo / Redo stack as a separate object
 	// based on the ObjectID loaded from this stream.
-	void load(BinaryInputArchive& ar, Slic3r::ModelConfig &cfg)
+	template<class T> void load_by_value(BinaryInputArchive& ar, T &cfg)
 	{
 		Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
 		size_t id;
 		ar(id);
-		stack.load_mutable_object<Slic3r::ModelConfig, Slic3r::DynamicPrintConfig>(Slic3r::ObjectID(id), cfg);
+		stack.load_mutable_object<T>(Slic3r::ObjectID(id), cfg);
 	}
 
 	// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
 	// store just the ObjectID to this stream.
 	template <class T> void save(BinaryOutputArchive &ar, const std::shared_ptr<const T> &ptr)
 	{
-		ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_immutable_object<T>(const_cast<std::shared_ptr<const T>&>(ptr)));
+		ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_immutable_object<T>(const_cast<std::shared_ptr<const T>&>(ptr), false));
+	}
+	template <class T> void save_optional(BinaryOutputArchive &ar, const std::shared_ptr<const T> &ptr)
+	{
+		ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_immutable_object<T>(const_cast<std::shared_ptr<const T>&>(ptr), true));
 	}
 
 	// Load ObjectBase derived class from the Undo / Redo stack as a separate object
@@ -538,7 +685,14 @@ namespace cereal
 		Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
 		size_t id;
 		ar(id);
-		ptr = stack.load_immutable_object<T>(Slic3r::ObjectID(id));
+		ptr = stack.load_immutable_object<T>(Slic3r::ObjectID(id), false);
+	}
+	template <class T> void load_optional(BinaryInputArchive &ar, std::shared_ptr<const T> &ptr)
+	{
+		Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
+		size_t id;
+		ar(id);
+		ptr = stack.load_immutable_object<T>(Slic3r::ObjectID(id), true);
 	}
 }
 
@@ -566,7 +720,7 @@ template<typename T> std::shared_ptr<const T>& 	ImmutableObjectHistory<T>::share
 	return m_shared_object;
 }
 
-template<typename T, typename T_AS> ObjectID StackImpl::save_mutable_object(const T &object)
+template<typename T> ObjectID StackImpl::save_mutable_object(const T &object)
 {
 	// First find or allocate a history stack for the ObjectID of this object instance.
 	auto it_object_history = m_objects.find(object.id());
@@ -577,20 +731,22 @@ template<typename T, typename T_AS> ObjectID StackImpl::save_mutable_object(cons
 	std::ostringstream oss;
 	{
 		Slic3r::UndoRedo::OutputArchive archive(*this, oss);
-		archive(static_cast<const T_AS&>(object));
+		archive(object);
 	}
 	object_history->save(m_active_snapshot_time, m_current_time, oss.str());
 	return object.id();
 }
 
-template<typename T> ObjectID StackImpl::save_immutable_object(std::shared_ptr<const T> &object)
+template<typename T> ObjectID StackImpl::save_immutable_object(std::shared_ptr<const T> &object, bool optional)
 {
 	// First allocate a temporary ObjectID for this pointer.
 	ObjectID object_id = this->immutable_object_id(object);
 	// and find or allocate a history stack for the ObjectID associated to this shared_ptr.
 	auto it_object_history = m_objects.find(object_id);
 	if (it_object_history == m_objects.end())
-		it_object_history = m_objects.emplace_hint(it_object_history, object_id, std::unique_ptr<ImmutableObjectHistory<T>>(new ImmutableObjectHistory<T>(object)));
+		it_object_history = m_objects.emplace_hint(it_object_history, object_id, std::unique_ptr<ImmutableObjectHistory<T>>(new ImmutableObjectHistory<T>(object, optional)));
+	else
+		assert(it_object_history->second.get()->is_optional() == optional);
 	// Then save the interval.
 	static_cast<ImmutableObjectHistory<T>*>(it_object_history->second.get())->save(m_active_snapshot_time, m_current_time);
 	return object_id;
@@ -599,21 +755,24 @@ template<typename T> ObjectID StackImpl::save_immutable_object(std::shared_ptr<c
 template<typename T> T* StackImpl::load_mutable_object(const Slic3r::ObjectID id)
 {
 	T *target = new T();
-	this->load_mutable_object<T, T>(id, *target);
+	this->load_mutable_object<T>(id, *target);
 	return target;
 }
 
-template<typename T> std::shared_ptr<const T> StackImpl::load_immutable_object(const Slic3r::ObjectID id)
+template<typename T> std::shared_ptr<const T> StackImpl::load_immutable_object(const Slic3r::ObjectID id, bool optional)
 {
 	// First find a history stack for the ObjectID of this object instance.
 	auto it_object_history = m_objects.find(id);
-	assert(it_object_history != m_objects.end());
+	assert(optional || it_object_history != m_objects.end());
+	if (it_object_history == m_objects.end())
+		return std::shared_ptr<const T>();
 	auto *object_history = static_cast<ImmutableObjectHistory<T>*>(it_object_history->second.get());
 	assert(object_history->has_snapshot(m_active_snapshot_time));
+	object_history->restore_optional();
 	return object_history->shared_ptr(*this);
 }
 
-template<typename T, typename T_AS> void StackImpl::load_mutable_object(const Slic3r::ObjectID id, T &target)
+template<typename T> void StackImpl::load_mutable_object(const Slic3r::ObjectID id, T &target)
 {
 	// First find a history stack for the ObjectID of this object instance.
 	auto it_object_history = m_objects.find(id);
@@ -623,11 +782,11 @@ template<typename T, typename T_AS> void StackImpl::load_mutable_object(const Sl
 	std::istringstream iss(object_history->load(m_active_snapshot_time));
 	Slic3r::UndoRedo::InputArchive archive(*this, iss);
 	target.m_id = id;
-	archive(static_cast<T_AS&>(target));
+	archive(target);
 }
 
 // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
-void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos)
+void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags)
 {
 	// Release old snapshot data.
 	assert(m_active_snapshot_time <= m_current_time);
@@ -638,20 +797,20 @@ void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Mo
 		m_snapshots.erase(it, m_snapshots.end());
 	}
 	// Take new snapshots.
-	this->save_mutable_object<Slic3r::Model, Slic3r::Model>(model);
+	this->save_mutable_object<Slic3r::Model>(model);
 	m_selection.volumes_and_instances.clear();
 	m_selection.volumes_and_instances.reserve(selection.get_volume_idxs().size());
 	m_selection.mode = selection.get_mode();
 	for (unsigned int volume_idx : selection.get_volume_idxs())
 		m_selection.volumes_and_instances.emplace_back(selection.get_volume(volume_idx)->geometry_id);
-	this->save_mutable_object<Selection, Selection>(m_selection);
-    this->save_mutable_object<Slic3r::GUI::GLGizmosManager, Slic3r::GUI::GLGizmosManager>(gizmos);
+	this->save_mutable_object<Selection>(m_selection);
+    this->save_mutable_object<Slic3r::GUI::GLGizmosManager>(gizmos);
     // Save the snapshot info.
-	m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id);
+	m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id, printer_technology, flags);
 	m_active_snapshot_time = m_current_time;
 	// Save snapshot info of the last "current" aka "top most" state, that is only being serialized
 	// if undoing an action. Such a snapshot has an invalid Model ID assigned if it was not taken yet.
-	m_snapshots.emplace_back(topmost_snapsnot_name, m_active_snapshot_time, 0);
+	m_snapshots.emplace_back(topmost_snapshot_name, m_active_snapshot_time, 0, printer_technology, flags);
 	// Release empty objects from the history.
 	this->collect_garbage();
 	assert(this->valid());
@@ -671,12 +830,12 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GU
 	m_active_snapshot_time = timestamp;
 	model.clear_objects();
 	model.clear_materials();
-	this->load_mutable_object<Slic3r::Model, Slic3r::Model>(ObjectID(it_snapshot->model_id), model);
+	this->load_mutable_object<Slic3r::Model>(ObjectID(it_snapshot->model_id), model);
 	model.update_links_bottom_up_recursive();
 	m_selection.volumes_and_instances.clear();
-	this->load_mutable_object<Selection, Selection>(m_selection.id(), m_selection);
+	this->load_mutable_object<Selection>(m_selection.id(), m_selection);
     gizmos.reset_all_states();
-    this->load_mutable_object<Slic3r::GUI::GLGizmosManager, Slic3r::GUI::GLGizmosManager>(gizmos.id(), gizmos);
+    this->load_mutable_object<Slic3r::GUI::GLGizmosManager>(gizmos.id(), gizmos);
     // Sort the volumes so that we may use binary search.
 	std::sort(m_selection.volumes_and_instances.begin(), m_selection.volumes_and_instances.end());
 	this->m_active_snapshot_time = timestamp;
@@ -697,7 +856,7 @@ bool StackImpl::has_redo_snapshot() const
 	return ++ it != m_snapshots.end();
 }
 
-bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load)
+bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t time_to_load)
 {
 	assert(this->valid());
 	if (time_to_load == SIZE_MAX) {
@@ -708,9 +867,10 @@ bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selecti
 	}
 	assert(time_to_load < m_active_snapshot_time);
 	assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load)));
+	bool new_snapshot_taken = false;
 	if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) {
 		// The current state is temporary. The current state needs to be captured to be redoable.
-        this->take_snapshot(topmost_snapsnot_name, model, selection, gizmos);
+        this->take_snapshot(topmost_snapshot_name, model, selection, gizmos, printer_technology, flags);
         // The line above entered another topmost_snapshot_name.
 		assert(m_snapshots.back().is_topmost());
 		assert(! m_snapshots.back().is_topmost_captured());
@@ -720,8 +880,15 @@ bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selecti
 		//-- m_current_time;
 		assert(m_snapshots.back().is_topmost());
 		assert(m_snapshots.back().is_topmost_captured());
+		new_snapshot_taken = true;
 	}
     this->load_snapshot(time_to_load, model, gizmos);
+	if (new_snapshot_taken) {
+		// Release old snapshots if the memory allocated due to capturing the top most state is excessive.
+		// Don't release the snapshots here, release them first after the scene and background processing gets updated, as this will release some references
+		// to the shared TriangleMeshes.
+		//this->release_least_recently_used();
+	}
 #ifdef SLIC3R_UNDOREDO_DEBUG
 	std::cout << "After undo" << std::endl;
  	this->print();
@@ -762,18 +929,108 @@ void StackImpl::collect_garbage()
 	}
 }
 
+void StackImpl::release_least_recently_used()
+{
+	assert(this->valid());
+	size_t current_memsize = this->memsize();
+#ifdef SLIC3R_UNDOREDO_DEBUG
+	bool released = false;
+#endif
+	// First try to release the optional immutable data (for example the convex hulls),
+	// or the shared vertices of triangle meshes.
+	for (auto it = m_objects.begin(); current_memsize > m_memory_limit && it != m_objects.end();) {
+		const void *ptr = it->second->immutable_object_ptr();
+		size_t mem_released = it->second->release_optional();
+		if (it->second->empty()) {
+			if (ptr != nullptr)
+				// Release the immutable object from the ptr to ObjectID map.
+				m_shared_ptr_to_object_id.erase(ptr);
+			mem_released += it->second->memsize();
+			it = m_objects.erase(it);
+		} else
+			++ it;
+		assert(current_memsize >= mem_released);
+		if (current_memsize >= mem_released)
+			current_memsize -= mem_released;
+		else
+			current_memsize = 0;
+	}
+	while (current_memsize > m_memory_limit && m_snapshots.size() >= 3) {
+		// From which side to remove a snapshot?
+		assert(m_snapshots.front().timestamp < m_active_snapshot_time);
+		size_t mem_released = 0;
+		if (m_snapshots[1].timestamp == m_active_snapshot_time) {
+			// Remove the last snapshot.
+#if 0
+			for (auto it = m_objects.begin(); it != m_objects.end();) {
+				mem_released += it->second->release_after_timestamp(m_snapshots.back().timestamp);
+				if (it->second->empty()) {
+					if (it->second->immutable_object_ptr() != nullptr)
+						// Release the immutable object from the ptr to ObjectID map.
+						m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr());
+					mem_released += it->second->memsize();
+					it = m_objects.erase(it);
+				} else
+					++ it;
+			}
+			m_snapshots.pop_back();
+			m_snapshots.back().name = topmost_snapshot_name;
+#else
+			// Rather don't release the last snapshot as it will be very confusing to the user
+			// as of why he cannot jump to the top most state. The Undo / Redo stack maximum size
+			// should be set low enough to accomodate for the top most snapshot.
+			break;
+#endif
+		} else {
+			// Remove the first snapshot.
+			for (auto it = m_objects.begin(); it != m_objects.end();) {
+				mem_released += it->second->release_before_timestamp(m_snapshots[1].timestamp);
+				if (it->second->empty()) {
+					if (it->second->immutable_object_ptr() != nullptr)
+						// Release the immutable object from the ptr to ObjectID map.
+						m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr());
+					mem_released += it->second->memsize();
+					it = m_objects.erase(it);
+				} else
+					++ it;
+			}
+			m_snapshots.erase(m_snapshots.begin());
+		}
+		assert(current_memsize >= mem_released);
+		if (current_memsize >= mem_released)
+			current_memsize -= mem_released;
+		else
+			current_memsize = 0;
+#ifdef SLIC3R_UNDOREDO_DEBUG
+		released = true;
+#endif
+	}
+	assert(this->valid());
+#ifdef SLIC3R_UNDOREDO_DEBUG
+	std::cout << "After release_least_recently_used" << std::endl;
+ 	this->print();
+#endif /* SLIC3R_UNDOREDO_DEBUG */
+}
+
 // Wrappers of the private implementation.
 Stack::Stack() : pimpl(new StackImpl()) {}
 Stack::~Stack() {}
-void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos) { pimpl->take_snapshot(snapshot_name, model, selection, gizmos); }
+void Stack::set_memory_limit(size_t memsize) { pimpl->set_memory_limit(memsize); }
+size_t Stack::get_memory_limit() const { return pimpl->get_memory_limit(); }
+size_t Stack::memsize() const { return pimpl->memsize(); }
+void Stack::release_least_recently_used() { pimpl->release_least_recently_used(); }
+void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags)
+	{ pimpl->take_snapshot(snapshot_name, model, selection, gizmos, printer_technology, flags); }
 bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); }
 bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); }
-bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->undo(model, selection, gizmos, time_to_load); }
+bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t time_to_load)
+	{ return pimpl->undo(model, selection, gizmos, printer_technology, flags, time_to_load); }
 bool Stack::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->redo(model, gizmos, time_to_load); }
 const Selection& Stack::selection_deserialized() const { return pimpl->selection_deserialized(); }
 
 const std::vector<Snapshot>& Stack::snapshots() const { return pimpl->snapshots(); }
 size_t Stack::active_snapshot_time() const { return pimpl->active_snapshot_time(); }
+bool Stack::temp_snapshot_active() const { return pimpl->temp_snapshot_active(); }
 
 } // namespace UndoRedo
 } // namespace Slic3r
diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp
index 236970b68..916e44aa2 100644
--- a/src/slic3r/Utils/UndoRedo.hpp
+++ b/src/slic3r/Utils/UndoRedo.hpp
@@ -12,6 +12,7 @@
 namespace Slic3r {
 
 class Model;
+enum PrinterTechnology : unsigned char;
 
 namespace GUI {
 	class Selection;
@@ -23,11 +24,20 @@ namespace UndoRedo {
 struct Snapshot
 {
 	Snapshot(size_t timestamp) : timestamp(timestamp) {}
-	Snapshot(const std::string &name, size_t timestamp, size_t model_id) : name(name), timestamp(timestamp), model_id(model_id) {}
+	Snapshot(const std::string &name, size_t timestamp, size_t model_id, Slic3r::PrinterTechnology printer_technology, unsigned int flags) :
+		name(name), timestamp(timestamp), model_id(model_id), printer_technology(printer_technology), flags(flags) {}
+
+	// Bitmask of various binary flags to be stored with the snapshot.
+	enum Flags {
+		VARIABLE_LAYER_EDITING_ACTIVE = 1,
+	};
 	
-	std::string name;
-	size_t 		timestamp;
-	size_t 		model_id;
+	std::string 		name;
+	size_t 				timestamp;
+	size_t 				model_id;
+	PrinterTechnology 	printer_technology;
+	// Bitmap of Flags (see the Flags enum).
+	unsigned int        flags;
 
 	bool		operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; }
 	bool		operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; }
@@ -56,8 +66,18 @@ public:
 	Stack();
 	~Stack();
 
+	// Set maximum memory threshold. If the threshold is exceeded, least recently used snapshots are released.
+	void set_memory_limit(size_t memsize);
+	size_t get_memory_limit() const;
+
+	// Estimate size of the RAM consumed by the Undo / Redo stack.
+	size_t memsize() const;
+
+	// Release least recently used snapshots up to the memory limit set above.
+	void release_least_recently_used();
+
 	// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
-    void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos);
+    void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags);
 
 	// To be queried to enable / disable the Undo / Redo buttons at the UI.
 	bool has_undo_snapshot() const;
@@ -65,7 +85,7 @@ public:
 
 	// Roll back the time. If time_to_load is SIZE_MAX, the previous snapshot is activated.
 	// Undoing an action may need to take a snapshot of the current application state, so that redo to the current state is possible.
-    bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX);
+    bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t time_to_load = SIZE_MAX);
 
 	// Jump forward in time. If time_to_load is SIZE_MAX, the next snapshot is activated.
     bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX);
@@ -79,6 +99,9 @@ public:
 	// The snapshot time indicates start of an operation, which is finished at the time of the following snapshot, therefore
 	// the active snapshot is the successive snapshot. The same logic applies to the time_to_load parameter of undo() and redo() operations.
 	size_t active_snapshot_time() const;
+	// Temporary snapshot is active if the topmost snapshot is active and it has not been captured yet.
+	// In that case the Undo action will capture the last snapshot.
+	bool   temp_snapshot_active() const;
 
 	// After load_snapshot() / undo() / redo() the selection is deserialized into a list of ObjectIDs, which needs to be converted
 	// into the list of GLVolume pointers once the 3D scene is updated.