diff --git a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt
index 995afcc76..462d1dd06 100644
--- a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt
+++ b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt
@@ -64,6 +64,7 @@ endif()
 target_include_directories(ClipperBackend INTERFACE ${Boost_INCLUDE_DIRS} )
 target_sources(ClipperBackend INTERFACE
     ${CMAKE_CURRENT_SOURCE_DIR}/geometries.hpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/clipper_polygon.hpp
     ${SRC_DIR}/libnest2d/utils/boost_alg.hpp )
 
 target_compile_definitions(ClipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER)
diff --git a/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp b/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp
new file mode 100644
index 000000000..e9fbfbd18
--- /dev/null
+++ b/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp
@@ -0,0 +1,72 @@
+#ifndef CLIPPER_POLYGON_HPP
+#define CLIPPER_POLYGON_HPP
+
+#include <clipper.hpp>
+
+namespace ClipperLib {
+
+struct Polygon {
+    Path Contour;
+    Paths Holes;
+
+    inline Polygon() = default;
+
+    inline explicit Polygon(const Path& cont): Contour(cont) {}
+    inline explicit Polygon(const Paths& holes):
+        Holes(holes) {}
+    inline Polygon(const Path& cont, const Paths& holes):
+        Contour(cont), Holes(holes) {}
+
+    inline explicit Polygon(Path&& cont): Contour(std::move(cont)) {}
+    inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {}
+    inline Polygon(Path&& cont, Paths&& holes):
+        Contour(std::move(cont)), Holes(std::move(holes)) {}
+};
+
+inline IntPoint& operator +=(IntPoint& p, const IntPoint& pa ) {
+    // This could be done with SIMD
+    p.X += pa.X;
+    p.Y += pa.Y;
+    return p;
+}
+
+inline IntPoint operator+(const IntPoint& p1, const IntPoint& p2) {
+    IntPoint ret = p1;
+    ret += p2;
+    return ret;
+}
+
+inline IntPoint& operator -=(IntPoint& p, const IntPoint& pa ) {
+    p.X -= pa.X;
+    p.Y -= pa.Y;
+    return p;
+}
+
+inline IntPoint operator -(IntPoint& p ) {
+    IntPoint ret = p;
+    ret.X = -ret.X;
+    ret.Y = -ret.Y;
+    return ret;
+}
+
+inline IntPoint operator-(const IntPoint& p1, const IntPoint& p2) {
+    IntPoint ret = p1;
+    ret -= p2;
+    return ret;
+}
+
+inline IntPoint& operator *=(IntPoint& p, const IntPoint& pa ) {
+    p.X *= pa.X;
+    p.Y *= pa.Y;
+    return p;
+}
+
+inline IntPoint operator*(const IntPoint& p1, const IntPoint& p2) {
+    IntPoint ret = p1;
+    ret *= p2;
+    return ret;
+}
+
+}
+
+#endif // CLIPPER_POLYGON_HPP
diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp
index 9f881e7e0..232668f61 100644
--- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp
+++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp
@@ -10,84 +10,15 @@
 #include <libnest2d/geometry_traits.hpp>
 #include <libnest2d/geometry_traits_nfp.hpp>
 
