diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2bca5a71a..f31c0eac1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -72,6 +72,9 @@ if (MSVC)
     # error C3859: virtual memory range for PCH exceeded; please recompile with a command line option of '-Zm90' or greater
     # Generate symbols at every build target, even for the release.
     add_compile_options(-bigobj -Zm520 /Zi)
+    # Disable STL4007: Many result_type typedefs and all argument_type, first_argument_type, and second_argument_type typedefs are deprecated in C++17.
+    #FIXME Remove this line after eigen library adapts to the new C++17 adaptor rules.
+    add_compile_options(-D_SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING)
 endif ()
 
 if (MINGW)
diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini
index 4ebf99d32..eba85fef1 100644
--- a/resources/profiles/Creality.ini
+++ b/resources/profiles/Creality.ini
@@ -18,6 +18,7 @@ config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/Prusa
 name = Creality Ender-3
 variants = 0.4
 technology = FFF
+default_materials = Creality PLA @ENDER3; Prusament PLA @ENDER3
 
 # All presets starting with asterisk, for example *common*, are intermediate and they will
 # not make it into the user interface.
diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini
index 746aaf966..a2dfc4913 100644
--- a/resources/profiles/PrusaResearch.ini
+++ b/resources/profiles/PrusaResearch.ini
@@ -22,72 +22,84 @@ name = Original Prusa MINI
 variants = 0.4; 0.25; 0.6
 technology = FFF
 family = MINI
+default_materials = Prusament PLA; Prusament PETG @MINI
 
 [printer_model:MK3S]
 name = Original Prusa i3 MK3S
 variants = 0.4; 0.25; 0.6
 technology = FFF
 family = MK3
+default_materials = Prusament PLA; Prusament PETG
 
 [printer_model:MK3]
 name = Original Prusa i3 MK3
 variants = 0.4; 0.25; 0.6
 technology = FFF
 family = MK3
+default_materials = Prusament PLA; Prusament PETG
 
 [printer_model:MK3SMMU2S]
 name = Original Prusa i3 MK3S MMU2S
 variants = 0.4; 0.25; 0.6
 technology = FFF
 family = MK3
+default_materials = Prusament PLA @MMU2; Prusament PETG @MMU2
 
 [printer_model:MK3MMU2]
 name = Original Prusa i3 MK3 MMU2
 variants = 0.4; 0.25; 0.6
 technology = FFF
 family = MK3
+default_materials = Prusament PLA @MMU2; Prusament PETG @MMU2
 
 [printer_model:MK2.5S]
 name = Original Prusa i3 MK2.5S
 variants = 0.4; 0.25; 0.6
 technology = FFF
 family = MK2.5
+default_materials = Prusament PLA; Prusament PETG
 
 [printer_model:MK2.5]
 name = Original Prusa i3 MK2.5
 variants = 0.4; 0.25; 0.6
 technology = FFF
 family = MK2.5
+default_materials = Prusament PLA; Prusament PETG
 
 [printer_model:MK2.5SMMU2S]
 name = Original Prusa i3 MK2.5S MMU2S
 variants = 0.4; 0.25; 0.6
 technology = FFF
 family = MK2.5
+default_materials = Prusament PLA @MMU2; Prusament PETG @MMU2
 
 [printer_model:MK2.5MMU2]
 name = Original Prusa i3 MK2.5 MMU2
 variants = 0.4; 0.25; 0.6
 technology = FFF
 family = MK2.5
+default_materials = Prusament PLA @MMU2; Prusament PETG @MMU2
 
 [printer_model:MK2S]
 name = Original Prusa i3 MK2S
 variants = 0.4; 0.25; 0.6
 technology = FFF
 family = MK2
+default_materials = Prusament PLA; Prusament PETG
 
 [printer_model:MK2SMM]
 name = Original Prusa i3 MK2S MMU1
 variants = 0.4; 0.6
 technology = FFF
 family = MK2
+default_materials = Prusament PLA; Prusament PETG @MMU1
 
 [printer_model:SL1]
 name = Original Prusa SL1
 variants = default
 technology = SLA
 family = SL1
+default_materials = Prusa Transparent Tough @0.05
 
 # All presets starting with asterisk, for example *common*, are intermediate and they will
 # not make it into the user interface.
diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp
index d755f0b2e..28aadc045 100644
--- a/src/PrusaSlicer.cpp
+++ b/src/PrusaSlicer.cpp
@@ -641,10 +641,18 @@ bool CLI::export_models(IO::ExportFormat format)
         const std::string path = this->output_filepath(model, format);
         bool success = false;
         switch (format) {
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+            case IO::AMF: success = Slic3r::store_amf(path.c_str(), &model, nullptr, false); break;
+#else
             case IO::AMF: success = Slic3r::store_amf(path.c_str(), &model, nullptr); break;
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
             case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model);          break;
             case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true);    break;
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+            case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr, false); break;
+#else
             case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr); break;
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
             default: assert(false); break;
         }
         if (success)
diff --git a/src/libnest2d/CMakeLists.txt b/src/libnest2d/CMakeLists.txt
index 6484da3d0..592ab069c 100644
--- a/src/libnest2d/CMakeLists.txt
+++ b/src/libnest2d/CMakeLists.txt
@@ -24,7 +24,7 @@ set(LIBNEST2D_SRCFILES
     src/libnest2d.cpp
     )
 
-add_library(libnest2d ${LIBNEST2D_SRCFILES})
+add_library(libnest2d STATIC ${LIBNEST2D_SRCFILES})
 
 target_include_directories(libnest2d PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
 target_link_libraries(libnest2d PUBLIC clipper NLopt::nlopt TBB::tbb Boost::boost)
diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp
index 003028758..e44e1dae1 100644
--- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp
+++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp
@@ -1116,12 +1116,8 @@ private:
         for(Item& item : items_) item.translate(d);
     }
 
-    void setInitialPosition(Item& item) {
-        auto sh = item.rawShape();
-        sl::translate(sh, item.translation());
-        sl::rotate(sh, item.rotation());
-        
-        Box bb = sl::boundingBox(sh);
+    void setInitialPosition(Item& item) {        
+        Box bb = item.boundingBox();
         
         Vertex ci, cb;
         auto bbin = sl::boundingBox(bin_);
diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp
index e7ecc2977..9b04ae026 100644
--- a/src/libslic3r/Config.hpp
+++ b/src/libslic3r/Config.hpp
@@ -500,7 +500,7 @@ public:
         		if (NULLABLE)
         			this->values.push_back(nil_value());
         		else
-        			std::runtime_error("Deserializing nil into a non-nullable object");
+        			throw std::runtime_error("Deserializing nil into a non-nullable object");
         	} else {
 	            std::istringstream iss(item_str);
 	            double value;
@@ -525,9 +525,9 @@ protected:
         		if (NULLABLE)
         			ss << "nil";
         		else
-        			std::runtime_error("Serializing NaN");
+                    throw std::runtime_error("Serializing NaN");
         	} else
-        		std::runtime_error("Serializing invalid number");		
+                throw std::runtime_error("Serializing invalid number");
 	}
     static bool vectors_equal(const std::vector<double> &v1, const std::vector<double> &v2) {
     	if (NULLABLE) {
@@ -646,7 +646,7 @@ public:
         		if (NULLABLE)
         			this->values.push_back(nil_value());
         		else
-        			std::runtime_error("Deserializing nil into a non-nullable object");
+                    throw std::runtime_error("Deserializing nil into a non-nullable object");
         	} else {
 	            std::istringstream iss(item_str);
 	            int value;
@@ -663,7 +663,7 @@ private:
         		if (NULLABLE)
         			ss << "nil";
         		else
-        			std::runtime_error("Serializing NaN");
+                    throw std::runtime_error("Serializing NaN");
         	} else
         		ss << v;
 	}
@@ -1126,7 +1126,7 @@ public:
         		if (NULLABLE)
         			this->values.push_back(nil_value());
         		else
-        			std::runtime_error("Deserializing nil into a non-nullable object");
+                    throw std::runtime_error("Deserializing nil into a non-nullable object");
         	} else
         		this->values.push_back(item_str.compare("1") == 0);	
         }
@@ -1139,7 +1139,7 @@ protected:
         		if (NULLABLE)
         			ss << "nil";
         		else
-        			std::runtime_error("Serializing NaN");
+                    throw std::runtime_error("Serializing NaN");
         	} else
         		ss << (v ? "1" : "0");
 	}
@@ -1638,7 +1638,7 @@ class DynamicConfig : public virtual ConfigBase
 public:
     DynamicConfig() {}
     DynamicConfig(const DynamicConfig &rhs) { *this = rhs; }
-    DynamicConfig(DynamicConfig &&rhs) : options(std::move(rhs.options)) { rhs.options.clear(); }
+    DynamicConfig(DynamicConfig &&rhs) noexcept : options(std::move(rhs.options)) { rhs.options.clear(); }
 	explicit DynamicConfig(const ConfigBase &rhs, const t_config_option_keys &keys);
 	explicit DynamicConfig(const ConfigBase& rhs) : DynamicConfig(rhs, rhs.keys()) {}
 	virtual ~DynamicConfig() override { clear(); }
@@ -1656,7 +1656,7 @@ public:
 
     // Move a content of one DynamicConfig to another DynamicConfig.
     // If rhs.def() is not null, then it has to be equal to this->def(). 
-    DynamicConfig& operator=(DynamicConfig &&rhs) 
+    DynamicConfig& operator=(DynamicConfig &&rhs) noexcept
     {
         assert(this->def() == nullptr || this->def() == rhs.def());
         this->clear();
diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp
index 4ee8974f4..7c0dfcce5 100644
--- a/src/libslic3r/ExPolygon.hpp
+++ b/src/libslic3r/ExPolygon.hpp
@@ -19,7 +19,7 @@ class ExPolygon
 public:
     ExPolygon() {}
 	ExPolygon(const ExPolygon &other) : contour(other.contour), holes(other.holes) {}
-    ExPolygon(ExPolygon &&other) : contour(std::move(other.contour)), holes(std::move(other.holes)) {}
+    ExPolygon(ExPolygon &&other) noexcept : contour(std::move(other.contour)), holes(std::move(other.holes)) {}
 	explicit ExPolygon(const Polygon &contour) : contour(contour) {}
 	explicit ExPolygon(Polygon &&contour) : contour(std::move(contour)) {}
 	explicit ExPolygon(const Points &contour) : contour(contour) {}
@@ -32,7 +32,7 @@ public:
 	ExPolygon(std::initializer_list<Point> contour, std::initializer_list<Point> hole) : contour(contour), holes({ hole }) {}
 
     ExPolygon& operator=(const ExPolygon &other) { contour = other.contour; holes = other.holes; return *this; }
-    ExPolygon& operator=(ExPolygon &&other) { contour = std::move(other.contour); holes = std::move(other.holes); return *this; }
+    ExPolygon& operator=(ExPolygon &&other) noexcept { contour = std::move(other.contour); holes = std::move(other.holes); return *this; }
 
     Polygon contour;
     Polygons holes;
diff --git a/src/libslic3r/Extruder.hpp b/src/libslic3r/Extruder.hpp
index df92bf84b..066a5f56d 100644
--- a/src/libslic3r/Extruder.hpp
+++ b/src/libslic3r/Extruder.hpp
@@ -48,9 +48,6 @@ public:
     double retract_length_toolchange() const;
     double retract_restart_extra_toolchange() const;
 
-    // Constructor for a key object, to be used by the stdlib search functions.
-    static Extruder key(unsigned int id) { return Extruder(id); }
-
 private:
     // Private constructor to create a key for a search in std::set.
     Extruder(unsigned int id) : m_id(id) {}
diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp
index e1a9709d1..147a60d95 100644
--- a/src/libslic3r/ExtrusionEntityCollection.cpp
+++ b/src/libslic3r/ExtrusionEntityCollection.cpp
@@ -6,6 +6,26 @@
 
 namespace Slic3r {
 
+void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role)
+{
+	if (role != erMixed) {
+		auto first  = extrusion_entities.begin();
+		auto last   = extrusion_entities.end();
+		auto result = first;
+		while (first != last) {
+		    // The caller wants only paths with a specific extrusion role.
+		    auto role2 = (*first)->role();
+		    if (role != role2) {
+		        // This extrusion entity does not match the role asked.
+		        assert(role2 != erMixed);
+		        *result = *first;
+	  			++ result;
+		    }
+			++ first;
+		}
+	}
+}
+
 ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths)
     : no_sort(false)
 {
@@ -74,31 +94,16 @@ void ExtrusionEntityCollection::remove(size_t i)
     this->entities.erase(this->entities.begin() + i);
 }
 
-ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const Point &start_near, ExtrusionRole role) const
+ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const ExtrusionEntitiesPtr& extrusion_entities, const Point &start_near, ExtrusionRole role)
 {
-	ExtrusionEntityCollection out;
-	if (this->no_sort) {
-		out = *this;
-	} else {
-		if (role == erMixed)
-			out = *this;
-		else {
-		    for (const ExtrusionEntity *ee : this->entities) {
-		        if (role != erMixed) {
-		            // The caller wants only paths with a specific extrusion role.
-		            auto role2 = ee->role();
-		            if (role != role2) {
-		                // This extrusion entity does not match the role asked.
-		                assert(role2 != erMixed);
-		                continue;
-		            }
-		        }
-		        out.entities.emplace_back(ee->clone());
-		    }
-		}
-		chain_and_reorder_extrusion_entities(out.entities, &start_near);
-	}
-	return out;
+	// Return a filtered copy of the collection.
+    ExtrusionEntityCollection out;
+    out.entities = filter_by_extrusion_role(extrusion_entities, role);
+	// Clone the extrusion entities.
+	for (auto &ptr : out.entities)
+		ptr = ptr->clone();
+	chain_and_reorder_extrusion_entities(out.entities, &start_near);
+    return out;
 }
 
 void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp
index 3084e5741..8aacb4d46 100644
--- a/src/libslic3r/ExtrusionEntityCollection.hpp
+++ b/src/libslic3r/ExtrusionEntityCollection.hpp
@@ -6,6 +6,21 @@
 
 namespace Slic3r {
 
+// Remove those items from extrusion_entities, that do not match role.
+// Do nothing if role is mixed.
+// Removed elements are NOT being deleted.
+void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role);
+
+// Return new vector of ExtrusionEntities* with only those items from input extrusion_entities, that match role.
+// Return all extrusion entities if role is mixed.
+// Returned extrusion entities are shared with the source vector, they are NOT cloned, they are considered to be owned by extrusion_entities.
+inline ExtrusionEntitiesPtr filter_by_extrusion_role(const ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role)
+{
+	ExtrusionEntitiesPtr out { extrusion_entities }; 
+	filter_by_extrusion_role_in_place(out, role);
+	return out;
+}
+
 class ExtrusionEntityCollection : public ExtrusionEntity
 {
 public:
@@ -65,7 +80,9 @@ public:
     }
     void replace(size_t i, const ExtrusionEntity &entity);
     void remove(size_t i);
-    ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const;
+    static ExtrusionEntityCollection chained_path_from(const ExtrusionEntitiesPtr &extrusion_entities, const Point &start_near, ExtrusionRole role = erMixed);
+    ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const 
+    	{ return this->no_sort ? *this : chained_path_from(this->entities, start_near, role); }
     void reverse();
     const Point& first_point() const { return this->entities.front()->first_point(); }
     const Point& last_point() const { return this->entities.back()->last_point(); }
@@ -105,6 +122,6 @@ public:
     }
 };
 
-}
+} // namespace Slic3r
 
 #endif
diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp
index 6069677a1..2c5c64fe7 100644
--- a/src/libslic3r/Flow.cpp
+++ b/src/libslic3r/Flow.cpp
@@ -107,9 +107,9 @@ double Flow::mm3_per_mm() const
 {
     float res = this->bridge ?
         // Area of a circle with dmr of this->width.
-        (this->width * this->width) * 0.25 * PI :
+        float((this->width * this->width) * 0.25 * PI) :
         // Rectangle with semicircles at the ends. ~ h (w - 0.215 h)
-        this->height * (this->width - this->height * (1. - 0.25 * PI));
+        float(this->height * (this->width - this->height * (1. - 0.25 * PI)));
     //assert(res > 0.);
 	if (res <= 0.)
 		throw std::runtime_error("Flow::mm3_per_mm() produced negative flow. Did you set some extrusion width too small?");
diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp
index 44e12db86..cca90b463 100644
--- a/src/libslic3r/Format/3mf.cpp
+++ b/src/libslic3r/Format/3mf.cpp
@@ -1876,12 +1876,24 @@ namespace Slic3r {
         typedef std::vector<BuildItem> BuildItemsList;
         typedef std::map<int, ObjectData> IdToObjectDataMap;
 
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+        bool m_fullpath_sources{ true };
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+
     public:
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+#if ENABLE_THUMBNAIL_GENERATOR
+        bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr);
+#else
+        bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources);
+#endif // ENABLE_THUMBNAIL_GENERATOR
+#else
 #if ENABLE_THUMBNAIL_GENERATOR
         bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data = nullptr);
 #else
         bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config);
 #endif // ENABLE_THUMBNAIL_GENERATOR
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
 
     private:
 #if ENABLE_THUMBNAIL_GENERATOR
@@ -1906,6 +1918,22 @@ namespace Slic3r {
         bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model);
     };
 
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+#if ENABLE_THUMBNAIL_GENERATOR
+    bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data)
+    {
+        clear_errors();
+        m_fullpath_sources = fullpath_sources;
+        return _save_model_to_file(filename, model, config, thumbnail_data);
+    }
+#else
+    bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources)
+    {
+        clear_errors();
+        return _save_model_to_file(filename, model, config);
+    }
+#endif // ENABLE_THUMBNAIL_GENERATOR
+#else
 #if ENABLE_THUMBNAIL_GENERATOR
     bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data)
     {
@@ -1919,6 +1947,7 @@ namespace Slic3r {
         return _save_model_to_file(filename, model, config);
     }
 #endif // ENABLE_THUMBNAIL_GENERATOR
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
 
 #if ENABLE_THUMBNAIL_GENERATOR
     bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data)
@@ -2557,7 +2586,12 @@ namespace Slic3r {
                             // stores volume's source data
                             if (!volume->source.input_file.empty())
                             {
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+                                std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string());
+                                stream << "   <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n";
+#else
                                 stream << "   <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->source.input_file) << "\"/>\n";
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
                                 stream << "   <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n";
                                 stream << "   <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n";
                                 stream << "   <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n";
@@ -2646,21 +2680,37 @@ bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool c
         return res;
     }
 
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+#if ENABLE_THUMBNAIL_GENERATOR
+bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data)
+#else
+bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources)
+#endif // ENABLE_THUMBNAIL_GENERATOR
+#else
 #if ENABLE_THUMBNAIL_GENERATOR
     bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data)
 #else
     bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config)
 #endif // ENABLE_THUMBNAIL_GENERATOR
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
     {
         if ((path == nullptr) || (model == nullptr))
             return false;
 
         _3MF_Exporter exporter;
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+#if ENABLE_THUMBNAIL_GENERATOR
+        bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data);
+#else
+        bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources);
+#endif // ENABLE_THUMBNAIL_GENERATOR
+#else
 #if ENABLE_THUMBNAIL_GENERATOR
         bool res = exporter.save_model_to_file(path, *model, config, thumbnail_data);
 #else
         bool res = exporter.save_model_to_file(path, *model, config);
 #endif // ENABLE_THUMBNAIL_GENERATOR
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
 
         if (!res)
             exporter.log_errors();
diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp
index 2e85b7f59..c28716fff 100644
--- a/src/libslic3r/Format/3mf.hpp
+++ b/src/libslic3r/Format/3mf.hpp
@@ -31,11 +31,19 @@ namespace Slic3r {
 
     // Save the given model and the config data contained in the given Print into a 3mf file.
     // The model could be modified during the export process if meshes are not repaired or have no shared vertices
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+#if ENABLE_THUMBNAIL_GENERATOR
+    extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr);
+#else
+    extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources);
+#endif // ENABLE_THUMBNAIL_GENERATOR
+#else
 #if ENABLE_THUMBNAIL_GENERATOR
     extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data = nullptr);
 #else
     extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config);
 #endif // ENABLE_THUMBNAIL_GENERATOR
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
 
 }; // namespace Slic3r
 
diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp
index 5b65d8fa2..947c04458 100644
--- a/src/libslic3r/Format/AMF.cpp
+++ b/src/libslic3r/Format/AMF.cpp
@@ -1019,7 +1019,11 @@ bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool c
         return false;
 }
 
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources)
+#else
 bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
 {
     if ((path == nullptr) || (model == nullptr))
         return false;
@@ -1177,7 +1181,12 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
             stream << "</metadata>\n";
             if (!volume->source.input_file.empty())
             {
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+                std::string input_file = xml_escape(fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string());
+                stream << "        <metadata type=\"slic3r.source_file\">" << input_file << "</metadata>\n";
+#else
                 stream << "        <metadata type=\"slic3r.source_file\">" << xml_escape(volume->source.input_file) << "</metadata>\n";
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
                 stream << "        <metadata type=\"slic3r.source_object_id\">" << volume->source.object_idx << "</metadata>\n";
                 stream << "        <metadata type=\"slic3r.source_volume_id\">" << volume->source.volume_idx << "</metadata>\n";
                 stream << "        <metadata type=\"slic3r.source_offset_x\">" << volume->source.mesh_offset(0) << "</metadata>\n";
diff --git a/src/libslic3r/Format/AMF.hpp b/src/libslic3r/Format/AMF.hpp
index 1206d60d0..7404e1e90 100644
--- a/src/libslic3r/Format/AMF.hpp
+++ b/src/libslic3r/Format/AMF.hpp
@@ -11,7 +11,11 @@ extern bool load_amf(const char* path, DynamicPrintConfig* config, Model* model,
 
 // Save the given model and the config data into an amf file.
 // The model could be modified during the export process if meshes are not repaired or have no shared vertices
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+extern bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources);
+#else
 extern bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config);
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
 
 }; // namespace Slic3r
 
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 40ca7b074..870749867 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -792,6 +792,297 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
     PROFILE_OUTPUT(debug_out_path("gcode-export-profile.txt").c_str());
 }
 
