diff --git a/CMakeLists.txt b/CMakeLists.txt
index 72fd87d22..6c6afe671 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -506,6 +506,12 @@ endif ()
 
 # Find the Cereal serialization library
 find_package(cereal REQUIRED)
+add_library(libcereal INTERFACE)
+if (NOT TARGET cereal::cereal)
+    target_link_libraries(libcereal INTERFACE cereal)
+else()
+    target_link_libraries(libcereal INTERFACE cereal::cereal)
+endif()
 
 # l10n
 set(L10N_DIR "${SLIC3R_RESOURCES_DIR}/localization")
diff --git a/resources/profiles/Anycubic.ini b/resources/profiles/Anycubic.ini
index 053aecbd5..821d87462 100644
--- a/resources/profiles/Anycubic.ini
+++ b/resources/profiles/Anycubic.ini
@@ -64,6 +64,13 @@ technology = FFF
 family = PREDATOR
 default_materials = Generic PLA @PREDATOR; Generic PETG @PREDATOR; Generic ABS @PREDATOR
 
+[printer_model:PHOTON MONO X]
+name = Photon Mono X
+variants = default
+technology = SLA
+family = PHOTON MONO
+default_materials = Generic Blue Resin @MONO 0.05
+
 # All presets starting with asterisk, for example *common*, are intermediate and they will
 # not make it into the user interface.
 
@@ -1898,3 +1905,94 @@ default_print_profile = 0.24mm 0.8 nozzle DETAILED QUALITY @PREDATOR
 #########################################
 ########## end printer presets ##########
 #########################################
+
+#########################################
+########## SLA printer presets ##########
+#########################################
+
+
+[sla_print:*common print ANYCUBIC SLA*]
+compatible_printers_condition = family=="PHOTON MONO"
+layer_height = 0.05
+output_filename_format = [input_filename_base].pwmx
+pad_edge_radius = 0.5
+pad_enable = 0
+pad_max_merge_distance = 50
+pad_wall_height = 0
+pad_wall_thickness = 1
+pad_wall_slope = 45
+faded_layers = 8
+slice_closing_radius = 0.005
+support_base_diameter = 3
+support_base_height = 1
+support_critical_angle = 45
+support_density_at_45 = 250
+support_density_at_horizontal = 500
+support_head_front_diameter = 0.4
+support_head_penetration = 0.4
+support_head_width = 3
+support_max_bridge_length = 10
+support_minimal_z = 0
+support_object_elevation = 5
+support_pillar_diameter = 1
+support_pillar_connection_mode = zigzag
+support_pillar_widening_factor = 0
+supports_enable = 1
+support_small_pillar_diameter_percent = 60%
+
+[sla_print:0.05 Normal @ANYCUBIC]
+inherits = *common print ANYCUBIC SLA*
+layer_height = 0.05
+
+########### Materials
+
+[sla_material:*common ANYCUBIC SLA*]
+compatible_printers_condition = printer_notes=~/.*PHOTONMONOX.*/
+compatible_prints_condition = layer_height == 0.05
+exposure_time = 7
+initial_exposure_time = 40
+initial_layer_height = 0.05
+material_correction = 1,1,1
+material_notes = LIFT_DISTANCE=8.0\nLIFT_SPEED=2.5\nRETRACT_SPEED=3.0\nBOTTOM_LIFT_SPEED=2.0\nBOTTOM_LIFT_DISTANCE=9.0\nDELAY_BEFORE_EXPOSURE=0.5
+
+[sla_material:*common 0.05 ANYCUBIC SLA*]
+inherits = *common ANYCUBIC SLA*
+
+[sla_material:Generic Blue Resin @MONO 0.05]
+inherits = *common 0.05 ANYCUBIC SLA*
+exposure_time = 2.5
+initial_exposure_time = 40
+material_type = Tough
+material_vendor = Generic
+material_colour = #6080EC
+compatible_printers_condition = printer_notes=~/.*PHOTONMONOX.*/
+
+########## Printers
+
+[printer:Anycubic Photon Mono X]
+printer_technology = SLA
+printer_model = PHOTON MONO X
+printer_variant = default
+default_sla_material_profile = Generic Blue Resin @MONO 0.05
+default_sla_print_profile = 0.05 Normal @ANYCUBIC
+thumbnails = 224x168
+sla_archive_format = pwmx
+bed_shape = 1.48x1.02,193.48x1.02,193.48x121.02,1.48x121.02
+display_height = 120
+display_orientation = landscape
+display_mirror_x = 1
+display_mirror_y = 0
+display_pixels_x = 3840
+display_pixels_y = 2400
+display_width = 192
+max_print_height = 245
+elefant_foot_compensation = 0.2
+elefant_foot_min_width = 0.2
+min_exposure_time = 1
+max_exposure_time = 120
+min_initial_exposure_time = 1
+max_initial_exposure_time = 300
+printer_correction = 1,1,1
+gamma_correction = 1
+area_fill = 45
+printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.'\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_PHOTONMONOX\n
diff --git a/resources/profiles/Anycubic/PHOTON MONO X_thumbnail.png b/resources/profiles/Anycubic/PHOTON MONO X_thumbnail.png
new file mode 100644
index 000000000..70ad47b63
Binary files /dev/null and b/resources/profiles/Anycubic/PHOTON MONO X_thumbnail.png differ
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ab1c7b964..61a2a90d8 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -126,7 +126,7 @@ if (NOT WIN32 AND NOT APPLE)
     set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer")
 endif ()
 
-target_link_libraries(PrusaSlicer libslic3r cereal)
+target_link_libraries(PrusaSlicer libslic3r libcereal)
 if (APPLE)
 #    add_compile_options(-stdlib=libc++)
 #    add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE)
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index d4c8d7edc..5e8d681f1 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -94,10 +94,14 @@ set(SLIC3R_SOURCES
     Format/objparser.hpp
     Format/STL.cpp
     Format/STL.hpp
+    Format/SLAArchive.hpp
+    Format/SLAArchive.cpp
     Format/SL1.hpp
     Format/SL1.cpp
     Format/SL1_SVG.hpp
     Format/SL1_SVG.cpp
+    Format/pwmx.hpp
+    Format/pwmx.cpp
     GCode/ThumbnailData.cpp
     GCode/ThumbnailData.hpp
     GCode/Thumbnails.cpp
@@ -354,7 +358,7 @@ find_package(JPEG REQUIRED)
 target_link_libraries(libslic3r
     libnest2d
     admesh
-    cereal
+    libcereal
     libigl
     miniz
     boost_libs
diff --git a/src/libslic3r/Execution/Execution.hpp b/src/libslic3r/Execution/Execution.hpp
index 62e49cfeb..dcfd86bde 100644
--- a/src/libslic3r/Execution/Execution.hpp
+++ b/src/libslic3r/Execution/Execution.hpp
@@ -5,6 +5,7 @@
 #include <utility>
 #include <cstddef>
 #include <iterator>
+#include <algorithm>
 
 #include "libslic3r/libslic3r.h"
 
@@ -44,7 +45,8 @@ size_t max_concurrency(const EP &ep)
 template<class EP, class It, class Fn, class = ExecutionPolicyOnly<EP>>
 void for_each(const EP &ep, It from, It to, Fn &&fn, size_t granularity = 1)
 {
-    AsTraits<EP>::for_each(ep, from, to, std::forward<Fn>(fn), granularity);
+    AsTraits<EP>::for_each(ep, from, to, std::forward<Fn>(fn),
+                           std::max(granularity, size_t(1)));
 }
 
 // A reduce operation with the execution policy passed as argument.
@@ -68,7 +70,7 @@ T reduce(const EP & ep,
     return AsTraits<EP>::reduce(ep, from, to, init,
                                 std::forward<MergeFn>(mergefn),
                                 std::forward<AccessFn>(accessfn),
-                                granularity);
+                                std::max(granularity, size_t(1)));
 }
 
 // An overload of reduce method to be used with iterators as 'from' and 'to'
@@ -87,7 +89,7 @@ T reduce(const EP &ep,
 {
     return reduce(
         ep, from, to, init, std::forward<MergeFn>(mergefn),
-        [](const auto &i) { return i; }, granularity);
+        [](const auto &i) { return i; }, std::max(granularity, size_t(1)));
 }
 
 template<class EP,
@@ -103,7 +105,8 @@ T accumulate(const EP & ep,
              size_t     granularity = 1)
 {
     return reduce(ep, from, to, init, std::plus<T>{},
-                  std::forward<AccessFn>(accessfn), granularity);
+                  std::forward<AccessFn>(accessfn),
+                  std::max(granularity, size_t(1)));
 }
 
 