-#include <clipper.hpp>
-
-namespace ClipperLib {
-using PointImpl = IntPoint;
-using PathImpl = Path;
-using HoleStore = std::vector<PathImpl>;
-
-struct PolygonImpl {
-    PathImpl Contour;
-    HoleStore Holes;
-
-    inline PolygonImpl() = default;
-
-    inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {}
-    inline explicit PolygonImpl(const HoleStore& holes):
-        Holes(holes) {}
-    inline PolygonImpl(const Path& cont, const HoleStore& holes):
-        Contour(cont), Holes(holes) {}
-
-    inline explicit PolygonImpl(PathImpl&& cont): Contour(std::move(cont)) {}
-    inline explicit PolygonImpl(HoleStore&& holes): Holes(std::move(holes)) {}
-    inline PolygonImpl(Path&& cont, HoleStore&& holes):
-        Contour(std::move(cont)), Holes(std::move(holes)) {}
-};
-
-inline PointImpl& operator +=(PointImpl& p, const PointImpl& pa ) {
-    // This could be done with SIMD
-    p.X += pa.X;
-    p.Y += pa.Y;
-    return p;
-}
-
-inline PointImpl operator+(const PointImpl& p1, const PointImpl& p2) {
-    PointImpl ret = p1;
-    ret += p2;
-    return ret;
-}
-
-inline PointImpl& operator -=(PointImpl& p, const PointImpl& pa ) {
-    p.X -= pa.X;
-    p.Y -= pa.Y;
-    return p;
-}
-
-inline PointImpl operator -(PointImpl& p ) {
-    PointImpl ret = p;
-    ret.X = -ret.X;
-    ret.Y = -ret.Y;
-    return ret;
-}
-
-inline PointImpl operator-(const PointImpl& p1, const PointImpl& p2) {
-    PointImpl ret = p1;
-    ret -= p2;
-    return ret;
-}
-
-inline PointImpl& operator *=(PointImpl& p, const PointImpl& pa ) {
-    p.X *= pa.X;
-    p.Y *= pa.Y;
-    return p;
-}
-
-inline PointImpl operator*(const PointImpl& p1, const PointImpl& p2) {
-    PointImpl ret = p1;
-    ret *= p2;
-    return ret;
-}
-
-}
+#include "clipper_polygon.hpp"
 
 namespace libnest2d {
 
 // Aliases for convinience
-using ClipperLib::PointImpl;
-using ClipperLib::PathImpl;
-using ClipperLib::PolygonImpl;
-using ClipperLib::HoleStore;
+using PointImpl = ClipperLib::IntPoint;
+using PathImpl  = ClipperLib::Path;
+using HoleStore = ClipperLib::Paths;
+using PolygonImpl = ClipperLib::Polygon;
 
 // Type of coordinate units used by Clipper
 template<> struct CoordType<PointImpl> {
@@ -158,33 +89,24 @@ template<> inline TCoord<PointImpl>& y(PointImpl& p)
 #define DISABLE_BOOST_AREA
 
 namespace _smartarea {
+
 template<Orientation o>
 inline double area(const PolygonImpl& /*sh*/) {
     return std::nan("");
 }
 
 template<>
-inline double area<Orientation::CLOCKWISE>(const PolygonImpl& sh) {
-    double a = 0;
-
-    std::for_each(sh.Holes.begin(), sh.Holes.end(), [&a](const PathImpl& h)
-    {
-        a -= ClipperLib::Area(h);
+inline double area<Orientation::COUNTER_CLOCKWISE>(const PolygonImpl& sh) {
+    return std::accumulate(sh.Holes.begin(), sh.Holes.end(),
+                           ClipperLib::Area(sh.Contour),
+                           [](double a, const ClipperLib::Path& pt){
+        return a + ClipperLib::Area(pt);
     });
-
-    return -ClipperLib::Area(sh.Contour) + a;
 }
 
 template<>
-inline double area<Orientation::COUNTER_CLOCKWISE>(const PolygonImpl& sh) {
-    double a = 0;
-
-    std::for_each(sh.Holes.begin(), sh.Holes.end(), [&a](const PathImpl& h)
-    {
-        a += ClipperLib::Area(h);
-    });
-
-    return ClipperLib::Area(sh.Contour) + a;
+inline double area<Orientation::CLOCKWISE>(const PolygonImpl& sh) {
+    return -area<Orientation::COUNTER_CLOCKWISE>(sh);
 }
 
 }
@@ -228,9 +150,10 @@ template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance)
             // but throwing would be an overkill. Instead, we should warn the
             // caller about the inability to create correct geometries
             if(!found_the_contour) {
-                sh.Contour = r;
+                sh.Contour = std::move(r);
                 ClipperLib::ReversePath(sh.Contour);
-                sh.Contour.push_back(sh.Contour.front());
+                auto front_p = sh.Contour.front();
+                sh.Contour.emplace_back(std::move(front_p));
                 found_the_contour = true;
             } else {
                 dout() << "Warning: offsetting result is invalid!";
@@ -240,9 +163,10 @@ template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance)
             // TODO If there are multiple contours we can't be sure which hole
             // belongs to the first contour. (But in this case the situation is
             // bad enough to let it go...)
-            sh.Holes.push_back(r);
+            sh.Holes.emplace_back(std::move(r));
             ClipperLib::ReversePath(sh.Holes.back());
-            sh.Holes.back().push_back(sh.Holes.back().front());
+            auto front_p = sh.Holes.back().front();
+            sh.Holes.back().emplace_back(std::move(front_p));
         }
     }
 }
@@ -390,34 +314,53 @@ inline void rotate(PolygonImpl& sh, const Radians& rads)
 } // namespace shapelike
 
 #define DISABLE_BOOST_NFP_MERGE
-inline std::vector<PolygonImpl> _merge(ClipperLib::Clipper& clipper) {
+inline std::vector<PolygonImpl> clipper_execute(
+        ClipperLib::Clipper& clipper,
+        ClipperLib::ClipType clipType,
+        ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd,
+        ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd)
+{
     shapelike::Shapes<PolygonImpl> retv;
 
     ClipperLib::PolyTree result;
-    clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative);
+    clipper.Execute(clipType, result, subjFillType, clipFillType);
+
     retv.reserve(static_cast<size_t>(result.Total()));
 
     std::function<void(ClipperLib::PolyNode*, PolygonImpl&)> processHole;
 
     auto processPoly = [&retv, &processHole](ClipperLib::PolyNode *pptr) {
-        PolygonImpl poly(pptr->Contour);
-        poly.Contour.push_back(poly.Contour.front());
+        PolygonImpl poly;
+        poly.Contour.swap(pptr->Contour);
+
+        assert(!pptr->IsHole());
+
+        if(pptr->IsOpen()) {
+            auto front_p = poly.Contour.front();
+            poly.Contour.emplace_back(front_p);
+        }
+
         for(auto h : pptr->Childs) { processHole(h, poly); }
         retv.push_back(poly);
     };
 
     processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly)
     {
-        poly.Holes.push_back(pptr->Contour);
-        poly.Holes.back().push_back(poly.Holes.back().front());
+        poly.Holes.emplace_back(std::move(pptr->Contour));
+
+        assert(pptr->IsHole());
+
+        if(pptr->IsOpen()) {
+            auto front_p = poly.Holes.back().front();
+            poly.Holes.back().emplace_back(front_p);
+        }
+
         for(auto c : pptr->Childs) processPoly(c);
     };
 
     auto traverse = [&processPoly] (ClipperLib::PolyNode *node)
     {
-        for(auto ch : node->Childs) {
-            processPoly(ch);
-        }
+        for(auto ch : node->Childs) processPoly(ch);
     };
 
     traverse(&result);
@@ -438,14 +381,13 @@ merge(const std::vector<PolygonImpl>& shapes)
     for(auto& path : shapes) {
         valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
 
-        for(auto& hole : path.Holes) {
-            valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed);
-        }
+        for(auto& h : path.Holes)
+            valid &= clipper.AddPath(h, ClipperLib::ptSubject, closed);
     }
 
     if(!valid) throw GeometryException(GeomErr::MERGE);
 
-    return _merge(clipper);
+    return clipper_execute(clipper, ClipperLib::ctUnion, ClipperLib::pftNegative);
 }
 
 }
diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp
index 49baa65f2..c7b252e5d 100644
--- a/src/libnest2d/include/libnest2d/libnest2d.hpp
+++ b/src/libnest2d/include/libnest2d/libnest2d.hpp
@@ -966,7 +966,7 @@ private:
 
         for(size_t i = 0; i < pckgrp.size(); i++) {
             auto items = pckgrp[i];
-            pg.push_back({});
+            pg.emplace_back();
             pg[i].reserve(items.size());
 
             for(Item& itemA : items) {
diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp
index 6fb717a7a..91affe978 100644
--- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp
+++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp
@@ -261,7 +261,7 @@ template<class RawShape> class EdgeCache {
             while(next != endit) {
                 contour_.emap.emplace_back(*(first++), *(next++));
                 contour_.full_distance += contour_.emap.back().length();
-                contour_.distances.push_back(contour_.full_distance);
+                contour_.distances.emplace_back(contour_.full_distance);
             }
         }
 
@@ -276,10 +276,10 @@ template<class RawShape> class EdgeCache {
             while(next != endit) {
                 hc.emap.emplace_back(*(first++), *(next++));
                 hc.full_distance += hc.emap.back().length();
-                hc.distances.push_back(hc.full_distance);
+                hc.distances.emplace_back(hc.full_distance);
             }
 
-            holes_.push_back(hc);
+            holes_.emplace_back(std::move(hc));
         }
     }
 
diff --git a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp
index 309a5007d..abcd86183 100644
--- a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp
+++ b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp
@@ -63,7 +63,7 @@ public:
     bool pack(Item& item, const Range& rem = Range()) {
         auto&& r = static_cast<Subclass*>(this)->trypack(item, rem);
         if(r) {
-            items_.push_back(*(r.item_ptr_));
+            items_.emplace_back(*(r.item_ptr_));
             farea_valid_ = false;
         }
         return r;
@@ -78,7 +78,7 @@ public:
         if(r) {
             r.item_ptr_->translation(r.move_);
             r.item_ptr_->rotation(r.rot_);
-            items_.push_back(*(r.item_ptr_));
+            items_.emplace_back(*(r.item_ptr_));
             farea_valid_ = false;
         }
     }
diff --git a/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp b/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp
index b03534dc4..25007e580 100644
--- a/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp
+++ b/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp
@@ -667,7 +667,7 @@ public:
                 addBin();
                 ItemList& not_packed = not_packeds[b];
                 for(unsigned idx = b; idx < store_.size(); idx+=bincount_guess) {
-                    not_packed.push_back(store_[idx]);
+                    not_packed.emplace_back(store_[idx]);
                 }
             }
 