+// free functions called by GCode::_do_export()
+namespace DoExport {
+	static void init_time_estimators(const PrintConfig &config, GCodeTimeEstimator &normal_time_estimator, GCodeTimeEstimator &silent_time_estimator, bool &silent_time_estimator_enabled)
+	{
+	    // resets time estimators
+	    normal_time_estimator.reset();
+	    normal_time_estimator.set_dialect(config.gcode_flavor);
+	    normal_time_estimator.set_extrusion_axis(config.get_extrusion_axis()[0]);
+	    silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlin) && config.silent_mode;
+
+	    // Until we have a UI support for the other firmwares than the Marlin, use the hardcoded default values
+	    // and let the user to enter the G-code limits into the start G-code.
+	    // If the following block is enabled for other firmwares than the Marlin, then the function
+	    // this->print_machine_envelope(file, print);
+	    // shall be adjusted as well to produce a G-code block compatible with the particular firmware flavor.
+	    if (config.gcode_flavor.value == gcfMarlin) {
+	        normal_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values[0]);
+	        normal_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values[0]);
+	        normal_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values[0]);
+	        normal_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values[0]);
+	        normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values[0]);
+	        normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values[0]);
+	        normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values[0]);
+	        normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values[0]);
+	        normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values[0]);
+	        normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values[0]);
+	        normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values[0]);
+	        normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values[0]);
+	        normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values[0]);
+	        normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values[0]);
+	        normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values[0]);
+	        normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values[0]);
+
+	        if (silent_time_estimator_enabled)
+	        {
+	            silent_time_estimator.reset();
+	            silent_time_estimator.set_dialect(config.gcode_flavor);
+	            silent_time_estimator.set_extrusion_axis(config.get_extrusion_axis()[0]);
+	            /* "Stealth mode" values can be just a copy of "normal mode" values
+	            * (when they aren't input for a printer preset).
+	            * Thus, use back value from values, instead of second one, which could be absent
+	            */
+	            silent_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values.back());
+	            silent_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values.back());
+	            silent_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values.back());
+	            silent_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values.back());
+	            silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values.back());
+	            silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values.back());
+	            silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values.back());
+	            silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values.back());
+	            silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values.back());
+	            silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values.back());
+	            silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values.back());
+	            silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values.back());
+	            silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values.back());
+	            silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values.back());
+	            silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values.back());
+	            silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values.back());
+	            if (config.single_extruder_multi_material) {
+	                // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
+	                // are considered to be active for the single extruder multi-material printers only.
+	                silent_time_estimator.set_filament_load_times(config.filament_load_time.values);
+	                silent_time_estimator.set_filament_unload_times(config.filament_unload_time.values);
+	            }
+	        }
+	    }
+	    // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful.
+	    if (config.single_extruder_multi_material) {
+	        // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
+	        // are considered to be active for the single extruder multi-material printers only.
+	        normal_time_estimator.set_filament_load_times(config.filament_load_time.values);
+	        normal_time_estimator.set_filament_unload_times(config.filament_unload_time.values);
+	    }
+	}
+
+	static void init_gcode_analyzer(const PrintConfig &config, GCodeAnalyzer &analyzer)
+	{
+	    // resets analyzer
+	    analyzer.reset();
+
+	    // send extruder offset data to analyzer
+	    GCodeAnalyzer::ExtruderOffsetsMap extruder_offsets;
+	    unsigned int num_extruders = static_cast<unsigned int>(config.nozzle_diameter.values.size());
+	    for (unsigned int extruder_id = 0; extruder_id < num_extruders; ++ extruder_id)
+	    {
+	        Vec2d offset = config.extruder_offset.get_at(extruder_id);
+	        if (!offset.isApprox(Vec2d::Zero()))
+	            extruder_offsets[extruder_id] = offset;
+	    }
+	    analyzer.set_extruder_offsets(extruder_offsets);
+
+	    // tell analyzer about the extrusion axis
+	    analyzer.set_extrusion_axis(config.get_extrusion_axis()[0]);
+
+	    // send extruders count to analyzer to allow it to detect invalid extruder idxs
+	    analyzer.set_extruders_count(num_extruders);
+
+	    // tell analyzer about the gcode flavor
+	    analyzer.set_gcode_flavor(config.gcode_flavor);
+	}
+
+	static double autospeed_volumetric_limit(const Print &print)
+	{
+	    // get the minimum cross-section used in the print
+	    std::vector<double> mm3_per_mm;
+	    for (auto object : print.objects()) {
+	        for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) {
+	            const PrintRegion* region = print.regions()[region_id];
+	            for (auto layer : object->layers()) {
+	                const LayerRegion* layerm = layer->regions()[region_id];
+	                if (region->config().get_abs_value("perimeter_speed") == 0 ||
+	                    region->config().get_abs_value("small_perimeter_speed") == 0 ||
+	                    region->config().get_abs_value("external_perimeter_speed") == 0 ||
+	                    region->config().get_abs_value("bridge_speed") == 0)
+	                    mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm());
+	                if (region->config().get_abs_value("infill_speed") == 0 ||
+	                    region->config().get_abs_value("solid_infill_speed") == 0 ||
+	                    region->config().get_abs_value("top_solid_infill_speed") == 0 ||
+	                    region->config().get_abs_value("bridge_speed") == 0)
+	                    mm3_per_mm.push_back(layerm->fills.min_mm3_per_mm());
+	            }
+	        }
+	        if (object->config().get_abs_value("support_material_speed") == 0 ||
+	            object->config().get_abs_value("support_material_interface_speed") == 0)
+	            for (auto layer : object->support_layers())
+	                mm3_per_mm.push_back(layer->support_fills.min_mm3_per_mm());
+	    }
+	    // filter out 0-width segments
+	    mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end());
+	    double volumetric_speed = 0.;
+	    if (! mm3_per_mm.empty()) {
+	        // In order to honor max_print_speed we need to find a target volumetric
+	        // speed that we can use throughout the print. So we define this target 
+	        // volumetric speed as the volumetric speed produced by printing the 
+	        // smallest cross-section at the maximum speed: any larger cross-section
+	        // will need slower feedrates.
+	        volumetric_speed = *std::min_element(mm3_per_mm.begin(), mm3_per_mm.end()) * print.config().max_print_speed.value;
+	        // limit such volumetric speed with max_volumetric_speed if set
+	        if (print.config().max_volumetric_speed.value > 0)
+	            volumetric_speed = std::min(volumetric_speed, print.config().max_volumetric_speed.value);
+	    }
+	    return volumetric_speed;
+	}
+
+	static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention)
+	{
+	    // Calculate wiping points if needed
+	    if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) {
+	        Points skirt_points;
+	        for (const ExtrusionEntity *ee : print.skirt().entities)
+	            for (const ExtrusionPath &path : dynamic_cast<const ExtrusionLoop*>(ee)->paths)
+	                append(skirt_points, path.polyline.points);
+	        if (! skirt_points.empty()) {
+	            Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points);
+	            Polygons skirts;
+	            for (unsigned int extruder_id : print.extruders()) {
+	                const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id);
+	                Polygon s(outer_skirt);
+	                s.translate(Point::new_scale(-extruder_offset(0), -extruder_offset(1)));
+	                skirts.emplace_back(std::move(s));
+	            }
+	            ooze_prevention.enable = true;
+	            ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), scale_(3.f)).front().equally_spaced_points(scale_(10.));
+	#if 0
+	                require "Slic3r/SVG.pm";
+	                Slic3r::SVG::output(
+	                    "ooze_prevention.svg",
+	                    red_polygons    => \@skirts,
+	                    polygons        => [$outer_skirt],
+	                    points          => $gcodegen->ooze_prevention->standby_points,
+	                );
+	#endif
+	        }
+	    }
+	}
+
+	#if ENABLE_THUMBNAIL_GENERATOR
+	template<typename WriteToOutput, typename ThrowIfCanceledCallback>
+	static void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector<Vec2d> &sizes, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled)
+	{
+	    // Write thumbnails using base64 encoding
+	    if (thumbnail_cb != nullptr)
+	    {
+	        const size_t max_row_length = 78;
+	        ThumbnailsList thumbnails;
+	        thumbnail_cb(thumbnails, sizes, true, true, true, true);
+	        for (const ThumbnailData& data : thumbnails)
+	        {
+	            if (data.is_valid())
+	            {
+	                size_t png_size = 0;
+	                void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1);
+	                if (png_data != nullptr)
+	                {
+	                    std::string encoded;
+	                    encoded.resize(boost::beast::detail::base64::encoded_size(png_size));
+	                    encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)png_data, png_size));
+
+	                    output((boost::format("\n;\n; thumbnail begin %dx%d %d\n") % data.width % data.height % encoded.size()).str().c_str());
+
+	                    unsigned int row_count = 0;
+	                    while (encoded.size() > max_row_length)
+	                    {
+	                        output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str());
+	                        encoded = encoded.substr(max_row_length);
+	                        ++row_count;
+	                    }
+
+	                    if (encoded.size() > 0)
+	                    	output((boost::format("; %s\n") % encoded).str().c_str());
+
+	                    output("; thumbnail end\n;\n");
+
+	                    mz_free(png_data);
+	                }
+	            }
+	            throw_if_canceled();
+	        }
+	    }
+	}
+	#endif // ENABLE_THUMBNAIL_GENERATOR
+
+	// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
+	static std::string update_print_stats_and_format_filament_stats(
+	    const GCodeTimeEstimator    &normal_time_estimator,
+	    const GCodeTimeEstimator    &silent_time_estimator,
+	    const bool                   silent_time_estimator_enabled,
+	    const bool                   has_wipe_tower,
+	    const WipeTowerData         &wipe_tower_data,
+	    const std::vector<Extruder> &extruders,
+		PrintStatistics 		    &print_statistics)
+	{
+		std::string filament_stats_string_out;
+
+	    print_statistics.clear();
+	    print_statistics.estimated_normal_print_time = normal_time_estimator.get_time_dhms();
+	    print_statistics.estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhms() : "N/A";
+	    print_statistics.estimated_normal_color_print_times = normal_time_estimator.get_color_times_dhms(true);
+	    if (silent_time_estimator_enabled)
+	        print_statistics.estimated_silent_color_print_times = silent_time_estimator.get_color_times_dhms(true);
+	    print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges);
+	    if (! extruders.empty()) {
+	        std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0);
+	        std::pair<std::string, unsigned int> out_filament_used_cm3("; filament used [cm3] = ", 0);
+	        std::pair<std::string, unsigned int> out_filament_used_g  ("; filament used [g] = ", 0);
+	        std::pair<std::string, unsigned int> out_filament_cost    ("; filament cost = ", 0);
+	        for (const Extruder &extruder : extruders) {
+	            double used_filament   = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f);
+	            double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter
+	            double filament_weight = extruded_volume * extruder.filament_density() * 0.001;
+	            double filament_cost   = filament_weight * extruder.filament_cost()    * 0.001;
+	            auto append = [&extruder, &extruders](std::pair<std::string, unsigned int> &dst, const char *tmpl, double value) {
+	                while (dst.second < extruder.id()) {
+	                    // Fill in the non-printing extruders with zeros.
+	                    dst.first += (dst.second > 0) ? ", 0" : "0";
+	                    ++ dst.second;
+	                }
+	                if (dst.second > 0)
+	                    dst.first += ", ";
+	                char buf[64];
+					sprintf(buf, tmpl, value);
+	                dst.first += buf;
+	                ++ dst.second;
+	            };
+	            print_statistics.filament_stats.insert(std::pair<size_t, float>{extruder.id(), (float)used_filament});
+	            append(out_filament_used_mm,  "%.1lf", used_filament);
+	            append(out_filament_used_cm3, "%.1lf", extruded_volume * 0.001);
+	            if (filament_weight > 0.) {
+	                print_statistics.total_weight = print_statistics.total_weight + filament_weight;
+	                append(out_filament_used_g, "%.1lf", filament_weight);
+	                if (filament_cost > 0.) {
+	                    print_statistics.total_cost = print_statistics.total_cost + filament_cost;
+	                    append(out_filament_cost, "%.1lf", filament_cost);
+	                }
+	            }
+	            print_statistics.total_used_filament += used_filament;
+	            print_statistics.total_extruded_volume += extruded_volume;
+	            print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.;
+	            print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.;
+	        }
+	        filament_stats_string_out += out_filament_used_mm.first;
+			filament_stats_string_out += out_filament_used_cm3.first;
+			if (out_filament_used_g.second)
+				filament_stats_string_out += out_filament_used_g.first;
+			if (out_filament_cost.second)
+				filament_stats_string_out += out_filament_cost.first;
+	    }
+	    return filament_stats_string_out;
+	}
+}
+
 #if ENABLE_THUMBNAIL_GENERATOR
 void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb)
 #else
@@ -800,99 +1091,10 @@ void GCode::_do_export(Print& print, FILE* file)
 {
     PROFILE_FUNC();
 
-    // resets time estimators
-    m_normal_time_estimator.reset();
-    m_normal_time_estimator.set_dialect(print.config().gcode_flavor);
-    m_normal_time_estimator.set_extrusion_axis(print.config().get_extrusion_axis()[0]);
-    m_silent_time_estimator_enabled = (print.config().gcode_flavor == gcfMarlin) && print.config().silent_mode;
-
-    // Until we have a UI support for the other firmwares than the Marlin, use the hardcoded default values
-    // and let the user to enter the G-code limits into the start G-code.
-    // If the following block is enabled for other firmwares than the Marlin, then the function
-    // this->print_machine_envelope(file, print);
-    // shall be adjusted as well to produce a G-code block compatible with the particular firmware flavor.
-    if (print.config().gcode_flavor.value == gcfMarlin) {
-        m_normal_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values[0]);
-        m_normal_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values[0]);
-        m_normal_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values[0]);
-        m_normal_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values[0]);
-        m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values[0]);
-        m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values[0]);
-        m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values[0]);
-        m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values[0]);
-        m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values[0]);
-        m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values[0]);
-        m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values[0]);
-        m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values[0]);
-        m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values[0]);
-        m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values[0]);
-        m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values[0]);
-        m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values[0]);
-
-        if (m_silent_time_estimator_enabled)
-        {
-            m_silent_time_estimator.reset();
-            m_silent_time_estimator.set_dialect(print.config().gcode_flavor);
-            m_silent_time_estimator.set_extrusion_axis(print.config().get_extrusion_axis()[0]);
-            /* "Stealth mode" values can be just a copy of "normal mode" values
-            * (when they aren't input for a printer preset).
-            * Thus, use back value from values, instead of second one, which could be absent
-            */
-            m_silent_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values.back());
-            m_silent_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values.back());
-            m_silent_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values.back());
-            m_silent_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values.back());
-            m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values.back());
-            m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values.back());
-            m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values.back());
-            m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values.back());
-            m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values.back());
-            m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values.back());
-            m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values.back());
-            m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values.back());
-            m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values.back());
-            m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values.back());
-            m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values.back());
-            m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values.back());
-            if (print.config().single_extruder_multi_material) {
-                // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
-                // are considered to be active for the single extruder multi-material printers only.
-                m_silent_time_estimator.set_filament_load_times(print.config().filament_load_time.values);
-                m_silent_time_estimator.set_filament_unload_times(print.config().filament_unload_time.values);
-            }
-        }
-    }
-    // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful.
-    if (print.config().single_extruder_multi_material) {
-        // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
-        // are considered to be active for the single extruder multi-material printers only.
-        m_normal_time_estimator.set_filament_load_times(print.config().filament_load_time.values);
-        m_normal_time_estimator.set_filament_unload_times(print.config().filament_unload_time.values);
-    }
-
-    // resets analyzer
-    m_analyzer.reset();
-
-    // send extruder offset data to analyzer
-    GCodeAnalyzer::ExtruderOffsetsMap extruder_offsets;
-    for (unsigned int extruder_id : print.extruders())
-    {
-        Vec2d offset = print.config().extruder_offset.get_at(extruder_id);
-        if (!offset.isApprox(Vec2d::Zero()))
-            extruder_offsets[extruder_id] = offset;
-    }
-    m_analyzer.set_extruder_offsets(extruder_offsets);
-
-    // tell analyzer about the extrusion axis
-    m_analyzer.set_extrusion_axis(print.config().get_extrusion_axis()[0]);
-
-    // send extruders count to analyzer to allow it to detect invalid extruder idxs
-    const ConfigOptionStrings* extruders_opt = dynamic_cast<const ConfigOptionStrings*>(print.config().option("extruder_colour"));
-    const ConfigOptionStrings* filamemts_opt = dynamic_cast<const ConfigOptionStrings*>(print.config().option("filament_colour"));
-    m_analyzer.set_extruders_count(std::max((unsigned int)extruders_opt->values.size(), (unsigned int)filamemts_opt->values.size()));
-
-    // tell analyzer about the gcode flavor
-    m_analyzer.set_gcode_flavor(print.config().gcode_flavor);
+    DoExport::init_time_estimators(print.config(), 
+    	// modifies the following:
+    	m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled);
+    DoExport::init_gcode_analyzer(print.config(), m_analyzer);
 
     // resets analyzer's tracking data
     m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm;
@@ -914,8 +1116,7 @@ void GCode::_do_export(Print& print, FILE* file)
             std::sort(zs.begin(), zs.end());
             m_layer_count += (unsigned int)(object->copies().size() * (std::unique(zs.begin(), zs.end()) - zs.begin()));
         }
-    }
-    else {
+    } else {
         // Print all objects with the same print_z together.
         std::vector<coordf_t> zs;
         for (auto object : print.objects()) {
@@ -932,53 +1133,8 @@ void GCode::_do_export(Print& print, FILE* file)
 
     m_enable_cooling_markers = true;
     this->apply_print_config(print.config());
-    this->set_extruders(print.extruders());
 
-    // Initialize custom gcode
-    Model* model = print.get_object(0)->model_object()->get_model();
-    m_custom_gcode_per_print_z = model->custom_gcode_per_print_z;
-
-    // Initialize autospeed.
-    {
-        // get the minimum cross-section used in the print
-        std::vector<double> mm3_per_mm;
-        for (auto object : print.objects()) {
-            for (size_t region_id = 0; region_id < object->region_volumes.size(); ++region_id) {
-                const PrintRegion* region = print.regions()[region_id];
-                for (auto layer : object->layers()) {
-                    const LayerRegion* layerm = layer->regions()[region_id];
-                    if (region->config().get_abs_value("perimeter_speed") == 0 ||
-                        region->config().get_abs_value("small_perimeter_speed") == 0 ||
-                        region->config().get_abs_value("external_perimeter_speed") == 0 ||
-                        region->config().get_abs_value("bridge_speed") == 0)
-                        mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm());
-                    if (region->config().get_abs_value("infill_speed") == 0 ||
-                        region->config().get_abs_value("solid_infill_speed") == 0 ||
-                        region->config().get_abs_value("top_solid_infill_speed") == 0 ||
-                        region->config().get_abs_value("bridge_speed") == 0)
-                        mm3_per_mm.push_back(layerm->fills.min_mm3_per_mm());
-                }
-            }
-            if (object->config().get_abs_value("support_material_speed") == 0 ||
-                object->config().get_abs_value("support_material_interface_speed") == 0)
-                for (auto layer : object->support_layers())
-                    mm3_per_mm.push_back(layer->support_fills.min_mm3_per_mm());
-        }
-        print.throw_if_canceled();
-        // filter out 0-width segments
-        mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end());
-        if (!mm3_per_mm.empty()) {
-            // In order to honor max_print_speed we need to find a target volumetric
-            // speed that we can use throughout the print. So we define this target 
-            // volumetric speed as the volumetric speed produced by printing the 
-            // smallest cross-section at the maximum speed: any larger cross-section
-            // will need slower feedrates.
-            m_volumetric_speed = *std::min_element(mm3_per_mm.begin(), mm3_per_mm.end()) * print.config().max_print_speed.value;
-            // limit such volumetric speed with max_volumetric_speed if set
-            if (print.config().max_volumetric_speed.value > 0)
-                m_volumetric_speed = std::min(m_volumetric_speed, print.config().max_volumetric_speed.value);
-        }
-    }
+    m_volumetric_speed = DoExport::autospeed_volumetric_limit(print);
     print.throw_if_canceled();
 
     m_cooling_buffer = make_unique<CoolingBuffer>(*this);
@@ -996,47 +1152,9 @@ void GCode::_do_export(Print& print, FILE* file)
     // Write information on the generator.
     _write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str());
 
-#if ENABLE_THUMBNAIL_GENERATOR
-    // Write thumbnails using base64 encoding
-    if (thumbnail_cb != nullptr)
-    {
-        const size_t max_row_length = 78;
-        ThumbnailsList thumbnails;
-        thumbnail_cb(thumbnails, print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true);
-        for (const ThumbnailData& data : thumbnails)
-        {
-            if (data.is_valid())
-            {
-                size_t png_size = 0;
-                void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1);
-                if (png_data != nullptr)
-                {
-                    std::string encoded;
-                    encoded.resize(boost::beast::detail::base64::encoded_size(png_size));
-                    encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)png_data, png_size));
-
-                    _write_format(file, "\n;\n; thumbnail begin %dx%d %d\n", data.width, data.height, encoded.size());
-
-                    unsigned int row_count = 0;
-                    while (encoded.size() > max_row_length)
-                    {
-                        _write_format(file, "; %s\n", encoded.substr(0, max_row_length).c_str());
-                        encoded = encoded.substr(max_row_length);
-                        ++row_count;
-                    }
-
-                    if (encoded.size() > 0)
-                        _write_format(file, "; %s\n", encoded.c_str());
-
-                    _write(file, "; thumbnail end\n;\n");
-
-                    mz_free(png_data);
-                }
-            }
-            print.throw_if_canceled();
-        }
-    }
-#endif // ENABLE_THUMBNAIL_GENERATOR
+    DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values, 
+        [this, file](const char* sz) { this->_write(file, sz); }, 
+        [&print]() { print.throw_if_canceled(); });
 
     // Write notes (content of the Print Settings tab -> Notes)
     {
@@ -1079,9 +1197,6 @@ void GCode::_do_export(Print& print, FILE* file)
             _writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag);
     }
 