@@ -119,7 +122,7 @@ T accumulate(const EP &ep,
 {
     return reduce(
         ep, from, to, init, std::plus<T>{}, [](const auto &i) { return i; },
-        granularity);
+        std::max(granularity, size_t(1)));
 }
 
 } // namespace execution_policy
diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp
index 6f1e95528..df10ce046 100644
--- a/src/libslic3r/Format/SL1.cpp
+++ b/src/libslic3r/Format/SL1.cpp
@@ -20,11 +20,14 @@
 #include "libslic3r/miniz_extension.hpp"
 #include "libslic3r/PNGReadWrite.hpp"
 #include "libslic3r/LocalesUtils.hpp"
+#include "libslic3r/GCode/ThumbnailData.hpp"
 
 #include <boost/property_tree/ini_parser.hpp>
 #include <boost/filesystem/path.hpp>
 #include <boost/algorithm/string.hpp>
 
+#include <miniz.h>
+
 namespace marchsq {
 
 template<> struct _RasterTraits<Slic3r::png::ImageGreyscale> {
@@ -482,10 +485,31 @@ sla::RasterEncoder SL1Archive::get_encoder() const
     return sla::PNGRasterEncoder{};
 }
 
-void SL1Archive::export_print(Zipper& zipper,
-                              const SLAPrint &print,
-                              const std::string &prjname)
+static void write_thumbnail(Zipper &zipper, const ThumbnailData &data)
 {
+    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) {
+        zipper.add_entry("thumbnail/thumbnail" + std::to_string(data.width) +
+                             "x" + std::to_string(data.height) + ".png",
+                         static_cast<const std::uint8_t *>(png_data),
+                         png_size);
+
+        mz_free(png_data);
+    }
+}
+
+void SL1Archive::export_print(const std::string     fname,
+                              const SLAPrint       &print,
+                              const ThumbnailsList &thumbnails,
+                              const std::string    &prjname)
+{
+    Zipper zipper{fname};
+
     std::string project =
         prjname.empty() ?
             boost::filesystem::path(zipper.get_filename()).stem().string() :
@@ -512,6 +536,12 @@ void SL1Archive::export_print(Zipper& zipper,
             
             zipper.add_entry(imgname.c_str(), rst.data(), rst.size());
         }
+
+        for (const ThumbnailData& data : thumbnails)
+            if (data.is_valid())
+                write_thumbnail(zipper, data);
+
+        zipper.finalize();
     } catch(std::exception& e) {
         BOOST_LOG_TRIVIAL(error) << e.what();
         // Rethrow the exception
diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp
index e6c0ff089..493550db4 100644
--- a/src/libslic3r/Format/SL1.hpp
+++ b/src/libslic3r/Format/SL1.hpp
@@ -3,8 +3,12 @@
 
 #include <string>
 
+#include "SLAArchive.hpp"
+
 #include "libslic3r/Zipper.hpp"
-#include "libslic3r/SLAPrint.hpp"
+#include "libslic3r/PrintConfig.hpp"
+
+struct indexed_triangle_set;
 
 namespace Slic3r {
 
@@ -23,8 +27,11 @@ public:
     SL1Archive() = default;
     explicit SL1Archive(const SLAPrinterConfig &cfg): m_cfg(cfg) {}
     explicit SL1Archive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {}
-    
-    void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = "") override;
+
+    void export_print(const std::string     fname,
+                      const SLAPrint       &print,
+                      const ThumbnailsList &thumbnails,
+                      const std::string    &projectname = "") override;
 };
     
 ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
diff --git a/src/libslic3r/Format/SL1_SVG.cpp b/src/libslic3r/Format/SL1_SVG.cpp
index 0ea230611..372348283 100644
--- a/src/libslic3r/Format/SL1_SVG.cpp
+++ b/src/libslic3r/Format/SL1_SVG.cpp
@@ -2,10 +2,13 @@
 #include "SLA/RasterBase.hpp"
 #include "libslic3r/LocalesUtils.hpp"
 #include "libslic3r/ClipperUtils.hpp"
+#include "libslic3r/BoundingBox.hpp"
 
 #include <limits>
 #include <cstdint>
 #include <algorithm>
+#include <string_view>
+using namespace std::literals;
 
 namespace Slic3r {
 
@@ -77,20 +80,23 @@ void append_svg(std::string &buf, const Polygon &poly)
 
     char intbuf[coord_t_bufsize];
 
-    buf += std::string("<path d=\"M ") + decimal_from(c.x(), intbuf);
-    buf += std::string(" ") + decimal_from(c.y(), intbuf) + " m";
+    buf += "<path d=\"M "sv;
+    buf += decimal_from(c.x(), intbuf);
+    buf += " "sv;
+    buf += decimal_from(c.y(), intbuf);
+    buf += " m"sv;
 
     for (auto &p : poly) {
         auto d = p - c;
         if (d.squaredNorm() == 0) continue;
-        buf += " ";
+        buf += " "sv;
         buf += decimal_from(p.x() - c.x(), intbuf);
-        buf += " ";
+        buf += " "sv;
         buf += decimal_from(p.y() - c.y(), intbuf);
         c = p;
     }
-    buf += " z\""; // mark path as closed
-    buf += " />\n";
+    buf += " z\""sv; // mark path as closed
+    buf += " />\n"sv;
 }
 
 } // namespace
@@ -167,12 +173,12 @@ public:
     sla::EncodedRaster encode(sla::RasterEncoder /*encoder*/) const override
     {
         std::vector<uint8_t> data;
-        constexpr const char finish[] = "</svg>\n";
+        constexpr auto finish = "</svg>\n"sv;
 
         data.reserve(m_svg.size() + std::size(finish));
 
         std::copy(m_svg.begin(), m_svg.end(), std::back_inserter(data));
-        std::copy(finish, finish + std::size(finish) - 1, std::back_inserter(data));
+        std::copy(finish.begin(), finish.end() - 1, std::back_inserter(data));
 
         return sla::EncodedRaster{std::move(data), "svg"};
     }