diff --git a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp
index a6988ca00..baf1c6a10 100644
--- a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp
+++ b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp
@@ -463,7 +463,7 @@ template<> inline std::string serialize<libnest2d::Formats::SVG>(
             auto& v = *it;
             hf.emplace_back(getX(v)*scale, getY(v)*scale);
         };
-        holes.push_back(hf);
+        holes.emplace_back(std::move(hf));
     }
 
     Polygonf poly;
diff --git a/src/libnest2d/tests/printer_parts.h b/src/libnest2d/tests/printer_parts.h
index b9a4eb8fa..1e65826bd 100644
--- a/src/libnest2d/tests/printer_parts.h
+++ b/src/libnest2d/tests/printer_parts.h
@@ -2,36 +2,10 @@
 #define PRINTER_PARTS_H
 
 #include <vector>
-#include <clipper.hpp>
-
-#ifndef CLIPPER_BACKEND_HPP
-namespace ClipperLib {
-using PointImpl = IntPoint;
-using PathImpl = Path;
-using HoleStore = std::vector<PathImpl>;
-
-struct PolygonImpl {
-    PathImpl Contour;
-    HoleStore Holes;
-
-    inline PolygonImpl() {}
-
-    inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {}
-    inline explicit PolygonImpl(const HoleStore& holes):
-        Holes(holes) {}
-    inline PolygonImpl(const Path& cont, const HoleStore& holes):
-        Contour(cont), Holes(holes) {}
-
-    inline explicit PolygonImpl(PathImpl&& cont): Contour(std::move(cont)) {}
-    inline explicit PolygonImpl(HoleStore&& holes): Holes(std::move(holes)) {}
-    inline PolygonImpl(Path&& cont, HoleStore&& holes):
-        Contour(std::move(cont)), Holes(std::move(holes)) {}
-};
-}
-#endif
+#include <libnest2d/backends/clipper/clipper_polygon.hpp>
 
 using TestData = std::vector<ClipperLib::Path>;
-using TestDataEx = std::vector<ClipperLib::PolygonImpl>;
+using TestDataEx = std::vector<ClipperLib::Polygon>;
 
 extern const TestData PRINTER_PART_POLYGONS;
 extern const TestData STEGOSAUR_POLYGONS;
diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp
index 4942e35b6..54627ba86 100644
--- a/src/libslic3r/ModelArrange.cpp
+++ b/src/libslic3r/ModelArrange.cpp
@@ -574,7 +574,7 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
 
             for(ModelInstance* objinst : objptr->instances) {
                 if(objinst) {
-                    ClipperLib::PolygonImpl pn;
+                    ClipperLib::Polygon pn;
                     pn.Contour = clpath;
 
                     // Efficient conversion to item.
diff --git a/src/libslic3r/PrintExport.hpp b/src/libslic3r/PrintExport.hpp
index cdb2cc008..04b993a52 100644
--- a/src/libslic3r/PrintExport.hpp
+++ b/src/libslic3r/PrintExport.hpp
@@ -42,8 +42,9 @@ template<FilePrinterFormat format>
 class FilePrinter {
 public:
 
-    // Draw an ExPolygon which is a polygon inside a slice on the specified layer.
+    // Draw a polygon which is a polygon inside a slice on the specified layer.
     void draw_polygon(const ExPolygon& p, unsigned lyr);
+    void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr);
 
     // Tell the printer how many layers should it consider.
     void layers(unsigned layernum);
@@ -221,6 +222,11 @@ public:
         m_layers_rst[lyr].raster.draw(p);
     }
 
+    inline void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr) {
+        assert(lyr < m_layers_rst.size());
+        m_layers_rst[lyr].raster.draw(p);
+    }
+
     inline void begin_layer(unsigned lyr) {
         if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1);
         m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_o);
diff --git a/src/libslic3r/Rasterizer/Rasterizer.cpp b/src/libslic3r/Rasterizer/Rasterizer.cpp
index c28e11352..496a584a2 100644
--- a/src/libslic3r/Rasterizer/Rasterizer.cpp
+++ b/src/libslic3r/Rasterizer/Rasterizer.cpp
@@ -1,5 +1,6 @@
 #include "Rasterizer.hpp"
 #include <ExPolygon.hpp>
+#include <libnest2d/backends/clipper/clipper_polygon.hpp>
 
 // For rasterizing
 #include <agg/agg_basics.h>
@@ -89,6 +90,25 @@ public:
         agg::render_scanlines(ras, scanlines, m_renderer);
     }
 
+    void draw(const ClipperLib::Polygon &poly) {
+        agg::rasterizer_scanline_aa<> ras;
+        agg::scanline_p8 scanlines;
+
+        auto&& path = to_path(poly.Contour);
+
+        if(m_o == Origin::TOP_LEFT) flipy(path);
+
+        ras.add_path(path);
+
+        for(auto h : poly.Holes) {
+            auto&& holepath = to_path(h);
+            if(m_o == Origin::TOP_LEFT) flipy(holepath);
+            ras.add_path(holepath);
+        }
+
+        agg::render_scanlines(ras, scanlines, m_renderer);
+    }
+
     inline void clear() {
         m_raw_renderer.clear(ColorBlack);
     }
@@ -108,14 +128,36 @@ private:
         return p(1) * SCALING_FACTOR/m_pxdim.h_mm;
     }
 
-    agg::path_storage to_path(const Polygon& poly) {
+    agg::path_storage to_path(const Polygon& poly)
+    {
         agg::path_storage path;
+
         auto it = poly.points.begin();
         path.move_to(getPx(*it), getPy(*it));
-        while(++it != poly.points.end())
+        while(++it != poly.points.end()) path.line_to(getPx(*it), getPy(*it));
+        path.line_to(getPx(poly.points.front()), getPy(poly.points.front()));
+
+        return path;
+    }
+
+
+    double getPx(const ClipperLib::IntPoint& p) {
+        return p.X * SCALING_FACTOR/m_pxdim.w_mm;
+    }
+
+    double getPy(const ClipperLib::IntPoint& p) {
+        return p.Y * SCALING_FACTOR/m_pxdim.h_mm;
+    }
+
+    agg::path_storage to_path(const ClipperLib::Path& poly)
+    {
+        agg::path_storage path;
+        auto it = poly.begin();
+        path.move_to(getPx(*it), getPy(*it));
+        while(++it != poly.end())
             path.line_to(getPx(*it), getPy(*it));
 
-        path.line_to(getPx(poly.points.front()), getPy(poly.points.front()));
+        path.line_to(getPx(poly.front()), getPy(poly.front()));
         return path;
     }
 
@@ -167,9 +209,13 @@ void Raster::clear()
     m_impl->clear();
 }
 
-void Raster::draw(const ExPolygon &poly)
+void Raster::draw(const ExPolygon &expoly)
+{
+    m_impl->draw(expoly);
+}
+
+void Raster::draw(const ClipperLib::Polygon &poly)
 {
-    assert(m_impl);
     m_impl->draw(poly);
 }
 