-	// Hold total number of print toolchanges. Check for negative toolchanges (single extruder mode) and set to 0 (no tool change).
-    int total_toolchanges = std::max(0, print.wipe_tower_data().number_of_toolchanges);
-
     // Prepare the helper object for replacing placeholders in custom G-code and output filename.
     m_placeholder_parser = print.placeholder_parser();
     m_placeholder_parser.update_timestamp();
@@ -1101,18 +1216,24 @@ void GCode::_do_export(Print& print, FILE* file)
             if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1)
                 break;
         }
+        // We don't allow switching of extruders per layer by Model::custom_gcode_per_print_z in sequential mode.
+        // Use the extruder IDs collected from Regions.
+    	this->set_extruders(print.extruders());
     } else {
 		// Find tool ordering for all the objects at once, and the initial extruder ID.
         // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it.
 		tool_ordering = print.wipe_tower_data().tool_ordering.empty() ?
-            ToolOrdering(print, initial_extruder_id) :
+            ToolOrdering(print, initial_extruder_id, false) :
             print.wipe_tower_data().tool_ordering;
         has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower();
-        initial_extruder_id = (has_wipe_tower && ! print.config().single_extruder_multi_material_priming) ? 
+        initial_extruder_id = (has_wipe_tower && ! print.config().single_extruder_multi_material_priming) ?
             // The priming towers will be skipped.
             tool_ordering.all_extruders().back() :
-            // Don't skip the priming towers. 
+            // Don't skip the priming towers.
             tool_ordering.first_extruder();
+        // In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z.
+        // Therefore initialize the printing extruders from there.
+    	this->set_extruders(tool_ordering.all_extruders());
     }
     if (initial_extruder_id == (unsigned int)-1) {
         // Nothing to print!
@@ -1124,49 +1245,6 @@ void GCode::_do_export(Print& print, FILE* file)
     }
     print.throw_if_canceled();
 
-// #ys_FIXME_no_exported_codes
-    /*
-    /* To avoid change filament for non-used extruder for Multi-material,
-     * check model->custom_gcode_per_print_z using tool_ordering values
-     * /
-    if (!m_custom_gcode_per_print_z. empty())
-    {
-        bool delete_executed = false;
-        auto it = m_custom_gcode_per_print_z.end();
-        while (it != m_custom_gcode_per_print_z.begin())
-        {
-            --it;
-            if (it->gcode != ColorChangeCode)
-                continue;
-
-            auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(it->print_z));
-
-            bool used_extruder = false;
-            for (; it_layer_tools != tool_ordering.end(); it_layer_tools++)
-            {
-                const std::vector<unsigned>& extruders = it_layer_tools->extruders;
-                if (std::find(extruders.begin(), extruders.end(), (unsigned)(it->extruder-1)) != extruders.end())
-                {
-                    used_extruder = true;
-                    break;
-                }
-            }
-            if (used_extruder)
-                continue;
-
-            /* If we are there, current extruder wouldn't be used,
-             * so this color change is a redundant move.
-             * Delete this item from m_custom_gcode_per_print_z
-             * /
-            it = m_custom_gcode_per_print_z.erase(it);
-            delete_executed = true;
-        }
-
-        if (delete_executed)
-            model->custom_gcode_per_print_z = m_custom_gcode_per_print_z;
-    }
-*/
-
     m_cooling_buffer->set_current_extruder(initial_extruder_id);
 
     // Emit machine envelope limits for the Marlin firmware.
@@ -1187,7 +1265,8 @@ void GCode::_do_export(Print& print, FILE* file)
     // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
     m_placeholder_parser.set("has_wipe_tower", has_wipe_tower);
     m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming);
-    m_placeholder_parser.set("total_toolchanges", total_toolchanges);
+    m_placeholder_parser.set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change).
+
     std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id);
     // Set bed temperature if the start G-code does not contain any bed temp control G-codes.
     this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
@@ -1195,12 +1274,8 @@ void GCode::_do_export(Print& print, FILE* file)
     this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false);
 
     if (m_enable_analyzer)
-    {
         // adds tag for analyzer
-        char buf[32];
-        sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
-        _writeln(file, buf);
-    }
+        _write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
 
     // Write the custom start G-code
     _writeln(file, start_gcode);
@@ -1228,35 +1303,8 @@ void GCode::_do_export(Print& print, FILE* file)
     }
 
     // Calculate wiping points if needed
-    if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) {
-        Points skirt_points;
-        for (const ExtrusionEntity *ee : print.skirt().entities)
-            for (const ExtrusionPath &path : dynamic_cast<const ExtrusionLoop*>(ee)->paths)
-                append(skirt_points, path.polyline.points);
-        if (! skirt_points.empty()) {
-            Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points);
-            Polygons skirts;
-            for (unsigned int extruder_id : print.extruders()) {
-                const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id);
-                Polygon s(outer_skirt);
-                s.translate(Point::new_scale(-extruder_offset(0), -extruder_offset(1)));
-                skirts.emplace_back(std::move(s));
-            }
-            m_ooze_prevention.enable = true;
-            m_ooze_prevention.standby_points =
-                offset(Slic3r::Geometry::convex_hull(skirts), scale_(3.f)).front().equally_spaced_points(scale_(10.));
-#if 0
-                require "Slic3r/SVG.pm";
-                Slic3r::SVG::output(
-                    "ooze_prevention.svg",
-                    red_polygons    => \@skirts,
-                    polygons        => [$outer_skirt],
-                    points          => $gcodegen->ooze_prevention->standby_points,
-                );
-#endif
-        }
-        print.throw_if_canceled();
-    }
+    DoExport::init_ooze_prevention(print, m_ooze_prevention);
+    print.throw_if_canceled();
     
     if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) {
         // Set initial extruder only after custom start G-code.
@@ -1387,12 +1435,8 @@ void GCode::_do_export(Print& print, FILE* file)
     _write(file, m_writer.set_fan(false));
 
     if (m_enable_analyzer)
-    {
         // adds tag for analyzer
-        char buf[32];
-        sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
-        _writeln(file, buf);
-    }
+        _write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
 
     // Process filament-specific gcode in extruder order.
     {
@@ -1432,60 +1476,13 @@ void GCode::_do_export(Print& print, FILE* file)
         m_silent_time_estimator.calculate_time(false);
 
     // Get filament stats.
-    print.m_print_statistics.clear();
-    print.m_print_statistics.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms();
-    print.m_print_statistics.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A";
-    print.m_print_statistics.estimated_normal_color_print_times = m_normal_time_estimator.get_color_times_dhms(true);
-    if (m_silent_time_estimator_enabled)
-        print.m_print_statistics.estimated_silent_color_print_times = m_silent_time_estimator.get_color_times_dhms(true);
-    print.m_print_statistics.total_toolchanges = total_toolchanges;
-    std::vector<Extruder> extruders = m_writer.extruders();
-    if (! extruders.empty()) {
-        std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0);
-        std::pair<std::string, unsigned int> out_filament_used_cm3("; filament used [cm3] = ", 0);
-        std::pair<std::string, unsigned int> out_filament_used_g  ("; filament used [g] = ", 0);
-        std::pair<std::string, unsigned int> out_filament_cost    ("; filament cost = ", 0);
-        for (const Extruder &extruder : extruders) {
-            double used_filament   = extruder.used_filament() + (has_wipe_tower ? print.wipe_tower_data().used_filament[extruder.id()] : 0.f);
-            double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? print.wipe_tower_data().used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter
-            double filament_weight = extruded_volume * extruder.filament_density() * 0.001;
-            double filament_cost   = filament_weight * extruder.filament_cost()    * 0.001;
-            auto append = [&extruder, &extruders](std::pair<std::string, unsigned int> &dst, const char *tmpl, double value) {
-                while (dst.second < extruder.id()) {
-                    // Fill in the non-printing extruders with zeros.
-                    dst.first += (dst.second > 0) ? ", 0" : "0";
-                    ++ dst.second;
-                }
-                if (dst.second > 0)
-                    dst.first += ", ";
-                char buf[64];
-				sprintf(buf, tmpl, value);
-                dst.first += buf;
-                ++ dst.second;
-            };
-            print.m_print_statistics.filament_stats.insert(std::pair<size_t, float>{extruder.id(), (float)used_filament});
-            append(out_filament_used_mm,  "%.1lf", used_filament);
-            append(out_filament_used_cm3, "%.1lf", extruded_volume * 0.001);
-            if (filament_weight > 0.) {
-                print.m_print_statistics.total_weight = print.m_print_statistics.total_weight + filament_weight;
-                append(out_filament_used_g, "%.1lf", filament_weight);
-                if (filament_cost > 0.) {
-                    print.m_print_statistics.total_cost = print.m_print_statistics.total_cost + filament_cost;
-                    append(out_filament_cost, "%.1lf", filament_cost);
-                }
-            }
-            print.m_print_statistics.total_used_filament += used_filament;
-            print.m_print_statistics.total_extruded_volume += extruded_volume;
-            print.m_print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.;
-            print.m_print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.;
-        }
-        _writeln(file, out_filament_used_mm.first);
-		_writeln(file, out_filament_used_cm3.first);
-		if (out_filament_used_g.second)
-			_writeln(file, out_filament_used_g.first);
-		if (out_filament_cost.second)
-			_writeln(file, out_filament_cost.first);
-    }
+    _write(file, DoExport::update_print_stats_and_format_filament_stats(
+    	// Const inputs
+        m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled, 
+        has_wipe_tower, print.wipe_tower_data(), 
+        m_writer.extruders(),
+        // Modifies
+        print.m_print_statistics));
     _write_format(file, "; total filament used [g] = %.1lf\n", print.m_print_statistics.total_weight);
     _write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost);
     if (print.m_print_statistics.total_toolchanges > 0)
@@ -1729,6 +1726,136 @@ std::vector<GCode::InstanceToPrint> GCode::sort_print_object_instances(
 	return out;
 }
 
+namespace ProcessLayer
+{
+
+    static std::string emit_custom_gcode_per_print_z(
+    	const Model::CustomGCode 								*custom_gcode,
+        // ID of the first extruder printing this layer.
+        unsigned int                                             first_extruder_id,
+		bool  											         single_material_print)
+	{
+        std::string gcode;
+        
+        if (custom_gcode != nullptr) {
+			// Extruder switches are processed by LayerTools, they should be filtered out.
+			assert(custom_gcode->gcode != ExtruderChangeCode);
+
+            const std::string &custom_code = custom_gcode->gcode;
+		    std::string pause_print_msg;
+		    int m600_extruder_before_layer = -1;
+	        if (custom_code == ColorChangeCode && custom_gcode->extruder > 0)
+	            m600_extruder_before_layer = custom_gcode->extruder - 1;
+	        else if (custom_code == PausePrintCode)
+	            pause_print_msg = custom_gcode->color;
+
+		    // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count	        
+	        if (custom_code == ColorChangeCode) // color change
+	        {
+	            // add tag for analyzer
+	            gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n";
+	            // add tag for time estimator
+	            gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n";
+
+	            if (!single_material_print && m600_extruder_before_layer >= 0 && first_extruder_id != m600_extruder_before_layer
+	                // && !MMU1
+	                ) {
+	                //! FIXME_in_fw show message during print pause
+	                gcode += "M601\n"; // pause print
+	                gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n";
+	            }
+	            else 
+	                gcode += custom_code + "\n";
+	        } 
+	        else
+	        {
+	            if (custom_code == PausePrintCode) // Pause print
+	            {
+	                // add tag for analyzer
+	                gcode += "; " + GCodeAnalyzer::Pause_Print_Tag + "\n";
+	                //! FIXME_in_fw show message during print pause
+	                if (!pause_print_msg.empty())
+	                    gcode += "M117 " + pause_print_msg + "\n";
+	                // add tag for time estimator
+	                //gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n";
+	            }
+	            else // custom Gcode
+	            {
+	                // add tag for analyzer
+	                gcode += "; " + GCodeAnalyzer::Custom_Code_Tag + "\n";
+	                // add tag for time estimator
+	                //gcode += "; " + GCodeTimeEstimator::Custom_Code_Tag + "\n";
+	            }
+	            gcode += custom_code + "\n";
+	        }
+		}
+
+	    return gcode;
+	}
+} // namespace ProcessLayer
+
+namespace Skirt {
+	static void skirt_loops_per_extruder_all_printing(const Print &print, const LayerTools &layer_tools, std::map<unsigned int, std::pair<size_t, size_t>> &skirt_loops_per_extruder_out)
+	{
+        // Prime all extruders printing over the 1st layer over the skirt lines.
+        size_t n_loops = print.skirt().entities.size();
+        size_t n_tools = layer_tools.extruders.size();
+        size_t lines_per_extruder = (n_loops + n_tools - 1) / n_tools;
+        for (size_t i = 0; i < n_loops; i += lines_per_extruder)
+            skirt_loops_per_extruder_out[layer_tools.extruders[i / lines_per_extruder]] = std::pair<size_t, size_t>(i, std::min(i + lines_per_extruder, n_loops));
+	}
+
+    static std::map<unsigned int, std::pair<size_t, size_t>> make_skirt_loops_per_extruder_1st_layer(
+        const Print             				&print,
+	    const std::vector<GCode::LayerToPrint> 	& /*layers */,
+	    const LayerTools                		&layer_tools,
+        // Heights (print_z) at which the skirt has already been extruded.
+        std::vector<coordf_t>  			    	&skirt_done)
+    {
+        // Extrude skirt at the print_z of the raft layers and normal object layers
+        // not at the print_z of the interlaced support material layers.
+        std::map<unsigned int, std::pair<size_t, size_t>> skirt_loops_per_extruder_out;
+        if (skirt_done.empty() && print.has_skirt() && ! print.skirt().entities.empty()) {
+        	skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out);
+            skirt_done.emplace_back(layer_tools.print_z);
+        }
+        return skirt_loops_per_extruder_out;
+    }
+
+    static std::map<unsigned int, std::pair<size_t, size_t>> make_skirt_loops_per_extruder_other_layers(
+        const Print 							&print,
+	    const std::vector<GCode::LayerToPrint> 	&layers,
+	    const LayerTools                		&layer_tools,
+        // Heights (print_z) at which the skirt has already been extruded.
+        std::vector<coordf_t>			    	&skirt_done)
+    {
+        // Extrude skirt at the print_z of the raft layers and normal object layers
+        // not at the print_z of the interlaced support material layers.
+        std::map<unsigned int, std::pair<size_t, size_t>> skirt_loops_per_extruder_out;
+        if (print.has_skirt() && ! print.skirt().entities.empty() &&
+            // Not enough skirt layers printed yet.
+            //FIXME infinite or high skirt does not make sense for sequential print!
+            (skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt()) &&
+            // This print_z has not been extruded yet (sequential print)
+            skirt_done.back() < layer_tools.print_z - EPSILON &&
+            // and this layer is an object layer, or it is a raft layer.
+            (layer_tools.has_object || layers.front().support_layer->id() < (size_t)layers.front().support_layer->object()->config().raft_layers.value)) {
+#if 0
+            // Prime just the first printing extruder. This is original Slic3r's implementation.
+            skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair<size_t, size_t>(0, print.config().skirts.value);
+#else
+            // Prime all extruders planned for this layer, see 
+            // https://github.com/prusa3d/PrusaSlicer/issues/469#issuecomment-322450619
+            skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out);
+#endif
+            assert(!skirt_done.empty());
+            skirt_done.emplace_back(layer_tools.print_z);
+        }
+        return skirt_loops_per_extruder_out;
+    }
+
+} // namespace Skirt
+
 // In sequential mode, process_layer is called once per each object and its copy, 
 // therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object.
 // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated.
@@ -1765,7 +1892,7 @@ void GCode::process_layer(
         if (l.support_layer != nullptr && support_layer == nullptr)
             support_layer = l.support_layer;
     }
-    const Layer         &layer         = (object_layer != nullptr) ? *object_layer : *support_layer;    
+    const Layer         &layer         = (object_layer != nullptr) ? *object_layer : *support_layer;
     coordf_t             print_z       = layer.print_z;
     bool                 first_layer   = layer.id() == 0;
     unsigned int         first_extruder_id = layer_tools.extruders.front();
@@ -1829,124 +1956,22 @@ void GCode::process_layer(
         m_second_layer_things_done = true;
     }
 
-    // Let's issue a filament change command if requested at this layer.
-    // In case there are more toolchange requests that weren't done yet and should happen simultaneously, erase them all.
-    // (Layers can be close to each other, model could have been resliced with bigger layer height, ...).
-    bool colorprint_change = false;
+    // Map from extruder ID to <begin, end> index of skirt loops to be extruded with that extruder.
+    std::map<unsigned int, std::pair<size_t, size_t>> skirt_loops_per_extruder;
 
-    std::string custom_code = "";
-    std::string pause_print_msg = "";
-    int m600_before_extruder = -1;
-    while (!m_custom_gcode_per_print_z.empty() && m_custom_gcode_per_print_z.front().print_z - EPSILON < layer.print_z) {
-        custom_code = m_custom_gcode_per_print_z.front().gcode;
-
-        if (custom_code == ColorChangeCode && m_custom_gcode_per_print_z.front().extruder > 0)
-            m600_before_extruder = m_custom_gcode_per_print_z.front().extruder - 1;
-        if (custom_code == PausePrintCode)
-            pause_print_msg = m_custom_gcode_per_print_z.front().color;
-
-        m_custom_gcode_per_print_z.erase(m_custom_gcode_per_print_z.begin());
-        colorprint_change = true;
+    if (single_object_instance_idx == size_t(-1)) {
+        // Normal (non-sequential) print.
+        gcode += ProcessLayer::emit_custom_gcode_per_print_z(layer_tools.custom_gcode, first_extruder_id, print.config().nozzle_diameter.size() == 1);
     }
-
-    // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
-
-    // don't save "tool_change"(ExtruderChangeCode) code to GCode
-    if (colorprint_change && custom_code != ExtruderChangeCode) {
-        const bool single_material_print = print.config().nozzle_diameter.size() == 1;
-        
-        if (custom_code == ColorChangeCode) // color change
-        {
-            // add tag for analyzer
-            gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_before_extruder) + "\n";
-            // add tag for time estimator
-            gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n";
-
-            if (!single_material_print && m600_before_extruder >= 0 && first_extruder_id != m600_before_extruder
-                // && !MMU1
-                ) {
-                //! FIXME_in_fw show message during print pause
-                gcode += "M601\n"; // pause print
-                gcode += "M117 Change filament for Extruder " + std::to_string(m600_before_extruder) + "\n";
-            }
-            else 
-                gcode += custom_code + "\n";
-        } 
-        else
-        {
-            if (custom_code == PausePrintCode) // Pause print
-            {
-                // add tag for analyzer
-                gcode += "; " + GCodeAnalyzer::Pause_Print_Tag + "\n";
-                //! FIXME_in_fw show message during print pause
-                if (!pause_print_msg.empty())
-                    gcode += "M117 " + pause_print_msg + "\n";
-                // add tag for time estimator
-                //gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n";
-            }
-            else // custom Gcode
-            {
-                // add tag for analyzer
-                gcode += "; " + GCodeAnalyzer::Custom_Code_Tag + "\n";
-                // add tag for time estimator
-                //gcode += "; " + GCodeTimeEstimator::Custom_Code_Tag + "\n";
-            }
-            gcode += custom_code + "\n";
-        }
-    }
-
-
     // Extrude skirt at the print_z of the raft layers and normal object layers
     // not at the print_z of the interlaced support material layers.
-    bool extrude_skirt = 
-		! print.skirt().entities.empty() &&
-        // Not enough skirt layers printed yet.
-        (m_skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt()) &&
-        // This print_z has not been extruded yet
-		(m_skirt_done.empty() ? 0. : m_skirt_done.back()) < print_z - EPSILON &&
-        // and this layer is the 1st layer, or it is an object layer, or it is a raft layer.
-        (first_layer || object_layer != nullptr || support_layer->id() < (size_t)m_config.raft_layers.value);
-    std::map<unsigned int, std::pair<size_t, size_t>> skirt_loops_per_extruder;
-    coordf_t                                          skirt_height = 0.;
-    if (extrude_skirt) {
-        // Fill in skirt_loops_per_extruder.
-		skirt_height = print_z - (m_skirt_done.empty() ? 0. : m_skirt_done.back());
-        m_skirt_done.push_back(print_z);
-        if (first_layer) {
-            // Prime the extruders over the skirt lines.
-            std::vector<unsigned int> extruder_ids = m_writer.extruder_ids();
-            // Reorder the extruders, so that the last used extruder is at the front.
-            for (size_t i = 1; i < extruder_ids.size(); ++ i)
-                if (extruder_ids[i] == first_extruder_id) {
-                    // Move the last extruder to the front.
-                    memmove(extruder_ids.data() + 1, extruder_ids.data(), i * sizeof(unsigned int));
-                    extruder_ids.front() = first_extruder_id;
-                    break;
-                }
-            size_t n_loops = print.skirt().entities.size();
-			if (n_loops <= extruder_ids.size()) {
-				for (size_t i = 0; i < n_loops; ++i)
-                    skirt_loops_per_extruder[extruder_ids[i]] = std::pair<size_t, size_t>(i, i + 1);
-            } else {
-                // Assign skirt loops to the extruders.
-                std::vector<unsigned int> extruder_loops(extruder_ids.size(), 1);
-                n_loops -= extruder_loops.size();
-                while (n_loops > 0) {
-                    for (size_t i = 0; i < extruder_ids.size() && n_loops > 0; ++ i, -- n_loops)
-                        ++ extruder_loops[i];
-                }
-                for (size_t i = 0; i < extruder_ids.size(); ++ i)
-                    skirt_loops_per_extruder[extruder_ids[i]] = std::make_pair<size_t, size_t>(
-                        (i == 0) ? 0 : extruder_loops[i - 1], 
-                        ((i == 0) ? 0 : extruder_loops[i - 1]) + extruder_loops[i]);
-            }
-        } else
-            // Extrude all skirts with the current extruder.
-            skirt_loops_per_extruder[first_extruder_id] = std::pair<size_t, size_t>(0, print.config().skirts.value);
-    }
+    skirt_loops_per_extruder = first_layer ?
+        Skirt::make_skirt_loops_per_extruder_1st_layer(print, layers, layer_tools, m_skirt_done) :
+        Skirt::make_skirt_loops_per_extruder_other_layers(print, layers, layer_tools, m_skirt_done);
 
     // Group extrusions by an extruder, then by an object, an island and a region.
     std::map<unsigned int, std::vector<ObjectByExtruder>> by_extruder;