diff --git a/src/libslic3r/Format/SLAArchive.cpp b/src/libslic3r/Format/SLAArchive.cpp
new file mode 100644
index 000000000..8e2bf2824
--- /dev/null
+++ b/src/libslic3r/Format/SLAArchive.cpp
@@ -0,0 +1,74 @@
+#include "SLAArchive.hpp"
+
+#include "SL1.hpp"
+#include "SL1_SVG.hpp"
+#include "pwmx.hpp"
+
+#include "libslic3r/libslic3r.h"
+
+#include <string>
+#include <map>
+#include <memory>
+#include <tuple>
+
+namespace Slic3r {
+
+using ArchiveFactory = std::function<std::unique_ptr<SLAArchive>(const SLAPrinterConfig&)>;
+
+struct ArchiveEntry {
+    const char *ext;
+    ArchiveFactory factoryfn;
+};
+
+static const std::map<std::string, ArchiveEntry> REGISTERED_ARCHIVES {
+    {
+        "SL1",
+        { "sl1",  [] (const auto &cfg) { return std::make_unique<SL1Archive>(cfg); } }
+    },
+    {
+        "SL2",
+        { "sl2",  [] (const auto &cfg) { return std::make_unique<SL1_SVGArchive>(cfg); } }
+    },
+    {
+        "pwmx",
+        { "pwmx", [] (const auto &cfg) { return std::make_unique<PwmxArchive>(cfg); } }
+    }
+};
+
+std::unique_ptr<SLAArchive>
+SLAArchive::create(const std::string &archtype, const SLAPrinterConfig &cfg)
+{
+    auto entry = REGISTERED_ARCHIVES.find(archtype);
+
+    if (entry != REGISTERED_ARCHIVES.end())
+        return entry->second.factoryfn(cfg);
+
+    return nullptr;
+}
+
+const std::vector<const char*>& SLAArchive::registered_archives()
+{
+    static std::vector<const char*> archnames;
+
+    if (archnames.empty()) {
+        archnames.reserve(REGISTERED_ARCHIVES.size());
+
+        for (auto &[name, _] : REGISTERED_ARCHIVES)
+            archnames.emplace_back(name.c_str());
+    }
+
+    return archnames;
+}
+
+const char *SLAArchive::get_extension(const char *archtype)
+{
+    static const char* DEFAULT_EXT = "zip";
+
+    auto entry = REGISTERED_ARCHIVES.find(archtype);
+    if (entry != REGISTERED_ARCHIVES.end())
+        return entry->second.ext;
+
+    return DEFAULT_EXT;
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/Format/SLAArchive.hpp b/src/libslic3r/Format/SLAArchive.hpp
new file mode 100644
index 000000000..971d7ba7b
--- /dev/null
+++ b/src/libslic3r/Format/SLAArchive.hpp
@@ -0,0 +1,64 @@
+#ifndef SLAARCHIVE_HPP
+#define SLAARCHIVE_HPP
+
+#include <vector>
+
+#include "libslic3r/SLA/RasterBase.hpp"
+#include "libslic3r/Execution/ExecutionTBB.hpp"
+#include "libslic3r/GCode/ThumbnailData.hpp"
+
+namespace Slic3r {
+
+class SLAPrint;
+class SLAPrinterConfig;
+
+class SLAArchive {
+protected:
+    std::vector<sla::EncodedRaster> m_layers;
+
+    virtual std::unique_ptr<sla::RasterBase> create_raster() const = 0;
+    virtual sla::RasterEncoder get_encoder() const = 0;
+
+public:
+    virtual ~SLAArchive() = default;
+
+    // Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid);
+    template<class Fn, class CancelFn, class EP = ExecutionTBB>
+    void draw_layers(
+        size_t     layer_num,
+        Fn &&      drawfn,
+        CancelFn cancelfn = []() { return false; },
+        const EP & ep       = {})
+    {
+        m_layers.resize(layer_num);
+        execution::for_each(
+            ep, size_t(0), m_layers.size(),
+            [this, &drawfn, &cancelfn](size_t idx) {
+                if (cancelfn()) return;
+
+                sla::EncodedRaster &enc = m_layers[idx];
+                auto                rst = create_raster();
+                drawfn(*rst, idx);
+                enc = rst->encode(get_encoder());
+            },
+            execution::max_concurrency(ep));
+    }
+
+       // Export the print into an archive using the provided filename.
+    virtual void export_print(const std::string     fname,
+                              const SLAPrint       &print,
+                              const ThumbnailsList &thumbnails,
+                              const std::string    &projectname = "") = 0;
+
+    // Factory method to create an archiver instance
+    static std::unique_ptr<SLAArchive> create(const std::string &archtype, const SLAPrinterConfig&);
+
+    // Get the names of currently known archiver implementations
+    static const std::vector<const char *> & registered_archives();
+
+    // Get the default file extension belonging to an archive format
+    static const char *get_extension(const char *archtype);
+};
+
+} // namespace Slic3r
+#endif // SLAARCHIVE_HPP
diff --git a/src/libslic3r/Format/pwmx.cpp b/src/libslic3r/Format/pwmx.cpp
new file mode 100644
index 000000000..f3f0f6802
--- /dev/null
+++ b/src/libslic3r/Format/pwmx.cpp
@@ -0,0 +1,564 @@
+#include "pwmx.hpp"
+#include "GCode/ThumbnailData.hpp"
+#include "SLA/RasterBase.hpp"
+#include "libslic3r/SLAPrint.hpp"
+
+#include <sstream>
+#include <iostream>
+#include <fstream>
+
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/log/trivial.hpp>
+
+
+#define TAG_INTRO "ANYCUBIC\0\0\0\0"
+#define TAG_HEADER "HEADER\0\0\0\0\0\0"
+#define TAG_PREVIEW "PREVIEW\0\0\0\0\0"
+#define TAG_LAYERS "LAYERDEF\0\0\0\0"
+
+#define CFG_LIFT_DISTANCE "LIFT_DISTANCE"
+#define CFG_LIFT_SPEED "LIFT_SPEED"
+#define CFG_RETRACT_SPEED "RETRACT_SPEED"
+#define CFG_DELAY_BEFORE_EXPOSURE "DELAY_BEFORE_EXPOSURE"
+#define CFG_BOTTOM_LIFT_SPEED "BOTTOM_LIFT_SPEED"
+#define CFG_BOTTOM_LIFT_DISTANCE "BOTTOM_LIFT_DISTANCE"
+
+#define PREV_W 224
+#define PREV_H 168
+#define PREV_DPI 42
+
+#define LAYER_SIZE_ESTIMATE (32 * 1024)
+
+namespace Slic3r {
+
+static void pwx_get_pixel_span(const std::uint8_t* ptr, const std::uint8_t* end,
+                               std::uint8_t& pixel, size_t& span_len)
+{
+    size_t max_len;
+
+    span_len = 0;
+    pixel = (*ptr) & 0xF0;
+    // the maximum length of the span depends on the pixel color
+    max_len = (pixel == 0 || pixel == 0xF0) ? 0xFFF : 0xF;
+    while (ptr < end && span_len < max_len && ((*ptr) & 0xF0) == pixel) {
+        span_len++;
+        ptr++;
+    }
+}
+
+struct PWXRasterEncoder
+{
+    sla::EncodedRaster operator()(const void *ptr,
+                                  size_t      w,
+                                  size_t      h,
+                                  size_t      num_components)
+    {
+        std::vector<uint8_t> dst;
+        size_t               span_len;
+        std::uint8_t         pixel;
+        auto                 size = w * h * num_components;
+        dst.reserve(size);
+
+        const std::uint8_t *src = reinterpret_cast<const std::uint8_t *>(ptr);
+        const std::uint8_t *src_end = src + size;
+        while (src < src_end) {
+            pwx_get_pixel_span(src, src_end, pixel, span_len);
+            src += span_len;
+            // fully transparent of fully opaque pixel
+            if (pixel == 0 || pixel == 0xF0) {
+                pixel = pixel | (span_len >> 8);
+                std::copy(&pixel, (&pixel) + 1, std::back_inserter(dst));
+                pixel = span_len & 0xFF;
+                std::copy(&pixel, (&pixel) + 1, std::back_inserter(dst));
+            }
+            // antialiased pixel
+            else {
+                pixel = pixel | span_len;
+                std::copy(&pixel, (&pixel) + 1, std::back_inserter(dst));
+            }
+        }
+
+        return sla::EncodedRaster(std::move(dst), "pwx");
+    }
+};
+
+using ConfMap = std::map<std::string, std::string>;
+
+typedef struct pwmx_format_intro
+{
+    char          tag[12];
+    std::uint32_t version;  // value 1
+    std::uint32_t area_num; // unknown - usually 4
+    std::uint32_t header_data_offset;
+    std::float_t  intro24; // unknown - usually 0
+    std::uint32_t preview_data_offset;
+    std::float_t  intro32; // unknown
+    std::uint32_t layer_data_offset;
+    std::float_t  intro40; // unknown
+    std::uint32_t image_data_offset;
+} pwmx_format_intro;
+
+typedef struct pwmx_format_header
+{
+    char          tag[12];
+    std::uint32_t payload_size;
+    std::float_t  pixel_size_um;
+    std::float_t  layer_height_mm;
+    std::float_t  exposure_time_s;
+    std::float_t  delay_before_exposure_s;
+    std::float_t  bottom_exposure_time_s;
+    std::float_t  bottom_layer_count;
+    std::float_t  lift_distance_mm;
+    std::float_t  lift_speed_mms;
+    std::float_t  retract_speed_mms;
+    std::float_t  volume_ml;
+    std::uint32_t antialiasing;
+    std::uint32_t res_x;
+    std::uint32_t res_y;
+    std::float_t  weight_g;
+    std::float_t  price;
+    std::uint32_t price_currency;
+    std::uint32_t per_layer_override; // ? unknown meaning ?
+    std::uint32_t print_time_s;
+    std::uint32_t transition_layer_count;
+    std::uint32_t unknown; // ? usually 0 ?
+
+} pwmx_format_header;
+
+typedef struct pwmx_format_preview
+{
+    char          tag[12];
+    std::uint32_t payload_size;
+    std::uint32_t preview_w;
+    std::uint32_t preview_dpi;
+    std::uint32_t preview_h;
+    // raw image data in BGR565 format
+     std::uint8_t pixels[PREV_W * PREV_H * 2];
+} pwmx_format_preview;
+
+typedef struct pwmx_format_layers_header
+{
+    char          tag[12];
+    std::uint32_t payload_size;
+    std::uint32_t layer_count;
+} pwmx_format_layers_header;
+
+typedef struct pwmx_format_layer
+{
+    std::uint32_t image_offset;
+    std::uint32_t image_size;
+    std::float_t  lift_distance_mm;
+    std::float_t  lift_speed_mms;
+    std::float_t  exposure_time_s;
+    std::float_t  layer_height_mm;
+    std::float_t  layer44; // unkown - usually 0
+    std::float_t  layer48; // unkown - usually 0
+} pwmx_format_layer;
+
+typedef struct pwmx_format_misc
+{
+    std::float_t bottom_layer_height_mm;
+    std::float_t bottom_lift_distance_mm;
+    std::float_t bottom_lift_speed_mms;
+
+} pwmx_format_misc;
+
+class PwmxFormatConfigDef : public ConfigDef
+{
+public:
+    PwmxFormatConfigDef()
+    {
+        add(CFG_LIFT_DISTANCE, coFloat);
+        add(CFG_LIFT_SPEED, coFloat);
+        add(CFG_RETRACT_SPEED, coFloat);
+        add(CFG_DELAY_BEFORE_EXPOSURE, coFloat);
+        add(CFG_BOTTOM_LIFT_DISTANCE, coFloat);
+        add(CFG_BOTTOM_LIFT_SPEED, coFloat);
+    }
+};
+
+class PwmxFormatDynamicConfig : public DynamicConfig
+{
+public:
+    PwmxFormatDynamicConfig(){};
+    const ConfigDef *def() const override { return &config_def; }
+
+private:
+    PwmxFormatConfigDef config_def;
+};
+
+namespace {
+
+std::float_t get_cfg_value_f(const DynamicConfig &cfg,
+                             const std::string   &key,
+                             const std::float_t  &def = 0.f)
+{
+    if (cfg.has(key)) {
+        if (auto opt = cfg.option(key))
+            return opt->getFloat();
+    }
+
+    return def;
+}
+
+int get_cfg_value_i(const DynamicConfig &cfg,
+                    const std::string   &key,
+                    const int           &def = 0)
+{
+    if (cfg.has(key)) {
+        if (auto opt = cfg.option(key))
+            return opt->getInt();
+    }
+
+    return def;
+}
+
+template<class T> void crop_value(T &val, T val_min, T val_max)
+{
+    if (val < val_min) {
+        val = val_min;
+    } else if (val > val_max) {
+        val = val_max;
+    }
+}
+
+void fill_preview(pwmx_format_preview &p,
+                  pwmx_format_misc   &/*m*/,
+                  const ThumbnailsList &thumbnails)
+{
+
+    p.preview_w    = PREV_W;
+    p.preview_h    = PREV_H;
+    p.preview_dpi  = PREV_DPI;
+    p.payload_size = sizeof(p) - sizeof(p.tag) - sizeof(p.payload_size);
+                     
+    std::memset(p.pixels, 0 , sizeof(p.pixels));
+    if (!thumbnails.empty()) {
+        std::uint32_t dst_index;
+        std::uint32_t i = 0;
+        size_t len;
+        size_t pixel_x = 0;
+        auto t = thumbnails[0]; //use the first thumbnail
+        len = t.pixels.size();
+        //sanity check        
+        if (len != PREV_W * PREV_H * 4)  {
+            printf("incorrect thumbnail size. expected %ix%i\n", PREV_W, PREV_H);
+            return;
+        }
+        // rearange pixels: they seem to be stored from bottom to top.
+        dst_index = (PREV_W * (PREV_H - 1) * 2);
+        while (i < len) {
+            std::uint32_t pixel;
+            std::uint32_t r = t.pixels[i++];
+            std::uint32_t g = t.pixels[i++];
+            std::uint32_t b = t.pixels[i++];
+            i++; // Alpha
+            // convert to BGRA565
+            pixel = ((b >> 3) << 11) | ((g >>2) << 5) | (r >> 3);
+            p.pixels[dst_index++] = pixel & 0xFF;
+            p.pixels[dst_index++] = (pixel >> 8) & 0xFF;
+            pixel_x++;
+            if (pixel_x == PREV_W) {
+                pixel_x = 0;
+                dst_index -= (PREV_W * 4);
+            }
+        }
+    }
+}
+
+
+void fill_header(pwmx_format_header &h,
+                 pwmx_format_misc   &m,
+                 const SLAPrint     &print,
+                 std::uint32_t       layer_count)
+{
+    CNumericLocalesSetter locales_setter;
+
+    std::float_t bottle_weight_g;
+    std::float_t bottle_volume_ml;
+    std::float_t bottle_cost;
+    std::float_t material_density;
+    auto        &cfg     = print.full_print_config();
+    auto         mat_opt = cfg.option("material_notes");
+    std::string  mnotes  = mat_opt? cfg.option("material_notes")->serialize() : "";
+    // create a config parser from the material notes
+    Slic3r::PwmxFormatDynamicConfig mat_cfg;
+    SLAPrintStatistics              stats = print.print_statistics();
+
+    // sanitize the string config
+    boost::replace_all(mnotes, "\\n", "\n");
+    boost::replace_all(mnotes, "\\r", "\r");
+    mat_cfg.load_from_ini_string(mnotes,
+                                 ForwardCompatibilitySubstitutionRule::Enable);
+
+    h.layer_height_mm        = get_cfg_value_f(cfg, "layer_height");
+    m.bottom_layer_height_mm = get_cfg_value_f(cfg, "initial_layer_height");
+    h.exposure_time_s        = get_cfg_value_f(cfg, "exposure_time");
+    h.bottom_exposure_time_s = get_cfg_value_f(cfg, "initial_exposure_time");
+    h.bottom_layer_count =     get_cfg_value_i(cfg, "faded_layers");
+    if (layer_count < h.bottom_layer_count) {
+        h.bottom_layer_count = layer_count;
+    }
+    h.res_x     = get_cfg_value_i(cfg, "display_pixels_x");
+    h.res_y     = get_cfg_value_i(cfg, "display_pixels_y");
+    bottle_weight_g = get_cfg_value_f(cfg, "bottle_weight") * 1000.0f;
+    bottle_volume_ml = get_cfg_value_f(cfg, "bottle_volume");
+    bottle_cost = get_cfg_value_f(cfg, "bottle_cost");
+    material_density = bottle_weight_g / bottle_volume_ml;
+
+    h.volume_ml = (stats.objects_used_material + stats.support_used_material) / 1000;
+    h.weight_g           = h.volume_ml * material_density;
+    h.price              = (h.volume_ml * bottle_cost) /  bottle_volume_ml;
+    h.price_currency     = '$';
+    h.antialiasing       = 1;
+    h.per_layer_override = 0;
+
+    // TODO - expose these variables to the UI rather than using material notes
+    h.delay_before_exposure_s = get_cfg_value_f(mat_cfg, CFG_DELAY_BEFORE_EXPOSURE, 0.5f);
+    crop_value(h.delay_before_exposure_s, 0.0f, 1000.0f);
+
+    h.lift_distance_mm = get_cfg_value_f(mat_cfg, CFG_LIFT_DISTANCE, 8.0f);
+    crop_value(h.lift_distance_mm, 0.0f, 100.0f);
+
+    if (mat_cfg.has(CFG_BOTTOM_LIFT_DISTANCE)) {
+        m.bottom_lift_distance_mm = get_cfg_value_f(mat_cfg,
+                                                    CFG_BOTTOM_LIFT_DISTANCE,
+                                                    8.0f);
+        crop_value(h.lift_distance_mm, 0.0f, 100.0f);
+    } else {
+        m.bottom_lift_distance_mm = h.lift_distance_mm;
+    }
+
+    h.lift_speed_mms = get_cfg_value_f(mat_cfg, CFG_LIFT_SPEED, 2.0f);
+    crop_value(m.bottom_lift_speed_mms, 0.1f, 20.0f);
+
+    if (mat_cfg.has(CFG_BOTTOM_LIFT_SPEED)) {
+        m.bottom_lift_speed_mms = get_cfg_value_f(mat_cfg, CFG_BOTTOM_LIFT_SPEED, 2.0f);
+        crop_value(m.bottom_lift_speed_mms, 0.1f, 20.0f);
+    } else {
+        m.bottom_lift_speed_mms = h.lift_speed_mms;
+    }
+
+    h.retract_speed_mms = get_cfg_value_f(mat_cfg, CFG_RETRACT_SPEED, 3.0f);
+    crop_value(h.lift_speed_mms, 0.1f, 20.0f);
+
+    h.print_time_s = (h.bottom_layer_count * h.bottom_exposure_time_s) +
+                     ((layer_count - h.bottom_layer_count) *
+                      h.exposure_time_s) +
+                     (layer_count * h.lift_distance_mm / h.retract_speed_mms) +
+                     (layer_count * h.lift_distance_mm / h.lift_speed_mms) +
+                     (layer_count * h.delay_before_exposure_s);
+
+
+    h.payload_size  = sizeof(h) - sizeof(h.tag) - sizeof(h.payload_size);
+    h.pixel_size_um = 50;
+}
+
+} // namespace
+
+std::unique_ptr<sla::RasterBase> PwmxArchive::create_raster() const
+{
+    sla::Resolution     res;
+    sla::PixelDim       pxdim;
+    std::array<bool, 2> mirror;
+
+    double w  = m_cfg.display_width.getFloat();
+    double h  = m_cfg.display_height.getFloat();
+    auto   pw = size_t(m_cfg.display_pixels_x.getInt());
+    auto   ph = size_t(m_cfg.display_pixels_y.getInt());
+
+    mirror[X] = m_cfg.display_mirror_x.getBool();
+    mirror[Y] = m_cfg.display_mirror_y.getBool();
+
+    auto                         ro = m_cfg.display_orientation.getInt();
+    sla::RasterBase::Orientation orientation =
+        ro == sla::RasterBase::roPortrait ? sla::RasterBase::roPortrait :
+                                            sla::RasterBase::roLandscape;
+
+    if (orientation == sla::RasterBase::roPortrait) {
+        std::swap(w, h);
+        std::swap(pw, ph);
+    }
+
+    res   = sla::Resolution{pw, ph};
+    pxdim = sla::PixelDim{w / pw, h / ph};
+    sla::RasterBase::Trafo tr{orientation, mirror};
+
+    double gamma = m_cfg.gamma_correction.getFloat();
+
+    return sla::create_raster_grayscale_aa(res, pxdim, gamma, tr);
+}
+
+sla::RasterEncoder PwmxArchive::get_encoder() const
+{
+    return PWXRasterEncoder{};
+}
+
+// Endian safe write of little endian 32bit ints
+static void pwmx_write_int32(std::ofstream &out, std::uint32_t val)
+{
+    const char i1 = (val & 0xFF);
+    const char i2 = (val >> 8) & 0xFF;
+    const char i3 = (val >> 16) & 0xFF;
+    const char i4 = (val >> 24) & 0xFF;
+
+    out.write((const char *) &i1, 1);
+    out.write((const char *) &i2, 1);
+    out.write((const char *) &i3, 1);
+    out.write((const char *) &i4, 1);
+}
+static void pwmx_write_float(std::ofstream &out, std::float_t val)
+{
+    std::uint32_t *f = (std::uint32_t *) &val;
+    pwmx_write_int32(out, *f);
+}
+
+static void pwmx_write_intro(std::ofstream &out, pwmx_format_intro &i)
+{
+    out.write(TAG_INTRO, sizeof(i.tag));
+    pwmx_write_int32(out, i.version);
+    pwmx_write_int32(out, i.area_num);
+    pwmx_write_int32(out, i.header_data_offset);
+    pwmx_write_int32(out, i.intro24);
+    pwmx_write_int32(out, i.preview_data_offset);
+    pwmx_write_int32(out, i.intro32);
+    pwmx_write_int32(out, i.layer_data_offset);
+    pwmx_write_int32(out, i.intro40);
+    pwmx_write_int32(out, i.image_data_offset);
+}
+
+static void pwmx_write_header(std::ofstream &out, pwmx_format_header &h)
+{
+    out.write(TAG_HEADER, sizeof(h.tag));
+    pwmx_write_int32(out, h.payload_size);
+    pwmx_write_float(out, h.pixel_size_um);
+    pwmx_write_float(out, h.layer_height_mm);
+    pwmx_write_float(out, h.exposure_time_s);
+    pwmx_write_float(out, h.delay_before_exposure_s);
+    pwmx_write_float(out, h.bottom_exposure_time_s);
+    pwmx_write_float(out, h.bottom_layer_count);
+    pwmx_write_float(out, h.lift_distance_mm);
+    pwmx_write_float(out, h.lift_speed_mms);
+    pwmx_write_float(out, h.retract_speed_mms);
+    pwmx_write_float(out, h.volume_ml);
+    pwmx_write_int32(out, h.antialiasing);
+    pwmx_write_int32(out, h.res_x);
+    pwmx_write_int32(out, h.res_y);
+    pwmx_write_float(out, h.weight_g);
+    pwmx_write_float(out, h.price);
+    pwmx_write_int32(out, h.price_currency);
+    pwmx_write_int32(out, h.per_layer_override);
+    pwmx_write_int32(out, h.print_time_s);
+    pwmx_write_int32(out, h.transition_layer_count);
+    pwmx_write_int32(out, h.unknown);
+}
+
+static void pwmx_write_preview(std::ofstream &out, pwmx_format_preview &p)
+{
+    out.write(TAG_PREVIEW, sizeof(p.tag));
+    pwmx_write_int32(out, p.payload_size);
+    pwmx_write_int32(out, p.preview_w);
+    pwmx_write_int32(out, p.preview_dpi);
+    pwmx_write_int32(out, p.preview_h);
+    out.write((const char*) p.pixels, sizeof(p.pixels));
+}
+
+static void pwmx_write_layers_header(std::ofstream &out, pwmx_format_layers_header &h)
+{
+    out.write(TAG_LAYERS, sizeof(h.tag));
+    pwmx_write_int32(out, h.payload_size);
+    pwmx_write_int32(out, h.layer_count);
+}
+
+static void pwmx_write_layer(std::ofstream &out, pwmx_format_layer &l)
+{
+    pwmx_write_int32(out, l.image_offset);
+    pwmx_write_int32(out, l.image_size);
+    pwmx_write_float(out, l.lift_distance_mm);
+    pwmx_write_float(out, l.lift_speed_mms);
+    pwmx_write_float(out, l.exposure_time_s);
+    pwmx_write_float(out, l.layer_height_mm);
+    pwmx_write_float(out, l.layer44);
+    pwmx_write_float(out, l.layer48);
+}
+
+void PwmxArchive::export_print(const std::string     fname,
+                               const SLAPrint       &print,
+                               const ThumbnailsList &thumbnails,
+                               const std::string    &/*projectname*/)
+{
+    std::uint32_t layer_count = m_layers.size();
+
+    pwmx_format_intro         intro = {};
+    pwmx_format_header        header = {};
+    pwmx_format_preview       preview = {};
+    pwmx_format_layers_header layers_header = {};
+    pwmx_format_misc          misc = {};
+    std::vector<uint8_t>      layer_images;
+    std::uint32_t             image_offset;
+
+    intro.version             = 1;
+    intro.area_num            = 4;
+    intro.header_data_offset  = sizeof(intro);
+    intro.preview_data_offset = sizeof(intro) + sizeof(header);
+    intro.layer_data_offset   = intro.preview_data_offset + sizeof(preview);
+    intro.image_data_offset = intro.layer_data_offset +
+                              sizeof(layers_header) +
+                              (sizeof(pwmx_format_layer) * layer_count);
+
+    fill_header(header, misc, print, layer_count);
+    fill_preview(preview, misc, thumbnails);
+
+    try {
+        // open the file and write the contents
+        std::ofstream out;
+        out.open(fname, std::ios::binary | std::ios::out | std::ios::trunc);
+        pwmx_write_intro(out, intro);
+        pwmx_write_header(out, header);
+        pwmx_write_preview(out, preview);
+
+        layers_header.payload_size = intro.image_data_offset - intro.layer_data_offset -
+                        sizeof(layers_header.tag)  - sizeof(layers_header.payload_size);
+        layers_header.layer_count = layer_count;
+        pwmx_write_layers_header(out, layers_header);
+
+        //layers
+        layer_images.reserve(layer_count * LAYER_SIZE_ESTIMATE);
+        image_offset = intro.image_data_offset;
+        size_t i = 0;
+        for (const sla::EncodedRaster &rst : m_layers) {
+            pwmx_format_layer l;
+            std::memset(&l, 0, sizeof(l));
+            l.image_offset = image_offset;
+            l.image_size = rst.size();
+            if (i < header.bottom_layer_count) {
+                l.exposure_time_s = header.bottom_exposure_time_s;
+                l.layer_height_mm = misc.bottom_layer_height_mm;
+                l.lift_distance_mm = misc.bottom_lift_distance_mm;
+                l.lift_speed_mms = misc.bottom_lift_speed_mms;
+            } else {
+                l.exposure_time_s = header.exposure_time_s;
+                l.layer_height_mm = header.layer_height_mm;
+                l.lift_distance_mm = header.lift_distance_mm;
+                l.lift_speed_mms = header.lift_speed_mms;
+            }
+            image_offset += l.image_size;
+            pwmx_write_layer(out, l);
+            // add the rle encoded layer image into the buffer
+            const char* img_start = reinterpret_cast<const char*>(rst.data());
+            const char* img_end = img_start + rst.size();
+            std::copy(img_start, img_end, std::back_inserter(layer_images));
+            i++;
+        }
+        const char* img_buffer = reinterpret_cast<const char*>(layer_images.data());
+        out.write(img_buffer, layer_images.size());
+        out.close();
+    } catch(std::exception& e) {
+        BOOST_LOG_TRIVIAL(error) << e.what();
+        // Rethrow the exception
+        throw;
+    }
+
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/Format/pwmx.hpp b/src/libslic3r/Format/pwmx.hpp
new file mode 100644
index 000000000..65fb19910
--- /dev/null
+++ b/src/libslic3r/Format/pwmx.hpp
@@ -0,0 +1,37 @@
+#ifndef _SLIC3R_FORMAT_PWMX_HPP_
+#define _SLIC3R_FORMAT_PWMX_HPP_
+
+#include <string>
+
+#include "SLAArchive.hpp"
+
+#include "libslic3r/PrintConfig.hpp"
+
+namespace Slic3r {
+
+class PwmxArchive: public SLAArchive {
+    SLAPrinterConfig m_cfg;
+    
+protected:
+    std::unique_ptr<sla::RasterBase> create_raster() const override;
+    sla::RasterEncoder get_encoder() const override;
+
+    SLAPrinterConfig & cfg() { return m_cfg; }
+    const SLAPrinterConfig & cfg() const { return m_cfg; }
+
+public:
+    
+    PwmxArchive() = default;
+    explicit PwmxArchive(const SLAPrinterConfig &cfg): m_cfg(cfg) {}
+    explicit PwmxArchive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {}
+
+    void export_print(const std::string     fname,
+                      const SLAPrint       &print,
+                      const ThumbnailsList &thumbnails,
+                      const std::string    &projectname = "") override;
+};
+
+
+} // namespace Slic3r::sla
+
+#endif // _SLIC3R_FORMAT_PWMX_HPP_
diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp
index 6134e1f5a..aa69fdc77 100644
--- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp
+++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp
@@ -982,7 +982,7 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head)
     double w = dist - 2 * head.r_pin_mm - head.r_back_mm;
 
     if (w < 0.) {
-        BOOST_LOG_TRIVIAL(error) << "Pinhead width is negative!";
+        BOOST_LOG_TRIVIAL(warning) << "Pinhead width is negative!";
         w = 0.;
     }
 
diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp
index 7b78dfea2..72cb96dd0 100644
--- a/src/libslic3r/SLAPrint.cpp
+++ b/src/libslic3r/SLAPrint.cpp
@@ -3,6 +3,7 @@
 
 #include "Format/SL1.hpp"
 #include "Format/SL1_SVG.hpp"
+#include "Format/pwmx.hpp"
 
 #include "ClipperUtils.hpp"
 #include "Geometry.hpp"
@@ -244,12 +245,8 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con
     // Handle changes to object config defaults
     m_default_object_config.apply_only(config, object_diff, true);
 
-    if (!m_archiver || !printer_diff.empty()) {
-        if (m_printer_config.sla_archive_format.value == "SL1")
-            m_archiver = std::make_unique<SL1Archive>(m_printer_config);
-        else if (m_printer_config.sla_archive_format.value == "SL2")
-            m_archiver = std::make_unique<SL1_SVGArchive>(m_printer_config);
-    }
+    if (!m_archiver || !printer_diff.empty())
+        m_archiver = SLAArchive::create(m_printer_config.sla_archive_format.value.c_str(), m_printer_config);
 
     struct ModelObjectStatus {
         enum Status {
diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp
index b2df0c4e9..0723382b3 100644
--- a/src/libslic3r/SLAPrint.hpp
+++ b/src/libslic3r/SLAPrint.hpp
@@ -9,6 +9,8 @@
 #include "Point.hpp"
 #include "MTUtils.hpp"
 #include "Zipper.hpp"
+#include "Format/SLAArchive.hpp"
+#include "GCode/ThumbnailData.hpp"
 
 #include "libslic3r/Execution/ExecutionTBB.hpp"
 
@@ -389,47 +391,6 @@ struct SLAPrintStatistics
     }
 };
 
-class SLAArchive {
-protected:
-    std::vector<sla::EncodedRaster> m_layers;
-    
-    virtual std::unique_ptr<sla::RasterBase> create_raster() const = 0;
-    virtual sla::RasterEncoder get_encoder() const = 0;
-    
-public:
-    virtual ~SLAArchive() = default;
-    
-    // Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid);
-    template<class Fn, class CancelFn, class EP = ExecutionTBB>
-    void draw_layers(
-        size_t     layer_num,
-        Fn &&      drawfn,
-        CancelFn cancelfn = []() { return false; },
-        const EP & ep       = {})
-    {
-        m_layers.resize(layer_num);
-        execution::for_each(
-            ep, size_t(0), m_layers.size(),
-            [this, &drawfn, &cancelfn](size_t idx) {
-                if (cancelfn()) return;
-
-                sla::EncodedRaster &enc = m_layers[idx];
-                auto                rst = create_raster();
-                drawfn(*rst, idx);
-                enc = rst->encode(get_encoder());
-            },
-            execution::max_concurrency(ep));
-    }
-
-    // Export the print into an archive using the provided zipper.
-    // TODO: Use an archive writer interface instead of Zipper.
-    // This is quite limiting as the Zipper is a complete class, not an interface.
-    // The output can only be a zip archive.
-    virtual void export_print(Zipper            &zipper,
-                              const SLAPrint    &print,
-                              const std::string &projectname = "") = 0;
-};
-
 /**
  * @brief This class is the high level FSM for the SLA printing process.
  *
@@ -534,15 +495,17 @@ public:
     // TODO: use this structure for the preview in the future.
     const std::vector<PrintLayer>& print_layers() const { return m_printer_input; }
 
-    void export_print(Zipper &zipper, const std::string &projectname = "")
-    {
-        m_archiver->export_print(zipper, *this, projectname);
-    }
-
     void export_print(const std::string &fname, const std::string &projectname = "")
     {
-        Zipper zipper(fname);
-        export_print(zipper, projectname);
+        ThumbnailsList thumbnails; //empty thumbnail list
+        export_print(fname, thumbnails, projectname);
+    }
+
+    void export_print(const std::string    &fname,
+                      const ThumbnailsList &thumbnails,
+                      const std::string    &projectname = "")
+    {
+        m_archiver->export_print(fname, *this, thumbnails, projectname);
     }
     
 private:
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 34c0efd01..022bba2a8 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -270,7 +270,7 @@ endforeach()
 
 encoding_check(libslic3r_gui)
 
-target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES})
+target_link_libraries(libslic3r_gui libslic3r avrdude libcereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES})
 
 if (MSVC)
     target_link_libraries(libslic3r_gui Setupapi.lib)
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
index 5d3d47c20..37e527d64 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
@@ -165,17 +165,6 @@ void BackgroundSlicingProcess::process_fff()
 	}
 }
 
-static void write_thumbnail(Zipper& zipper, const ThumbnailData& data)
-{
-    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)
-    {
-        zipper.add_entry("thumbnail/thumbnail" + std::to_string(data.width) + "x" + std::to_string(data.height) + ".png", (const std::uint8_t*)png_data, png_size);
-        mz_free(png_data);
-    }
-}
-
 void BackgroundSlicingProcess::process_sla()
 {
     assert(m_print == m_sla_print);
@@ -189,12 +178,7 @@ void BackgroundSlicingProcess::process_sla()
             ThumbnailsList thumbnails = this->render_thumbnails(
             	ThumbnailsParams{current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true});
 
-            Zipper zipper(export_path);
-            m_sla_print->export_print(zipper);
-			for (const ThumbnailData& data : thumbnails)
-                if (data.is_valid())
-                    write_thumbnail(zipper, data);
-            zipper.finalize();
+            m_sla_print->export_print(export_path, thumbnails);
 
             m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str());
         } else if (! m_upload_job.empty()) {
@@ -739,13 +723,7 @@ void BackgroundSlicingProcess::prepare_upload()
         
         ThumbnailsList thumbnails = this->render_thumbnails(
         	ThumbnailsParams{current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true});
-																												 // true, false, true, true); // renders also supports and pad
-        Zipper zipper{source_path.string()};
-        m_sla_print->export_print(zipper, m_upload_job.upload_data.upload_path.string());
-        for (const ThumbnailData& data : thumbnails)
-	        if (data.is_valid())
-	            write_thumbnail(zipper, data);
-        zipper.finalize();
+        m_sla_print->export_print(source_path.string(),thumbnails, m_upload_job.upload_data.upload_path.string());
     }
 
     m_print->set_status(100, (boost::format(_utf8(L("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue"))) % m_upload_job.printhost->get_host()).str());
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 93c3c848f..a37d32074 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -5086,9 +5086,9 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be
 
     // clamp max bb size with respect to bed bb size
     if (!m_picking_enabled) {
-        static const double max_scale_factor = 1.5;
+        static const double max_scale_factor = 2.0;
         const Vec3d bb_size = bb.size();
-        const Vec3d bed_bb_size = bed_bb.size();
+        const Vec3d bed_bb_size = m_bed.build_volume().bounding_volume().size();
         if (bb_size.x() > max_scale_factor * bed_bb_size.x() ||
             bb_size.y() > max_scale_factor * bed_bb_size.y() ||
             bb_size.z() > max_scale_factor * bed_bb_size.z()) {
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index b55ba0d75..b914d8db4 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -496,7 +496,7 @@ static const FileWildcards file_wildcards_by_type[FT_SIZE] = {
 
     /* FT_TEX */     { "Texture"sv,         { ".png"sv, ".svg"sv } },
 
-    /* FT_SL1 */     { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv } },
+    /* FT_SL1 */     { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } },
 };
 
 // This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms.
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
index 362e25309..dca578bd7 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
@@ -134,15 +134,14 @@ void GLGizmoRotate::on_render()
     if (shader != nullptr) {
         shader->start_using();
 
-        const float radius = Offset + m_parent.get_selection().get_bounding_box().radius();
-        const bool radius_changed = std::abs(m_old_radius - radius) > EPSILON;
-        m_old_radius = radius;
+        const bool radius_changed = std::abs(m_old_radius - m_radius) > EPSILON;
+        m_old_radius = m_radius;
 
         ColorRGBA color((m_hover_id != -1) ? m_drag_color : m_highlight_color);
         render_circle(color, radius_changed);
         if (m_hover_id != -1) {
-            const bool hover_radius_changed = std::abs(m_old_hover_radius - radius) > EPSILON;
-            m_old_hover_radius = radius;
+            const bool hover_radius_changed = std::abs(m_old_hover_radius - m_radius) > EPSILON;
+            m_old_hover_radius = m_radius;
 
             render_scale(color, hover_radius_changed);
             render_snap_radii(color, hover_radius_changed);
diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp
index 1bb8cdf6c..96702d158 100644
--- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp
+++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp
@@ -1,5 +1,6 @@
 #include "SLAImportJob.hpp"
 
+#include "libslic3r/SLAPrint.hpp"
 #include "libslic3r/Format/SL1.hpp"
 
 #include "slic3r/GUI/GUI.hpp"
diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp
index 11ebf9386..cc961ee8f 100644
--- a/src/slic3r/GUI/MeshUtils.hpp
+++ b/src/slic3r/GUI/MeshUtils.hpp
@@ -3,6 +3,7 @@
 
 #include "libslic3r/Point.hpp"
 #include "libslic3r/Geometry.hpp"
+#include "libslic3r/TriangleMesh.hpp"
 #include "libslic3r/SLA/IndexedMesh.hpp"
 #include "admesh/stl.h"
 
diff --git a/src/slic3r/GUI/Notebook.hpp b/src/slic3r/GUI/Notebook.hpp
index ff5020b9c..af03a6a08 100644
--- a/src/slic3r/GUI/Notebook.hpp
+++ b/src/slic3r/GUI/Notebook.hpp
@@ -245,7 +245,7 @@ public:
         GetBtnsListCtrl()->Rescale();
     }
 
-    void Notebook::OnNavigationKey(wxNavigationKeyEvent& event)
+    void OnNavigationKey(wxNavigationKeyEvent& event)
     {
         if (event.IsWindowChange()) {
             // change pages
diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp
index 6616cc20d..41a11cff6 100644
--- a/src/slic3r/GUI/OpenGLManager.cpp
+++ b/src/slic3r/GUI/OpenGLManager.cpp
@@ -233,8 +233,9 @@ OpenGLManager::~OpenGLManager()
 bool OpenGLManager::init_gl()
 {
     if (!m_gl_initialized) {
-        if (glewInit() != GLEW_OK) {
-            BOOST_LOG_TRIVIAL(error) << "Unable to init glew library";
+        GLenum err = glewInit();
+        if (err != GLEW_OK) {
+            BOOST_LOG_TRIVIAL(error) << "Unable to init glew library: " << glewGetErrorString(err);
             return false;
         }
         m_gl_initialized = true;
diff --git a/tests/libnest2d/CMakeLists.txt b/tests/libnest2d/CMakeLists.txt
index bcb759452..9bafe84a0 100644
--- a/tests/libnest2d/CMakeLists.txt
+++ b/tests/libnest2d/CMakeLists.txt
@@ -4,4 +4,6 @@ target_link_libraries(${_TEST_NAME}_tests test_common libnest2d )
 set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
 
 # catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
-add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "${CATCH_EXTRA_ARGS} exclude:[NotWorking]")
+set(_catch_args "exclude:[NotWorking]")
+list(APPEND _catch_args "${CATCH_EXTRA_ARGS}")
+add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${_catch_args})
diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt
index dc583f1a1..88c1cd186 100644
--- a/tests/sla_print/CMakeLists.txt
+++ b/tests/sla_print/CMakeLists.txt
@@ -3,9 +3,14 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp
     sla_print_tests.cpp
     sla_test_utils.hpp sla_test_utils.cpp
     sla_supptgen_tests.cpp
-    sla_raycast_tests.cpp)
+    sla_raycast_tests.cpp
+    sla_archive_export_tests.cpp)
 target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
 set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
 
+if (WIN32)
+    prusaslicer_copy_dlls(${_TEST_NAME}_tests)
+endif()
+
 # catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
 add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS})