diff --git a/src/libslic3r/Rasterizer/Rasterizer.hpp b/src/libslic3r/Rasterizer/Rasterizer.hpp
index 768488adc..a8c8e1866 100644
--- a/src/libslic3r/Rasterizer/Rasterizer.hpp
+++ b/src/libslic3r/Rasterizer/Rasterizer.hpp
@@ -6,6 +6,8 @@
 #include <vector>
 #include <cstdint>
 
+namespace ClipperLib { class Polygon; }
+
 namespace Slic3r {
 
 class ExPolygon;
@@ -123,6 +125,7 @@ public:
 
     /// Draw a polygon with holes.
     void draw(const ExPolygon& poly);
+    void draw(const ClipperLib::Polygon& poly);
 
     /// Save the raster on the specified stream.
     void save(std::ostream& stream, Compression comp = Compression::RAW);
diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp
index ee24cf3ec..424881491 100644
--- a/src/libslic3r/SLAPrint.cpp
+++ b/src/libslic3r/SLAPrint.cpp
@@ -12,6 +12,9 @@
 #include <boost/filesystem/path.hpp>
 #include <boost/log/trivial.hpp>
 
+// For geometry algorithms with native Clipper types (no copies and conversions)
+#include <libnest2d/backends/clipper/geometries.hpp>
+
 //#include <tbb/spin_mutex.h>//#include "tbb/mutex.h"
 
 #include "I18N.hpp"
@@ -43,8 +46,7 @@ const std::array<unsigned, slaposCount>     OBJ_STEP_LEVELS =
     30,     // slaposSupportPoints,
     25,     // slaposSupportTree,
     25,     // slaposBasePool,
-    5,      // slaposSliceSupports,
-    5       // slaposIndexSlices
+    10,      // slaposSliceSupports,
 };
 
 const std::array<std::string, slaposCount> OBJ_STEP_LABELS =
@@ -54,22 +56,19 @@ const std::array<std::string, slaposCount> OBJ_STEP_LABELS =
     L("Generating support tree"),       // slaposSupportTree,
     L("Generating pad"),                // slaposBasePool,
     L("Slicing supports"),              // slaposSliceSupports,
-    L("Slicing supports")               // slaposIndexSlices,
 };
 
 // Should also add up to 100 (%)
 const std::array<unsigned, slapsCount> PRINT_STEP_LEVELS =
 {
     5,      // slapsStats
-    94,     // slapsRasterize
-    1,      // slapsValidate
+    95,     // slapsRasterize
 };
 
 const std::array<std::string, slapsCount> PRINT_STEP_LABELS =
 {
-    L("Calculating statistics"),     // slapsStats
+    L("Merging slices and calculating statistics"),     // slapsStats
     L("Rasterizing layers"),         // slapsRasterize
-    L("Validating"),                 // slapsValidate
 };
 
 }
@@ -206,7 +205,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
                 model_object_status.emplace(model_object->id(), ModelObjectStatus::Old);
         } 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(slapsRasterize));
+            update_apply_status(this->invalidate_step(slapsMergeSlicesAndEval));
             for (const ModelObject *model_object : m_model.objects)
                 model_object_status.emplace(model_object->id(), ModelObjectStatus::Old);
             for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) {
@@ -218,7 +217,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
             // Reorder the objects, add new objects.
             // First stop background processing before shuffling or deleting the PrintObjects in the object list.
             this->call_cancel_callback();
-            update_apply_status(this->invalidate_step(slapsRasterize));
+            update_apply_status(this->invalidate_step(slapsMergeSlicesAndEval));
             // Second create a new list of objects.
             std::vector<ModelObject*> model_objects_old(std::move(m_model.objects));
             m_model.objects.clear();
@@ -390,7 +389,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
 				if (new_instances != it_print_object_status->print_object->instances()) {
 					// Instances changed.
 					it_print_object_status->print_object->set_instances(new_instances);
-					update_apply_status(this->invalidate_step(slapsRasterize));
+                    update_apply_status(this->invalidate_step(slapsMergeSlicesAndEval));
 				}
 				print_objects_new.emplace_back(it_print_object_status->print_object);
 				const_cast<PrintObjectStatus&>(*it_print_object_status).status = PrintObjectStatus::Reused;
@@ -579,11 +578,6 @@ sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) {
 
     return pcfg;
 }
-
-void swapXY(ExPolygon& expoly) {
-    for(auto& p : expoly.contour.points) std::swap(p(X), p(Y));
-    for(auto& h : expoly.holes) for(auto& p : h.points) std::swap(p(X), p(Y));
-}
 }
 
 std::string SLAPrint::validate() const
@@ -613,6 +607,18 @@ std::string SLAPrint::validate() const
     return "";
 }
 