+    bool is_anything_overridden = const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden();
     for (const LayerToPrint &layer_to_print : layers) {
         if (layer_to_print.support_layer != nullptr) {
             const SupportLayer &support_layer = *layer_to_print.support_layer;
@@ -2014,7 +2039,7 @@ void GCode::process_layer(
             slices_test_order.reserve(n_slices);
             for (size_t i = 0; i < n_slices; ++ i)
             	slices_test_order.emplace_back(i);
-            std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](int i, int j) {
+            std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](size_t i, size_t j) {
             	const Vec2d s1 = layer_surface_bboxes[i].size().cast<double>();
             	const Vec2d s2 = layer_surface_bboxes[j].size().cast<double>();
             	return s1.x() * s1.y() < s2.x() * s2.y();
@@ -2026,58 +2051,71 @@ void GCode::process_layer(
                        layer.lslices[i].contour.contains(point);
             };
 
-            for (size_t region_id = 0; region_id < print.regions().size(); ++ region_id) {
-                const LayerRegion *layerm = (region_id < layer.regions().size()) ? layer.regions()[region_id] : nullptr;
+            for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) {
+                const LayerRegion *layerm = layer.regions()[region_id];
                 if (layerm == nullptr)
                     continue;
                 const PrintRegion &region = *print.regions()[region_id];
 
-
                 // Now we must process perimeters and infills and create islands of extrusions in by_region std::map.
                 // It is also necessary to save which extrusions are part of MM wiping and which are not.
                 // The process is almost the same for perimeters and infills - we will do it in a cycle that repeats twice:
-                for (std::string entity_type("infills") ; entity_type != "done" ; entity_type = entity_type=="infills" ? "perimeters" : "done") {
-
-                    const ExtrusionEntitiesPtr& source_entities = entity_type=="infills" ? layerm->fills.entities : layerm->perimeters.entities;
-
-                    for (const ExtrusionEntity *ee : source_entities) {
-                        // fill represents infill extrusions of a single island.
-                        const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
-                        if (fill->entities.empty()) // This shouldn't happen but first_point() would fail.
+                std::vector<unsigned int> printing_extruders;
+                for (const ObjectByExtruder::Island::Region::Type entity_type : { ObjectByExtruder::Island::Region::INFILL, ObjectByExtruder::Island::Region::PERIMETERS }) {
+                    for (const ExtrusionEntity *ee : (entity_type == ObjectByExtruder::Island::Region::INFILL) ? layerm->fills.entities : layerm->perimeters.entities) {
+                        // extrusions represents infill or perimeter extrusions of a single island.
+                        assert(dynamic_cast<const ExtrusionEntityCollection*>(ee) != nullptr);
+                        const auto *extrusions = static_cast<const ExtrusionEntityCollection*>(ee);
+                        if (extrusions->entities.empty()) // This shouldn't happen but first_point() would fail.
                             continue;
 
                         // This extrusion is part of certain Region, which tells us which extruder should be used for it:
-                        int correct_extruder_id = Print::get_extruder(*fill, region);
+                        int correct_extruder_id = layer_tools.extruder(*extrusions, region);
 
                         // Let's recover vector of extruder overrides:
-                        const ExtruderPerCopy* entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(fill, correct_extruder_id, layer_to_print.object()->copies().size());
+                        const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr;
+                        if (is_anything_overridden) {
+	                        printing_extruders.clear();
+	                        if (! layer_tools.has_extruder(correct_extruder_id)) {
+								// this entity is not overridden, but its extruder is not in layer_tools - we'll print it
+	                            // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
+	                            correct_extruder_id = layer_tools.extruders.back();
+	                        }
+                        	entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->copies().size());
+	                        if (entity_overrides == nullptr) {
+	                        	printing_extruders.emplace_back(correct_extruder_id);
+	                        } else {
+	                        	printing_extruders.reserve(entity_overrides->size());
+	                        	for (int extruder : *entity_overrides)
+	                        		printing_extruders.emplace_back(extruder >= 0 ? 
+	                        			// at least one copy is overridden to use this extruder
+	                        			extruder : 
+	                        			// at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
+	                        			static_cast<unsigned int>(- extruder - 1));
+	                        }
+	                        Slic3r::sort_remove_duplicates(printing_extruders);
+	                    } else
+	                    	printing_extruders = { (unsigned int)correct_extruder_id };
 
                         // Now we must add this extrusion into the by_extruder map, once for each extruder that will print it:
-                        for (unsigned int extruder : layer_tools.extruders)
+                        for (unsigned int extruder : printing_extruders)
                         {
-                            // Init by_extruder item only if we actually use the extruder:
-                            if (std::find(entity_overrides->begin(), entity_overrides->end(), extruder) != entity_overrides->end() ||      // at least one copy is overridden to use this extruder
-                                std::find(entity_overrides->begin(), entity_overrides->end(), -extruder-1) != entity_overrides->end() ||   // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
-                                (std::find(layer_tools.extruders.begin(), layer_tools.extruders.end(), correct_extruder_id) == layer_tools.extruders.end() && extruder == layer_tools.extruders.back())) // this entity is not overridden, but its extruder is not in layer_tools - we'll print it
-                                                                                                                                            //by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
-                            {
-                                std::vector<ObjectByExtruder::Island> &islands = object_islands_by_extruder(
-                                    by_extruder,
-                                    extruder,
-                                    &layer_to_print - layers.data(),
-                                    layers.size(), n_slices+1);
-                                for (size_t i = 0; i <= n_slices; ++ i) {
-									bool   last = i == n_slices;
-                                	size_t island_idx = last ? n_slices : slices_test_order[i];
-                                    if (// fill->first_point does not fit inside any slice
-										last ||
-                                        // fill->first_point fits inside ith slice
-                                        point_inside_surface(island_idx, fill->first_point())) {
-                                        if (islands[island_idx].by_region.empty())
-                                            islands[island_idx].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region());
-                                        islands[island_idx].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->copies().size());
-                                        break;
-                                    }
+                            std::vector<ObjectByExtruder::Island> &islands = object_islands_by_extruder(
+                                by_extruder,
+                                extruder,
+                                &layer_to_print - layers.data(),
+                                layers.size(), n_slices+1);
+                            for (size_t i = 0; i <= n_slices; ++ i) {
+								bool   last = i == n_slices;
+                            	size_t island_idx = last ? n_slices : slices_test_order[i];
+                                if (// extrusions->first_point does not fit inside any slice
+									last ||
+                                    // extrusions->first_point fits inside ith slice
+                                    point_inside_surface(island_idx, extrusions->first_point())) {
+                                    if (islands[island_idx].by_region.empty())
+                                        islands[island_idx].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region());
+                                    islands[island_idx].by_region[region_id].append(entity_type, extrusions, entity_overrides);
+                                    break;
                                 }
                             }
                         }
@@ -2099,30 +2137,27 @@ void GCode::process_layer(
         if (m_enable_analyzer && layer_tools.has_wipe_tower && m_wipe_tower)
             m_last_analyzer_extrusion_role = erWipeTower;
 
-        if (extrude_skirt) {
-            auto loops_it = skirt_loops_per_extruder.find(extruder_id);
-            if (loops_it != skirt_loops_per_extruder.end()) {
-                const std::pair<size_t, size_t> loops = loops_it->second;
-                this->set_origin(0.,0.);
-                m_avoid_crossing_perimeters.use_external_mp = true;
-                Flow skirt_flow = print.skirt_flow();
-                for (size_t i = loops.first; i < loops.second; ++ i) {
-                    // Adjust flow according to this layer's layer height.
-                    ExtrusionLoop loop = *dynamic_cast<const ExtrusionLoop*>(print.skirt().entities[i]);
-                    Flow layer_skirt_flow(skirt_flow);
-                    layer_skirt_flow.height = (float)skirt_height;
-                    double mm3_per_mm = layer_skirt_flow.mm3_per_mm();
-                    for (ExtrusionPath &path : loop.paths) {
-                        path.height     = (float)layer.height;
-                        path.mm3_per_mm = mm3_per_mm;
-                    }
-                    gcode += this->extrude_loop(loop, "skirt", m_config.support_material_speed.value);
+        if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) {
+            const std::pair<size_t, size_t> loops = loops_it->second;
+            this->set_origin(0., 0.);
+            m_avoid_crossing_perimeters.use_external_mp = true;
+            Flow layer_skirt_flow(print.skirt_flow());
+            layer_skirt_flow.height = float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]));
+            double mm3_per_mm = layer_skirt_flow.mm3_per_mm();
+            for (size_t i = loops.first; i < loops.second; ++i) {
+                // Adjust flow according to this layer's layer height.
+                ExtrusionLoop loop = *dynamic_cast<const ExtrusionLoop*>(print.skirt().entities[i]);
+                for (ExtrusionPath &path : loop.paths) {
+                    path.height = layer_skirt_flow.height;
+                    path.mm3_per_mm = mm3_per_mm;
                 }
-                m_avoid_crossing_perimeters.use_external_mp = false;
-                // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers).
-                if (first_layer && loops.first == 0)
-                    m_avoid_crossing_perimeters.disable_once = true;
+                //FIXME using the support_material_speed of the 1st object printed.
+                gcode += this->extrude_loop(loop, "skirt", m_config.support_material_speed.value);
             }
+            m_avoid_crossing_perimeters.use_external_mp = false;
+            // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers).
+            if (first_layer && loops.first == 0)
+                m_avoid_crossing_perimeters.disable_once = true;
         }
 
         // Extrude brim with the extruder of the 1st region.
@@ -2146,7 +2181,7 @@ void GCode::process_layer(
 		std::vector<InstanceToPrint> instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
 
         // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
-        bool is_anything_overridden = const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden();
+		std::vector<ObjectByExtruder::Island::Region> by_region_per_copy_cache;
         for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) {
             if (is_anything_overridden && print_wipe_extrusions == 0)
                 gcode+="; PURGING FINISHED\n";
@@ -2174,8 +2209,8 @@ void GCode::process_layer(
                     m_layer = layers[instance_to_print.layer_id].layer();
                 }
                 for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) {
-                    const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(instance_to_print.instance_id, extruder_id, print_wipe_extrusions) : island.by_region;
-
+                    const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast<unsigned int>(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region;
+                	//FIXME the following code prints regions in the order they are defined, the path is not optimized in any way.
                     if (print.config().infill_first) {
                         gcode += this->extrude_infill(print, by_region_specific);
                         gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]);
@@ -2783,7 +2818,7 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vector<Obje
     std::string gcode;
     for (const ObjectByExtruder::Island::Region &region : by_region) {
         m_config.apply(print.regions()[&region - &by_region.front()]->config());
-        for (ExtrusionEntity *ee : region.perimeters.entities)
+        for (const ExtrusionEntity *ee : region.perimeters)
             gcode += this->extrude_entity(*ee, "perimeter", -1., &lower_layer_edge_grid);
     }
     return gcode;
@@ -2795,8 +2830,10 @@ std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectBy
     std::string gcode;
     for (const ObjectByExtruder::Island::Region &region : by_region) {
         m_config.apply(print.regions()[&region - &by_region.front()]->config());
-        for (ExtrusionEntity *fill : region.infills.chained_path_from(m_last_pos).entities) {
-            auto *eec = dynamic_cast<ExtrusionEntityCollection*>(fill);
+		ExtrusionEntitiesPtr extrusions { region.infills };
+		chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
+        for (const ExtrusionEntity *fill : extrusions) {
+            auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill);
             if (eec) {
 				for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
                     gcode += this->extrude_entity(*ee, "infill");
@@ -3260,59 +3297,96 @@ Point GCode::gcode_to_point(const Vec2d &point) const
 
 // Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed
 // during infill/perimeter wiping, or normally (depends on wiping_entities parameter)
-// Returns a reference to member to avoid copying.
-const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtruder::Island::by_region_per_copy(unsigned int copy, int extruder, bool wiping_entities)
+// Fills in by_region_per_copy_cache and returns its reference.
+const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtruder::Island::by_region_per_copy(std::vector<Region> &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities) const
 {
+    bool has_overrides = false;
+    for (const auto& reg : by_region)
+    	if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) {
+    		has_overrides = true;
+    		break;
+    	}
+    if (! has_overrides)
+    	// Simple case. No need to copy the regions.
+    	return this->by_region;
+
+    // Complex case. Some of the extrusions of some object instances are to be printed first - those are the wiping extrusions.
+    // Some of the extrusions of some object instances are printed later - those are the clean print extrusions.
+    // Filter out the extrusions based on the infill_overrides / perimeter_overrides:
+
+	// Data is cleared, but the memory is not.
     by_region_per_copy_cache.clear();
 
     for (const auto& reg : by_region) {
-        by_region_per_copy_cache.push_back(ObjectByExtruder::Island::Region()); // creates a region in the newly created Island
+        by_region_per_copy_cache.emplace_back(); // creates a region in the newly created Island
 
         // Now we are going to iterate through perimeters and infills and pick ones that are supposed to be printed
         // References are used so that we don't have to repeat the same code
         for (int iter = 0; iter < 2; ++iter) {
-            const ExtrusionEntitiesPtr&         entities     = (iter ? reg.infills.entities : reg.perimeters.entities);
-            ExtrusionEntityCollection&          target_eec   = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters);
-            const std::vector<const ExtruderPerCopy*>& overrides   = (iter ? reg.infills_overrides : reg.perimeters_overrides);
+            const ExtrusionEntitiesPtr&										entities    = (iter ? reg.infills : reg.perimeters);
+            ExtrusionEntitiesPtr&   										target_eec  = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters);
+            const std::vector<const WipingExtrusions::ExtruderPerCopy*>& 	overrides   = (iter ? reg.infills_overrides : reg.perimeters_overrides);
 
             // Now the most important thing - which extrusion should we print.
             // See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack.
-            int this_extruder_mark = wiping_entities ? extruder : -extruder-1;
-
-            for (unsigned int i=0;i<entities.size();++i)
-                if (overrides[i]->at(copy) == this_extruder_mark)   // this copy should be printed with this extruder
-                    target_eec.append((*entities[i]));
+            if (wiping_entities) {
+            	// Apply overrides for this region.
+	            for (unsigned int i = 0; i < overrides.size(); ++ i) {
+            		const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
+            		// This copy (aka object instance) should be printed with this extruder, which overrides the default one.
+	                if (this_override != nullptr && (*this_override)[copy] == int(extruder))
+	                    target_eec.emplace_back(entities[i]);
+            	}
+	        } else {
+	        	// Apply normal extrusions (non-overrides) for this region.
+				unsigned int i = 0;
+	            for (; i < overrides.size(); ++ i) {
+            		const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
+            		// This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one.
+            		if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1)
+	                    target_eec.emplace_back(entities[i]);
+	            }
+	            for (; i < overrides.size(); ++ i)
+                    target_eec.emplace_back(entities[i]);
+		    }
         }
     }
     return by_region_per_copy_cache;
 }
 
-
-
 // This function takes the eec and appends its entities to either perimeters or infills of this Region (depending on the first parameter)
 // It also saves pointer to ExtruderPerCopy struct (for each entity), that holds information about which extruders should be used for which copy.
-void GCode::ObjectByExtruder::Island::Region::append(const std::string& type, const ExtrusionEntityCollection* eec, const ExtruderPerCopy* copies_extruder, size_t object_copies_num)
+void GCode::ObjectByExtruder::Island::Region::append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copies_extruder)
 {
     // We are going to manipulate either perimeters or infills, exactly in the same way. Let's create pointers to the proper structure to not repeat ourselves:
-    ExtrusionEntityCollection* perimeters_or_infills = &infills;
-    std::vector<const ExtruderPerCopy*>* perimeters_or_infills_overrides = &infills_overrides;
+    ExtrusionEntitiesPtr*									perimeters_or_infills;
+    std::vector<const WipingExtrusions::ExtruderPerCopy*>* 	perimeters_or_infills_overrides;
 
-    if (type == "perimeters") {
-        perimeters_or_infills = &perimeters;
-        perimeters_or_infills_overrides    = &perimeters_overrides;
+    switch (type) {
+    case PERIMETERS:
+        perimeters_or_infills 			= &perimeters;
+        perimeters_or_infills_overrides = &perimeters_overrides;
+        break;
+    case INFILL:
+    	perimeters_or_infills 			= &infills;
+    	perimeters_or_infills_overrides = &infills_overrides;
+        break;
+    default:
+    	throw std::invalid_argument("Unknown parameter!");
     }
-    else
-        if (type != "infills") {
-            throw std::invalid_argument("Unknown parameter!");
-            return;
-        }
-
 
     // First we append the entities, there are eec->entities.size() of them:
-    perimeters_or_infills->append(eec->entities);
+    size_t old_size = perimeters_or_infills->size();
+    perimeters_or_infills->reserve(perimeters_or_infills->size() + eec->entities.size());
+    for (auto* ee : eec->entities)
+        perimeters_or_infills->emplace_back(ee);
 
-    for (unsigned int i=0;i<eec->entities.size();++i)
-        perimeters_or_infills_overrides->push_back(copies_extruder);
+    if (copies_extruder != nullptr) {
+    	perimeters_or_infills_overrides->reserve(old_size + eec->entities.size());
+    	perimeters_or_infills_overrides->resize(old_size, nullptr);
+	    for (unsigned int i = 0; i < eec->entities.size(); ++ i)
+	        perimeters_or_infills_overrides->emplace_back(copies_extruder);
+	}
 }
 
 }   // namespace Slic3r
diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp
index 497dc8c71..0344924a1 100644
--- a/src/libslic3r/GCode.hpp
+++ b/src/libslic3r/GCode.hpp
@@ -197,23 +197,25 @@ public:
     // append full config to the given string
     static void append_full_config(const Print& print, std::string& str);
 
-protected:
+    // Object and support extrusions of the same PrintObject at the same print_z.
+    // public, so that it could be accessed by free helper functions from GCode.cpp
+    struct LayerToPrint
+    {
+        LayerToPrint() : object_layer(nullptr), support_layer(nullptr) {}
+        const Layer* 		object_layer;
+        const SupportLayer* support_layer;
+        const Layer* 		layer()   const { return (object_layer != nullptr) ? object_layer : support_layer; }
+        const PrintObject* 	object()  const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; }
+        coordf_t            print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; }
+    };
+
+private:
 #if ENABLE_THUMBNAIL_GENERATOR
-    void            _do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb);
+    void            _do_export(Print &print, FILE *file, ThumbnailsGeneratorCallback thumbnail_cb);
 #else
     void            _do_export(Print &print, FILE *file);
 #endif //ENABLE_THUMBNAIL_GENERATOR
 
-    // Object and support extrusions of the same PrintObject at the same print_z.
-    struct LayerToPrint
-    {
-        LayerToPrint() : object_layer(nullptr), support_layer(nullptr) {}
-        const Layer          *object_layer;
-        const SupportLayer   *support_layer;
-        const Layer*          layer() const { return (object_layer != nullptr) ? object_layer : support_layer; }
-        const PrintObject*    object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; }
-        coordf_t              print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; }
-    };
     static std::vector<LayerToPrint>        		                   collect_layers_to_print(const PrintObject &object);
     static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print);
     void            process_layer(
@@ -239,7 +241,6 @@ protected:
     std::string     extrude_multi_path(ExtrusionMultiPath multipath, std::string description = "", double speed = -1.);
     std::string     extrude_path(ExtrusionPath path, std::string description = "", double speed = -1.);
 
-    typedef std::vector<int> ExtruderPerCopy;
     // Extruding multiple objects with soluble / non-soluble / combined supports
     // on a multi-material printer, trying to minimize tool switches.
     // Following structures sort extrusions by the extruder ID, by an order of objects and object islands.
@@ -253,21 +254,29 @@ protected:
         struct Island
         {
             struct Region {
-                ExtrusionEntityCollection perimeters;
-                ExtrusionEntityCollection infills;
+            	// Non-owned references to LayerRegion::perimeters::entities
+            	// std::vector<const ExtrusionEntity*> would be better here, but there is no way in C++ to convert from std::vector<T*> std::vector<const T*> without copying.
+                ExtrusionEntitiesPtr perimeters;
+            	// Non-owned references to LayerRegion::fills::entities
+                ExtrusionEntitiesPtr infills;
 
-                std::vector<const ExtruderPerCopy*> infills_overrides;
-                std::vector<const ExtruderPerCopy*> perimeters_overrides;
+                std::vector<const WipingExtrusions::ExtruderPerCopy*> infills_overrides;
+                std::vector<const WipingExtrusions::ExtruderPerCopy*> perimeters_overrides;
+
+	            enum Type {
+	            	PERIMETERS,
+	            	INFILL,
+	            };
 
                 // Appends perimeter/infill entities and writes don't indices of those that are not to be extruder as part of perimeter/infill wiping
-                void append(const std::string& type, const ExtrusionEntityCollection* eec, const ExtruderPerCopy* copy_extruders, size_t object_copies_num);
+                void append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copy_extruders);
             };
 
-            std::vector<Region> by_region;                                    // all extrusions for this island, grouped by regions
-            const std::vector<Region>& by_region_per_copy(unsigned int copy, int extruder, bool wiping_entities = false); // returns reference to subvector of by_region
 
-        private:
-            std::vector<Region> by_region_per_copy_cache;   // caches vector generated by function above to avoid copying and recalculating
+            std::vector<Region> by_region;                                    // all extrusions for this island, grouped by regions
+
+            // Fills in by_region_per_copy_cache and returns its reference.
+            const std::vector<Region>& by_region_per_copy(std::vector<Region> &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities = false) const;
         };
         std::vector<Island>         islands;
     };