diff --git a/tests/sla_print/sla_archive_export_tests.cpp b/tests/sla_print/sla_archive_export_tests.cpp
new file mode 100644
index 000000000..9dbe5bc64
--- /dev/null
+++ b/tests/sla_print/sla_archive_export_tests.cpp
@@ -0,0 +1,40 @@
+#include <catch2/catch.hpp>
+#include <test_utils.hpp>
+
+#include "libslic3r/SLAPrint.hpp"
+#include "libslic3r/Format/SLAArchive.hpp"
+
+#include <boost/filesystem.hpp>
+
+using namespace Slic3r;
+
+TEST_CASE("Archive export test", "[sla_archives]") {
+    constexpr const char *PNAME = "20mm_cube";
+
+    for (auto &archname : SLAArchive::registered_archives()) {
+        INFO(std::string("Testing archive type: ") + archname);
+        SLAPrint print;
+        SLAFullPrintConfig fullcfg;
+
+        auto m = Model::read_from_file(TEST_DATA_DIR PATH_SEPARATOR + std::string(PNAME) + ".obj", nullptr);
+
+        fullcfg.set("sla_archive_format", archname);
+        fullcfg.set("supports_enable", false);
+        fullcfg.set("pad_enable", false);
+
+        DynamicPrintConfig cfg;
+        cfg.apply(fullcfg);
+
+        print.set_status_callback([](const PrintBase::SlicingStatus&) {});
+        print.apply(m, cfg);
+        print.process();
+
+        ThumbnailsList thumbnails;
+        auto outputfname = std::string("output.") + SLAArchive::get_extension(archname);
+
+        print.export_print(outputfname, thumbnails, PNAME);
+
+        // Not much can be checked about the archives...
+        REQUIRE(boost::filesystem::exists(outputfname));
+    }
+}
diff --git a/tests/slic3rutils/CMakeLists.txt b/tests/slic3rutils/CMakeLists.txt
index be1b645d7..256e6efd6 100644
--- a/tests/slic3rutils/CMakeLists.txt
+++ b/tests/slic3rutils/CMakeLists.txt
@@ -15,4 +15,6 @@ if (WIN32)
 endif()
 
 # catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
-add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "${CATCH_EXTRA_ARGS} exclude:[NotWorking]")
+set(_catch_args "exclude:[NotWorking]")
+list(APPEND _catch_args "${CATCH_EXTRA_ARGS}")
+add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${_catch_args})