+bool SLAPrint::invalidate_step(SLAPrintStep step)
+{
+    bool invalidated = Inherited::invalidate_step(step);
+
+    // propagate to dependent steps
+    if (step == slapsMergeSlicesAndEval) {
+        invalidated |= this->invalidate_all_steps();
+    }
+
+    return invalidated;
+}
+
 template<class...Args>
 void report_status(SLAPrint& p, int st, const std::string& msg, Args&&...args)
 {
@@ -639,7 +645,7 @@ void SLAPrint::process()
     const size_t objcount = m_objects.size();
 
     const unsigned min_objstatus = 0;   // where the per object operations start
-    const unsigned max_objstatus = PRINT_STEP_LEVELS[slapsRasterize];  // where the per object operations end
+    const unsigned max_objstatus = PRINT_STEP_LEVELS[slapsMergeSlicesAndEval];  // where the per object operations end
 
     // the coefficient that multiplies the per object status values which
     // are set up for <0, 100>. They need to be scaled into the whole process
@@ -883,7 +889,7 @@ void SLAPrint::process()
     // Slicing the support geometries similarly to the model slicing procedure.
     // If the pad had been added previously (see step "base_pool" than it will
     // be part of the slices)
-    auto slice_supports = [](SLAPrintObject& po) {
+    auto slice_supports = [this](SLAPrintObject& po) {
         auto& sd = po.m_supportdata;
 
         if(sd) sd->support_slices.clear();
@@ -906,28 +912,14 @@ void SLAPrint::process()
         {
             po.m_slice_index[i].set_support_slice_idx(po, i);
         }
-    };
 
-    // We have the layer polygon collection but we need to unite them into
-    // an index where the key is the height level in discrete levels (clipper)
-    auto index_slices = [this/*, ilhd*/](SLAPrintObject& /*po*/) {
         // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update status to the 3D preview to load the SLA slices.
         report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
     };
 
-    auto fillstats = [this]() {
-
-        m_print_statistics.clear();
-
-        // Fill statistics
-        fill_statistics();
-
-        report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
-    };
-
-    // Rasterizing the model objects, and their supports
-    auto rasterize = [this, max_objstatus, ilhs]() {
-        if(canceled()) return;
+    // Merging the slices from all the print objects into one slice grid and
+    // calculating print statistics from the merge result.
+    auto merge_slices_and_eval_stats = [this, ilhs]() {
 
         // clear the rasterizer input
         m_printer_input.clear();
@@ -962,6 +954,272 @@ void SLAPrint::process()
             }
         }
 
+        m_print_statistics.clear();
+
+        using ClipperPoint  = ClipperLib::IntPoint;
+        using ClipperPolygon = ClipperLib::Polygon; // see clipper_polygon.hpp in libnest2d
+        using ClipperPolygons = std::vector<ClipperPolygon>;
+        namespace sl = libnest2d::shapelike;    // For algorithms
+
+        // If the raster has vertical orientation, we will flip the coordinates
+        bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait;
+
+        // Set up custom union and diff functions for clipper polygons
+        auto polyunion = [] (const ClipperPolygons& subjects)
+        {
+            ClipperLib::Clipper clipper;
+
+            bool closed = true;
+
+            for(auto& path : subjects) {
+                clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
+                clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
+            }
+
+            auto mode = ClipperLib::pftPositive;
+
+            return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode);
+        };
+
+        auto polydiff = [](const ClipperPolygons& subjects, const ClipperPolygons& clips)
+        {
+            ClipperLib::Clipper clipper;
+
+            bool closed = true;
+
+            for(auto& path : subjects) {
+                clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
+                clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
+            }
+
+            for(auto& path : clips) {
+                clipper.AddPath(path.Contour, ClipperLib::ptClip, closed);
+                clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed);
+            }
+
+            auto mode = ClipperLib::pftPositive;
+
+            return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode);
+        };
+
+        // libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise
+        auto areafn = [](const ClipperPolygon& poly) { return - sl::area(poly); };
+
+        const double area_fill          = m_printer_config.area_fill.getFloat()*0.01;// 0.5 (50%);
+        const double fast_tilt          = m_printer_config.fast_tilt_time.getFloat();// 5.0;
+        const double slow_tilt          = m_printer_config.slow_tilt_time.getFloat();// 8.0;
+
+        const double init_exp_time      = m_material_config.initial_exposure_time.getFloat();
+        const double exp_time           = m_material_config.exposure_time.getFloat();
+
+        const int    fade_layers_cnt    = m_default_object_config.faded_layers.getInt();// 10 // [3;20]
+
+        const double width              = m_printer_config.display_width.getFloat() / SCALING_FACTOR;
+        const double height             = m_printer_config.display_height.getFloat() / SCALING_FACTOR;
+        const double display_area       = width*height;
+
+        // get polygons for all instances in the object
+        auto get_all_polygons =
+                [flpXY](const ExPolygons& input_polygons,
+                        const std::vector<SLAPrintObject::Instance>& instances)
+        {
+            ClipperPolygons polygons;
+            polygons.reserve(input_polygons.size() * instances.size());
+
+            for (const ExPolygon& polygon : input_polygons) {
+                if(polygon.contour.empty()) continue;
+
+                for (size_t i = 0; i < instances.size(); ++i)
+                {
+                    ClipperPolygon poly;
+
+                    // should be a move
+                    poly.Contour.reserve(polygon.contour.size() + 1);
+
+                    for(auto& p : polygon.contour.points)
+                        poly.Contour.emplace_back(p.x(), p.y());
+
+                    auto pfirst = poly.Contour.front();
+                    poly.Contour.emplace_back(pfirst);
+
+                    for(auto& h : polygon.holes) {
+                        poly.Holes.emplace_back();
+                        auto& hole = poly.Holes.back();
+                        hole.reserve(h.points.size() + 1);
+
+                        for(auto& p : h.points) hole.emplace_back(p.x(), p.y());
+                        auto pfirst = hole.front(); hole.emplace_back(pfirst);
+                    }
+
+                    sl::rotate(poly, double(instances[i].rotation));
+                    sl::translate(poly, ClipperPoint{instances[i].shift(X),
+                                                     instances[i].shift(Y)});
+                    if (flpXY) {
+                        for(auto& p : poly.Contour) std::swap(p.X, p.Y);
+                        std::reverse(poly.Contour.begin(), poly.Contour.end());
+
+                        for(auto& h : poly.Holes) {
+                            for(auto& p : h) std::swap(p.X, p.Y);
+                            std::reverse(h.begin(), h.end());
+                        }
+                    }
+
+                    polygons.emplace_back(std::move(poly));
+                }
+            }
+            return polygons;
+        };
+
+        double supports_volume(0.0);
+        double models_volume(0.0);
+
+        double estim_time(0.0);
+
+        size_t slow_layers = 0;
+        size_t fast_layers = 0;
+
+        const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1);
+        double fade_layer_time = init_exp_time;
+
+        SpinMutex mutex;
+        using Lock = std::lock_guard<SpinMutex>;
+
+        // Going to parallel:
+        auto printlayerfn = [this,
+                // functions and read only vars
+                get_all_polygons, polyunion, polydiff, areafn,
+                area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time,
+
+                // write vars
+                &mutex, &models_volume, &supports_volume, &estim_time, &slow_layers,
+                &fast_layers, &fade_layer_time](size_t sliced_layer_cnt)
+        {
+            PrintLayer& layer = m_printer_input[sliced_layer_cnt];
+
+            // vector of slice record references
+            auto& slicerecord_references = layer.slices();
+
+            if(slicerecord_references.empty()) return;
+
+            // Layer height should match for all object slices for a given level.
+            const auto l_height = double(slicerecord_references.front().get().layer_height());
+
+            // Calculation of the consumed material
+
+            ClipperPolygons model_polygons;
+            ClipperPolygons supports_polygons;
+
+            size_t c = std::accumulate(layer.slices().begin(), layer.slices().end(), 0u, [](size_t a, const SliceRecord& sr) {
+                                           return a + sr.get_slice(soModel).size();
+                                       });
+
+            model_polygons.reserve(c);
+
+            c = std::accumulate(layer.slices().begin(), layer.slices().end(), 0u, [](size_t a, const SliceRecord& sr) {
+                                    return a + sr.get_slice(soModel).size();
+                                });
+
+            supports_polygons.reserve(c);
+
+            for(const SliceRecord& record : layer.slices()) {
+                const SLAPrintObject *po = record.print_obj();
+
+                const ExPolygons &modelslices = record.get_slice(soModel);
+                if (!modelslices.empty()) {
+                    ClipperPolygons v = get_all_polygons(modelslices, po->instances());
+                    for(ClipperPolygon& p_tmp : v) model_polygons.emplace_back(std::move(p_tmp));
+                }
+
+                const ExPolygons &supportslices = record.get_slice(soSupport);
+                if (!supportslices.empty()) {
+                    ClipperPolygons v = get_all_polygons(supportslices, po->instances());
+                    for(ClipperPolygon& p_tmp : v) supports_polygons.emplace_back(std::move(p_tmp));
+                }
+            }
+
+            model_polygons = polyunion(model_polygons);
+            double layer_model_area = 0;
+            for (const ClipperPolygon& polygon : model_polygons)
+                layer_model_area += areafn(polygon);
+
+            if (layer_model_area < 0 || layer_model_area > 0) {
+                Lock lck(mutex); models_volume += layer_model_area * l_height;
+            }
+
+            if(!supports_polygons.empty()) {
+                if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons);
+                else supports_polygons = polydiff(supports_polygons, model_polygons);
+                // allegedly, union of subject is done withing the diff according to the pftPositive polyFillType
+            }
+
+            double layer_support_area = 0;
+            for (const ClipperPolygon& polygon : supports_polygons)
+                layer_support_area += areafn(polygon);
+
+            if (layer_support_area < 0 || layer_support_area > 0) {
+                Lock lck(mutex); supports_volume += layer_support_area * l_height;
+            }
+
+            // Here we can save the expensively calculated polygons for printing
+            ClipperPolygons trslices;
+            trslices.reserve(model_polygons.size() + supports_polygons.size());
+            for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly));
+            for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly));
+
+            layer.transformed_slices(polyunion(trslices));
+
+            // Calculation of the slow and fast layers to the future controlling those values on FW
+
+            const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill;
+            const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt;
+
+            { Lock lck(mutex);
+                if (is_fast_layer)
+                    fast_layers++;
+                else
+                    slow_layers++;
+
+
+                // Calculation of the printing time
+
+                if (sliced_layer_cnt < 3)
+                    estim_time += init_exp_time;
+                else if (fade_layer_time > exp_time)
+                {
+                    fade_layer_time -= delta_fade_time;
+                    estim_time += fade_layer_time;
+                }
+                else
+                    estim_time += exp_time;
+
+                estim_time += tilt_time;
+            }
+        };
+
+        // sequential version for debugging:
+        // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i);
+        tbb::parallel_for<size_t, decltype(printlayerfn)>(0, m_printer_input.size(), printlayerfn);
+
+        m_print_statistics.support_used_material = supports_volume * SCALING_FACTOR * SCALING_FACTOR;
+        m_print_statistics.objects_used_material = models_volume  * SCALING_FACTOR * SCALING_FACTOR;
+
+        // Estimated printing time
+        // A layers count o the highest object
+        if (m_printer_input.size() == 0)
+            m_print_statistics.estimated_print_time = "N/A";
+        else
+            m_print_statistics.estimated_print_time = get_time_dhms(float(estim_time));
+
+        m_print_statistics.fast_layers_count = fast_layers;
+        m_print_statistics.slow_layers_count = slow_layers;
+
+        report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
+    };
+
+    // Rasterizing the model objects, and their supports
+    auto rasterize = [this, max_objstatus]() {
+        if(canceled()) return;
+
         // collect all the keys
 
         // If the raster has vertical orientation, we will flip the coordinates
@@ -1005,7 +1263,7 @@ void SLAPrint::process()
 
         // procedure to process one height level. This will run in parallel
         auto lvlfn =
-        [this, &slck, &printer, slot, sd, ist, &pst, flpXY]
+        [this, &slck, &printer, slot, sd, ist, &pst]
             (unsigned level_id)
         {
             if(canceled()) return;
@@ -1015,28 +1273,8 @@ void SLAPrint::process()
             // Switch to the appropriate layer in the printer
             printer.begin_layer(level_id);
 
-            using Instance = SLAPrintObject::Instance;
-
-            auto draw =
-                [&printer, flpXY, level_id](ExPolygon& poly, const Instance& tr)
-            {
-                poly.rotate(double(tr.rotation));
-                poly.translate(tr.shift(X), tr.shift(Y));
-                if(flpXY) swapXY(poly);
+            for(const ClipperLib::Polygon& poly : printlayer.transformed_slices())
                 printer.draw_polygon(poly, level_id);
-            };
-
-            for(const SliceRecord& sr : printlayer.slices()) {
-                if(! sr.print_obj()) continue;
-
-                for(const Instance& inst : sr.print_obj()->instances()) {
-                    ExPolygons objsl = sr.get_slice(soModel);
-                    for(ExPolygon& poly : objsl) draw(poly, inst);
-
-                    ExPolygons supsl = sr.get_slice(soSupport);
-                    for(ExPolygon& poly : supsl) draw(poly, inst);
-                }
-            }
 
             // Finish the layer for later saving it.
             printer.finish_layer(level_id);
@@ -1079,15 +1317,13 @@ void SLAPrint::process()
         support_points,
         support_tree,
         base_pool,
-        slice_supports,
-        index_slices
+        slice_supports
     };
 
     std::array<slapsFn, slapsCount> print_program =
     {
-        fillstats,
-        rasterize,
-        [](){}  // validate
+        merge_slices_and_eval_stats,
+        rasterize
     };
 
     unsigned st = min_objstatus;
@@ -1127,7 +1363,7 @@ void SLAPrint::process()
     }
 
     std::array<SLAPrintStep, slapsCount> printsteps = {
-        slapsStats, slapsRasterize, slapsValidate
+        slapsMergeSlicesAndEval, slapsRasterize
     };
 
     // this would disable the rasterization step