@@ -277,7 +286,9 @@ protected:
 		InstanceToPrint(ObjectByExtruder &object_by_extruder, size_t layer_id, const PrintObject &print_object, size_t instance_id) :
 			object_by_extruder(object_by_extruder), layer_id(layer_id), print_object(print_object), instance_id(instance_id) {}
 
-		ObjectByExtruder	&object_by_extruder;
+		// Repository 
+		ObjectByExtruder		&object_by_extruder;
+		// Index into std::vector<LayerToPrint>, which contains Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances.
 		const size_t       		 layer_id;
 		const PrintObject 		&print_object;
 		// Instance idx of the copy of a print object.
@@ -285,7 +296,8 @@ protected:
 	};
 
 	std::vector<InstanceToPrint> sort_print_object_instances(
-		std::vector<ObjectByExtruder> 			&objects_by_extruder,
+		std::vector<ObjectByExtruder> 					&objects_by_extruder,
+		// Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances.
 		const std::vector<LayerToPrint> 				&layers,
 		// Ordering must be defined for normal (non-sequential print).
 		const std::vector<std::pair<size_t, size_t>> 	*ordering,
@@ -354,7 +366,7 @@ protected:
 #endif /* HAS_PRESSURE_EQUALIZER */
     std::unique_ptr<WipeTowerIntegration> m_wipe_tower;
 
-    // Heights at which the skirt has already been extruded.
+    // Heights (print_z) at which the skirt has already been extruded.
     std::vector<coordf_t>               m_skirt_done;
     // Has the brim been extruded already? Brim is being extruded only for the first object of a multi-object print.
     bool                                m_brim_done;
@@ -362,11 +374,6 @@ protected:
     bool                                m_second_layer_things_done;
     // Index of a last object copy extruded.
     std::pair<const PrintObject*, Point> m_last_obj_copy;
-    // Extensions for colorprint - now it's not a just color_print_heights,
-    // there can be some custom gcode.
-    // Updated before the export and erased during the process,
-    // so no toolchange occurs twice.
-    std::vector<Model::CustomGCode> 	m_custom_gcode_per_print_z;
 
     // Time estimators
     GCodeTimeEstimator m_normal_time_estimator;
diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp
index 7b8004ab0..26e125bbc 100644
--- a/src/libslic3r/GCode/Analyzer.cpp
+++ b/src/libslic3r/GCode/Analyzer.cpp
@@ -311,24 +311,22 @@ void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line)
 {
     auto axis_absolute_position = [this](GCodeAnalyzer::EAxis axis, const GCodeReader::GCodeLine& lineG1) -> float
     {
-        float current_absolute_position = _get_axis_position(axis);
-        float current_origin = _get_axis_origin(axis);
-        float lengthsScaleFactor = (_get_units() == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f;
-
         bool is_relative = (_get_global_positioning_type() == Relative);
         if (axis == E)
             is_relative |= (_get_e_local_positioning_type() == Relative);
 
         if (lineG1.has(Slic3r::Axis(axis)))
         {
+            float lengthsScaleFactor = (_get_units() == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f;
             float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
-            return is_relative ? current_absolute_position + ret : ret + current_origin;
+            return is_relative ? _get_axis_position(axis) + ret : _get_axis_origin(axis) + ret;
         }
         else
-            return current_absolute_position;
+            return _get_axis_position(axis);
     };
 
     // updates axes positions from line
+
     float new_pos[Num_Axis];
     for (unsigned char a = X; a < Num_Axis; ++a)
     {
@@ -352,7 +350,7 @@ void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line)
     if (delta_pos[E] < 0.0f)
     {
         if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f))
-            type = GCodeMove::Move;
+        type = GCodeMove::Move;
         else
             type = GCodeMove::Retract;
     }
@@ -440,7 +438,9 @@ void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line)
 
     if (line.has_e())
     {
-        _set_axis_origin(E, _get_axis_position(E) - line.e() * lengthsScaleFactor);
+        // extruder coordinate can grow to the point where its float representation does not allow for proper addition with small increments,
+        // we set the value taken from the G92 line as the new current position for it
+        _set_axis_position(E, line.e() * lengthsScaleFactor);
         anyFound = true;
     }
 
@@ -956,7 +956,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
 				GCodePreviewData::Extrusion::Path &path = paths.back();
                 path.polyline = polyline;
 				path.extrusion_role = data.extrusion_role;
-				path.mm3_per_mm = data.mm3_per_mm;
+				path.mm3_per_mm = float(data.mm3_per_mm);
 				path.width = data.width;
 				path.height = data.height;
                 path.feedrate = data.feedrate;
diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp
index b00bc73eb..6815ea73a 100644
--- a/src/libslic3r/GCode/CoolingBuffer.cpp
+++ b/src/libslic3r/GCode/CoolingBuffer.cpp
@@ -303,8 +303,8 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
         unsigned int            extruder_id = extruders[i].id();
         adj.extruder_id               = extruder_id;
         adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id);
-        adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id);
-        adj.min_print_speed           = config.min_print_speed.get_at(extruder_id);
+        adj.slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(extruder_id));
+        adj.min_print_speed           = float(config.min_print_speed.get_at(extruder_id));
         map_extruder_to_per_extruder_adjustment[extruder_id] = i;
     }
 
diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp
index d44ef1aad..1fedcf3f0 100644
--- a/src/libslic3r/GCode/PrintExtents.cpp
+++ b/src/libslic3r/GCode/PrintExtents.cpp
@@ -29,7 +29,7 @@ static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, c
 
 static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusion_path)
 {
-    BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width));
+    BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width)));
     BoundingBoxf bboxf;
     if (! empty(bbox)) {
         bboxf.min = unscale(bbox.min);
@@ -43,7 +43,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusio
 {
     BoundingBox bbox;
     for (const ExtrusionPath &extrusion_path : extrusion_loop.paths)
-        bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width)));
+        bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
     BoundingBoxf bboxf;
     if (! empty(bbox)) {
         bboxf.min = unscale(bbox.min);
@@ -57,7 +57,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &ext
 {
     BoundingBox bbox;
     for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths)
-        bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width)));
+        bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
     BoundingBoxf bboxf;
     if (! empty(bbox)) {
         bboxf.min = unscale(bbox.min);
diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp
index bcb65554d..a76f0fafb 100644
--- a/src/libslic3r/GCode/ToolOrdering.cpp
+++ b/src/libslic3r/GCode/ToolOrdering.cpp
@@ -13,13 +13,17 @@
 #include <cassert>
 #include <limits>
 
+#include <libslic3r.h>
+
+#include "../GCodeWriter.hpp"
+
 namespace Slic3r {
 
 
 // Returns true in case that extruder a comes before b (b does not have to be present). False otherwise.
 bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const
 {
-    if (a==b)
+    if (a == b)
         return false;
 
     for (auto extruder : extruders) {
@@ -32,6 +36,39 @@ bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const
     return false;
 }
 
+// Return a zero based extruder from the region, or extruder_override if overriden.
+unsigned int LayerTools::perimeter_extruder(const PrintRegion &region) const
+{
+	assert(region.config().perimeter_extruder.value > 0);
+	return ((this->extruder_override == 0) ? region.config().perimeter_extruder.value : this->extruder_override) - 1;
+}
+
+unsigned int LayerTools::infill_extruder(const PrintRegion &region) const
+{
+	assert(region.config().infill_extruder.value > 0);
+	return ((this->extruder_override == 0) ? region.config().infill_extruder.value : this->extruder_override) - 1;
+}
+
+unsigned int LayerTools::solid_infill_extruder(const PrintRegion &region) const
+{
+	assert(region.config().solid_infill_extruder.value > 0);
+	return ((this->extruder_override == 0) ? region.config().solid_infill_extruder.value : this->extruder_override) - 1;
+}
+
+// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden.
+unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion &region) const
+{
+	assert(region.config().perimeter_extruder.value > 0);
+	assert(region.config().infill_extruder.value > 0);
+	assert(region.config().solid_infill_extruder.value > 0);
+	// 1 based extruder ID.
+	unsigned int extruder = ((this->extruder_override == 0) ?
+	    (is_infill(extrusions.role()) ?
+	    	(is_solid_infill(extrusions.entities.front()->role()) ? region.config().solid_infill_extruder : region.config().infill_extruder) :
+			region.config().perimeter_extruder.value) :
+		this->extruder_override);
+	return (extruder == 0) ? 0 : extruder - 1;
+}
 
 // For the use case when each object is printed separately
 // (print.config().complete_objects is true).
@@ -52,7 +89,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude
     }
 
     // Collect extruders reuqired to print the layers.
-    this->collect_extruders(object);
+    this->collect_extruders(object, std::vector<std::pair<double, unsigned int>>());
 
     // Reorder the extruders to minimize tool switches.
     this->reorder_extruders(first_extruder);
@@ -89,9 +126,15 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
         this->initialize_layers(zs);
     }
 
+	// Use the extruder switches from Model::custom_gcode_per_print_z to override the extruder to print the object.
+	// Do it only if all the objects were configured to be printed with a single extruder.
+	std::vector<std::pair<double, unsigned int>> per_layer_extruder_switches;
+	if (print.object_extruders().size() == 1)
+		per_layer_extruder_switches = custom_tool_changes(print.model(), (unsigned int)print.config().nozzle_diameter.size());
+
     // Collect extruders reuqired to print the layers.
     for (auto object : print.objects())
-        this->collect_extruders(*object);
+        this->collect_extruders(*object, per_layer_extruder_switches);
 
     // Reorder the extruders to minimize tool switches.
     this->reorder_extruders(first_extruder);
@@ -99,6 +142,11 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
     this->fill_wipe_tower_partitions(print.config(), object_bottom_z);
 
     this->collect_extruder_statistics(prime_multi_material);
+
+    // Assign custom G-code actions from Model::custom_gcode_per_print_z to their respecive layers,
+    // ignoring the extruder switches, which were processed above, and ignoring color changes for extruders,
+    // that do not print above their respective print_z.
+    this->assign_custom_gcodes(print);
 }
 
 void ToolOrdering::initialize_layers(std::vector<coordf_t> &zs)
@@ -111,13 +159,13 @@ void ToolOrdering::initialize_layers(std::vector<coordf_t> &zs)
         coordf_t zmax = zs[i] + EPSILON;
         for (; j < zs.size() && zs[j] <= zmax; ++ j) ;
         // Assign an average print_z to the set of layers with nearly equal print_z.
-        m_layer_tools.emplace_back(LayerTools(0.5 * (zs[i] + zs[j-1]), m_print_config_ptr));
+        m_layer_tools.emplace_back(LayerTools(0.5 * (zs[i] + zs[j-1])));
         i = j;
     }
 }
 
 // Collect extruders reuqired to print layers.
-void ToolOrdering::collect_extruders(const PrintObject &object)
+void ToolOrdering::collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches)
 {
     // Collect the support extruders.
     for (auto support_layer : object.support_layers()) {
@@ -134,9 +182,23 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
         if (has_support || has_interface)
             layer_tools.has_support = true;
     }
+
+    // Extruder overrides are ordered by print_z.
+    std::vector<std::pair<double, unsigned int>>::const_iterator it_per_layer_extruder_override;
+	it_per_layer_extruder_override = per_layer_extruder_switches.begin();
+    unsigned int extruder_override = 0;
+
     // Collect the object extruders.
     for (auto layer : object.layers()) {
         LayerTools &layer_tools = this->tools_for_layer(layer->print_z);
+
+        // Override extruder with the next 
+    	for (; it_per_layer_extruder_override != per_layer_extruder_switches.end() && it_per_layer_extruder_override->first < layer->print_z + EPSILON; ++ it_per_layer_extruder_override)
+    		extruder_override = (int)it_per_layer_extruder_override->second;
+
+        // Store the current extruder override (set to zero if no overriden), so that layer_tools.wiping_extrusions().is_overridable_and_mark() will use it.
+        layer_tools.extruder_override = extruder_override;
+
         // What extruders are required to print this object layer?
         for (size_t region_id = 0; region_id < object.region_volumes.size(); ++ region_id) {
             const LayerRegion *layerm = (region_id < layer->regions().size()) ? layer->regions()[region_id] : nullptr;
@@ -150,19 +212,18 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
                 if (m_print_config_ptr) { // in this case complete_objects is false (see ToolOrdering constructors)
                     something_nonoverriddable = false;
                     for (const auto& eec : layerm->perimeters.entities) // let's check if there are nonoverriddable entities
-                        if (!layer_tools.wiping_extrusions().is_overriddable(dynamic_cast<const ExtrusionEntityCollection&>(*eec), *m_print_config_ptr, object, region)) {
+                        if (!layer_tools.wiping_extrusions().is_overriddable_and_mark(dynamic_cast<const ExtrusionEntityCollection&>(*eec), *m_print_config_ptr, object, region)) {
                             something_nonoverriddable = true;
                             break;
                         }
                 }
 
                 if (something_nonoverriddable)
-                        layer_tools.extruders.push_back(region.config().perimeter_extruder.value);
+               		layer_tools.extruders.emplace_back((extruder_override == 0) ? region.config().perimeter_extruder.value : extruder_override);
 
                 layer_tools.has_object = true;
             }
 
-
             bool has_infill       = false;
             bool has_solid_infill = false;
             bool something_nonoverriddable = false;
@@ -176,17 +237,19 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
                     has_infill = true;
 
                 if (m_print_config_ptr) {
-                    if (!something_nonoverriddable && !layer_tools.wiping_extrusions().is_overriddable(*fill, *m_print_config_ptr, object, region))
+                    if (!something_nonoverriddable && !layer_tools.wiping_extrusions().is_overriddable_and_mark(*fill, *m_print_config_ptr, object, region))
                         something_nonoverriddable = true;
                 }
             }
 
-            if (something_nonoverriddable || !m_print_config_ptr)
-            {
-                if (has_solid_infill)
-                    layer_tools.extruders.push_back(region.config().solid_infill_extruder);
-                if (has_infill)
-                    layer_tools.extruders.push_back(region.config().infill_extruder);
+            if (something_nonoverriddable || !m_print_config_ptr) {
+            	if (extruder_override == 0) {
+	                if (has_solid_infill)
+	                    layer_tools.extruders.emplace_back(region.config().solid_infill_extruder);
+	                if (has_infill)
+	                    layer_tools.extruders.emplace_back(region.config().infill_extruder);
+            	} else if (has_solid_infill || has_infill)
+            		layer_tools.extruders.emplace_back(extruder_override);
             }
             if (has_solid_infill || has_infill)
                 layer_tools.has_object = true;
@@ -199,7 +262,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
 
         // make sure that there are some tools for each object layer (e.g. tall wiping object will result in empty extruders vector)
         if (layer.extruders.empty() && layer.has_object)
-            layer.extruders.push_back(0); // 0="dontcare" extruder - it will be taken care of in reorder_extruders
+            layer.extruders.emplace_back(0); // 0="dontcare" extruder - it will be taken care of in reorder_extruders
     }
 }
 
@@ -254,11 +317,9 @@ void ToolOrdering::reorder_extruders(unsigned int last_extruder_id)
         for (unsigned int &extruder_id : lt.extruders) {
             assert(extruder_id > 0);
             -- extruder_id;
-        }
+        }    
 }
 
-
-
 void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z)
 {
     if (m_layer_tools.empty())
@@ -394,6 +455,46 @@ void ToolOrdering::collect_extruder_statistics(bool prime_multi_material)
     }
 }
 
+// Assign a pointer to a custom G-code to the respective ToolOrdering::LayerTools.
+// Ignore color changes, which are performed on a layer and for such an extruder, that the extruder will not be printing above that layer.
+// If multiple events are planned over a span of a single layer, use the last one.
+void ToolOrdering::assign_custom_gcodes(const Print &print)
+{
+	const std::vector<Model::CustomGCode>	&custom_gcode_per_print_z = print.model().custom_gcode_per_print_z;
+	if (custom_gcode_per_print_z.empty())
+		return;
+
+	unsigned int 							 num_extruders = *std::max_element(m_all_printing_extruders.begin(), m_all_printing_extruders.end()) + 1;
+	std::vector<unsigned char> 				 extruder_printing_above(num_extruders, false);
+	auto 									 custom_gcode_it = custom_gcode_per_print_z.rbegin();
+	// From the last layer to the first one:
+	for (auto it_lt = m_layer_tools.rbegin(); it_lt != m_layer_tools.rend(); ++ it_lt) {
+		LayerTools &lt = *it_lt;
+		// Add the extruders of the current layer to the set of extruders printing at and above this print_z.
+		for (unsigned int i : lt.extruders)
+			extruder_printing_above[i] = true;
+		// Skip all custom G-codes above this layer and skip all extruder switches.
+		for (; custom_gcode_it != custom_gcode_per_print_z.rend() && (custom_gcode_it->print_z > lt.print_z + EPSILON || custom_gcode_it->gcode == ExtruderChangeCode); ++ custom_gcode_it);
+		if (custom_gcode_it == custom_gcode_per_print_z.rend())
+			// Custom G-codes were processed.
+			break;
+		// Some custom G-code is configured for this layer or a layer below.
+		const Model::CustomGCode &custom_gcode = *custom_gcode_it;
+		// print_z of the layer below the current layer.
+		coordf_t print_z_below = 0.;
+		if (auto it_lt_below = it_lt; ++ it_lt_below != m_layer_tools.rend())
+			print_z_below = it_lt_below->print_z;
+		if (custom_gcode.print_z > print_z_below + 0.5 * EPSILON) {
+			// The custom G-code applies to the current layer.
+			if (custom_gcode.gcode != ColorChangeCode || extruder_printing_above[unsigned(custom_gcode.extruder - 1)])
+				// If it is color change, it will actually be useful as the exturder above will print.
+        		lt.custom_gcode = &custom_gcode;
+			// Consume that custom G-code event.
+			++ custom_gcode_it;
+		}
+	}
+}
+
 const LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) const
 {
     auto it_layer_tools = std::lower_bound(m_layer_tools.begin(), m_layer_tools.end(), LayerTools(print_z - EPSILON));
@@ -411,14 +512,13 @@ const LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) const
 }
 
 // This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual)
-void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, unsigned int copy_id, int extruder, unsigned int num_of_copies)
+void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies)
 {
     something_overridden = true;
 
-    auto entity_map_it = (entity_map.insert(std::make_pair(entity, std::vector<int>()))).first; // (add and) return iterator
-    auto& copies_vector = entity_map_it->second;
-    if (copies_vector.size() < num_of_copies)
-        copies_vector.resize(num_of_copies, -1);
+    auto entity_map_it = (entity_map.emplace(entity, ExtruderPerCopy())).first; // (add and) return iterator
+    ExtruderPerCopy& copies_vector = entity_map_it->second;
+    copies_vector.resize(num_of_copies, -1);
 
     if (copies_vector[copy_id] != -1)
         std::cout << "ERROR: Entity extruder overriden multiple times!!!\n";    // A debugging message - this must never happen.
@@ -426,7 +526,6 @@ void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, unsi
     copies_vector[copy_id] = extruder;
 }
 
-
 // Finds first non-soluble extruder on the layer
 int WipingExtrusions::first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const
 {
@@ -449,11 +548,10 @@ int WipingExtrusions::last_nonsoluble_extruder_on_layer(const PrintConfig& print
     return (-1);
 }
 
-
 // Decides whether this entity could be overridden
 bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const
 {
-    if (print_config.filament_soluble.get_at(Print::get_extruder(eec, region)))
+    if (print_config.filament_soluble.get_at(m_layer_tools->extruder(eec, region)))
         return false;
 
     if (object.config().wipe_into_objects)
@@ -465,7 +563,6 @@ bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, con
     return true;
 }
 
-
 // Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange
 // and returns volume that is left to be wiped on the wipe tower.
 float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe)
@@ -473,8 +570,8 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
     const LayerTools& lt = *m_layer_tools;
     const float min_infill_volume = 0.f; // ignore infill with smaller volume than this
 
-    if (print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder))
-        return volume_to_wipe;      // Soluble filament cannot be wiped in a random infill, neither the filament after it
+    if (! this->something_overridable || volume_to_wipe <= 0. || print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder))
+        return std::max(0.f, volume_to_wipe); // Soluble filament cannot be wiped in a random infill, neither the filament after it
 
     // we will sort objects so that dedicated for wiping are at the beginning:
     PrintObjectPtrs object_list = print.objects();
@@ -497,13 +594,13 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
         const PrintObject* object = object_list[i];
 
         // Finds this layer:
-        auto this_layer_it = std::find_if(object->layers().begin(), object->layers().end(), [&lt](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; });
-        if (this_layer_it == object->layers().end())
-            continue;
-        const Layer* this_layer = *this_layer_it;
+        const Layer* this_layer = object->get_layer_at_printz(lt.print_z, EPSILON);
+        if (this_layer == nullptr)
+        	continue;
         size_t num_of_copies = object->copies().size();
 
-        for (unsigned int copy = 0; copy < num_of_copies; ++copy) {    // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
+        // iterate through copies (aka PrintObject instances) first, so that we mark neighbouring infills to minimize travel moves
+        for (unsigned int copy = 0; copy < num_of_copies; ++copy) {
 
             for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) {
                 const auto& region = *object->print()->regions()[region_id];
@@ -511,51 +608,48 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
                 if (!region.config().wipe_into_infill && !object->config().wipe_into_objects)
                     continue;
 
-
-                if ((!print.config().infill_first ? perimeters_done : !perimeters_done) || (!object->config().wipe_into_objects && region.config().wipe_into_infill)) {
+                bool wipe_into_infill_only = ! object->config().wipe_into_objects && region.config().wipe_into_infill;
+                if (print.config().infill_first != perimeters_done || wipe_into_infill_only) {
                     for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->fills.entities) {                      // iterate through all infill Collections
                         auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
 
                         if (!is_overriddable(*fill, print.config(), *object, region))
                             continue;
 
-                        if (volume_to_wipe<=0)
-                            continue;
-
-                        if (!object->config().wipe_into_objects && !print.config().infill_first && region.config().wipe_into_infill)
+                        if (wipe_into_infill_only && ! print.config().infill_first)
                             // In this case we must check that the original extruder is used on this layer before the one we are overridding
                             // (and the perimeters will be finished before the infill is printed):
-                            if (!lt.is_extruder_order(region.config().perimeter_extruder - 1, new_extruder))
+                            if (!lt.is_extruder_order(lt.perimeter_extruder(region), new_extruder))
                                 continue;
 
                         if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) {     // this infill will be used to wipe this extruder
                             set_extruder_override(fill, copy, new_extruder, num_of_copies);
-                            volume_to_wipe -= float(fill->total_volume());
+                            if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f)
+                            	// More material was purged already than asked for.
+	                            return 0.f;
                         }
                     }
                 }
 
                 // Now the same for perimeters - see comments above for explanation:
-                if (object->config().wipe_into_objects && (print.config().infill_first ? perimeters_done : !perimeters_done))
+                if (object->config().wipe_into_objects && print.config().infill_first == perimeters_done)
                 {
                     for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) {
                         auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
-                        if (!is_overriddable(*fill, print.config(), *object, region))
-                            continue;
-
-                        if (volume_to_wipe<=0)
-                            continue;
-
-                        if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) {
+                        if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume) {
                             set_extruder_override(fill, copy, new_extruder, num_of_copies);
-                            volume_to_wipe -= float(fill->total_volume());
+                            if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f)
+                            	// More material was purged already than asked for.
+	                            return 0.f;
                         }
                     }
                 }
             }
         }
     }
-    return std::max(0.f, volume_to_wipe);
+	// Some purge remains to be done on the Wipe Tower.
+    assert(volume_to_wipe > 0.);
+    return volume_to_wipe;
 }
 
 
@@ -566,16 +660,18 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
 // them again and make sure we override it.
 void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
 {
+	if (! this->something_overridable)
+		return;
+
     const LayerTools& lt = *m_layer_tools;
     unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config());
     unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config());
 
     for (const PrintObject* object : print.objects()) {
         // Finds this layer:
-        auto this_layer_it = std::find_if(object->layers().begin(), object->layers().end(), [&lt](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; });
-        if (this_layer_it == object->layers().end())
-            continue;
-        const Layer* this_layer = *this_layer_it;
+        const Layer* this_layer = object->get_layer_at_printz(lt.print_z, EPSILON);
+        if (this_layer == nullptr)
+        	continue;
         size_t num_of_copies = object->copies().size();
 
         for (size_t copy = 0; copy < num_of_copies; ++copy) {    // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
@@ -598,9 +694,8 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
                     // Either way, we will now force-override it with something suitable:
                     if (print.config().infill_first
                     || object->config().wipe_into_objects  // in this case the perimeter is overridden, so we can override by the last one safely
-                    || lt.is_extruder_order(region.config().perimeter_extruder - 1, last_nonsoluble_extruder    // !infill_first, but perimeter is already printed when last extruder prints
-                    || std::find(lt.extruders.begin(), lt.extruders.end(), region.config().infill_extruder - 1) == lt.extruders.end()) // we have to force override - this could violate infill_first (FIXME)
-                      )
+                    || lt.is_extruder_order(lt.perimeter_extruder(region), last_nonsoluble_extruder    // !infill_first, but perimeter is already printed when last extruder prints
+                    || ! lt.has_extruder(lt.infill_extruder(region)))) // we have to force override - this could violate infill_first (FIXME)
                         set_extruder_override(fill, copy, (print.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies);
                     else {
                         // In this case we can (and should) leave it to be printed normally.
@@ -611,42 +706,31 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
                 // Now the same for perimeters - see comments above for explanation:
                 for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) {                      // iterate through all perimeter Collections
                     auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
-                    if (!is_overriddable(*fill, print.config(), *object, region)
-                     || is_entity_overridden(fill, copy) )
-                        continue;
-
-                    set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies);
+                    if (is_overriddable(*fill, print.config(), *object, region) && ! is_entity_overridden(fill, copy))
+                        set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies);
                 }
             }
         }
     }
 }
 
-
-
-
-
-
-
-// Following function is called from process_layer and returns pointer to vector with information about which extruders should be used for given copy of this entity.
-// It first makes sure the pointer is valid (creates the vector if it does not exist) and contains a record for each copy
-// It also modifies the vector in place and changes all -1 to correct_extruder_id (at the time the overrides were created, correct extruders were not known,
-// so -1 was used as "print as usual".
-// The resulting vector has to keep track of which extrusions are the ones that were overridden and which were not. In the extruder is used as overridden,
-// its number is saved as it is (zero-based index). Usual extrusions are saved as -number-1 (unfortunately there is no negative zero).
-const std::vector<int>* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies)
+// Following function is called from GCode::process_layer and returns pointer to vector with information about which extruders should be used for given copy of this entity.
+// If this extrusion does not have any override, nullptr is returned.
+// Otherwise it modifies the vector in place and changes all -1 to correct_extruder_id (at the time the overrides were created, correct extruders were not known,
+// so -1 was used as "print as usual").
+// The resulting vector therefore keeps track of which extrusions are the ones that were overridden and which were not. If the extruder used is overridden,
+// its number is saved as is (zero-based index). Regular extrusions are saved as -number-1 (unfortunately there is no negative zero).
+const WipingExtrusions::ExtruderPerCopy* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies)
 {
+	ExtruderPerCopy *overrides = nullptr;
     auto entity_map_it = entity_map.find(entity);
-    if (entity_map_it == entity_map.end())
-        entity_map_it = (entity_map.insert(std::make_pair(entity, std::vector<int>()))).first;
-
-    // Now the entity_map_it should be valid, let's make sure the vector is long enough:
-    entity_map_it->second.resize(num_of_copies, -1);
-
-    // Each -1 now means "print as usual" - we will replace it with actual extruder id (shifted it so we don't lose that information):
-    std::replace(entity_map_it->second.begin(), entity_map_it->second.end(), -1, -correct_extruder_id-1);
-
-    return &(entity_map_it->second);
+    if (entity_map_it != entity_map.end()) {
+        overrides = &entity_map_it->second;
+    	overrides->resize(num_of_copies, -1);
+	    // Each -1 now means "print as usual" - we will replace it with actual extruder id (shifted it so we don't lose that information):
+	    std::replace(overrides->begin(), overrides->end(), -1, -correct_extruder_id-1);
+	}
+    return overrides;
 }
 
 
diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp
index c37c90823..a9d5a98e7 100644
--- a/src/libslic3r/GCode/ToolOrdering.hpp
+++ b/src/libslic3r/GCode/ToolOrdering.hpp
@@ -7,6 +7,8 @@
 
 #include <utility>
 
+#include <boost/container/small_vector.hpp>
+
 namespace Slic3r {
 
 class Print;
@@ -25,8 +27,19 @@ public:
         return something_overridden;
     }
 
+    // When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place.
+    typedef boost::container::small_vector<int32_t, 3> ExtruderPerCopy;
+
+    class ExtruderOverrides
+    {
+    public:
+    	ExtruderOverrides(const ExtruderPerCopy *overrides, const int correct_extruder_id) : m_overrides(overrides) {}
+    private:
+    	const ExtruderPerCopy *m_overrides;
+    };
+
     // This is called from GCode::process_layer - see implementation for further comments:
-    const std::vector<int>* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies);
+    const ExtruderPerCopy* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies);
 
     // This function goes through all infill entities, decides which ones will be used for wiping and
     // marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower:
@@ -35,6 +48,11 @@ public:
     void ensure_perimeters_infills_order(const Print& print);
 
     bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const;
+    bool is_overriddable_and_mark(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) {
+    	bool out = this->is_overriddable(ee, print_config, object, region);
+    	this->something_overridable |= out;
+    	return out;
+    }
 
     void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; }
 
@@ -43,14 +61,16 @@ private:
     int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
 
     // This function is called from mark_wiping_extrusions and sets extruder that it should be printed with (-1 .. as usual)
-    void set_extruder_override(const ExtrusionEntity* entity, unsigned int copy_id, int extruder, unsigned int num_of_copies);
+    void set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies);
 
     // Returns true in case that entity is not printed with its usual extruder for a given copy:
     bool is_entity_overridden(const ExtrusionEntity* entity, size_t copy_id) const {
-        return (entity_map.find(entity) == entity_map.end() ? false : entity_map.at(entity).at(copy_id) != -1);
+        auto it = entity_map.find(entity);
+        return it == entity_map.end() ? false : it->second[copy_id] != -1;
     }
 
-    std::map<const ExtrusionEntity*, std::vector<int>> entity_map;  // to keep track of who prints what
+    std::map<const ExtrusionEntity*, ExtruderPerCopy> entity_map;  // to keep track of who prints what
+    bool something_overridable = false;
     bool something_overridden = false;
     const LayerTools* m_layer_tools;    // so we know which LayerTools object this belongs to
 };
@@ -60,13 +80,7 @@ private:
 class LayerTools
 {
 public:
-    LayerTools(const coordf_t z, const PrintConfig* print_config_ptr = nullptr) :
-        print_z(z),
-        has_object(false),
-        has_support(false),
-        has_wipe_tower(false),
-        wipe_tower_partitions(0),
-        wipe_tower_layer_height(0.) {}
+    LayerTools(const coordf_t z) : print_z(z) {}
 
     // Changing these operators to epsilon version can make a problem in cases where support and object layers get close to each other.
     // In case someone tries to do it, make sure you know what you're doing and test it properly (slice multiple objects at once with supports).
@@ -74,20 +88,33 @@ public:
     bool operator==(const LayerTools &rhs) const { return print_z == rhs.print_z; }
 
     bool is_extruder_order(unsigned int a, unsigned int b) const;
+    bool has_extruder(unsigned int extruder) const { return std::find(this->extruders.begin(), this->extruders.end(), extruder) != this->extruders.end(); }
 
-    coordf_t 					print_z;
-    bool 						has_object;
-    bool						has_support;
+    // Return a zero based extruder from the region, or extruder_override if overriden.
+    unsigned int perimeter_extruder(const PrintRegion &region) const;
+    unsigned int infill_extruder(const PrintRegion &region) const;
+    unsigned int solid_infill_extruder(const PrintRegion &region) const;
+	// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden.
+	unsigned int extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion &region) const;
+
+    coordf_t 					print_z	= 0.;
+    bool 						has_object = false;
+    bool						has_support = false;
     // Zero based extruder IDs, ordered to minimize tool switches.
     std::vector<unsigned int> 	extruders;
+    // If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with.
+    // If not overriden, it is set to 0.
+    unsigned int 				extruder_override = 0;
     // Will there be anything extruded on this layer for the wipe tower?
     // Due to the support layers possibly interleaving the object layers,
     // wipe tower will be disabled for some support only layers.
-    bool 						has_wipe_tower;
+    bool 						has_wipe_tower = false;
     // Number of wipe tower partitions to support the required number of tool switches
     // and to support the wipe tower partitions above this one.
-    size_t                      wipe_tower_partitions;
-    coordf_t 					wipe_tower_layer_height;
+    size_t                      wipe_tower_partitions = 0;
+    coordf_t 					wipe_tower_layer_height = 0.;
+    // Custom G-code (color change, extruder switch, pause) to be performed before this layer starts to print.
+    const Model::CustomGCode   *custom_gcode = nullptr;
 
     WipingExtrusions& wiping_extrusions() {
         m_wiping_extrusions.set_layer_tools_ptr(this);
@@ -108,11 +135,11 @@ public:
 
     // For the use case when each object is printed separately
     // (print.config.complete_objects is true).
-    ToolOrdering(const PrintObject &object, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false);
+    ToolOrdering(const PrintObject &object, unsigned int first_extruder, bool prime_multi_material = false);
 
     // For the use case when all objects are printed at once.
     // (print.config.complete_objects is false).
-    ToolOrdering(const Print &print, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false);
+    ToolOrdering(const Print &print, unsigned int first_extruder, bool prime_multi_material = false);
 
     void 				clear() { m_layer_tools.clear(); }
 
@@ -139,10 +166,11 @@ public:
 
 private:
     void				initialize_layers(std::vector<coordf_t> &zs);
-    void 				collect_extruders(const PrintObject &object);
+    void 				collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches);
     void				reorder_extruders(unsigned int last_extruder_id);
     void 				fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z);
     void 				collect_extruder_statistics(bool prime_multi_material);
+	void 				assign_custom_gcodes(const Print &print);
 
     std::vector<LayerTools>    m_layer_tools;
     // First printing extruder, including the multi-material priming sequence.
@@ -152,7 +180,6 @@ private:
     // All extruders, which extrude some material over m_layer_tools.
     std::vector<unsigned int>  m_all_printing_extruders;
 
-
     const PrintConfig*         m_print_config_ptr = nullptr;
 };
 
diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp
index c624c0fce..36127cad7 100644
--- a/src/libslic3r/GCodeTimeEstimator.cpp
+++ b/src/libslic3r/GCodeTimeEstimator.cpp
@@ -1261,7 +1261,9 @@ namespace Slic3r {
 
         if (line.has_e())
         {
-            set_axis_origin(E, get_axis_position(E) - line.e() * lengthsScaleFactor);
+            // extruder coordinate can grow to the point where its float representation does not allow for proper addition with small increments,
+            // we set the value taken from the G92 line as the new current position for it
+            set_axis_position(E, line.e() * lengthsScaleFactor);
             anyFound = true;
         }
         else
diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp
index 364ba12ae..4c53048dc 100644
--- a/src/libslic3r/GCodeWriter.cpp
+++ b/src/libslic3r/GCodeWriter.cpp
@@ -19,12 +19,13 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config)
     this->config.apply(print_config, true);
     m_extrusion_axis = this->config.get_extrusion_axis();
     m_single_extruder_multi_material = print_config.single_extruder_multi_material.value;
-    m_max_acceleration = (print_config.gcode_flavor.value == gcfMarlin) ?
-        print_config.machine_max_acceleration_extruding.values.front() : 0;
+    m_max_acceleration = std::lrint((print_config.gcode_flavor.value == gcfMarlin) ?
+        print_config.machine_max_acceleration_extruding.values.front() : 0);
 }
 
-void GCodeWriter::set_extruders(const std::vector<unsigned int> &extruder_ids)
+void GCodeWriter::set_extruders(std::vector<unsigned int> extruder_ids)
 {
+    std::sort(extruder_ids.begin(), extruder_ids.end());
     m_extruders.clear();
     m_extruders.reserve(extruder_ids.size());
     for (unsigned int extruder_id : extruder_ids)
@@ -247,9 +248,9 @@ std::string GCodeWriter::toolchange_prefix() const
 std::string GCodeWriter::toolchange(unsigned int extruder_id)
 {
     // set the new extruder
-    auto it_extruder = std::lower_bound(m_extruders.begin(), m_extruders.end(), Extruder::key(extruder_id));
-    assert(it_extruder != m_extruders.end());
-    m_extruder = const_cast<Extruder*>(&*it_extruder);
+	auto it_extruder = Slic3r::lower_bound_by_predicate(m_extruders.begin(), m_extruders.end(), [extruder_id](const Extruder &e) { return e.id() < extruder_id; });
+    assert(it_extruder != m_extruders.end() && it_extruder->id() == extruder_id);
+    m_extruder = &*it_extruder;
 
     // return the toolchange command
     // if we are running a single-extruder setup, just set the extruder and return nothing
diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp
index 98abdda28..667c1ef95 100644
--- a/src/libslic3r/GCodeWriter.hpp
+++ b/src/libslic3r/GCodeWriter.hpp
@@ -33,7 +33,7 @@ public:
     std::string          extrusion_axis() const { return m_extrusion_axis; }
     void                 apply_print_config(const PrintConfig &print_config);
     // Extruders are expected to be sorted in an increasing order.
-    void                 set_extruders(const std::vector<unsigned int> &extruder_ids);
+    void                 set_extruders(std::vector<unsigned int> extruder_ids);
     const std::vector<Extruder>& extruders() const { return m_extruders; }
     std::vector<unsigned int> extruder_ids() const { 
         std::vector<unsigned int> out; 
@@ -74,7 +74,8 @@ public:
     Vec3d       get_position() const { return m_pos; }
 
 private:
-    std::vector<Extruder>    m_extruders;
+	// Extruders are sorted by their ID, so that binary search is possible.
+    std::vector<Extruder> m_extruders;
     std::string     m_extrusion_axis;
     bool            m_single_extruder_multi_material;
     Extruder*       m_extruder;
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 96a32680e..812180641 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -598,21 +598,6 @@ std::string Model::propose_export_file_name_and_path(const std::string &new_exte
     return boost::filesystem::path(this->propose_export_file_name_and_path()).replace_extension(new_extension).string();
 }
 
-std::vector<std::pair<double, DynamicPrintConfig>> Model::get_custom_tool_changes(double default_layer_height, size_t num_extruders) const
-{
-    std::vector<std::pair<double, DynamicPrintConfig>> custom_tool_changes;
-    for (const CustomGCode& custom_gcode : custom_gcode_per_print_z)
-        if (custom_gcode.gcode == ExtruderChangeCode) {
-            DynamicPrintConfig config;
-            // If extruder count in PrinterSettings was changed, use default (0) extruder for extruders, more than num_extruders
-            config.set_key_value("extruder", new ConfigOptionInt(custom_gcode.extruder > num_extruders ? 0 : custom_gcode.extruder));
-            // For correct extruders(tools) changing, we should decrease custom_gcode.height value by one default layer height
-            custom_tool_changes.push_back({ custom_gcode.print_z - default_layer_height, config });
-        }
-
-    return custom_tool_changes;
-}
-
 ModelObject::~ModelObject()
 {
     this->clear_volumes();
@@ -1856,6 +1841,19 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const
     return ret;
 }
 
+// Return pairs of <print_z, 1-based extruder ID> sorted by increasing print_z from custom_gcode_per_print_z.
+// print_z corresponds to the first layer printed with the new extruder.
+std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Model &model, size_t num_extruders)
+{
+    std::vector<std::pair<double, unsigned int>> custom_tool_changes;
+    for (const Model::CustomGCode &custom_gcode : model.custom_gcode_per_print_z)
+        if (custom_gcode.gcode == ExtruderChangeCode) {
+            // If extruder count in PrinterSettings was changed, use default (0) extruder for extruders, more than num_extruders
+            custom_tool_changes.emplace_back(custom_gcode.print_z, static_cast<unsigned int>(custom_gcode.extruder > num_extruders ? 1 : custom_gcode.extruder));
+        }
+    return custom_tool_changes;
+}
+
 // Test whether the two models contain the same number of ModelObjects with the same set of IDs
 // ordered in the same order. In that case it is not necessary to kill the background processing.
 bool model_object_list_equal(const Model &model_old, const Model &model_new)
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index 44f5049c9..26bb4cb92 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -838,9 +838,6 @@ public:
     // Propose an output path, replace extension. The new_extension shall contain the initial dot.
     std::string   propose_export_file_name_and_path(const std::string &new_extension) const;
 
-    // from custom_gcode_per_print_z get just tool_change codes
-    std::vector<std::pair<double, DynamicPrintConfig>> get_custom_tool_changes(double default_layer_height, size_t num_extruders) const;
-
 private:
 	explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); };
 	void assign_new_unique_ids_recursive();
@@ -857,6 +854,10 @@ private:
 #undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
 #undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE
 
+// Return pairs of <print_z, 1-based extruder ID> sorted by increasing print_z from custom_gcode_per_print_z.
+// print_z corresponds to the first layer printed with the new extruder.
+extern std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Model &model, size_t num_extruders);
+
 // Test whether the two models contain the same number of ModelObjects with the same set of IDs
 // ordered in the same order. In that case it is not necessary to kill the background processing.
 extern bool model_object_list_equal(const Model &model_old, const Model &model_new);
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
index 02d9da784..ed8817882 100644
--- a/src/libslic3r/Print.cpp
+++ b/src/libslic3r/Print.cpp
@@ -638,48 +638,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
                 m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr);
         }
 
-        // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs,
-        // considering custom_tool_change values
-        void assign(const t_layer_config_ranges &in, const std::vector<std::pair<double, DynamicPrintConfig>> &custom_tool_changes) {
-            m_ranges.clear();
-            m_ranges.reserve(in.size());
-            // Input ranges are sorted lexicographically. First range trims the other ranges.
-            coordf_t last_z = 0;
-            for (const std::pair<const t_layer_height_range, DynamicPrintConfig> &range : in)
-				if (range.first.second > last_z) {
-                    coordf_t min_z = std::max(range.first.first, 0.);
-                    if (min_z > last_z + EPSILON) {
-                        m_ranges.emplace_back(t_layer_height_range(last_z, min_z), nullptr);
-                        last_z = min_z;
-                    }
-                    if (range.first.second > last_z + EPSILON) {
-						const DynamicPrintConfig* cfg = &range.second;
-                        m_ranges.emplace_back(t_layer_height_range(last_z, range.first.second), cfg);
-                        last_z = range.first.second;
-                    }
-                }
-
-            // add ranges for extruder changes from custom_tool_changes
-            for (size_t i = 0; i < custom_tool_changes.size(); i++) {
-                const DynamicPrintConfig* cfg = &custom_tool_changes[i].second;
-                coordf_t cur_Z = custom_tool_changes[i].first;
-                coordf_t next_Z = i == custom_tool_changes.size()-1 ? DBL_MAX : custom_tool_changes[i+1].first;
-                if (cur_Z > last_z + EPSILON) {
-                    if (i==0)
-                        m_ranges.emplace_back(t_layer_height_range(last_z, cur_Z), nullptr);
-                    m_ranges.emplace_back(t_layer_height_range(cur_Z, next_Z), cfg);
-                }
-                else if (next_Z > last_z + EPSILON)
-                    m_ranges.emplace_back(t_layer_height_range(last_z, next_Z), cfg);
-            }
-
-            if (m_ranges.empty())
-                m_ranges.emplace_back(t_layer_height_range(0, DBL_MAX), nullptr);
-            else if (m_ranges.back().second == nullptr)
-                m_ranges.back().first.second = DBL_MAX;
-            else if (m_ranges.back().first.second != DBL_MAX)
-                m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr);
-        }
         const DynamicPrintConfig* config(const t_layer_height_range &range) const {
             auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), std::make_pair< t_layer_height_range, const DynamicPrintConfig*>(t_layer_height_range(range.first - EPSILON, range.second - EPSILON), nullptr));
             // #ys_FIXME_COLOR