@@ -1193,11 +1429,11 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
         if (steps_rasterize.find(opt_key) != steps_rasterize.end()) {
             // These options only affect the final rasterization, or they are just notes without influence on the output,
             // so there is nothing to invalidate.
-            steps.emplace_back(slapsRasterize);
+            steps.emplace_back(slapsMergeSlicesAndEval);
         } else if (steps_ignore.find(opt_key) != steps_ignore.end()) {
             // These steps have no influence on the output. Just ignore them.
         } else if (opt_key == "initial_layer_height") {
-            steps.emplace_back(slapsRasterize);
+            steps.emplace_back(slapsMergeSlicesAndEval);
             osteps.emplace_back(slaposObjectSlice);
         } else {
             // All values should be covered.
@@ -1215,165 +1451,6 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
     return invalidated;
 }
 
-void SLAPrint::fill_statistics()
-{
-    const double init_layer_height  = m_material_config.initial_layer_height.getFloat();
-    const double layer_height       = m_default_object_config.layer_height.getFloat();
-
-    const double area_fill          = m_printer_config.area_fill.getFloat()*0.01;// 0.5 (50%);
-    const double fast_tilt          = m_printer_config.fast_tilt_time.getFloat();// 5.0;
-    const double slow_tilt          = m_printer_config.slow_tilt_time.getFloat();// 8.0;
-
-    const double init_exp_time      = m_material_config.initial_exposure_time.getFloat();
-    const double exp_time           = m_material_config.exposure_time.getFloat();
-
-    const int    fade_layers_cnt    = m_default_object_config.faded_layers.getInt();// 10 // [3;20]
-
-    const double width              = m_printer_config.display_width.getFloat() / SCALING_FACTOR;
-    const double height             = m_printer_config.display_height.getFloat() / SCALING_FACTOR;
-    const double display_area       = width*height;
-
-    // get polygons for all instances in the object
-    auto get_all_polygons = [](const ExPolygons& input_polygons, const std::vector<SLAPrintObject::Instance>& instances) {
-        const size_t inst_cnt = instances.size();
-
-        size_t polygon_cnt = 0;
-        for (const ExPolygon& polygon : input_polygons)
-			polygon_cnt += polygon.holes.size() + 1;
-
-        Polygons polygons;
-        polygons.reserve(polygon_cnt * inst_cnt);
-        for (const ExPolygon& polygon : input_polygons) {
-            for (size_t i = 0; i < inst_cnt; ++i)
-            {
-                ExPolygon tmp = polygon;
-                tmp.rotate(double(instances[i].rotation));
-                tmp.translate(instances[i].shift.x(), instances[i].shift.y());
-                polygons_append(polygons, to_polygons(std::move(tmp)));
-            }
-        }
-        return polygons;
-    };
-
-    double supports_volume = 0.0;
-    double models_volume = 0.0;
-
-    double estim_time = 0.0;
-
-    size_t slow_layers = 0;
-    size_t fast_layers = 0;
-
-    // find highest object
-    // Which is a better bet? To compare by max_z or by number of layers in the index?
-    // float max_z = 0.;
-	size_t max_layers_cnt = 0;
-    size_t highest_obj_idx = 0;
-	for (SLAPrintObject *&po : m_objects) {
-        auto& slice_index = po->get_slice_index();
-        if (! slice_index.empty()) {
-            // float z = (-- slice_index.end())->slice_level();
-            size_t cnt = slice_index.size();
-            //if (z > max_z) {
-            if (cnt > max_layers_cnt) {
-                max_layers_cnt = cnt;
-                // max_z = z;
-                highest_obj_idx = &po - &m_objects.front();
-            }
-        }
-    }
-
-    const SLAPrintObject * highest_obj = m_objects[highest_obj_idx];
-    auto& highest_obj_slice_index = highest_obj->get_slice_index();
-
-    const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1);
-    double fade_layer_time = init_exp_time;
-
-    int sliced_layer_cnt = 0;
-    for (const SliceRecord& layer : highest_obj_slice_index)
-    {
-        const auto l_height = double(layer.layer_height());
-
-        // Calculation of the consumed material 
-
-        Polygons model_polygons;
-        Polygons supports_polygons;
-
-        for (SLAPrintObject * po : m_objects)
-        {
-            const SliceRecord *record = nullptr;
-            {
-                const SliceRecord& slr = po->closest_slice_to_slice_level(layer.slice_level(), float(EPSILON));
-                if (!slr.is_valid()) continue;
-                record = &slr;
-            }
-
-            const ExPolygons &modelslices = record->get_slice(soModel);
-            if (!modelslices.empty())
-                append(model_polygons, get_all_polygons(modelslices, po->instances()));
-
-            const ExPolygons &supportslices = record->get_slice(soSupport);
-            if (!supportslices.empty())
-                append(supports_polygons, get_all_polygons(supportslices, po->instances()));
-        }
-        
-        model_polygons = union_(model_polygons);
-        double layer_model_area = 0;
-        for (const Polygon& polygon : model_polygons)
-            layer_model_area += polygon.area();
-
-        if (layer_model_area != 0)
-            models_volume += layer_model_area * l_height;
-
-        if (!supports_polygons.empty() && !model_polygons.empty())
-            supports_polygons = diff(supports_polygons, model_polygons);
-        double layer_support_area = 0;
-        for (const Polygon& polygon : supports_polygons)
-            layer_support_area += polygon.area();
-
-        if (layer_support_area != 0)
-            supports_volume += layer_support_area * l_height;
-
-        // Calculation of the slow and fast layers to the future controlling those values on FW
-
-        const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill;
-        const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt;
-        if (is_fast_layer)
-            fast_layers++;
-        else
-            slow_layers++;
-
-
-        // Calculation of the printing time
-
-        if (sliced_layer_cnt < 3)
-            estim_time += init_exp_time;
-        else if (fade_layer_time > exp_time)
-        {
-            fade_layer_time -= delta_fade_time;
-            estim_time += fade_layer_time;
-        }
-        else
-            estim_time += exp_time;
-
-        estim_time += tilt_time;
-
-        sliced_layer_cnt++;
-    }
-
-    m_print_statistics.support_used_material = supports_volume * SCALING_FACTOR * SCALING_FACTOR; 
-    m_print_statistics.objects_used_material = models_volume  * SCALING_FACTOR * SCALING_FACTOR;
-
-    // Estimated printing time
-    // A layers count o the highest object 
-    if (max_layers_cnt == 0)
-        m_print_statistics.estimated_print_time = "N/A";
-    else
-        m_print_statistics.estimated_print_time = get_time_dhms(float(estim_time));
-
-    m_print_statistics.fast_layers_count = fast_layers;
-    m_print_statistics.slow_layers_count = slow_layers;
-}
-
 // Returns true if an object step is done on all objects and there's at least one object.
 bool SLAPrint::is_step_done(SLAPrintObjectStep step) const
 {
@@ -1459,19 +1536,16 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step)
     if (step == slaposObjectSlice) {
         invalidated |= this->invalidate_all_steps();
     } else if (step == slaposSupportPoints) {
-        invalidated |= this->invalidate_steps({ slaposSupportTree, slaposBasePool, slaposSliceSupports, slaposIndexSlices });
-        invalidated |= m_print->invalidate_step(slapsRasterize);
+        invalidated |= this->invalidate_steps({ slaposSupportTree, slaposBasePool, slaposSliceSupports });
+        invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
     } else if (step == slaposSupportTree) {
-        invalidated |= this->invalidate_steps({ slaposBasePool, slaposSliceSupports, slaposIndexSlices });
-        invalidated |= m_print->invalidate_step(slapsRasterize);
+        invalidated |= this->invalidate_steps({ slaposBasePool, slaposSliceSupports });
+        invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
     } else if (step == slaposBasePool) {
-        invalidated |= this->invalidate_steps({slaposSliceSupports, slaposIndexSlices});
-        invalidated |= m_print->invalidate_step(slapsRasterize);
+        invalidated |= this->invalidate_steps({slaposSliceSupports});
+        invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
     } else if (step == slaposSliceSupports) {
-        invalidated |= this->invalidate_step(slaposIndexSlices);
-        invalidated |= m_print->invalidate_step(slapsRasterize);
-    } else if(step == slaposIndexSlices) {
-        invalidated |= m_print->invalidate_step(slapsRasterize);
+        invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
     }
     return invalidated;
 }
@@ -1547,18 +1621,6 @@ const ExPolygons &SliceRecord::get_slice(SliceOrigin o) const
     return idx >= v.size() ? EMPTY_SLICE : v[idx];
 }
 
-const std::vector<SliceRecord> & SLAPrintObject::get_slice_index() const
-{
-    // assert(is_step_done(slaposIndexSlices));
-    return m_slice_index;
-}
-
-const std::vector<ExPolygons> &SLAPrintObject::get_model_slices() const
-{
-    // assert(is_step_done(slaposObjectSlice));
-    return m_model_slices;
-}
-
 bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const
 {
     switch (step) {
diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp
index d831908c7..9cf826097 100644
--- a/src/libslic3r/SLAPrint.hpp
+++ b/src/libslic3r/SLAPrint.hpp
@@ -6,14 +6,14 @@
 #include "PrintExport.hpp"
 #include "Point.hpp"
 #include "MTUtils.hpp"
+#include <libnest2d/backends/clipper/clipper_polygon.hpp>
 #include "Zipper.hpp"
 
 namespace Slic3r {
 
 enum SLAPrintStep : unsigned int {
-    slapsStats,
-	slapsRasterize,
-	slapsValidate,
+    slapsMergeSlicesAndEval,
+    slapsRasterize,
 	slapsCount
 };
 
@@ -22,8 +22,7 @@ enum SLAPrintObjectStep : unsigned int {
 	slaposSupportPoints,
 	slaposSupportTree,
 	slaposBasePool,
-	slaposSliceSupports,
-    slaposIndexSlices,
+    slaposSliceSupports,
 	slaposCount
 };
 
@@ -127,7 +126,7 @@ public:
 
         bool is_valid() const { return ! std::isnan(m_slice_z); }
 
-        const SLAPrintObject* print_obj() const { return m_po; }
+        const SLAPrintObject* print_obj() const { assert(m_po); return m_po; }
 
         // Methods for setting the indices into the slice vectors.
         void set_model_slice_idx(const SLAPrintObject &po, size_t id) {
@@ -190,7 +189,7 @@ private:
         return it;
     }
 
-    const std::vector<ExPolygons>& get_model_slices() const;
+    const std::vector<ExPolygons>& get_model_slices() const { return m_model_slices; }
     const std::vector<ExPolygons>& get_support_slices() const;
 
 public:
@@ -205,7 +204,9 @@ public:
     // /////////////////////////////////////////////////////////////////////////
 
     // Retrieve the slice index.
-    const std::vector<SliceRecord>& get_slice_index() const;
+    const std::vector<SliceRecord>& get_slice_index() const {
+        return m_slice_index;
+    }
 
     // Search slice index for the closest slice to given print_level.
     // max_epsilon gives the allowable deviation of the returned slice record's
@@ -367,31 +368,6 @@ private: // Prevents erroneous use by other classes.
 
 public:
 
-    // An aggregation of SliceRecord-s from all the print objects for each
-    // occupied layer. Slice record levels dont have to match exactly.
-    // They are unified if the level difference is within +/- SCALED_EPSILON
-    class PrintLayer {
-        coord_t m_level;
-
-        // The collection of slice records for the current level.
-        std::vector<std::reference_wrapper<const SliceRecord>> m_slices;
-
-    public:
-
-        explicit PrintLayer(coord_t lvl) : m_level(lvl) {}
-
-        // for being sorted in their container (see m_printer_input)
-        bool operator<(const PrintLayer& other) const {
-            return m_level < other.m_level;
-        }
-
-        void add(const SliceRecord& sr) { m_slices.emplace_back(sr); }
-
-        coord_t level() const { return m_level; }
-
-        auto slices() const -> const decltype (m_slices)& { return m_slices; }
-    };
-
     SLAPrint(): m_stepmask(slapsCount, true) {}
 
     virtual ~SLAPrint() override { this->clear(); }
@@ -407,7 +383,7 @@ public:
     // Returns true if an object step is done on all objects and there's at least one object.    
     bool                is_step_done(SLAPrintObjectStep step) const;
     // Returns true if the last step was finished with success.
-	bool                finished() const override { return this->is_step_done(slaposIndexSlices) && this->Inherited::is_step_done(slapsRasterize); }
+    bool                finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); }
 
     template<class Fmt = SLAminzZipper>
     void export_raster(const std::string& fname) {
@@ -427,6 +403,43 @@ public:
 
     std::string validate() const override;
 
+    // An aggregation of SliceRecord-s from all the print objects for each
+    // occupied layer. Slice record levels dont have to match exactly.
+    // They are unified if the level difference is within +/- SCALED_EPSILON
+    class PrintLayer {
+        coord_t m_level;
+
+        // The collection of slice records for the current level.
+        std::vector<std::reference_wrapper<const SliceRecord>> m_slices;
+
+        std::vector<ClipperLib::Polygon> m_transformed_slices;
+
+        template<class Container> void transformed_slices(Container&& c) {
+            m_transformed_slices = std::forward<Container>(c);
+        }
+
+        friend void SLAPrint::process();
+
+    public:
+
+        explicit PrintLayer(coord_t lvl) : m_level(lvl) {}
+
+        // for being sorted in their container (see m_printer_input)
+        bool operator<(const PrintLayer& other) const {
+            return m_level < other.m_level;
+        }
+
+        void add(const SliceRecord& sr) { m_slices.emplace_back(sr); }
+
+        coord_t level() const { return m_level; }
+
+        auto slices() const -> const decltype (m_slices)& { return m_slices; }
+
+        const std::vector<ClipperLib::Polygon> & transformed_slices() const {
+            return m_transformed_slices;
+        }
+    };
+
     // The aggregated and leveled print records from various objects.
     // TODO: use this structure for the preview in the future.
     const std::vector<PrintLayer>& print_layers() const { return m_printer_input; }
@@ -435,11 +448,12 @@ private:
     using SLAPrinter = FilePrinter<FilePrinterFormat::SLA_PNGZIP>;
     using SLAPrinterPtr = std::unique_ptr<SLAPrinter>;
 
+    // Implement same logic as in SLAPrintObject
+    bool invalidate_step(SLAPrintStep st);
+
     // Invalidate steps based on a set of parameters changed.
     bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
 
-    void fill_statistics();
-
     SLAPrintConfig                  m_print_config;
     SLAPrinterConfig                m_printer_config;
     SLAMaterialConfig               m_material_config;
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 5fd058ff7..4178dcb43 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -5022,7 +5022,7 @@ void GLCanvas3D::_render_sla_slices() const
         }
 
         if ((bottom_obj_triangles.empty() || bottom_sup_triangles.empty() || top_obj_triangles.empty() || top_sup_triangles.empty()) &&
-            obj->is_step_done(slaposIndexSlices) && !obj->get_slice_index().empty())
+            obj->is_step_done(slaposSliceSupports) && !obj->get_slice_index().empty())
         {
             double layer_height         = print->default_object_config().layer_height.value;
             double initial_layer_height = print->material_config().initial_layer_height.value;
@@ -6224,7 +6224,7 @@ void GLCanvas3D::_load_shells_sla()
     int obj_idx = 0;
     for (const SLAPrintObject* obj : print->objects())
     {
-        if (!obj->is_step_done(slaposIndexSlices))
+        if (!obj->is_step_done(slaposSliceSupports))
             continue;
 
         unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size();
diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp
index f41d148de..14d19e251 100644
--- a/src/slic3r/GUI/GUI_Preview.cpp
+++ b/src/slic3r/GUI/GUI_Preview.cpp
@@ -773,7 +773,7 @@ void Preview::load_print_as_sla()
     std::vector<double> zs;
     double initial_layer_height = print->material_config().initial_layer_height.value;
     for (const SLAPrintObject* obj : print->objects())
-        if (obj->is_step_done(slaposIndexSlices) && !obj->get_slice_index().empty())
+        if (obj->is_step_done(slaposSliceSupports) && !obj->get_slice_index().empty())
         {
             auto low_coord = obj->get_slice_index().front().print_level();
             for (auto& rec : obj->get_slice_index())