@@ -733,17 +691,15 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
 		for (const ModelObject *model_object : m_model.objects)
 			model_object_status.emplace(model_object->id(), ModelObjectStatus::New);
     } else {
+        if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) {
+	        // If custom gcode per layer height was changed, we should stop background processing.
+            update_apply_status(this->invalidate_steps({ psWipeTower, psGCodeExport }));
+            m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
+        }
         if (model_object_list_equal(m_model, model)) {
             // The object list did not change.
 			for (const ModelObject *model_object : m_model.objects)
 				model_object_status.emplace(model_object->id(), ModelObjectStatus::Old);
-
-            // But if custom gcode per layer height was changed
-            if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) {
-                // we should stop background processing
-                update_apply_status(this->invalidate_step(psGCodeExport));
-                m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
-            }
         } else if (model_object_list_extended(m_model, model)) {
             // Add new objects. Their volumes and configs will be synchronized later.
             update_apply_status(this->invalidate_step(psGCodeExport));
@@ -835,9 +791,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
     for (PrintObject *print_object : m_objects)
         print_object_status.emplace(PrintObjectStatus(print_object));
 
-    std::vector<std::pair<double, DynamicPrintConfig>> custom_tool_changes = 
-        m_model.get_custom_tool_changes(m_default_object_config.layer_height, num_extruders);
-
     // 3) Synchronize ModelObjects & PrintObjects.
     for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) {
         ModelObject &model_object = *m_model.objects[idx_model_object];
@@ -845,9 +798,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
         assert(it_status != model_object_status.end());
         assert(it_status->status != ModelObjectStatus::Deleted);
 		const ModelObject& model_object_new = *model.objects[idx_model_object];
-        // ys_FIXME_COLOR
-		// const_cast<ModelObjectStatus&>(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges);
-        const_cast<ModelObjectStatus&>(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges, custom_tool_changes);
+		const_cast<ModelObjectStatus&>(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges);
         if (it_status->status == ModelObjectStatus::New)
             // PrintObject instances will be added in the next loop.
             continue;
@@ -1015,8 +966,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
         PrintRegionConfig  this_region_config;
         bool               this_region_config_set = false;
         for (PrintObject *print_object : m_objects) {
-            if(m_force_update_print_regions && !custom_tool_changes.empty())
-                goto print_object_end;
             const LayerRanges *layer_ranges;
             {
                 auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id()));
@@ -1963,8 +1912,8 @@ const WipeTowerData& Print::wipe_tower_data(size_t extruders_cnt, double first_l
     // If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default.
     if (! is_step_done(psWipeTower) && extruders_cnt !=0) {
 
-        float width = m_config.wipe_tower_width;
-        float brim_spacing = nozzle_diameter * 1.25f - first_layer_height * (1. - M_PI_4);
+        float width = float(m_config.wipe_tower_width);
+        float brim_spacing = float(nozzle_diameter * 1.25f - first_layer_height * (1. - M_PI_4));
 
         const_cast<Print*>(this)->m_wipe_tower_data.depth = (900.f/width) * float(extruders_cnt - 1);
         const_cast<Print*>(this)->m_wipe_tower_data.brim_width = 4.5f * brim_spacing;
@@ -1990,6 +1939,7 @@ void Print::_make_wipe_tower()
 
     // Let the ToolOrdering class know there will be initial priming extrusions at the start of the print.
     m_wipe_tower_data.tool_ordering = ToolOrdering(*this, (unsigned int)-1, true);
+
     if (! m_wipe_tower_data.tool_ordering.has_wipe_tower())
         // Don't generate any wipe tower.
         return;
@@ -2107,13 +2057,6 @@ void Print::_make_wipe_tower()
     m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges();
 }
 
-// Returns extruder this eec should be printed with, according to PrintRegion config
-int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion &region)
-{
-    return is_infill(fill.role()) ? std::max<int>(0, (is_solid_infill(fill.entities.front()->role()) ? region.config().solid_infill_extruder : region.config().infill_extruder) - 1) :
-                                    std::max<int>(region.config().perimeter_extruder.value - 1, 0);
-}
-
 // Generate a recommended G-code output file name based on the format template, default extension, and template parameters
 // (timestamps, object placeholders derived from the model, current placeholder prameters and print statistics.
 // Use the final print statistics if available, or just keep the print statistics placeholders if not available yet (before G-code is finalized).
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
index 95c7b656a..42f8d761e 100644
--- a/src/libslic3r/Print.hpp
+++ b/src/libslic3r/Print.hpp
@@ -15,6 +15,8 @@
 #include "GCode/ThumbnailData.hpp"
 #endif // ENABLE_THUMBNAIL_GENERATOR
 
+#include "libslic3r.h"
+
 namespace Slic3r {
 
 class Print;
@@ -50,7 +52,7 @@ public:
     // Average diameter of nozzles participating on extruding this region.
     coordf_t                    bridging_height_avg(const PrintConfig &print_config) const;
 
-    // Collect extruder indices used to print this region's object.
+    // Collect 0-based extruder indices used to print this region's object.
 	void                        collect_object_printing_extruders(std::vector<unsigned int> &object_extruders) const;
 	static void                 collect_object_printing_extruders(const PrintConfig &print_config, const PrintRegionConfig &region_config, std::vector<unsigned int> &object_extruders);
 
@@ -116,8 +118,21 @@ public:
     size_t total_layer_count() const { return this->layer_count() + this->support_layer_count(); }
     size_t layer_count() const { return m_layers.size(); }
     void clear_layers();
-    Layer* get_layer(int idx) { return m_layers[idx]; }
-    const Layer* get_layer(int idx) const { return m_layers[idx]; }
+    const Layer* 	get_layer(int idx) const { return m_layers[idx]; }
+    Layer* 			get_layer(int idx) 		 { return m_layers[idx]; }
+    // Get a layer exactly at print_z.
+    const Layer*	get_layer_at_printz(coordf_t print_z) const {
+    	auto it = Slic3r::lower_bound_by_predicate(m_layers.begin(), m_layers.end(), [print_z](const Layer *layer) { return layer->print_z < print_z; });
+		return (it == m_layers.end() || (*it)->print_z != print_z) ? nullptr : *it;
+	}
+    Layer*			get_layer_at_printz(coordf_t print_z) { return const_cast<Layer*>(std::as_const(*this).get_layer_at_printz(print_z)); }
+    // Get a layer approximately at print_z.
+    const Layer*	get_layer_at_printz(coordf_t print_z, coordf_t epsilon) const {
+        coordf_t limit = print_z + epsilon;
+    	auto it = Slic3r::lower_bound_by_predicate(m_layers.begin(), m_layers.end(), [limit](const Layer *layer) { return layer->print_z < limit; });
+		return (it == m_layers.end() || (*it)->print_z < print_z - epsilon) ? nullptr : *it;
+	}
+    Layer*			get_layer_at_printz(coordf_t print_z, coordf_t epsilon) { return const_cast<Layer*>(std::as_const(*this).get_layer_at_printz(print_z, epsilon)); }
 
     // print_z: top of the layer; slice_z: center of the layer.
     Layer* add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z);
@@ -345,6 +360,7 @@ public:
     const PrintConfig&          config() const { return m_config; }
     const PrintObjectConfig&    default_object_config() const { return m_default_object_config; }
     const PrintRegionConfig&    default_region_config() const { return m_default_region_config; }
+    //FIXME returning const vector to non-const PrintObject*, caller could modify PrintObjects!
     const PrintObjectPtrs&      objects() const { return m_objects; }
     PrintObject*                get_object(size_t idx) { return m_objects[idx]; }
     const PrintObject*          get_object(size_t idx) const { return m_objects[idx]; }
@@ -353,9 +369,6 @@ public:
     // If zero, then the print is empty and the print shall not be executed.
     unsigned int                num_object_instances() const;
 
-    // Returns extruder this eec should be printed with, according to PrintRegion config:
-    static int                  get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion &region);
-
     const ExtrusionEntityCollection& skirt() const { return m_skirt; }
     const ExtrusionEntityCollection& brim() const { return m_brim; }
 
@@ -370,9 +383,6 @@ public:
     // Accessed by SupportMaterial
     const PrintRegion*  get_region(size_t idx) const  { return m_regions[idx]; }
 
-    // force update of PrintRegions, when custom_tool_change is not empty and (Re)Slicing is started
-    void set_force_update_print_regions(bool force_update_print_regions) { m_force_update_print_regions = force_update_print_regions; }
-
 protected:
     // methods for handling regions
     PrintRegion*        get_region(size_t idx)        { return m_regions[idx]; }
@@ -415,9 +425,6 @@ private:
     // Estimated print time, filament consumed.
     PrintStatistics                         m_print_statistics;
 
-    // flag used
-    bool                                    m_force_update_print_regions = false;
-
     // To allow GCode to set the Print's GCodeExport step status.
     friend class GCode;
     // Allow PrintObject to access m_mutex and m_cancel_callback.
diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp
index bc3ec6c44..23c516a34 100644
--- a/src/libslic3r/Technologies.hpp
+++ b/src/libslic3r/Technologies.hpp
@@ -71,5 +71,8 @@
 // Enable a modified version of the toolbar textures where all the icons are separated by 1 pixel
 #define ENABLE_MODIFIED_TOOLBAR_TEXTURES (1 && ENABLE_2_2_0_BETA1)
 
+// Enable configurable paths export (fullpath or not) to 3mf and amf
+#define ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF (1 && ENABLE_2_2_0_BETA1)
+
 
 #endif // _technologies_h_
diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h
index afbf94fa3..d3e4992ce 100644
--- a/src/libslic3r/libslic3r.h
+++ b/src/libslic3r/libslic3r.h
@@ -158,6 +158,53 @@ inline std::unique_ptr<T> make_unique(Args&&... args) {
     return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
 }
 
+// Variant of std::lower_bound() with compare predicate, but without the key.
+// This variant is very useful in case that the T type is large or it does not even have a public constructor.
+template<class ForwardIt, class LowerThanKeyPredicate>
+ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_thank_key)
+{
+    ForwardIt it;
+    typename std::iterator_traits<ForwardIt>::difference_type count, step;
+    count = std::distance(first, last);
+ 
+    while (count > 0) {
+        it = first;
+        step = count / 2;
+        std::advance(it, step);
+        if (lower_thank_key(*it)) {
+            first = ++it;
+            count -= step + 1;
+        }
+        else
+            count = step;
+    }
+    return first;
+}
+
+// from https://en.cppreference.com/w/cpp/algorithm/lower_bound
+template<class ForwardIt, class T, class Compare=std::less<>>
+ForwardIt binary_find(ForwardIt first, ForwardIt last, const T& value, Compare comp={})
+{
+    // Note: BOTH type T and the type after ForwardIt is dereferenced 
+    // must be implicitly convertible to BOTH Type1 and Type2, used in Compare. 
+    // This is stricter than lower_bound requirement (see above)
+ 
+    first = std::lower_bound(first, last, value, comp);
+    return first != last && !comp(value, *first) ? first : last;
+}
+
+// from https://en.cppreference.com/w/cpp/algorithm/lower_bound
+template<class ForwardIt, class LowerThanKeyPredicate, class EqualToKeyPredicate>
+ForwardIt binary_find_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_thank_key, EqualToKeyPredicate equal_to_key)
+{
+    // Note: BOTH type T and the type after ForwardIt is dereferenced 
+    // must be implicitly convertible to BOTH Type1 and Type2, used in Compare. 
+    // This is stricter than lower_bound requirement (see above)
+ 
+    first = lower_bound_by_predicate(first, last, lower_thank_key);
+    return first != last && equal_to_key(*first) ? first : last;
+}
+
 template<typename T>
 static inline T sqr(T x)
 {
diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp
index 67b3d3a21..1339368bd 100644
--- a/src/libslic3r/pchheader.hpp
+++ b/src/libslic3r/pchheader.hpp
@@ -63,6 +63,7 @@
 #include <boost/bind.hpp>
 #include <boost/config.hpp>
 #include <boost/config/warning_disable.hpp>
+#include <boost/container/small_vector.hpp>
 #include <boost/date_time/local_time/local_time.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/filesystem.hpp>
diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp
index ecbca4228..c66c8efdd 100644
--- a/src/slic3r/GUI/3DBed.cpp
+++ b/src/slic3r/GUI/3DBed.cpp
@@ -341,24 +341,10 @@ void Bed3D::calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox)
         printf("Unable to create bed grid lines\n");
 }
 
-static const VendorProfile::PrinterModel* system_printer_model(const Preset &preset)
-{
-	const VendorProfile::PrinterModel *out = nullptr;
-	if (preset.vendor != nullptr) {
-		auto *printer_model = preset.config.opt<ConfigOptionString>("printer_model");
-		if (printer_model != nullptr && ! printer_model->value.empty()) {
-			auto it = std::find_if(preset.vendor->models.begin(), preset.vendor->models.end(), [printer_model](const VendorProfile::PrinterModel &pm) { return pm.id == printer_model->value; });
-			if (it != preset.vendor->models.end())
-				out = &(*it);
-		}
-	}
-	return out;
-}
-
 static std::string system_print_bed_model(const Preset &preset)
 {
 	std::string out;
-	const VendorProfile::PrinterModel *pm = system_printer_model(preset);
+	const VendorProfile::PrinterModel *pm = PresetUtils::system_printer_model(preset);
 	if (pm != nullptr && ! pm->bed_model.empty())
 		out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_model;
 	return out;
@@ -367,7 +353,7 @@ static std::string system_print_bed_model(const Preset &preset)
 static std::string system_print_bed_texture(const Preset &preset)
 {
 	std::string out;
-	const VendorProfile::PrinterModel *pm = system_printer_model(preset);
+	const VendorProfile::PrinterModel *pm = PresetUtils::system_printer_model(preset);
 	if (pm != nullptr && ! pm->bed_texture.empty())
 		out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_texture;
 	return out;
diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp
index bbfcabd36..f6b1719db 100644
--- a/src/slic3r/GUI/3DScene.cpp
+++ b/src/slic3r/GUI/3DScene.cpp
@@ -877,13 +877,10 @@ bool can_export_to_obj(const GLVolume& volume)
     if (!volume.is_active || !volume.is_extrusion_path)
         return false;
 
-    if (volume.indexed_vertex_array.triangle_indices.empty() && (std::min(volume.indexed_vertex_array.triangle_indices_size, volume.tverts_range.second - volume.tverts_range.first) == 0))
-        return false;
+    bool has_triangles = !volume.indexed_vertex_array.triangle_indices.empty() || (std::min(volume.indexed_vertex_array.triangle_indices_size, volume.tverts_range.second - volume.tverts_range.first) > 0);
+    bool has_quads = !volume.indexed_vertex_array.quad_indices.empty() || (std::min(volume.indexed_vertex_array.quad_indices_size, volume.qverts_range.second - volume.qverts_range.first) > 0);
 
-    if (volume.indexed_vertex_array.quad_indices.empty() && (std::min(volume.indexed_vertex_array.quad_indices_size, volume.qverts_range.second - volume.qverts_range.first) == 0))
-        return false;
-
-    return true;
+    return has_triangles || has_quads;
 }
 
 bool GLVolumeCollection::has_toolpaths_to_export() const
diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp
index eb0a7fca6..d277fc095 100644
--- a/src/slic3r/GUI/AppConfig.cpp
+++ b/src/slic3r/GUI/AppConfig.cpp
@@ -61,6 +61,11 @@ void AppConfig::set_defaults()
     if (get("preset_update").empty())
         set("preset_update", "1");
 
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+    if (get("export_sources_full_pathnames").empty())
+        set("export_sources_full_pathnames", "0");
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+
     // remove old 'use_legacy_opengl' parameter from this config, if present
     if (!get("use_legacy_opengl").empty())
         erase("", "use_legacy_opengl");
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
index 9d6a2a5ec..8af721f9d 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
@@ -95,14 +95,6 @@ void BackgroundSlicingProcess::process_fff()
     m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
 #endif // ENABLE_THUMBNAIL_GENERATOR
 
-    /* #ys_FIXME_no_exported_codes
-    if (m_fff_print->model().custom_gcode_per_print_z != GUI::wxGetApp().model().custom_gcode_per_print_z) {
-        GUI::wxGetApp().model().custom_gcode_per_print_z = m_fff_print->model().custom_gcode_per_print_z;
-        GUI::show_info(nullptr, _(L("To except of redundant tool manipulation, \n"
-                                    "Color change(s) for unused extruder(s) was(were) deleted")), _(L("Info")));
-    }
-    */
-
 	if (this->set_step_started(bspsGCodeFinalize)) {
 	    if (! m_export_path.empty()) {
 	    	//FIXME localize the messages
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
index a66dcf39c..984686e35 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
@@ -132,11 +132,6 @@ public:
     // This "finished" flag does not account for the final export of the output file (.gcode or zipped PNGs),
     // and it does not account for the OctoPrint scheduling.
     bool    finished() const { return m_print->finished(); }
-
-    void    set_force_update_print_regions(bool force_update_print_regions) {
-        if (m_fff_print)
-	        m_fff_print->set_force_update_print_regions(force_update_print_regions);
-	}
     
 private:
 	void 	thread_proc();
diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp
index f7cefe72b..7322a88d1 100644
--- a/src/slic3r/GUI/BitmapCache.cpp
+++ b/src/slic3r/GUI/BitmapCache.cpp
@@ -1,6 +1,7 @@
 #include "BitmapCache.hpp"
 
 #include "libslic3r/Utils.hpp"
+#include <boost/filesystem.hpp>
 
 #if ! defined(WIN32) && ! defined(__APPLE__)
 #define BROKEN_ALPHA
@@ -15,7 +16,7 @@
 #include "nanosvg/nanosvg.h"
 #define NANOSVGRAST_IMPLEMENTATION
 #include "nanosvg/nanosvgrast.h"
-#include "GUI_App.hpp"
+//#include "GUI_App.hpp"
 
 namespace Slic3r { namespace GUI {
 
@@ -226,7 +227,7 @@ wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned width,
 }
 
 wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_width, unsigned target_height, 
-    float scale /* = 1.0f */, const bool grayscale/* = false*/)
+    float scale /* = 1.0f */, const bool grayscale/* = false*/, const bool dark_mode/* = false*/)
 {
     std::string bitmap_key = bitmap_name + ( target_height !=0 ? 
                                            "-h" + std::to_string(target_height) : 
@@ -234,16 +235,45 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_
                                          + (scale != 1.0f ? "-s" + std::to_string(scale) : "")
                                          + (grayscale ? "-gs" : "");
 
-    target_height != 0 ? target_height *= scale : target_width *= scale;
+    /* For the Dark mode of any platform, we should draw icons in respect to OS background
+     * Note: All standard(regular) icons are collected in "icons" folder,
+     *       SVG-icons, which have "Dark mode" variant, are collected in "icons/white" folder
+     */
+    std::string folder;
+    if (dark_mode)
+    {
+#ifdef __WXMSW__
+        folder = "white\\";
+#else
+        folder = "white/";
+#endif
+        auto it = m_map.find(folder + bitmap_key);
+        if (it != m_map.end())
+            return it->second;
+        else {
+            it = m_map.find(bitmap_key);
+            if (it != m_map.end())
+                return it->second;
+        }
 
-    auto it = m_map.find(bitmap_key);
-    if (it != m_map.end())
-        return it->second;
+        if (!boost::filesystem::exists(Slic3r::var(folder + bitmap_name + ".svg")))
+            folder.clear();
+        else
+            bitmap_key = folder + bitmap_key;
+    }
+    else 
+    {
+        auto it = m_map.find(bitmap_key);
+        if (it != m_map.end())
+            return it->second;
+    }
 
-    NSVGimage *image = ::nsvgParseFromFile(Slic3r::var(bitmap_name + ".svg").c_str(), "px", 96.0f);
+    NSVGimage *image = ::nsvgParseFromFile(Slic3r::var(folder + bitmap_name + ".svg").c_str(), "px", 96.0f);
     if (image == nullptr)
         return nullptr;
 
+    target_height != 0 ? target_height *= scale : target_width *= scale;
+
     float svg_scale = target_height != 0 ? 
                   (float)target_height / image->height  : target_width != 0 ?
                   (float)target_width / image->width    : 1;
diff --git a/src/slic3r/GUI/BitmapCache.hpp b/src/slic3r/GUI/BitmapCache.hpp
index ce77057c8..041e7d892 100644
--- a/src/slic3r/GUI/BitmapCache.hpp
+++ b/src/slic3r/GUI/BitmapCache.hpp
@@ -34,7 +34,7 @@ public:
 	// Load png from resources/icons. bitmap_key is given without the .png suffix. Bitmap will be rescaled to provided height/width if nonzero.
     wxBitmap* 		load_png(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false);
 	// Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width.
-    wxBitmap* 		load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, float scale = 1.0f, const bool grayscale = false);
+    wxBitmap* 		load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, float scale = 1.0f, const bool grayscale = false, const bool dark_mode = false);
 
 	static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency);
 	static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); }
diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp
index 265e7f203..add680a40 100644
--- a/src/slic3r/GUI/ConfigWizard.cpp
+++ b/src/slic3r/GUI/ConfigWizard.cpp
@@ -350,6 +350,21 @@ bool PrinterPicker::any_selected() const
     return false;
 }
 
+std::set<std::string> PrinterPicker::get_selected_models() const 
+{
+    std::set<std::string> ret_set;
+
+    for (const auto& cb : cboxes)
+        if (cb->GetValue())
+            ret_set.emplace(cb->model);
+
+    for (const auto& cb : cboxes_alt)
+        if (cb->GetValue())
+            ret_set.emplace(cb->model);
+
+    return ret_set;
+}
+
 void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked)
 {
     PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked);
@@ -500,6 +515,19 @@ bool PagePrinters::any_selected() const
     return false;
 }
 
+std::set<std::string> PagePrinters::get_selected_models()
+{
+    std::set<std::string> ret_set;
+
+    for (const auto *picker : printer_pickers)
+    {
+        std::set<std::string> tmp_models = picker->get_selected_models();
+        ret_set.insert(tmp_models.begin(), tmp_models.end());
+    }
+
+    return ret_set;
+}
+
 void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason)
 {
     if (technology == T_FFF
@@ -765,6 +793,23 @@ PageUpdate::PageUpdate(ConfigWizard *parent)
     box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); });
 }
 
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent)
+    : ConfigWizardPage(parent, _(L("Reload from disk")), _(L("Reload from disk")))
+    , full_pathnames(false)
+{
+    auto* box_pathnames = new wxCheckBox(this, wxID_ANY, _(L("Export full pathnames of models and parts sources into 3mf and amf files")));
+    box_pathnames->SetValue(wxGetApp().app_config->get("export_sources_full_pathnames") == "1");
+    append(box_pathnames);
+    append_text(_(L(
+        "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n"
+        "If not enabled, the Reload from disk command will ask to select each file using an open file dialog."
+    )));
+
+    box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); });
+}
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+
 PageMode::PageMode(ConfigWizard *parent)
     : ConfigWizardPage(parent, _(L("View mode")), _(L("View mode")))
 {
@@ -1356,6 +1401,9 @@ void ConfigWizard::priv::load_pages()
     btn_finish->Enable(any_fff_selected || any_sla_selected);
 
     index->add_page(page_update);
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+    index->add_page(page_reload_from_disk);
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
     index->add_page(page_mode);
 
     index->go_to(former_active);   // Will restore the active item/page if possible
@@ -1580,6 +1628,10 @@ void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPicker
                 preset.is_visible = evt.enable;
             }
         }
+
+        // if at list one printer is selected but there in no one selected material,
+        // select materials which is default for selected printer(s)
+        select_default_materials_if_needed(pair.second.vendor_profile, page->technology, evt.model_id);
     }
 
     if (page->technology & T_FFF) {
@@ -1589,6 +1641,57 @@ void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPicker
     }
 }
 
+void ConfigWizard::priv::select_default_materials_for_printer_model(const std::vector<VendorProfile::PrinterModel>& models, Technology technology, const std::string& model_id)
+{
+    PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials;
+
+    auto it = std::find_if(models.begin(), models.end(), [model_id](VendorProfile::PrinterModel model) {return model_id == model.id; });
+    if (it != models.end())
+        for (const std::string& material : it->default_materials)
+            appconfig_new.set(page_materials->materials->appconfig_section(), material, "1");
+}
+
+void ConfigWizard::priv::select_default_materials_if_needed(VendorProfile* vendor_profile, Technology technology, const std::string& model_id)
+{
+    if ((technology & T_FFF && !any_fff_selected) ||
+        (technology & T_SLA && !any_sla_selected) ||
+        check_materials_in_config(technology, false))
+        return;
+
+    select_default_materials_for_printer_model(vendor_profile->models, technology, model_id);
+}
+
+void ConfigWizard::priv::selected_default_materials(Technology technology)
+{
+    auto select_default_materials_for_printer_page = [this](PagePrinters * page_printers, Technology technology)
+    {
+        std::set<std::string>   selected_models = page_printers->get_selected_models();
+        const std::string       vendor_id       = page_printers->get_vendor_id();
+
+        for (auto& pair : bundles)
+        {
+            if (pair.first != vendor_id)
+                continue;
+
+            for (const std::string& model_id : selected_models)
+                select_default_materials_for_printer_model(pair.second.vendor_profile->models, technology, model_id);
+        }
+    };
+
+    PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla;
+    select_default_materials_for_printer_page(page_printers, technology);
+
+    for (const auto& printer : pages_3rdparty) 
+    {
+        page_printers = technology & T_FFF ? printer.second.first : printer.second.second;
+        if (page_printers)
+            select_default_materials_for_printer_page(page_printers, technology);
+    }
+
+    update_materials(technology);
+    (technology& T_FFF ? page_filaments : page_sla_materials)->reload_presets();
+}
+
 void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install)
 {
     auto it = pages_3rdparty.find(vendor->id);
@@ -1625,7 +1728,7 @@ bool ConfigWizard::priv::on_bnt_finish()
     return check_materials_in_config(T_ANY);
 }
 
-bool ConfigWizard::priv::check_materials_in_config(Technology technology)
+bool ConfigWizard::priv::check_materials_in_config(Technology technology, bool show_info_msg)
 {
     const auto exist_preset = [this](const std::string& section, const Materials& materials)
     {
@@ -1640,15 +1743,32 @@ bool ConfigWizard::priv::check_materials_in_config(Technology technology)
         return false;
     };
 
+    const auto ask_and_selected_default_materials = [this](wxString message, Technology technology)
+    {
+        wxMessageDialog msg(q, message, _(L("Notice")), wxYES_NO);
+        if (msg.ShowModal() == wxID_YES)
+            selected_default_materials(technology);
+    };
+
     if (any_fff_selected && technology & T_FFF && !exist_preset(AppConfig::SECTION_FILAMENTS, filaments))
     {
-        show_info(q, _(L("You have to select at least one filament for selected printers")), "");
+        if (show_info_msg)
+        {
+            wxString message = _(L("You have to select at least one filament for selected printers")) + "\n\n\t" +
+                               _(L("Do you want to automatic select default filaments?"));
+            ask_and_selected_default_materials(message, T_FFF);
+        }
         return false;
     }
 
     if (any_sla_selected && technology & T_SLA && !exist_preset(AppConfig::SECTION_MATERIALS, sla_materials))
     {
-        show_info(q, _(L("You have to select at least one material for selected printers")), "");
+        if (show_info_msg)
+        {
+            wxString message = _(L("You have to select at least one material for selected printers")) + "\n\n\t" +
+                               _(L("Do you want to automatic select default materials?"));
+            ask_and_selected_default_materials(message, T_SLA);
+        }
         return false;
     }
 
@@ -1730,6 +1850,11 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
     }
     app_config->set("version_check", page_update->version_check ? "1" : "0");
     app_config->set("preset_update", page_update->preset_update ? "1" : "0");
+
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+    app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0");
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+
     page_mode->serialize_mode(app_config);
 
     std::string preferred_model;
@@ -1885,6 +2010,9 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
 
     p->add_page(p->page_custom   = new PageCustom(this));
     p->add_page(p->page_update   = new PageUpdate(this));
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+    p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this));
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
     p->add_page(p->page_mode     = new PageMode(this));
     p->add_page(p->page_firmware = new PageFirmware(this));
     p->add_page(p->page_bed      = new PageBedShape(this));
diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp
index a90832a3f..9c14633c9 100644
--- a/src/slic3r/GUI/ConfigWizard_private.hpp
+++ b/src/slic3r/GUI/ConfigWizard_private.hpp
@@ -149,6 +149,7 @@ struct PrinterPicker: wxPanel
     void select_all(bool select, bool alternates = false);
     void select_one(size_t i, bool select);
     bool any_selected() const;
+    std::set<std::string> get_selected_models() const ;
 
     int get_width() const { return width; }
     const std::vector<int>& get_button_indexes() { return m_button_indexes; }
@@ -215,6 +216,9 @@ struct PagePrinters: ConfigWizardPage
     void select_all(bool select, bool alternates = false);
     int get_width() const;
     bool any_selected() const;
+    std::set<std::string> get_selected_models();
+
+    std::string get_vendor_id() const { return printer_pickers.empty() ? "" : printer_pickers[0]->vendor_id; }
 
     virtual void set_run_reason(ConfigWizard::RunReason run_reason) override;
 };
@@ -301,6 +305,17 @@ struct PageUpdate: ConfigWizardPage
     PageUpdate(ConfigWizard *parent);
 };
 
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+struct PageReloadFromDisk : ConfigWizardPage
+{
+    bool full_pathnames;
+
+    PageReloadFromDisk(ConfigWizard* parent);
+};
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+
 struct PageMode: ConfigWizardPage
 {
     wxRadioButton *radio_simple;
@@ -455,6 +470,11 @@ struct ConfigWizard::priv
     PageMaterials    *page_sla_materials = nullptr;
     PageCustom       *page_custom = nullptr;
     PageUpdate       *page_update = nullptr;
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+    PageReloadFromDisk *page_reload_from_disk = nullptr;
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     PageMode         *page_mode = nullptr;
     PageVendors      *page_vendors = nullptr;
     Pages3rdparty     pages_3rdparty;
@@ -487,10 +507,17 @@ struct ConfigWizard::priv
 
     void on_custom_setup();
     void on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt);
+    void select_default_materials_for_printer_model(const std::vector<VendorProfile::PrinterModel> &models,
+                                                    Technology                                      technology,
+                                                    const std::string &                             model_id);
+    void select_default_materials_if_needed(VendorProfile*     vendor_profile,
+                                            Technology         technology,
+                                            const std::string &model_id);
+    void selected_default_materials(Technology technology);
     void on_3rdparty_install(const VendorProfile *vendor, bool install);
 
     bool on_bnt_finish();
-    bool check_materials_in_config(Technology technology);
+    bool check_materials_in_config(Technology technology, bool show_info_msg = true);
     void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
     // #ys_FIXME_alise
     void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
index 959d7d3ed..abcb39c14 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
@@ -886,7 +886,7 @@ void GLGizmosManager::do_render_overlay() const
 #else
     float du = (float)(tex_width - 1) / (3.0f * (float)tex_width); // 3 is the number of possible states if the icons
 #endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
-    float dv = (float)(tex_height - 1) / (float)(selectable_idxs.size() * tex_height);
+    float dv = (float)(tex_height - 1) / (float)(m_gizmos.size() * tex_height);
 
     // tiles in the texture are spaced by 1 pixel
     float u_offset = 1.0f / (float)tex_width;
diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp
index 38432d0d3..8485c4b27 100644
--- a/src/slic3r/GUI/Mouse3DController.cpp
+++ b/src/slic3r/GUI/Mouse3DController.cpp
@@ -631,13 +631,16 @@ bool Mouse3DController::connect_device()
 
     if (m_device != nullptr)
     {
-        std::vector<wchar_t> manufacturer(1024, 0);
-        hid_get_manufacturer_string(m_device, manufacturer.data(), 1024);
-        m_device_str = boost::nowide::narrow(manufacturer.data());
+        wchar_t buffer[1024];
+        hid_get_manufacturer_string(m_device, buffer, 1024);
+        m_device_str = boost::nowide::narrow(buffer);
+        // #3479 seems to show that sometimes an extra whitespace is added, so we remove it
+        boost::algorithm::trim(m_device_str);
 
-        std::vector<wchar_t> product(1024, 0);
-        hid_get_product_string(m_device, product.data(), 1024);
-        m_device_str += "/" + boost::nowide::narrow(product.data());
+        hid_get_product_string(m_device, buffer, 1024);
+        m_device_str += "/" + boost::nowide::narrow(buffer);
+        // #3479 seems to show that sometimes an extra whitespace is added, so we remove it
+        boost::algorithm::trim(m_device_str);
 
         BOOST_LOG_TRIVIAL(info) << "Connected 3DConnexion device:";
         BOOST_LOG_TRIVIAL(info) << "Manufacturer/product: " << m_device_str;
diff --git a/src/slic3r/GUI/Mouse3DController.hpp b/src/slic3r/GUI/Mouse3DController.hpp
index 3598d7df7..1cddf254b 100644
--- a/src/slic3r/GUI/Mouse3DController.hpp
+++ b/src/slic3r/GUI/Mouse3DController.hpp
@@ -68,7 +68,7 @@ class Mouse3DController
         CustomParameters<double> m_translation_params;
         CustomParameters<float> m_rotation_params;
 #if ENABLE_3DCONNEXION_Y_AS_ZOOM
-        CustomParameters<float> m_zoom_params;
+        CustomParameters<double> m_zoom_params;
 #endif // ENABLE_3DCONNEXION_Y_AS_ZOOM
 
         // When the 3Dconnexion driver is running the system gets, by default, mouse wheel events when rotations around the X axis are detected.
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 2c0742559..dc720ddae 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -3022,7 +3022,6 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
     this->update_print_volume_state();
     // Apply new config to the possibly running background task.
     bool               was_running = this->background_process.running();
-    this->background_process.set_force_update_print_regions(force_validation);
     Print::ApplyStatus invalidated = this->background_process.apply(this->q->model(), wxGetApp().preset_bundle->full_config());
 
     // Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile.
@@ -4909,7 +4908,12 @@ void Plater::export_amf()
     wxBusyCursor wait;
     bool export_config = true;
     DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+    bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1";
+    if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames)) {
+#else
     if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) {
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
         // Success
         p->statusbar()->set_status_text(wxString::Format(_(L("AMF file exported to %s")), path));
     } else {
@@ -4938,6 +4942,16 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
     DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
     const std::string path_u8 = into_u8(path);
     wxBusyCursor wait;
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+    bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1";
+#if ENABLE_THUMBNAIL_GENERATOR
+    ThumbnailData thumbnail_data;
+    p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true);
+    if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data)) {
+#else
+    if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames)) {
+#endif // ENABLE_THUMBNAIL_GENERATOR
+#else
 #if ENABLE_THUMBNAIL_GENERATOR
     ThumbnailData thumbnail_data;
     p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true);
@@ -4945,6 +4959,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
 #else
     if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) {
 #endif // ENABLE_THUMBNAIL_GENERATOR
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
         // Success
         p->statusbar()->set_status_text(wxString::Format(_(L("3MF file exported to %s")), path));
         p->set_project_filename(path);
diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp
index 827d1f4e0..295c1a6ec 100644
--- a/src/slic3r/GUI/Preferences.cpp
+++ b/src/slic3r/GUI/Preferences.cpp
@@ -73,6 +73,16 @@ void PreferencesDialog::build()
 	option = Option (def, "version_check");
 	m_optgroup->append_single_option_line(option);
 
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+	// Please keep in sync with ConfigWizard
+	def.label = L("Export sources full pathnames to 3mf and amf");
+	def.type = coBool;
+	def.tooltip = L("If enabled, allows the Reload from disk command to automatically find and load the files when invoked.");
+	def.set_default_value(new ConfigOptionBool(app_config->get("export_sources_full_pathnames") == "1"));
+	option = Option(def, "export_sources_full_pathnames");
+	m_optgroup->append_single_option_line(option);
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+
 	// Please keep in sync with ConfigWizard
 	def.label = L("Update built-in Presets automatically");
 	def.type = coBool;
diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp
index c20a5bb28..b89000891 100644
--- a/src/slic3r/GUI/Preset.cpp
+++ b/src/slic3r/GUI/Preset.cpp
@@ -184,6 +184,14 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
             } else {
                 BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Malformed variants field: `%2%`") % id % variants_field;
             }
+            auto default_materials_field = section.second.get<std::string>("default_materials", "");
+            if (default_materials_field.empty())
+            	default_materials_field = section.second.get<std::string>("default_filaments", "");
+            if (Slic3r::unescape_strings_cstyle(default_materials_field, model.default_materials)) {
+            	Slic3r::sort_remove_duplicates(model.default_materials);
+            } else {
+                BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Malformed default_materials field: `%2%`") % id % default_materials_field;
+            }
             model.bed_model   = section.second.get<std::string>("bed_model", "");
             model.bed_texture = section.second.get<std::string>("bed_texture", "");
             if (! model.id.empty() && ! model.variants.empty())
@@ -1502,4 +1510,20 @@ const Preset* PrinterPresetCollection::find_by_model_id(const std::string &model
     return it != cend() ? &*it : nullptr;
 }
 
+namespace PresetUtils {
+	const VendorProfile::PrinterModel* system_printer_model(const Preset &preset)
+	{
+		const VendorProfile::PrinterModel *out = nullptr;
+		if (preset.vendor != nullptr) {
+			auto *printer_model = preset.config.opt<ConfigOptionString>("printer_model");
+			if (printer_model != nullptr && ! printer_model->value.empty()) {
+				auto it = std::find_if(preset.vendor->models.begin(), preset.vendor->models.end(), [printer_model](const VendorProfile::PrinterModel &pm) { return pm.id == printer_model->value; });
+				if (it != preset.vendor->models.end())
+					out = &(*it);
+			}
+		}
+		return out;
+	}
+} // namespace PresetUtils
+
 } // namespace Slic3r
diff --git a/src/slic3r/GUI/Preset.hpp b/src/slic3r/GUI/Preset.hpp
index 19d07d007..0a26f90cc 100644
--- a/src/slic3r/GUI/Preset.hpp
+++ b/src/slic3r/GUI/Preset.hpp
@@ -61,6 +61,7 @@ public:
         PrinterTechnology           technology;
         std::string                 family;
         std::vector<PrinterVariant> variants;
+        std::vector<std::string>	default_materials;
         // Vendor & Printer Model specific print bed model & texture.
         std::string 			 	bed_model;
         std::string 				bed_texture;
@@ -563,6 +564,11 @@ public:
     const Preset*   find_by_model_id(const std::string &model_id) const;
 };
 
+namespace PresetUtils {
+	// PrinterModel of a system profile, from which this preset is derived, or null if it is not derived from a system profile.
+	const VendorProfile::PrinterModel* system_printer_model(const Preset &preset);
+} // namespace PresetUtils
+
 } // namespace Slic3r
 
 #endif /* slic3r_Preset_hpp_ */
diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp
index 43af0ab7f..3872db3fb 100644
--- a/src/slic3r/GUI/wxExtensions.cpp
+++ b/src/slic3r/GUI/wxExtensions.cpp
@@ -13,9 +13,7 @@
 #include <wx/numformatter.h>
 #include <wx/colordlg.h>
 
-#include <boost/filesystem.hpp>
 #include <boost/algorithm/string/replace.hpp>
-#include <boost/nowide/cstdio.hpp>
 
 #include "BitmapCache.hpp"
 #include "GUI.hpp"
@@ -426,28 +424,6 @@ static float get_svg_scale_factor(wxWindow *win)
 #endif
 }
 
-// in the Dark mode of any platform, we should draw icons in respect to OS background
-static std::string icon_name_respected_to_mode(const std::string& bmp_name_in)
-{
-#ifdef __WXMSW__
-    const std::string folder = "white\\";
-#else
-    const std::string folder = "white/";
-#endif
-    std::string bmp_name;
-    if (Slic3r::GUI::wxGetApp().dark_mode()) {
-     	bmp_name = folder + bmp_name_in;
-	    boost::replace_last(bmp_name, ".png", "");
-        if (! boost::filesystem::exists(Slic3r::var(bmp_name + ".svg")))
-            bmp_name.clear();
-	}
-	if (bmp_name.empty()) {
-		bmp_name = bmp_name_in;
-		boost::replace_last(bmp_name, ".png", "");
-	}
-    return bmp_name;
-}
-
 // If an icon has horizontal orientation (width > height) call this function with is_horizontal = true
 wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in, 
     const int px_cnt/* = 16*/, const bool is_horizontal /* = false*/, const bool grayscale/* = false*/)
@@ -474,13 +450,13 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in,
 
     scale_base = (unsigned int)(em_unit(win) * px_cnt * 0.1f + 0.5f);
 
-//    std::string bmp_name = bmp_name_in;
-//    boost::replace_last(bmp_name, ".png", "");
+    std::string bmp_name = bmp_name_in;
+    boost::replace_last(bmp_name, ".png", "");
 
-    std::string bmp_name = icon_name_respected_to_mode(bmp_name_in);
+//    std::string bmp_name = icon_name_respected_to_mode(bmp_name_in);
 
     // Try loading an SVG first, then PNG if SVG is not found:
-    wxBitmap *bmp = cache.load_svg(bmp_name, width, height, scale_factor, grayscale);
+    wxBitmap *bmp = cache.load_svg(bmp_name, width, height, scale_factor, grayscale, Slic3r::GUI::wxGetApp().dark_mode());
     if (bmp == nullptr) {
         bmp = cache.load_png(bmp_name, width, height, grayscale);
     }
diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp
index b7eb12e02..28f8b54d5 100644
--- a/src/slic3r/Utils/FixModelByWin10.cpp
+++ b/src/slic3r/Utils/FixModelByWin10.cpp
@@ -363,10 +363,17 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx)
 				ModelObject *model_object = model.add_object();
 				model_object->add_volume(*volumes[ivolume]);
 				model_object->add_instance();
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+				if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false)) {
+					boost::filesystem::remove(path_src);
+					throw std::runtime_error(L("Export of a temporary 3mf file failed"));
+				}
+#else
 				if (! Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr)) {
 					boost::filesystem::remove(path_src);
 					throw std::runtime_error(L("Export of a temporary 3mf file failed"));
 				}
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
 				model.clear_objects();
 				model.clear_materials();
 				boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
diff --git a/tests/libslic3r/test_3mf.cpp b/tests/libslic3r/test_3mf.cpp
index 5215e2ebd..5f1c74c36 100644
--- a/tests/libslic3r/test_3mf.cpp
+++ b/tests/libslic3r/test_3mf.cpp
@@ -51,7 +51,11 @@ SCENARIO("Export+Import geometry to/from 3mf file cycle", "[3mf]") {
         WHEN("model is saved+loaded to/from 3mf file") {
             // save the model to 3mf file
             std::string test_file = std::string(TEST_DATA_DIR) + "/test_3mf/prusa.3mf";
+#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
+            store_3mf(test_file.c_str(), &src_model, nullptr, false);
+#else
             store_3mf(test_file.c_str(), &src_model, nullptr);
+#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
 
             // load back the model from the 3mf file
             Model dst_model;