diff --git a/src/clipper/CMakeLists.txt b/src/clipper/CMakeLists.txt
index d6f3861dc..412ab53c7 100644
--- a/src/clipper/CMakeLists.txt
+++ b/src/clipper/CMakeLists.txt
@@ -4,4 +4,6 @@ cmake_minimum_required(VERSION 2.6)
 add_library(clipper STATIC
     clipper.cpp
     clipper.hpp
+    clipper_z.cpp
+    clipper_z.hpp
 )
diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp
index 228e0c6ef..b85cf9025 100644
--- a/src/clipper/clipper.cpp
+++ b/src/clipper/clipper.cpp
@@ -51,7 +51,11 @@
 #include <Shiny/Shiny.h>
 #include <libslic3r/Int128.hpp>
 
+#ifdef use_xyz
+namespace ClipperLib_Z {
+#else /* use_xyz */
 namespace ClipperLib {
+#endif /* use_xyz */
 
 static double const pi = 3.141592653589793238;
 static double const two_pi = pi *2;
@@ -1616,7 +1620,7 @@ void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2)
   else if (pt == e1.Top) pt.Z = e1.Top.Z;
   else if (pt == e2.Bot) pt.Z = e2.Bot.Z;
   else if (pt == e2.Top) pt.Z = e2.Top.Z;
-  else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt); 
+  else m_ZFill(e1.Bot, e1.Top, e2.Bot, e2.Top, pt);
 }
 //------------------------------------------------------------------------------
 #endif
diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp
index 8a28fe46f..8b34779e3 100644
--- a/src/clipper/clipper.hpp
+++ b/src/clipper/clipper.hpp
@@ -35,6 +35,7 @@
 #define clipper_hpp
 
 #include <inttypes.h>
+#include <functional>
 
 #define CLIPPER_VERSION "6.2.6"
 
@@ -56,7 +57,11 @@
 #include <functional>
 #include <queue>
 
+#ifdef use_xyz
+namespace ClipperLib_Z {
+#else /* use_xyz */
 namespace ClipperLib {
+#endif /* use_xyz */
 
 enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };
 enum PolyType { ptSubject, ptClip };
@@ -114,7 +119,7 @@ struct DoublePoint
 //------------------------------------------------------------------------------
 
 #ifdef use_xyz
-typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt);
+typedef std::function<void(const IntPoint& e1bot, const IntPoint& e1top, const IntPoint& e2bot, const IntPoint& e2top, IntPoint& pt)> ZFillCallback;
 #endif
 
 enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4};
diff --git a/src/clipper/clipper_z.cpp b/src/clipper/clipper_z.cpp
new file mode 100644
index 000000000..4a54ef346
--- /dev/null
+++ b/src/clipper/clipper_z.cpp
@@ -0,0 +1,7 @@
+// Hackish wrapper around the ClipperLib library to compile the Clipper library with the Z support.
+
+// Enable the Z coordinate support.
+#define use_xyz
+
+// and let it compile
+#include "clipper.cpp"
diff --git a/src/clipper/clipper_z.hpp b/src/clipper/clipper_z.hpp
new file mode 100644
index 000000000..0f31ac11c
--- /dev/null
+++ b/src/clipper/clipper_z.hpp
@@ -0,0 +1,18 @@
+// Hackish wrapper around the ClipperLib library to compile the Clipper library with the Z support.
+
+#ifndef clipper_z_hpp
+#ifdef clipper_hpp
+#error "You should include the clipper_z.hpp first"
+#endif
+
+#define clipper_z_hpp
+
+// Enable the Z coordinate support.
+#define use_xyz
+
+#include "clipper.hpp"
+
+#undef clipper_hpp
+#undef use_xyz
+
+#endif clipper_z_hpp
diff --git a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp
index 4bb2e72af..2df9a26c3 100644
--- a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp
+++ b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp
@@ -33,10 +33,16 @@ protected:
         // then it should be removed from the list
         auto it = c.begin();
         while (it != c.end() && !stopcond_()) {
-            Placer p{bin};
-            p.configure(pcfg);
+            
+            // WARNING: The copy of itm needs to be created before Placer.
+            // Placer is working with references and its destructor still
+            // manipulates the item this is why the order of stack creation
+            // matters here.            
             const Item& itm = *it;
             Item cpy{itm};
+            
+            Placer p{bin};
+            p.configure(pcfg);
             if (!p.pack(cpy)) it = c.erase(it);
             else it++;
         }
diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp
index 035fe3242..7f57b78af 100644
--- a/src/libslic3r/ExtrusionEntity.cpp
+++ b/src/libslic3r/ExtrusionEntity.cpp
@@ -12,44 +12,35 @@
 
 namespace Slic3r {
     
-void
-ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
+void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
 {
     this->_inflate_collection(intersection_pl(this->polyline, collection), retval);
 }
 
-void
-ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
+void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
 {
     this->_inflate_collection(diff_pl(this->polyline, collection), retval);
 }
 
-void
-ExtrusionPath::clip_end(double distance)
+void ExtrusionPath::clip_end(double distance)
 {
     this->polyline.clip_end(distance);
 }
 
-void
-ExtrusionPath::simplify(double tolerance)
+void ExtrusionPath::simplify(double tolerance)
 {
     this->polyline.simplify(tolerance);
 }
 
-double
-ExtrusionPath::length() const
+double ExtrusionPath::length() const
 {
     return this->polyline.length();
 }
 
-void
-ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const
+void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const
 {
-    for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) {
-        ExtrusionPath* path = this->clone();
-        path->polyline = *it;
-        collection->entities.push_back(path);
-    }
+    for (const Polyline &polyline : polylines)
+        collection->entities.emplace_back(new ExtrusionPath(polyline, *this));
 }
 
 void ExtrusionPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
@@ -67,36 +58,36 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale
 
 void ExtrusionMultiPath::reverse()
 {
-    for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path)
-        path->reverse();
+    for (ExtrusionPath &path : this->paths)
+        path.reverse();
     std::reverse(this->paths.begin(), this->paths.end());
 }
 
 double ExtrusionMultiPath::length() const
 {
     double len = 0;
-    for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
-        len += path->polyline.length();
+    for (const ExtrusionPath &path : this->paths)
+        len += path.polyline.length();
     return len;
 }
 
 void ExtrusionMultiPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
 {
-    for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
-        path->polygons_covered_by_width(out, scaled_epsilon);
+    for (const ExtrusionPath &path : this->paths)
+        path.polygons_covered_by_width(out, scaled_epsilon);
 }
 
 void ExtrusionMultiPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
 {
-    for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
-        path->polygons_covered_by_spacing(out, scaled_epsilon);
+    for (const ExtrusionPath &path : this->paths)
+        path.polygons_covered_by_spacing(out, scaled_epsilon);
 }
 
 double ExtrusionMultiPath::min_mm3_per_mm() const
 {
     double min_mm3_per_mm = std::numeric_limits<double>::max();
-    for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
-        min_mm3_per_mm = std::min(min_mm3_per_mm, path->mm3_per_mm);
+    for (const ExtrusionPath &path : this->paths)
+        min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm);
     return min_mm3_per_mm;
 }
 
@@ -121,52 +112,46 @@ Polyline ExtrusionMultiPath::as_polyline() const
     return out;
 }
 
-bool
-ExtrusionLoop::make_clockwise()
+bool ExtrusionLoop::make_clockwise()
 {
     bool was_ccw = this->polygon().is_counter_clockwise();
     if (was_ccw) this->reverse();
     return was_ccw;
 }
 
-bool
-ExtrusionLoop::make_counter_clockwise()
+bool ExtrusionLoop::make_counter_clockwise()
 {
     bool was_cw = this->polygon().is_clockwise();
     if (was_cw) this->reverse();
     return was_cw;
 }
 
-void
-ExtrusionLoop::reverse()
+void ExtrusionLoop::reverse()
 {
-    for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path)
-        path->reverse();
+    for (ExtrusionPath &path : this->paths)
+        path.reverse();
     std::reverse(this->paths.begin(), this->paths.end());
 }
 
-Polygon
-ExtrusionLoop::polygon() const
+Polygon ExtrusionLoop::polygon() const
 {
     Polygon polygon;
-    for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
+    for (const ExtrusionPath &path : this->paths) {
         // for each polyline, append all points except the last one (because it coincides with the first one of the next polyline)
-        polygon.points.insert(polygon.points.end(), path->polyline.points.begin(), path->polyline.points.end()-1);
+        polygon.points.insert(polygon.points.end(), path.polyline.points.begin(), path.polyline.points.end()-1);
     }
     return polygon;
 }
 
-double
-ExtrusionLoop::length() const
+double ExtrusionLoop::length() const
 {
     double len = 0;
-    for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
-        len += path->polyline.length();
+    for (const ExtrusionPath &path : this->paths)
+        len += path.polyline.length();
     return len;
 }
 
-bool
-ExtrusionLoop::split_at_vertex(const Point &point)
+bool ExtrusionLoop::split_at_vertex(const Point &point)
 {
     for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
         int idx = path->polyline.find_point(point);
@@ -220,18 +205,18 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang)
         Point  p_non_overhang;
         size_t path_idx_non_overhang = 0;
         double min_non_overhang = std::numeric_limits<double>::max();
-        for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
-            Point p_tmp = point.projection_onto(path->polyline);
+        for (const ExtrusionPath &path : this->paths) {
+            Point p_tmp = point.projection_onto(path.polyline);
             double dist = (p_tmp - point).cast<double>().norm();
             if (dist < min) {
                 p = p_tmp;
                 min = dist;
-                path_idx = path - this->paths.begin();
+                path_idx = &path - &this->paths.front();
             } 
-            if (prefer_non_overhang && ! is_bridge(path->role()) && dist < min_non_overhang) {
+            if (prefer_non_overhang && ! is_bridge(path.role()) && dist < min_non_overhang) {
                 p_non_overhang = p_tmp;
                 min_non_overhang = dist;
-                path_idx_non_overhang = path - this->paths.begin();
+                path_idx_non_overhang = &path - &this->paths.front();
             }
         }
         if (prefer_non_overhang && min_non_overhang != std::numeric_limits<double>::max()) {
@@ -267,8 +252,7 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang)
     this->split_at_vertex(p);
 }
 
-void
-ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const
+void ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const
 {
     *paths = this->paths;
     
@@ -285,15 +269,14 @@ ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const
     }
 }
 
-bool
-ExtrusionLoop::has_overhang_point(const Point &point) const
+bool ExtrusionLoop::has_overhang_point(const Point &point) const
 {
-    for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
-        int pos = path->polyline.find_point(point);
+    for (const ExtrusionPath &path : this->paths) {
+        int pos = path.polyline.find_point(point);
         if (pos != -1) {
             // point belongs to this path
             // we consider it overhang only if it's not an endpoint
-            return (is_bridge(path->role()) && pos > 0 && pos != (int)(path->polyline.points.size())-1);
+            return (is_bridge(path.role()) && pos > 0 && pos != (int)(path.polyline.points.size())-1);
         }
     }
     return false;
@@ -301,22 +284,21 @@ ExtrusionLoop::has_overhang_point(const Point &point) const
 
 void ExtrusionLoop::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
 {
-    for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
-        path->polygons_covered_by_width(out, scaled_epsilon);
+    for (const ExtrusionPath &path : this->paths)
+        path.polygons_covered_by_width(out, scaled_epsilon);
 }
 
 void ExtrusionLoop::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
 {
-    for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
-        path->polygons_covered_by_spacing(out, scaled_epsilon);
+    for (const ExtrusionPath &path : this->paths)
+        path.polygons_covered_by_spacing(out, scaled_epsilon);
 }
 
-double
-ExtrusionLoop::min_mm3_per_mm() const
+double ExtrusionLoop::min_mm3_per_mm() const
 {
     double min_mm3_per_mm = std::numeric_limits<double>::max();
-    for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
-        min_mm3_per_mm = std::min(min_mm3_per_mm, path->mm3_per_mm);
+    for (const ExtrusionPath &path : this->paths)
+        min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm);
     return min_mm3_per_mm;
 }
 
@@ -344,15 +326,4 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role)
     return "";
 }
 
-//std::string ExtrusionLoop::role_to_string(ExtrusionLoopRole role)
-//{
-//    switch (role) {
-//        case elrDefault                 : return "elrDefault";
-//        case elrContourInternalPerimeter: return "elrContourInternalPerimeter";
-//        case elrSkirt                   : return "elrSkirt";
-//        default                         : assert(false);
-//    }
-//};
-
-
 }
diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp
index 1d2465ae4..ce52ae152 100644
--- a/src/libslic3r/ExtrusionEntity.hpp
+++ b/src/libslic3r/ExtrusionEntity.hpp
@@ -75,6 +75,8 @@ public:
     virtual bool is_loop() const { return false; }
     virtual bool can_reverse() const { return true; }
     virtual ExtrusionEntity* clone() const = 0;
+    // Create a new object, initialize it with this object using the move semantics.
+    virtual ExtrusionEntity* clone_move() = 0;
     virtual ~ExtrusionEntity() {}
     virtual void reverse() = 0;
     virtual Point first_point() const = 0;
@@ -111,25 +113,29 @@ public:
     double mm3_per_mm;
     // Width of the extrusion, used for visualization purposes.
     float width;
-    // Height of the extrusion, used for visualization purposed.
+    // Height of the extrusion, used for visualization purposes.
     float height;
-    // Feedrate of the extrusion, used for visualization purposed.
+    // Feedrate of the extrusion, used for visualization purposes.
     float feedrate;
-    // Id of the extruder, used for visualization purposed.
+    // Id of the extruder, used for visualization purposes.
     unsigned int extruder_id;
-    // Id of the color, used for visualization purposed in the color printing case.
+    // Id of the color, used for visualization purposes in the color printing case.
     unsigned int cp_color_id;
 
     ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), feedrate(0.0f), extruder_id(0), cp_color_id(0), m_role(role) {}
     ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), feedrate(0.0f), extruder_id(0), cp_color_id(0), m_role(role) {}
     ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {}
-    ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {}
+	ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {}
+	ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {}
+	ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {}
 //    ExtrusionPath(ExtrusionRole role, const Flow &flow) : m_role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height), feedrate(0.0f), extruder_id(0) {};
 
     ExtrusionPath& operator=(const ExtrusionPath &rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate; this->extruder_id = rhs.extruder_id; this->cp_color_id = rhs.cp_color_id; this->polyline = rhs.polyline; return *this; }
     ExtrusionPath& operator=(ExtrusionPath &&rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate; this->extruder_id = rhs.extruder_id; this->cp_color_id = rhs.cp_color_id; this->polyline = std::move(rhs.polyline); return *this; }
 
-    ExtrusionPath* clone() const override { return new ExtrusionPath (*this); }
+	ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); }
+    // Create a new object, initialize it with this object using the move semantics.
+	ExtrusionEntity* clone_move() override { return new ExtrusionPath(std::move(*this)); }
     void reverse() override { this->polyline.reverse(); }
     Point first_point() const override { return this->polyline.points.front(); }
     Point last_point() const override { return this->polyline.points.back(); }
@@ -188,7 +194,9 @@ public:
 
     bool is_loop() const override { return false; }
     bool can_reverse() const override { return true; }
-    ExtrusionMultiPath* clone() const override { return new ExtrusionMultiPath(*this); }
+	ExtrusionEntity* clone() const override { return new ExtrusionMultiPath(*this); }
+    // Create a new object, initialize it with this object using the move semantics.
+	ExtrusionEntity* clone_move() override { return new ExtrusionMultiPath(std::move(*this)); }
     void reverse() override;
     Point first_point() const override { return this->paths.front().polyline.points.front(); }
     Point last_point() const override { return this->paths.back().polyline.points.back(); }
@@ -227,7 +235,9 @@ public:
         { this->paths.emplace_back(std::move(path)); }
     bool is_loop() const override{ return true; }
     bool can_reverse() const override { return false; }
-    ExtrusionLoop* clone() const override{ return new ExtrusionLoop (*this); }
+	ExtrusionEntity* clone() const override{ return new ExtrusionLoop (*this); }
+    // Create a new object, initialize it with this object using the move semantics.
+	ExtrusionEntity* clone_move() override { return new ExtrusionLoop(std::move(*this)); }
     bool make_clockwise();
     bool make_counter_clockwise();
     void reverse() override;
diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp
index 7a086bcbf..9ae116c47 100644
--- a/src/libslic3r/ExtrusionEntityCollection.cpp
+++ b/src/libslic3r/ExtrusionEntityCollection.cpp
@@ -21,8 +21,7 @@ ExtrusionEntityCollection& ExtrusionEntityCollection::operator= (const Extrusion
     return *this;
 }
 
-void
-ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c)
+void ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c)
 {
     std::swap(this->entities, c.entities);
     std::swap(this->orig_indices, c.orig_indices);
@@ -39,15 +38,14 @@ void ExtrusionEntityCollection::clear()
 ExtrusionEntityCollection::operator ExtrusionPaths() const
 {
     ExtrusionPaths paths;
-    for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
-        if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(*it))
+    for (const ExtrusionEntity *ptr : this->entities) {
+        if (const ExtrusionPath *path = dynamic_cast<const ExtrusionPath*>(ptr))
             paths.push_back(*path);
     }
     return paths;
 }
 
-ExtrusionEntityCollection*
-ExtrusionEntityCollection::clone() const
+ExtrusionEntity* ExtrusionEntityCollection::clone() const
 {
     ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(*this);
     for (size_t i = 0; i < coll->entities.size(); ++i)
@@ -55,41 +53,36 @@ ExtrusionEntityCollection::clone() const
     return coll;
 }
 
-void
-ExtrusionEntityCollection::reverse()
+void ExtrusionEntityCollection::reverse()
 {
-    for (ExtrusionEntitiesPtr::iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
+    for (ExtrusionEntity *ptr : this->entities)
         // Don't reverse it if it's a loop, as it doesn't change anything in terms of elements ordering
         // and caller might rely on winding order
-        if (!(*it)->is_loop()) (*it)->reverse();
-    }
+        if (! ptr->is_loop())
+        	ptr->reverse();
     std::reverse(this->entities.begin(), this->entities.end());
 }
 
-void
-ExtrusionEntityCollection::replace(size_t i, const ExtrusionEntity &entity)
+void ExtrusionEntityCollection::replace(size_t i, const ExtrusionEntity &entity)
 {
     delete this->entities[i];
     this->entities[i] = entity.clone();
 }
 
-void
-ExtrusionEntityCollection::remove(size_t i)
+void ExtrusionEntityCollection::remove(size_t i)
 {
     delete this->entities[i];
     this->entities.erase(this->entities.begin() + i);
 }
 
-ExtrusionEntityCollection
-ExtrusionEntityCollection::chained_path(bool no_reverse, ExtrusionRole role) const
+ExtrusionEntityCollection ExtrusionEntityCollection::chained_path(bool no_reverse, ExtrusionRole role) const
 {
     ExtrusionEntityCollection coll;
     this->chained_path(&coll, no_reverse, role);
     return coll;
 }
 
-void
-ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector<size_t>* orig_indices) const
+void ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector<size_t>* orig_indices) const
 {
     if (this->entities.empty()) return;
     this->chained_path_from(this->entities.front()->first_point(), retval, no_reverse, role, orig_indices);
@@ -108,6 +101,7 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt
         *retval = *this;
         return;
     }
+
     retval->entities.reserve(this->entities.size());
     retval->orig_indices.reserve(this->entities.size());
     
@@ -115,10 +109,10 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt
     std::map<ExtrusionEntity*,size_t> indices_map;
     
     ExtrusionEntitiesPtr my_paths;
-    for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
+    for (ExtrusionEntity * const &entity_src : this->entities) {
         if (role != erMixed) {
             // The caller wants only paths with a specific extrusion role.
-            auto role2 = (*it)->role();
+            auto role2 = entity_src->role();
             if (role != role2) {
                 // This extrusion entity does not match the role asked.
                 assert(role2 != erMixed);
@@ -126,32 +120,30 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt
             }
         }
 
-        ExtrusionEntity* entity = (*it)->clone();
+        ExtrusionEntity *entity = entity_src->clone();
         my_paths.push_back(entity);
-        if (orig_indices != NULL) indices_map[entity] = it - this->entities.begin();
+        if (orig_indices != nullptr)
+        	indices_map[entity] = &entity_src - &this->entities.front();
     }
     
     Points endpoints;
-    for (ExtrusionEntitiesPtr::iterator it = my_paths.begin(); it != my_paths.end(); ++it) {
-        endpoints.push_back((*it)->first_point());
-        if (no_reverse || !(*it)->can_reverse()) {
-            endpoints.push_back((*it)->first_point());
-        } else {
-            endpoints.push_back((*it)->last_point());
-        }
+    for (const ExtrusionEntity *entity : my_paths) {
+        endpoints.push_back(entity->first_point());
+        endpoints.push_back((no_reverse || ! entity->can_reverse()) ?
+        	entity->first_point() : entity->last_point());
     }
     
-    while (!my_paths.empty()) {
+    while (! my_paths.empty()) {
         // find nearest point
         int start_index = start_near.nearest_point_index(endpoints);
         int path_index = start_index/2;
         ExtrusionEntity* entity = my_paths.at(path_index);
         // never reverse loops, since it's pointless for chained path and callers might depend on orientation
-        if (start_index % 2 && !no_reverse && entity->can_reverse()) {
+        if (start_index % 2 && !no_reverse && entity->can_reverse())
             entity->reverse();
-        }
         retval->entities.push_back(my_paths.at(path_index));
-        if (orig_indices != NULL) orig_indices->push_back(indices_map[entity]);
+        if (orig_indices != nullptr)
+        	orig_indices->push_back(indices_map[entity]);
         my_paths.erase(my_paths.begin() + path_index);
         endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2);
         start_near = retval->entities.back()->last_point();
@@ -160,60 +152,50 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt
 
 void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
 {
-    for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it)
-        (*it)->polygons_covered_by_width(out, scaled_epsilon);
+    for (const ExtrusionEntity *entity : this->entities)
+        entity->polygons_covered_by_width(out, scaled_epsilon);
 }
 
 void ExtrusionEntityCollection::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
 {
-    for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it)
-        (*it)->polygons_covered_by_spacing(out, scaled_epsilon);
+    for (const ExtrusionEntity *entity : this->entities)
+        entity->polygons_covered_by_spacing(out, scaled_epsilon);
 }
 
-/* Recursively count paths and loops contained in this collection */
-size_t
-ExtrusionEntityCollection::items_count() const
+// Recursively count paths and loops contained in this collection.
+size_t ExtrusionEntityCollection::items_count() const
 {
     size_t count = 0;
-    for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
-        if ((*it)->is_collection()) {
-            ExtrusionEntityCollection* collection = dynamic_cast<ExtrusionEntityCollection*>(*it);
-            count += collection->items_count();
-        } else {
-            ++count;
-        }
-    }
+    for (const ExtrusionEntity *entity : this->entities)
+        if (entity->is_collection())
+            count += static_cast<const ExtrusionEntityCollection*>(entity)->items_count();
+        else
+            ++ count;
     return count;
 }
 
-/* Returns a single vector of pointers to all non-collection items contained in this one */
-void
-ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const
+// Returns a single vector of pointers to all non-collection items contained in this one.
+void ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const
 {
-    for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
-        if ((*it)->is_collection()) {
-            ExtrusionEntityCollection* collection = dynamic_cast<ExtrusionEntityCollection*>(*it);
-            retval->append(collection->flatten().entities);
-        } else {
-            retval->append(**it);
-        }
-    }
+    for (const ExtrusionEntity *entity : this->entities)
+        if (entity->is_collection())
+            retval->append(static_cast<const ExtrusionEntityCollection*>(entity)->flatten().entities);
+        else
+            retval->append(*entity);
 }
 
-ExtrusionEntityCollection
-ExtrusionEntityCollection::flatten() const
+ExtrusionEntityCollection ExtrusionEntityCollection::flatten() const
 {
     ExtrusionEntityCollection coll;
     this->flatten(&coll);
     return coll;
 }
 
-double
-ExtrusionEntityCollection::min_mm3_per_mm() const
+double ExtrusionEntityCollection::min_mm3_per_mm() const
 {
     double min_mm3_per_mm = std::numeric_limits<double>::max();
-    for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it)
-        min_mm3_per_mm = std::min(min_mm3_per_mm, (*it)->min_mm3_per_mm());
+    for (const ExtrusionEntity *entity : this->entities)
+    	min_mm3_per_mm = std::min(min_mm3_per_mm, entity->min_mm3_per_mm());
     return min_mm3_per_mm;
 }
 
diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp
index 230c04160..4fe964ee1 100644
--- a/src/libslic3r/ExtrusionEntityCollection.hpp
+++ b/src/libslic3r/ExtrusionEntityCollection.hpp
@@ -9,7 +9,10 @@ namespace Slic3r {
 class ExtrusionEntityCollection : public ExtrusionEntity
 {
 public:
-    ExtrusionEntityCollection* clone() const;
+    ExtrusionEntity* clone() const override;
+    // Create a new object, initialize it with this object using the move semantics.
+	ExtrusionEntity* clone_move() override { return new ExtrusionEntityCollection(std::move(*this)); }
+
     ExtrusionEntitiesPtr entities;     // we own these entities
     std::vector<size_t> orig_indices;  // handy for XS
     bool no_sort;
@@ -36,11 +39,12 @@ public:
     bool empty() const { return this->entities.empty(); };
     void clear();
     void swap (ExtrusionEntityCollection &c);
-    void append(const ExtrusionEntity &entity) { this->entities.push_back(entity.clone()); }
-    void append(const ExtrusionEntitiesPtr &entities) { 
+    void append(const ExtrusionEntity &entity) { this->entities.emplace_back(entity.clone()); }
+    void append(ExtrusionEntity &&entity) { this->entities.emplace_back(entity.clone_move()); }
+    void append(const ExtrusionEntitiesPtr &entities) {
         this->entities.reserve(this->entities.size() + entities.size());
-        for (ExtrusionEntitiesPtr::const_iterator ptr = entities.begin(); ptr != entities.end(); ++ptr)
-            this->entities.push_back((*ptr)->clone());
+        for (const ExtrusionEntity *ptr : entities)
+            this->entities.emplace_back(ptr->clone());
     }
     void append(ExtrusionEntitiesPtr &&src) {
         if (entities.empty())
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index c35afe9fc..0ccc3ddf5 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -1869,8 +1869,9 @@ void GCode::process_layer(
         if (! m_brim_done) {
             this->set_origin(0., 0.);
             m_avoid_crossing_perimeters.use_external_mp = true;
-            for (const ExtrusionEntity *ee : print.brim().entities)
-                gcode += this->extrude_loop(*dynamic_cast<const ExtrusionLoop*>(ee), "brim", m_config.support_material_speed.value);
+            for (const ExtrusionEntity *ee : print.brim().entities) {
+                gcode += this->extrude_entity(*ee, "brim", m_config.support_material_speed.value);
+            }
             m_brim_done = true;
             m_avoid_crossing_perimeters.use_external_mp = false;
             // Allow a straight travel move to the first object point.
@@ -2504,10 +2505,9 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des
         return this->extrude_multi_path(*multipath, description, speed);
     else if (const ExtrusionLoop* loop = dynamic_cast<const ExtrusionLoop*>(&entity))
         return this->extrude_loop(*loop, description, speed, lower_layer_edge_grid);
-    else {
+    else
         throw std::invalid_argument("Invalid argument supplied to extrude()");
-        return "";
-    }
+    return "";
 }
 
 std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed)
diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp
index 74df07935..0c16f4a1d 100644
--- a/src/libslic3r/PerimeterGenerator.cpp
+++ b/src/libslic3r/PerimeterGenerator.cpp
@@ -6,21 +6,240 @@
 
 namespace Slic3r {
 
+static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, Flow &flow, const float tolerance)
+{
+    ExtrusionPaths paths;
+    ExtrusionPath path(role);
+    ThickLines lines = thick_polyline.thicklines();
+    
+    for (int i = 0; i < (int)lines.size(); ++i) {
+        const ThickLine& line = lines[i];
+        
+        const coordf_t line_len = line.length();
+        if (line_len < SCALED_EPSILON) continue;
+        
+        double thickness_delta = fabs(line.a_width - line.b_width);
+        if (thickness_delta > tolerance) {
+            const unsigned int segments = (unsigned int)ceil(thickness_delta / tolerance);
+            const coordf_t seg_len = line_len / segments;
+            Points pp;
+            std::vector<coordf_t> width;
+            {
+                pp.push_back(line.a);
+                width.push_back(line.a_width);
+                for (size_t j = 1; j < segments; ++j) {
+                    pp.push_back((line.a.cast<double>() + (line.b - line.a).cast<double>().normalized() * (j * seg_len)).cast<coord_t>());
+                    
+                    coordf_t w = line.a_width + (j*seg_len) * (line.b_width-line.a_width) / line_len;
+                    width.push_back(w);
+                    width.push_back(w);
+                }
+                pp.push_back(line.b);
+                width.push_back(line.b_width);
+                
+                assert(pp.size() == segments + 1u);
+                assert(width.size() == segments*2);
+            }
+            
+            // delete this line and insert new ones
+            lines.erase(lines.begin() + i);
+            for (size_t j = 0; j < segments; ++j) {
+                ThickLine new_line(pp[j], pp[j+1]);
+                new_line.a_width = width[2*j];
+                new_line.b_width = width[2*j+1];
+                lines.insert(lines.begin() + i + j, new_line);
+            }
+            
+            -- i;
+            continue;
+        }
+        
+        const double w = fmax(line.a_width, line.b_width);
+        if (path.polyline.points.empty()) {
+            path.polyline.append(line.a);
+            path.polyline.append(line.b);
+            // Convert from spacing to extrusion width based on the extrusion model
+            // of a square extrusion ended with semi circles.
+            flow.width = unscale<float>(w) + flow.height * float(1. - 0.25 * PI);
+            #ifdef SLIC3R_DEBUG
+            printf("  filling %f gap\n", flow.width);
+            #endif
+            path.mm3_per_mm  = flow.mm3_per_mm();
+            path.width       = flow.width;
+            path.height      = flow.height;
+        } else {
+            thickness_delta = fabs(scale_(flow.width) - w);
+            if (thickness_delta <= tolerance) {
+                // the width difference between this line and the current flow width is 
+                // within the accepted tolerance
+                path.polyline.append(line.b);
+            } else {
+                // we need to initialize a new line
+                paths.emplace_back(std::move(path));
+                path = ExtrusionPath(role);
+                -- i;
+            }
+        }
+    }
+    if (path.polyline.is_valid())
+        paths.emplace_back(std::move(path));
+    return paths;
+}
+
+static ExtrusionEntityCollection variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow)
+{
+	// This value determines granularity of adaptive width, as G-code does not allow
+	// variable extrusion within a single move; this value shall only affect the amount
+	// of segments, and any pruning shall be performed before we apply this tolerance.
+	ExtrusionEntityCollection coll;
+	const float tolerance = float(scale_(0.05));
+	for (const ThickPolyline &p : polylines) {
+		ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance);
+		// Append paths to collection.
+		if (! paths.empty()) {
+			if (paths.front().first_point() == paths.back().last_point())
+				coll.append(ExtrusionLoop(std::move(paths)));
+			else
+				coll.append(std::move(paths));
+		}
+	}
+	return coll;
+}
+
+// Hierarchy of perimeters.
+class PerimeterGeneratorLoop {
+public:
+    // Polygon of this contour.
+    Polygon polygon;
+    // Is it a contour or a hole?
+    // Contours are CCW oriented, holes are CW oriented.
+    bool is_contour;
+    // Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
+    unsigned short depth;
+    // Children contour, may be both CCW and CW oriented (outer contours or holes).
+    std::vector<PerimeterGeneratorLoop> children;
+    
+    PerimeterGeneratorLoop(Polygon polygon, unsigned short depth, bool is_contour) : 
+        polygon(polygon), is_contour(is_contour), depth(depth) {}
+    // External perimeter. It may be CCW or CW oriented (outer contour or hole contour).
+    bool is_external() const { return this->depth == 0; }
+    // An island, which may have holes, but it does not have another internal island.
+    bool is_internal_contour() const;
+};
+
+typedef std::vector<PerimeterGeneratorLoop> PerimeterGeneratorLoops;
+
+static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls)
+{
+    // loops is an arrayref of ::Loop objects
+    // turn each one into an ExtrusionLoop object
+    ExtrusionEntityCollection coll;
+    for (const PerimeterGeneratorLoop &loop : loops) {
+        bool is_external = loop.is_external();
+        
+        ExtrusionRole role;
+        ExtrusionLoopRole loop_role;
+        role = is_external ? erExternalPerimeter : erPerimeter;
+        if (loop.is_internal_contour()) {
+            // Note that we set loop role to ContourInternalPerimeter
+            // also when loop is both internal and external (i.e.
+            // there's only one contour loop).
+            loop_role = elrContourInternalPerimeter;
+        } else {
+            loop_role = elrDefault;
+        }
+        
+        // detect overhanging/bridging perimeters
+        ExtrusionPaths paths;
+        if (perimeter_generator.config->overhangs && perimeter_generator.layer_id > 0
+            && !(perimeter_generator.object_config->support_material && perimeter_generator.object_config->support_material_contact_distance.value == 0)) {
+            // get non-overhang paths by intersecting this loop with the grown lower slices
+            extrusion_paths_append(
+                paths,
+                intersection_pl(loop.polygon, perimeter_generator.lower_slices_polygons()),
+                role,
+                is_external ? perimeter_generator.ext_mm3_per_mm()          : perimeter_generator.mm3_per_mm(),
+                is_external ? perimeter_generator.ext_perimeter_flow.width  : perimeter_generator.perimeter_flow.width,
+                (float)perimeter_generator.layer_height);
+            
+            // get overhang paths by checking what parts of this loop fall 
+            // outside the grown lower slices (thus where the distance between
+            // the loop centerline and original lower slices is >= half nozzle diameter
+            extrusion_paths_append(
+                paths,
+                diff_pl(loop.polygon, perimeter_generator.lower_slices_polygons()),
+                erOverhangPerimeter,
+                perimeter_generator.mm3_per_mm_overhang(),
+                perimeter_generator.overhang_flow.width,
+                perimeter_generator.overhang_flow.height);
+            
+            // reapply the nearest point search for starting point
+            // We allow polyline reversal because Clipper may have randomly
+            // reversed polylines during clipping.
+            paths = (ExtrusionPaths)ExtrusionEntityCollection(paths).chained_path();
+        } else {
+            ExtrusionPath path(role);
+            path.polyline   = loop.polygon.split_at_first_point();
+            path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm()          : perimeter_generator.mm3_per_mm();
+            path.width      = is_external ? perimeter_generator.ext_perimeter_flow.width  : perimeter_generator.perimeter_flow.width;
+            path.height     = (float)perimeter_generator.layer_height;
+            paths.push_back(path);
+        }
+        
+        coll.append(ExtrusionLoop(paths, loop_role));
+    }
+    
+    // Append thin walls to the nearest-neighbor search (only for first iteration)
+    if (! thin_walls.empty()) {
+        ExtrusionEntityCollection tw = variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow);
+        coll.append(tw.entities);
+        thin_walls.clear();
+    }
+    
+    // Sort entities into a new collection using a nearest-neighbor search,
+    // preserving the original indices which are useful for detecting thin walls.
+    ExtrusionEntityCollection sorted_coll;
+    coll.chained_path(&sorted_coll, false, erMixed, &sorted_coll.orig_indices);
+    
+    // traverse children and build the final collection
+    ExtrusionEntityCollection entities;
+    for (const size_t &idx : sorted_coll.orig_indices) {
+        if (idx >= loops.size()) {
+            // This is a thin wall. Let's get it from the sorted collection as it might have been reversed.
+            entities.append(std::move(*sorted_coll.entities[&idx - &sorted_coll.orig_indices.front()]));
+        } else {
+            const PerimeterGeneratorLoop &loop = loops[idx];
+            ExtrusionLoop eloop = *dynamic_cast<ExtrusionLoop*>(coll.entities[idx]);
+            ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls);
+            if (loop.is_contour) {
+                eloop.make_counter_clockwise();
+                entities.append(std::move(children.entities));
+                entities.append(std::move(eloop));
+            } else {
+                eloop.make_clockwise();
+                entities.append(std::move(eloop));
+                entities.append(std::move(children.entities));
+            }
+        }
+    }
+    return entities;
+}
+
 void PerimeterGenerator::process()
 {
     // other perimeters
-    this->_mm3_per_mm               = this->perimeter_flow.mm3_per_mm();
+    m_mm3_per_mm               		= this->perimeter_flow.mm3_per_mm();
     coord_t perimeter_width         = this->perimeter_flow.scaled_width();
     coord_t perimeter_spacing       = this->perimeter_flow.scaled_spacing();
     
     // external perimeters
-    this->_ext_mm3_per_mm           = this->ext_perimeter_flow.mm3_per_mm();
+    m_ext_mm3_per_mm           		= this->ext_perimeter_flow.mm3_per_mm();
     coord_t ext_perimeter_width     = this->ext_perimeter_flow.scaled_width();
     coord_t ext_perimeter_spacing   = this->ext_perimeter_flow.scaled_spacing();
     coord_t ext_perimeter_spacing2  = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow);
     
     // overhang perimeters
-    this->_mm3_per_mm_overhang      = this->overhang_flow.mm3_per_mm();
+    m_mm3_per_mm_overhang      		= this->overhang_flow.mm3_per_mm();
     
     // solid infill
     coord_t solid_infill_spacing    = this->solid_infill_flow.scaled_spacing();
@@ -35,8 +254,8 @@ void PerimeterGenerator::process()
     // which is the spacing between external and internal, which is not correct
     // and would make the collapsing (thus the details resolution) dependent on 
     // internal flow which is unrelated.
-    coord_t min_spacing         = perimeter_spacing      * (1 - INSET_OVERLAP_TOLERANCE);
-    coord_t ext_min_spacing     = ext_perimeter_spacing  * (1 - INSET_OVERLAP_TOLERANCE);
+    coord_t min_spacing         = coord_t(perimeter_spacing      * (1 - INSET_OVERLAP_TOLERANCE));
+    coord_t ext_min_spacing     = coord_t(ext_perimeter_spacing  * (1 - INSET_OVERLAP_TOLERANCE));
     bool    has_gap_fill 		= this->config->gap_fill_speed.value > 0;
 
     // prepare grown lower layer slices for overhang detection
@@ -45,7 +264,7 @@ void PerimeterGenerator::process()
         // lower layer, so we take lower slices and offset them by half the nozzle diameter used 
         // in the current layer
         double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1);
-        this->_lower_slices_p = offset(*this->lower_slices, float(scale_(+nozzle_diameter/2)));
+        m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter/2)));
     }
     
     // we need to process each island separately because we might have different
@@ -70,20 +289,20 @@ void PerimeterGenerator::process()
                     offsets = this->config->thin_walls ? 
                         offset2_ex(
                             last,
-                            -(ext_perimeter_width / 2 + ext_min_spacing / 2 - 1),
-                            +(ext_min_spacing / 2 - 1)) :
-                        offset_ex(last, - ext_perimeter_width / 2);
+                            - float(ext_perimeter_width / 2. + ext_min_spacing / 2. - 1),
+                            + float(ext_min_spacing / 2. - 1)) :
+                        offset_ex(last, - float(ext_perimeter_width / 2.));
                     // look for thin walls
                     if (this->config->thin_walls) {
                         // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
                         // (actually, something larger than that still may exist due to mitering or other causes)
-                        coord_t min_width = scale_(this->ext_perimeter_flow.nozzle_diameter / 3);
+                        coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter / 3));
                         ExPolygons expp = offset2_ex(
                             // medial axis requires non-overlapping geometry
                             diff_ex(to_polygons(last),
-                                    offset(offsets, ext_perimeter_width / 2),
+                                    offset(offsets, float(ext_perimeter_width / 2.)),
                                     true),
-                            - min_width / 2, min_width / 2);
+                            - float(min_width / 2.), float(min_width / 2.));
                         // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
                         for (ExPolygon &ex : expp)
                             ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls);
@@ -100,19 +319,19 @@ void PerimeterGenerator::process()
                         // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than
                         // the original.
                         offset2_ex(last,
-                                - (distance + min_spacing / 2 - 1),
-                                min_spacing / 2 - 1) :
+                                - float(distance + min_spacing / 2. - 1.),
+                                float(min_spacing / 2. - 1.)) :
                         // If "detect thin walls" is not enabled, this paths will be entered, which 
                         // leads to overflows, as in prusa3d/Slic3r GH #32
-                        offset_ex(last, - distance);
+                        offset_ex(last, - float(distance));
                     // look for gaps
                     if (has_gap_fill)
                         // not using safety offset here would "detect" very narrow gaps
                         // (but still long enough to escape the area threshold) that gap fill
                         // won't be able to fill but we'd still remove from infill area
                         append(gaps, diff_ex(
-                            offset(last,    -0.5 * distance),
-                            offset(offsets,  0.5 * distance + 10)));  // safety offset
+                            offset(last,    - float(0.5 * distance)),
+                            offset(offsets,   float(0.5 * distance + 10))));  // safety offset
                 }
                 if (offsets.empty()) {
                     // Store the number of loops actually generated.
@@ -125,6 +344,11 @@ void PerimeterGenerator::process()
                     break;
                 }
                 for (const ExPolygon &expolygon : offsets) {
+	                // Outer contour may overlap with an inner contour,
+	                // inner contour may overlap with another inner contour,
+	                // outer contour may overlap with itself.
+	                //FIXME evaluate the overlaps, annotate each point with an overlap depth,
+	                // compensate for the depth of intersection.
                     contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true));
                     if (! expolygon.holes.empty()) {
                         holes[i].reserve(holes[i].size() + expolygon.holes.size());
@@ -195,7 +419,7 @@ void PerimeterGenerator::process()
                 }
             }
             // at this point, all loops should be in contours[0]
-            ExtrusionEntityCollection entities = this->_traverse_loops(contours.front(), thin_walls);
+            ExtrusionEntityCollection entities = traverse_loops(*this, contours.front(), thin_walls);
             // if brim will be printed, reverse the order of perimeters so that
             // we continue inwards after having finished the brim
             // TODO: add test for perimeter order
@@ -214,15 +438,14 @@ void PerimeterGenerator::process()
             double max = 2. * perimeter_spacing;
             ExPolygons gaps_ex = diff_ex(
                 //FIXME offset2 would be enough and cheaper.
-                offset2_ex(gaps, -min/2, +min/2),
-                offset2_ex(gaps, -max/2, +max/2),
+                offset2_ex(gaps, - float(min / 2.), float(min / 2.)),
+                offset2_ex(gaps, - float(max / 2.), float(max / 2.)),
                 true);
             ThickPolylines polylines;
             for (const ExPolygon &ex : gaps_ex)
                 ex.medial_axis(max, min, &polylines);
             if (! polylines.empty()) {
-                ExtrusionEntityCollection gap_fill = this->_variable_width(polylines, 
-                    erGapFill, this->solid_infill_flow);
+                ExtrusionEntityCollection gap_fill = variable_width(polylines, erGapFill, this->solid_infill_flow);
                 this->gap_fill->append(gap_fill.entities);
                 /*  Make sure we don't infill narrow parts that are already gap-filled
                     (we only consider this surface's gaps to reduce the diff() complexity).
@@ -249,229 +472,23 @@ void PerimeterGenerator::process()
                 perimeter_spacing / 2;
         // only apply infill overlap if we actually have one perimeter
         if (inset > 0)
-            inset -= scale_(this->config->get_abs_value("infill_overlap", unscale<double>(inset + solid_infill_spacing / 2)));
+            inset -= coord_t(scale_(this->config->get_abs_value("infill_overlap", unscale<double>(inset + solid_infill_spacing / 2))));
         // simplify infill contours according to resolution
         Polygons pp;
         for (ExPolygon &ex : last)
             ex.simplify_p(SCALED_RESOLUTION, &pp);
         // collapse too narrow infill areas
-        coord_t min_perimeter_infill_spacing = solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE);
+        coord_t min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
         // append infill areas to fill_surfaces
         this->fill_surfaces->append(
             offset2_ex(
                 union_ex(pp),
-                - inset - min_perimeter_infill_spacing / 2,
-                min_perimeter_infill_spacing / 2),
+                float(- inset - min_perimeter_infill_spacing / 2.),
+                float(min_perimeter_infill_spacing / 2.)),
             stInternal);
     } // for each island
 }
 
-ExtrusionEntityCollection PerimeterGenerator::_traverse_loops(
-    const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) const
-{
-    // loops is an arrayref of ::Loop objects
-    // turn each one into an ExtrusionLoop object
-    ExtrusionEntityCollection coll;
-    for (PerimeterGeneratorLoops::const_iterator loop = loops.begin();
-        loop != loops.end(); ++loop) {
-        bool is_external = loop->is_external();
-        
-        ExtrusionRole role;
-        ExtrusionLoopRole loop_role;
-        role = is_external ? erExternalPerimeter : erPerimeter;
-        if (loop->is_internal_contour()) {
-            // Note that we set loop role to ContourInternalPerimeter
-            // also when loop is both internal and external (i.e.
-            // there's only one contour loop).
-            loop_role = elrContourInternalPerimeter;
-        } else {
-            loop_role = elrDefault;
-        }
-        
-        // detect overhanging/bridging perimeters
-        ExtrusionPaths paths;
-        if (this->config->overhangs && this->layer_id > 0
-            && !(this->object_config->support_material && this->object_config->support_material_contact_distance.value == 0)) {
-            // get non-overhang paths by intersecting this loop with the grown lower slices
-            extrusion_paths_append(
-                paths,
-                intersection_pl(loop->polygon, this->_lower_slices_p),
-                role,
-                is_external ? this->_ext_mm3_per_mm           : this->_mm3_per_mm,
-                is_external ? this->ext_perimeter_flow.width  : this->perimeter_flow.width,
-                this->layer_height);
-            
-            // get overhang paths by checking what parts of this loop fall 
-            // outside the grown lower slices (thus where the distance between
-            // the loop centerline and original lower slices is >= half nozzle diameter
-            extrusion_paths_append(
-                paths,
-                diff_pl(loop->polygon, this->_lower_slices_p),
-                erOverhangPerimeter,
-                this->_mm3_per_mm_overhang,
-                this->overhang_flow.width,
-                this->overhang_flow.height);
-            
-            // reapply the nearest point search for starting point
-            // We allow polyline reversal because Clipper may have randomly
-            // reversed polylines during clipping.
-            paths = (ExtrusionPaths)ExtrusionEntityCollection(paths).chained_path();
-        } else {
-            ExtrusionPath path(role);
-            path.polyline   = loop->polygon.split_at_first_point();
-            path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm           : this->_mm3_per_mm;
-            path.width      = is_external ? this->ext_perimeter_flow.width  : this->perimeter_flow.width;
-            path.height     = this->layer_height;
-            paths.push_back(path);
-        }
-        
-        coll.append(ExtrusionLoop(paths, loop_role));
-    }
-    
-    // append thin walls to the nearest-neighbor search (only for first iteration)
-    if (!thin_walls.empty()) {
-        ExtrusionEntityCollection tw = this->_variable_width
-            (thin_walls, erExternalPerimeter, this->ext_perimeter_flow);
-        
-        coll.append(tw.entities);
-        thin_walls.clear();
-    }
-    
-    // sort entities into a new collection using a nearest-neighbor search,
-    // preserving the original indices which are useful for detecting thin walls
-    ExtrusionEntityCollection sorted_coll;
-    coll.chained_path(&sorted_coll, false, erMixed, &sorted_coll.orig_indices);
-    
-    // traverse children and build the final collection
-    ExtrusionEntityCollection entities;
-    for (std::vector<size_t>::const_iterator idx = sorted_coll.orig_indices.begin();
-        idx != sorted_coll.orig_indices.end();
-        ++idx) {
-        
-        if (*idx >= loops.size()) {
-            // this is a thin wall
-            // let's get it from the sorted collection as it might have been reversed
-            size_t i = idx - sorted_coll.orig_indices.begin();
-            entities.append(*sorted_coll.entities[i]);
-        } else {
-            const PerimeterGeneratorLoop &loop = loops[*idx];
-            ExtrusionLoop eloop = *dynamic_cast<ExtrusionLoop*>(coll.entities[*idx]);
-            
-            ExtrusionEntityCollection children = this->_traverse_loops(loop.children, thin_walls);
-            if (loop.is_contour) {
-                eloop.make_counter_clockwise();
-                entities.append(children.entities);
-                entities.append(eloop);
-            } else {
-                eloop.make_clockwise();
-                entities.append(eloop);
-                entities.append(children.entities);
-            }
-        }
-    }
-    return entities;
-}
-
-static inline ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, Flow &flow, const float tolerance)
-{
-    ExtrusionPaths paths;
-    ExtrusionPath path(role);
-    ThickLines lines = thick_polyline.thicklines();
-    
-    for (int i = 0; i < (int)lines.size(); ++i) {
-        const ThickLine& line = lines[i];
-        
-        const coordf_t line_len = line.length();
-        if (line_len < SCALED_EPSILON) continue;
-        
-        double thickness_delta = fabs(line.a_width - line.b_width);
-        if (thickness_delta > tolerance) {
-            const unsigned short segments = ceil(thickness_delta / tolerance);
-            const coordf_t seg_len = line_len / segments;
-            Points pp;
-            std::vector<coordf_t> width;
-            {
-                pp.push_back(line.a);
-                width.push_back(line.a_width);
-                for (size_t j = 1; j < segments; ++j) {
-                    pp.push_back((line.a.cast<double>() + (line.b - line.a).cast<double>().normalized() * (j * seg_len)).cast<coord_t>());
-                    
-                    coordf_t w = line.a_width + (j*seg_len) * (line.b_width-line.a_width) / line_len;
-                    width.push_back(w);
-                    width.push_back(w);
-                }
-                pp.push_back(line.b);
-                width.push_back(line.b_width);
-                
-                assert(pp.size() == segments + 1u);
-                assert(width.size() == segments*2);
-            }
-            
-            // delete this line and insert new ones
-            lines.erase(lines.begin() + i);
-            for (size_t j = 0; j < segments; ++j) {
-                ThickLine new_line(pp[j], pp[j+1]);
-                new_line.a_width = width[2*j];
-                new_line.b_width = width[2*j+1];
-                lines.insert(lines.begin() + i + j, new_line);
-            }
-            
-            -- i;
-            continue;
-        }
-        
-        const double w = fmax(line.a_width, line.b_width);
-        if (path.polyline.points.empty()) {
-            path.polyline.append(line.a);
-            path.polyline.append(line.b);
-            // Convert from spacing to extrusion width based on the extrusion model
-            // of a square extrusion ended with semi circles.
-            flow.width = unscale<float>(w) + flow.height * (1. - 0.25 * PI);
-            #ifdef SLIC3R_DEBUG
-            printf("  filling %f gap\n", flow.width);
-            #endif
-            path.mm3_per_mm  = flow.mm3_per_mm();
-            path.width       = flow.width;
-            path.height      = flow.height;
-        } else {
-            thickness_delta = fabs(scale_(flow.width) - w);
-            if (thickness_delta <= tolerance) {
-                // the width difference between this line and the current flow width is 
-                // within the accepted tolerance
-                path.polyline.append(line.b);
-            } else {
-                // we need to initialize a new line
-                paths.emplace_back(std::move(path));
-                path = ExtrusionPath(role);
-                -- i;
-            }
-        }
-    }
-    if (path.polyline.is_valid())
-        paths.emplace_back(std::move(path));
-    return paths;
-}
-
-ExtrusionEntityCollection PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const
-{
-    // This value determines granularity of adaptive width, as G-code does not allow
-    // variable extrusion within a single move; this value shall only affect the amount
-    // of segments, and any pruning shall be performed before we apply this tolerance.
-    ExtrusionEntityCollection coll;
-    const double tolerance = scale_(0.05);
-    for (const ThickPolyline &p : polylines) {
-        ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance);
-        // Append paths to collection.
-        if (! paths.empty()) {
-            if (paths.front().first_point() == paths.back().last_point())
-                coll.append(ExtrusionLoop(std::move(paths)));
-            else
-                coll.append(std::move(paths));
-        }
-    }
-    return coll;
-}
-
 bool PerimeterGeneratorLoop::is_internal_contour() const
 {
     // An internal contour is a contour containing no other contours
diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp
index c01abbb15..8cd71e697 100644
--- a/src/libslic3r/PerimeterGenerator.hpp
+++ b/src/libslic3r/PerimeterGenerator.hpp
@@ -11,29 +11,6 @@
 
 namespace Slic3r {
 
-// Hierarchy of perimeters.
-class PerimeterGeneratorLoop {
-public:
-    // Polygon of this contour.
-    Polygon polygon;
-    // Is it a contour or a hole?
-    // Contours are CCW oriented, holes are CW oriented.
-    bool is_contour;
-    // Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
-    unsigned short depth;
-    // Children contour, may be both CCW and CW oriented (outer contours or holes).
-    std::vector<PerimeterGeneratorLoop> children;
-    
-    PerimeterGeneratorLoop(Polygon polygon, unsigned short depth, bool is_contour) : 
-        polygon(polygon), is_contour(is_contour), depth(depth) {}
-    // External perimeter. It may be CCW or CW oriented (outer contour or hole contour).
-    bool is_external() const { return this->depth == 0; }
-    // An island, which may have holes, but it does not have another internal island.
-    bool is_internal_contour() const;
-};
-
-typedef std::vector<PerimeterGeneratorLoop> PerimeterGeneratorLoops;
-
 class PerimeterGenerator {
 public:
     // Inputs:
@@ -73,18 +50,21 @@ public:
             overhang_flow(flow), solid_infill_flow(flow),
             config(config), object_config(object_config), print_config(print_config),
             loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces),
-            _ext_mm3_per_mm(-1), _mm3_per_mm(-1), _mm3_per_mm_overhang(-1)
+            m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1)
         {}
-    void process();
+
+    void        process();
+
+    double      ext_mm3_per_mm()        const { return m_ext_mm3_per_mm; }
+    double      mm3_per_mm()            const { return m_mm3_per_mm; }
+    double      mm3_per_mm_overhang()   const { return m_mm3_per_mm_overhang; }
+    Polygons    lower_slices_polygons() const { return m_lower_slices_polygons; }
 
 private:
-    double      _ext_mm3_per_mm;
-    double      _mm3_per_mm;
-    double      _mm3_per_mm_overhang;
-    Polygons    _lower_slices_p;
-    
-    ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) const;
-    ExtrusionEntityCollection _variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const;
+    double      m_ext_mm3_per_mm;
+    double      m_mm3_per_mm;
+    double      m_mm3_per_mm_overhang;
+    Polygons    m_lower_slices_polygons;
 };
 
 }
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
index 9d4df56d0..cbbbf1f1a 100644
--- a/src/libslic3r/Print.cpp
+++ b/src/libslic3r/Print.cpp
@@ -1,3 +1,5 @@
+#include "clipper/clipper_z.hpp"
+
 #include "Print.hpp"
 #include "BoundingBox.hpp"
 #include "ClipperUtils.hpp"
@@ -1639,9 +1641,7 @@ void Print::_make_skirt()
 
     // Initial offset of the brim inner edge from the object (possible with a support & raft).
     // The skirt will touch the brim if the brim is extruded.
-    Flow   brim_flow = this->brim_flow();
-    double actual_brim_width = brim_flow.spacing() * floor(m_config.brim_width.value / brim_flow.spacing());
-    auto   distance = float(scale_(std::max(m_config.skirt_distance.value, actual_brim_width) - spacing/2.));
+    auto   distance = float(scale_(m_config.skirt_distance.value) - spacing/2.);
     // Draw outlines from outside to inside.
     // Loop while we have less skirts than required or any extruder hasn't reached the min length if any.
     std::vector<coordf_t> extruded_length(extruders.size(), 0.);
@@ -1723,12 +1723,134 @@ void Print::_make_brim()
         }
         polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing())));
     }
-
     loops = union_pt_chained(loops, false);
     // The function above produces ordering well suited for concentric infill (from outside to inside).
     // For Brim, the ordering should be reversed (from inside to outside).
     std::reverse(loops.begin(), loops.end());
-    extrusion_entities_append_loops(m_brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()));
+
+    // If there is a possibility that brim intersects skirt, go through loops and split those extrusions
+    // The result is either the original Polygon or a list of Polylines
+    if (! m_skirt.empty() && m_config.skirt_distance.value < m_config.brim_width)
+    {
+        // Find the bounding polygons of the skirt
+        const Polygons skirt_inners = offset(dynamic_cast<ExtrusionLoop*>(m_skirt.entities.back())->polygon(),
+                                              -float(scale_(this->skirt_flow().spacing()))/2.f,
+                                              ClipperLib::jtRound,
+                                              float(scale_(0.1)));
+        const Polygons skirt_outers = offset(dynamic_cast<ExtrusionLoop*>(m_skirt.entities.front())->polygon(),
+                                              float(scale_(this->skirt_flow().spacing()))/2.f,
+                                              ClipperLib::jtRound,
+                                              float(scale_(0.1)));
+
+        // First calculate the trimming region.
+		ClipperLib_Z::Paths trimming;
+		{
+		    ClipperLib_Z::Paths input_subject;
+		    ClipperLib_Z::Paths input_clip;
+		    for (const Polygon &poly : skirt_outers) {
+		    	input_subject.emplace_back();
+		    	ClipperLib_Z::Path &out = input_subject.back();
+		    	out.reserve(poly.points.size());
+			    for (const Point &pt : poly.points)
+					out.emplace_back(pt.x(), pt.y(), 0);
+		    }
+		    for (const Polygon &poly : skirt_inners) {
+		    	input_clip.emplace_back();
+		    	ClipperLib_Z::Path &out = input_clip.back();
+		    	out.reserve(poly.points.size());
+			    for (const Point &pt : poly.points)
+					out.emplace_back(pt.x(), pt.y(), 0);
+		    }
+		    // init Clipper
+		    ClipperLib_Z::Clipper clipper;	    
+		    // add polygons
+		    clipper.AddPaths(input_subject, ClipperLib_Z::ptSubject, true);
+		    clipper.AddPaths(input_clip,    ClipperLib_Z::ptClip,    true);
+		    // perform operation
+		    clipper.Execute(ClipperLib_Z::ctDifference, trimming, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd);
+		}
+
+		// Second, trim the extrusion loops with the trimming regions.
+		ClipperLib_Z::Paths loops_trimmed;
+		{
+			// Produce a closed polyline (repeat the first point at the end).
+			ClipperLib_Z::Paths input_clip;
+			for (const Polygon &loop : loops) {
+				input_clip.emplace_back();
+				ClipperLib_Z::Path& out = input_clip.back();
+				out.reserve(loop.points.size());
+				int64_t loop_idx = &loop - &loops.front();
+				for (const Point& pt : loop.points)
+					// The Z coordinate carries index of the source loop.
+					out.emplace_back(pt.x(), pt.y(), loop_idx + 1);
+				out.emplace_back(out.front());
+			}
+			// init Clipper
+			ClipperLib_Z::Clipper clipper;
+			clipper.ZFillFunction([](const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top, const ClipperLib_Z::IntPoint& e2bot, const ClipperLib_Z::IntPoint& e2top, ClipperLib_Z::IntPoint& pt) {
+				// Assign a valid input loop identifier. Such an identifier is strictly positive, the next line is safe even in case one side of a segment
+				// hat the Z coordinate not set to the contour coordinate.
+				pt.Z = std::max(std::max(e1bot.Z, e1top.Z), std::max(e2bot.Z, e2top.Z));
+			});
+			// add polygons
+			clipper.AddPaths(input_clip, ClipperLib_Z::ptSubject, false);
+			clipper.AddPaths(trimming,   ClipperLib_Z::ptClip,    true);
+			// perform operation
+			ClipperLib_Z::PolyTree loops_trimmed_tree;
+			clipper.Execute(ClipperLib_Z::ctDifference, loops_trimmed_tree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd);
+			ClipperLib_Z::PolyTreeToPaths(loops_trimmed_tree, loops_trimmed);
+		}
+
+		// Third, produce the extrusions, sorted by the source loop indices.
+		{
+			std::vector<std::pair<const ClipperLib_Z::Path*, size_t>> loops_trimmed_order;
+			loops_trimmed_order.reserve(loops_trimmed.size());
+			for (const ClipperLib_Z::Path &path : loops_trimmed) {
+				size_t input_idx = 0;
+				for (const ClipperLib_Z::IntPoint &pt : path)
+					if (pt.Z > 0) {
+						input_idx = (size_t)pt.Z;
+						break;
+					}
+				assert(input_idx != 0);
+				loops_trimmed_order.emplace_back(&path, input_idx);
+			}
+			std::stable_sort(loops_trimmed_order.begin(), loops_trimmed_order.end(),
+				[](const std::pair<const ClipperLib_Z::Path*, size_t> &l, const std::pair<const ClipperLib_Z::Path*, size_t> &r) {
+					return l.second < r.second;
+				});
+			Vec3f last_pt(0.f, 0.f, 0.f);
+
+			for (size_t i = 0; i < loops_trimmed_order.size();) {
+				// Find all pieces that the initial loop was split into.
+				size_t j = i + 1;
+				for (; j < loops_trimmed_order.size() && loops_trimmed_order[i].first == loops_trimmed_order[j].first; ++ j) ;
+				const ClipperLib_Z::Path &first_path = *loops_trimmed_order[i].first;
+				if (i + 1 == j && first_path.size() > 3 && first_path.front().X == first_path.back().X && first_path.front().Y == first_path.back().Y) {
+					auto *loop = new ExtrusionLoop();
+					m_brim.entities.emplace_back(loop);
+					loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()));
+		            Points &points = loop->paths.front().polyline.points;
+		            points.reserve(first_path.size());
+		            for (const ClipperLib_Z::IntPoint &pt : first_path)
+		            	points.emplace_back(coord_t(pt.X), coord_t(pt.Y));
+		            i = j;
+				} else {
+			    	//FIXME this is not optimal as the G-code generator will follow the sequence of paths verbatim without respect to minimum travel distance.
+			    	for (; i < j; ++ i) {
+			            m_brim.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height())));
+						const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first;
+			            Points &points = static_cast<ExtrusionPath*>(m_brim.entities.back())->polyline.points;
+			            points.reserve(path.size());
+			            for (const ClipperLib_Z::IntPoint &pt : path)
+			            	points.emplace_back(coord_t(pt.X), coord_t(pt.Y));
+		           	}
+		        }
+			}
+		}
+    } else {
+    	extrusion_entities_append_loops(m_brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()));
+    }
 }
 
 // Wipe tower support.
diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp
index c0ffe2108..67b3d3a21 100644
--- a/src/libslic3r/pchheader.hpp
+++ b/src/libslic3r/pchheader.hpp
@@ -105,6 +105,8 @@
 #include <cereal/access.hpp>
 #include <cereal/types/base_class.hpp>
 
+#include <clipper/clipper_z.hpp>
+#include <clipper/clipper.hpp>
 #include "BoundingBox.hpp"
 #include "ClipperUtils.hpp"
 #include "Config.hpp"
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 161e1a1ff..17b76e629 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -85,6 +85,8 @@ set(SLIC3R_GUI_SOURCES
     GUI/GUI_ObjectLayers.hpp
     GUI/LambdaObjectDialog.cpp
     GUI/LambdaObjectDialog.hpp
+    GUI/MeshUtils.cpp
+    GUI/MeshUtils.hpp
     GUI/Tab.cpp
     GUI/Tab.hpp
     GUI/ConfigManipulation.cpp
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index 202d3ef77..b15402a52 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -12,6 +12,7 @@
 #include "Selection.hpp"
 #include "Gizmos/GLGizmosManager.hpp"
 #include "GUI_ObjectLayers.hpp"
+#include "MeshUtils.hpp"
 
 #include <float.h>
 
@@ -67,36 +68,6 @@ public:
 };
 
 
-class ClippingPlane
-{
-    double m_data[4];
-
-public:
-    ClippingPlane()
-    {
-        m_data[0] = 0.0;
-        m_data[1] = 0.0;
-        m_data[2] = 1.0;
-        m_data[3] = 0.0;
-    }
-
-    ClippingPlane(const Vec3d& direction, double offset)
-    {
-        Vec3d norm_dir = direction.normalized();
-        m_data[0] = norm_dir(0);
-        m_data[1] = norm_dir(1);
-        m_data[2] = norm_dir(2);
-        m_data[3] = offset;
-    }
-
-    bool is_active() const { return m_data[3] != DBL_MAX; }
-
-    static ClippingPlane ClipsNothing() { return ClippingPlane(Vec3d(0., 0., 1.), DBL_MAX); }
-
-    const double* get_data() const { return m_data; }
-};
-
-
 wxDECLARE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
 
 using Vec2dEvent = Event<Vec2d>;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
index 0a5c21cce..80afb29b1 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
@@ -1,6 +1,7 @@
 // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
 #include "GLGizmoSlaSupports.hpp"
 #include "slic3r/GUI/GLCanvas3D.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmos.hpp"
 
 #include <GL/glew.h>
 
@@ -12,10 +13,10 @@
 #include "slic3r/GUI/GUI.hpp"
 #include "slic3r/GUI/GUI_ObjectSettings.hpp"
 #include "slic3r/GUI/GUI_ObjectList.hpp"
+#include "slic3r/GUI/MeshUtils.hpp"
 #include "slic3r/GUI/Plater.hpp"
 #include "slic3r/GUI/PresetBundle.hpp"
 #include "libslic3r/SLAPrint.hpp"
-#include "libslic3r/Tesselate.hpp"
 
 
 namespace Slic3r {
@@ -26,6 +27,7 @@ GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& ic
     , m_quadric(nullptr)
     , m_its(nullptr)
 {
+    m_clipping_plane.reset(new ClippingPlane(Vec3d::Zero(), 0.));
     m_quadric = ::gluNewQuadric();
     if (m_quadric != nullptr)
         // using GLU_FILL does not work when the instance's transformation
@@ -137,127 +139,82 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const
     if (m_clipping_plane_distance == 0.f)
         return;
 
-    if (m_clipping_plane_normal == Vec3d::Zero())
-        reset_clipping_plane_normal();
-
-    const Vec3d& direction_to_camera = m_clipping_plane_normal;
-
-    // First cache instance transformation to be used later.
+    // Get transformation of the instance
     const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin());
-    Transform3f instance_matrix = vol->get_instance_transformation().get_matrix().cast<float>();
-    Transform3f instance_matrix_no_translation_no_scaling = vol->get_instance_transformation().get_matrix(true,false,true).cast<float>();
-    Vec3f scaling = vol->get_instance_scaling_factor().cast<float>();
-    Vec3d instance_offset = vol->get_instance_offset();
+    Geometry::Transformation trafo = vol->get_instance_transformation();
+    trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
 
-    // Calculate distance from mesh origin to the clipping plane (in mesh coordinates).
-    Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera.cast<float>();
-    Vec3f up = Vec3f(up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2));
-    float height_mesh = (m_active_instance_bb_radius - m_clipping_plane_distance * 2*m_active_instance_bb_radius) * (up_noscale.norm()/up.norm());
+    // Get transformation of supports
+    Geometry::Transformation supports_trafo;
+    supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), vol->get_sla_shift_z()));
+    supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2)));
+    // I don't know why, but following seems to be correct.
+    supports_trafo.set_mirror(Vec3d(trafo.get_mirror()(0) * trafo.get_mirror()(1) * trafo.get_mirror()(2),
+                                    1,
+                                    1.));
 
-    // Get transformation of the supports and calculate how far from its origin the clipping plane is.
-    Transform3d supports_trafo = Transform3d::Identity();
-    supports_trafo = supports_trafo.rotate(Eigen::AngleAxisd(vol->get_instance_rotation()(2), Vec3d::UnitZ()));
-    Vec3f up_supports = (supports_trafo.inverse() * direction_to_camera).cast<float>();
-    supports_trafo = supports_trafo.pretranslate(Vec3d(instance_offset(0), instance_offset(1), vol->get_sla_shift_z()));
-    // Instance and supports origin do not coincide, so the following is quite messy:
-    float height_supports = height_mesh * (up.norm() / up_supports.norm()) + instance_offset(2) * (direction_to_camera(2) / direction_to_camera.norm());
+    // Now initialize the TMS for the object, perform the cut and save the result.
+    if (! m_object_clipper) {
+        m_object_clipper.reset(new MeshClipper);
+        m_object_clipper->set_mesh(*m_mesh);
+    }
+    m_object_clipper->set_plane(*m_clipping_plane);
+    m_object_clipper->set_transformation(trafo);
 
-    // In case either of these was recently changed, the cached triangulated ExPolygons are invalid now.
-    // We are gonna recalculate them both for the object and for the support structures.
-    if (m_clipping_plane_distance != m_old_clipping_plane_distance
-     || m_old_clipping_plane_normal != direction_to_camera) {
 
-        m_old_clipping_plane_normal = direction_to_camera;
-        m_old_clipping_plane_distance = m_clipping_plane_distance;
-
-        // Now initialize the TMS for the object, perform the cut and save the result.
-        if (! m_tms) {
-            m_tms.reset(new TriangleMeshSlicer);
-            m_tms->init(m_mesh, [](){});
+    // Next, ask the backend if supports are already calculated. If so, we are gonna cut them too.
+    // First we need a pointer to the respective SLAPrintObject. The index into objects vector is
+    // cached so we don't have todo it on each render. We only search for the po if needed:
+    if (m_print_object_idx < 0 || (int)m_parent.sla_print()->objects().size() != m_print_objects_count) {
+        m_print_objects_count = m_parent.sla_print()->objects().size();
+        m_print_object_idx = -1;
+        for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
+            ++m_print_object_idx;
+            if (po->model_object()->id() == m_model_object->id())
+                break;
         }
-        std::vector<ExPolygons> list_of_expolys;
-        m_tms->set_up_direction(up);
-        m_tms->slice(std::vector<float>{height_mesh}, 0.f, &list_of_expolys, [](){});
-        m_triangles = triangulate_expolygons_2f(list_of_expolys[0]);
+    }
+    if (m_print_object_idx >= 0) {
+        const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_print_object_idx];
 
+        if (print_object->is_step_done(slaposSupportTree)) {
+            // If the supports are already calculated, save the timestamp of the respective step
+            // so we can later tell they were recalculated.
+            size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp;
 
-
-        // Next, ask the backend if supports are already calculated. If so, we are gonna cut them too.
-        // First we need a pointer to the respective SLAPrintObject. The index into objects vector is
-        // cached so we don't have todo it on each render. We only search for the po if needed:
-        if (m_print_object_idx < 0 || (int)m_parent.sla_print()->objects().size() != m_print_objects_count) {
-            m_print_objects_count = m_parent.sla_print()->objects().size();
-            m_print_object_idx = -1;
-            for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
-                ++m_print_object_idx;
-                if (po->model_object()->id() == m_model_object->id())
-                    break;
-            }
-        }
-        if (m_print_object_idx >= 0) {
-            const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_print_object_idx];
-
-            if (print_object->is_step_done(slaposSupportTree)) {
-                // If the supports are already calculated, save the timestamp of the respective step
-                // so we can later tell they were recalculated.
-                size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp;
-
-                if (!m_supports_tms || (int)timestamp != m_old_timestamp) {
-                    // The timestamp has changed - stash the mesh and initialize the TMS.
-                    m_supports_mesh = &print_object->support_mesh();
-                    m_supports_tms.reset(new TriangleMeshSlicer);
-                    // The mesh should already have the shared vertices calculated.
-                    m_supports_tms->init(m_supports_mesh, [](){});
-                    m_old_timestamp = timestamp;
-                }
-
-                // The TMS is initialized - let's do the cutting:
-                list_of_expolys.clear();
-                m_supports_tms->set_up_direction(up_supports);
-                m_supports_tms->slice(std::vector<float>{height_supports}, 0.f, &list_of_expolys, [](){});
-                m_supports_triangles = triangulate_expolygons_2f(list_of_expolys[0]);
-            }
-            else {
-                // The supports are not valid. We better dump the cached data.
-                m_supports_tms.reset();
-                m_supports_triangles.clear();
+            if (! m_supports_clipper || (int)timestamp != m_old_timestamp) {
+                // The timestamp has changed.
+                m_supports_clipper.reset(new MeshClipper);
+                // The mesh should already have the shared vertices calculated.
+                m_supports_clipper->set_mesh(print_object->support_mesh());
+                m_old_timestamp = timestamp;
             }
+            m_supports_clipper->set_plane(*m_clipping_plane);
+            m_supports_clipper->set_transformation(supports_trafo);
         }
+        else
+            // The supports are not valid. We better dump the cached data.
+            m_supports_clipper.reset();
     }
 
     // At this point we have the triangulated cuts for both the object and supports - let's render.
-	if (! m_triangles.empty()) {
+    if (! m_object_clipper->get_triangles().empty()) {
 		::glPushMatrix();
-		::glTranslated(0.0, 0.0, m_z_shift);
-		::glMultMatrixf(instance_matrix.data());
-		Eigen::Quaternionf q;
-		q.setFromTwoVectors(Vec3f::UnitZ(), up);
-		Eigen::AngleAxisf aa(q);
-		::glRotatef(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2));
-		::glTranslatef(0.f, 0.f, 0.01f); // to make sure the cut does not intersect the structure itself
         ::glColor3f(1.0f, 0.37f, 0.0f);
         ::glBegin(GL_TRIANGLES);
-        for (const Vec2f& point : m_triangles)
-            ::glVertex3f(point(0), point(1), height_mesh);
-
+        for (const Vec3f& point : m_object_clipper->get_triangles())
+            ::glVertex3f(point(0), point(1), point(2));
         ::glEnd();
 		::glPopMatrix();
 	}
 
-    if (! m_supports_triangles.empty() && !m_editing_mode) {
+    if (m_supports_clipper && ! m_supports_clipper->get_triangles().empty() && !m_editing_mode) {
         // The supports are hidden in the editing mode, so it makes no sense to render the cuts.
-		::glPushMatrix();
-        ::glMultMatrixd(supports_trafo.data());
-        Eigen::Quaternionf q;
-		q.setFromTwoVectors(Vec3f::UnitZ(), up_supports);
-		Eigen::AngleAxisf aa(q);
-		::glRotatef(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2));
-		::glTranslatef(0.f, 0.f, 0.01f);
+        ::glPushMatrix();
         ::glColor3f(1.0f, 0.f, 0.37f);
         ::glBegin(GL_TRIANGLES);
-        for (const Vec2f& point : m_supports_triangles)
-            ::glVertex3f(point(0), point(1), height_supports);
-
+        for (const Vec3f& point : m_supports_clipper->get_triangles())
+            ::glVertex3f(point(0), point(1), point(2));
         ::glEnd();
 		::glPopMatrix();
 	}
@@ -379,15 +336,12 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
 
 bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point) const
 {
-    const Vec3d& direction_to_camera = m_clipping_plane_normal;
-
     if (m_clipping_plane_distance == 0.f)
         return false;
 
     Vec3d transformed_point = m_model_object->instances.front()->get_transformation().get_matrix() * point;
     transformed_point(2) += m_z_shift;
-    return direction_to_camera.dot(m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift)) + m_active_instance_bb_radius
-            - m_clipping_plane_distance * 2*m_active_instance_bb_radius < direction_to_camera.dot(transformed_point);
+    return m_clipping_plane->distance(transformed_point) < 0.;
 }
 
 
@@ -693,18 +647,18 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
 
     if (action == SLAGizmoEventType::MouseWheelUp && control_down) {
         m_clipping_plane_distance = std::min(1.f, m_clipping_plane_distance + 0.01f);
-        m_parent.set_as_dirty();
+        update_clipping_plane(true);
         return true;
     }
 
     if (action == SLAGizmoEventType::MouseWheelDown && control_down) {
         m_clipping_plane_distance = std::max(0.f, m_clipping_plane_distance - 0.01f);
-        m_parent.set_as_dirty();
+        update_clipping_plane(true);
         return true;
     }
 
     if (action == SLAGizmoEventType::ResetClippingPlane) {
-        reset_clipping_plane_normal();
+        update_clipping_plane();
         return true;
     }
 
@@ -796,18 +750,10 @@ void GLGizmoSlaSupports::update_cache_entry_normal(size_t i) const
 
 ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const
 {
-    if (!m_model_object || m_state == Off)
+    if (!m_model_object || m_state == Off || m_clipping_plane_distance == 0.f)
         return ClippingPlane::ClipsNothing();
-
-    //Eigen::Matrix<GLdouble, 4, 4, Eigen::DontAlign> modelview_matrix;
-    //::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data());
-    // we'll recover current look direction from the modelview matrix (in world coords):
-    //Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]);
-
-    const Vec3d& direction_to_camera = m_clipping_plane_normal;
-    float dist = direction_to_camera.dot(m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift));
-
-    return ClippingPlane(-direction_to_camera.normalized(),(dist - (-m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_active_instance_bb_radius));
+    else
+        return ClippingPlane(-m_clipping_plane->get_normal(), m_clipping_plane->get_data()[3]);
 }
 
 
@@ -1019,14 +965,15 @@ RENDER_AGAIN:
     else {
         if (m_imgui->button(m_desc.at("reset_direction"))) {
             wxGetApp().CallAfter([this](){
-                    reset_clipping_plane_normal();
+                    update_clipping_plane();
                 });
         }
     }
 
     ImGui::SameLine(clipping_slider_left);
     ImGui::PushItemWidth(window_width - clipping_slider_left);
-    ImGui::SliderFloat("  ", &m_clipping_plane_distance, 0.f, 1.f, "%.2f");
+    if (ImGui::SliderFloat("  ", &m_clipping_plane_distance, 0.f, 1.f, "%.2f"))
+        update_clipping_plane(true);
 
 
     if (m_imgui->button("?")) {
@@ -1156,8 +1103,8 @@ void GLGizmoSlaSupports::on_set_state()
             // Release triangle mesh slicer and the AABB spatial search structure.
             m_AABB.deinit();
             m_its = nullptr;
-            m_tms.reset();
-            m_supports_tms.reset();
+            m_object_clipper.reset();
+            m_supports_clipper.reset();
         }
     }
     m_old_state = m_state;
@@ -1198,7 +1145,7 @@ void GLGizmoSlaSupports::on_stop_dragging()
 void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar)
 {
     ar(m_clipping_plane_distance,
-       m_clipping_plane_normal,
+       *m_clipping_plane,
        m_model_object_id,
        m_new_point_head_diameter,
        m_normal_cache,
@@ -1212,7 +1159,7 @@ void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar)
 void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const
 {
     ar(m_clipping_plane_distance,
-       m_clipping_plane_normal,
+       *m_clipping_plane,
        m_model_object_id,
        m_new_point_head_diameter,
        m_normal_cache,
@@ -1401,17 +1348,19 @@ bool GLGizmoSlaSupports::unsaved_changes() const
 }
 
 
-void GLGizmoSlaSupports::reset_clipping_plane_normal() const
+void GLGizmoSlaSupports::update_clipping_plane(bool keep_normal) const
 {
-    Eigen::Matrix<double, 4, 4, Eigen::DontAlign> modelview_matrix;
-    ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data());
-    m_clipping_plane_normal = Vec3d(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]);
+    Vec3d normal = (keep_normal && m_clipping_plane->get_normal() != Vec3d::Zero() ?
+                        m_clipping_plane->get_normal() : -m_parent.get_camera().get_dir_forward());
+
+    const Vec3d& center = m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift);
+    float dist = normal.dot(center);
+    *m_clipping_plane = ClippingPlane(normal, (dist - (-m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_active_instance_bb_radius));
     m_parent.set_as_dirty();
 }
 
-
 SlaGizmoHelpDialog::SlaGizmoHelpDialog()
-: wxDialog(NULL, wxID_ANY, _(L("SLA gizmo keyboard shortcuts")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
+: wxDialog(nullptr, wxID_ANY, _(L("SLA gizmo keyboard shortcuts")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
 {
     SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
     const wxString ctrl = GUI::shortkey_ctrl_prefix();
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
index d2451f64e..7bef33e1b 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
@@ -2,7 +2,6 @@
 #define slic3r_GLGizmoSlaSupports_hpp_
 
 #include "GLGizmoBase.hpp"
-#include "GLGizmos.hpp"
 #include "slic3r/GUI/GLSelectionRectangle.hpp"
 
 // There is an L function in igl that would be overridden by our localization macro - let's undefine it...
@@ -19,9 +18,9 @@
 namespace Slic3r {
 namespace GUI {
 
-
 class ClippingPlane;
-
+class MeshClipper;
+enum class SLAGizmoEventType : unsigned char;
 
 class GLGizmoSlaSupports : public GLGizmoBase
 {
@@ -114,9 +113,7 @@ private:
     std::vector<sla::SupportPoint> m_normal_cache; // to restore after discarding changes or undo/redo
 
     float m_clipping_plane_distance = 0.f;
-    mutable float m_old_clipping_plane_distance = 0.f;
-    mutable Vec3d m_old_clipping_plane_normal;
-    mutable Vec3d m_clipping_plane_normal = Vec3d::Zero();
+    std::unique_ptr<ClippingPlane> m_clipping_plane;
 
     // This map holds all translated description texts, so they can be easily referenced during layout calculations
     // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
@@ -128,8 +125,8 @@ private:
     bool m_selection_empty = true;
     EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
 
-    mutable std::unique_ptr<TriangleMeshSlicer> m_tms;
-    mutable std::unique_ptr<TriangleMeshSlicer> m_supports_tms;
+    mutable std::unique_ptr<MeshClipper> m_object_clipper;
+    mutable std::unique_ptr<MeshClipper> m_supports_clipper;
 
     std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
     bool is_point_clipped(const Vec3d& point) const;
@@ -151,6 +148,7 @@ private:
     void switch_to_editing_mode();
     void disable_editing_mode();
     void reset_clipping_plane_normal() const;
+    void update_clipping_plane(bool keep_normal = false) const;
 
 protected:
     void on_set_state() override;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmos.hpp b/src/slic3r/GUI/Gizmos/GLGizmos.hpp
index 2e98899be..272fa098a 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmos.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmos.hpp
@@ -2,7 +2,10 @@
 #define slic3r_GLGizmos_hpp_
 
 // this describes events being passed from GLCanvas3D to SlaSupport gizmo
-enum class SLAGizmoEventType {
+namespace Slic3r {
+namespace GUI {
+
+enum class SLAGizmoEventType : unsigned char {
     LeftDown = 1,
     LeftUp,
     RightDown,
@@ -20,6 +23,9 @@ enum class SLAGizmoEventType {
     ResetClippingPlane
 };
 
+} // namespace GUI
+} // namespace Slic3r
+
 #include "slic3r/GUI/Gizmos/GLGizmoMove.hpp"
 #include "slic3r/GUI/Gizmos/GLGizmoScale.hpp"
 #include "slic3r/GUI/Gizmos/GLGizmoRotate.hpp"
diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp
new file mode 100644
index 000000000..9542f0b1f
--- /dev/null
+++ b/src/slic3r/GUI/MeshUtils.cpp
@@ -0,0 +1,95 @@
+#include "MeshUtils.hpp"
+
+#include "libslic3r/Tesselate.hpp"
+#include "libslic3r/TriangleMesh.hpp"
+
+namespace Slic3r {
+namespace GUI {
+
+void MeshClipper::set_plane(const ClippingPlane& plane)
+{
+    if (m_plane != plane) {
+        m_plane = plane;
+        m_triangles_valid = false;
+    }
+}
+
+
+
+void MeshClipper::set_mesh(const TriangleMesh& mesh)
+{
+    if (m_mesh != &mesh) {
+        m_mesh = &mesh;
+        m_triangles_valid = false;
+        m_triangles2d.resize(0);
+        m_triangles3d.resize(0);
+        m_tms.reset(nullptr);
+    }
+}
+
+
+
+void MeshClipper::set_transformation(const Geometry::Transformation& trafo)
+{
+    if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) {
+        m_trafo = trafo;
+        m_triangles_valid = false;
+        m_triangles2d.resize(0);
+        m_triangles3d.resize(0);
+    }
+}
+
+
+
+const std::vector<Vec3f>& MeshClipper::get_triangles()
+{
+    if (! m_triangles_valid)
+        recalculate_triangles();
+
+    return m_triangles3d;
+}
+
+
+
+void MeshClipper::recalculate_triangles()
+{
+    if (! m_tms) {
+        m_tms.reset(new TriangleMeshSlicer);
+        m_tms->init(m_mesh, [](){});
+    }
+
+    const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast<float>();
+    const Vec3f& scaling = m_trafo.get_scaling_factor().cast<float>();
+    // Calculate clipping plane normal in mesh coordinates.
+    Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast<float>();
+    Vec3f up (up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2));
+    // Calculate distance from mesh origin to the clipping plane (in mesh coordinates).
+    float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm());
+
+    // Now do the cutting
+    std::vector<ExPolygons> list_of_expolys;
+    m_tms->set_up_direction(up);
+    m_tms->slice(std::vector<float>{height_mesh}, 0.f, &list_of_expolys, [](){});
+    m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.);
+
+    // Rotate the cut into world coords:
+    Eigen::Quaternionf q;
+    q.setFromTwoVectors(Vec3f::UnitZ(), up);
+    Transform3f tr = Transform3f::Identity();
+    tr.rotate(q);
+    tr = m_trafo.get_matrix().cast<float>() * tr;
+
+    m_triangles3d.clear();
+    m_triangles3d.reserve(m_triangles2d.size());
+    for (const Vec2f& pt : m_triangles2d) {
+        m_triangles3d.push_back(Vec3f(pt(0), pt(1), height_mesh+0.001f));
+        m_triangles3d.back() = tr * m_triangles3d.back();
+    }
+
+    m_triangles_valid = true;
+}
+
+
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp
new file mode 100644
index 000000000..f97003a91
--- /dev/null
+++ b/src/slic3r/GUI/MeshUtils.hpp
@@ -0,0 +1,94 @@
+#ifndef slic3r_MeshUtils_hpp_
+#define slic3r_MeshUtils_hpp_
+
+#include "libslic3r/Point.hpp"
+#include "libslic3r/Geometry.hpp"
+
+
+#include <cfloat>
+
+namespace Slic3r {
+
+class TriangleMesh;
+class TriangleMeshSlicer;
+
+namespace GUI {
+
+
+
+class ClippingPlane
+{
+    double m_data[4];
+
+public:
+    ClippingPlane()
+    {
+        m_data[0] = 0.0;
+        m_data[1] = 0.0;
+        m_data[2] = 1.0;
+        m_data[3] = 0.0;
+    }
+
+    ClippingPlane(const Vec3d& direction, double offset)
+    {
+        Vec3d norm_dir = direction.normalized();
+        m_data[0] = norm_dir(0);
+        m_data[1] = norm_dir(1);
+        m_data[2] = norm_dir(2);
+        m_data[3] = offset;
+    }
+
+    bool operator==(const ClippingPlane& cp) const {
+        return m_data[0]==cp.m_data[0] && m_data[1]==cp.m_data[1] && m_data[2]==cp.m_data[2] && m_data[3]==cp.m_data[3];
+    }
+    bool operator!=(const ClippingPlane& cp) const { return ! (*this==cp); }
+
+    double distance(const Vec3d& pt) const {
+        assert(is_approx(get_normal().norm(), 1.));
+        return (-get_normal().dot(pt) + m_data[3]);
+    }
+
+    void set_normal(const Vec3d& normal) { for (size_t i=0; i<3; ++i) m_data[i] = normal(i); }
+    void set_offset(double offset) { m_data[3] = offset; }
+    Vec3d get_normal() const { return Vec3d(m_data[0], m_data[1], m_data[2]); }
+    bool is_active() const { return m_data[3] != DBL_MAX; }
+    static ClippingPlane ClipsNothing() { return ClippingPlane(Vec3d(0., 0., 1.), DBL_MAX); }
+    const double* get_data() const { return m_data; }
+
+    // Serialization through cereal library
+    template <class Archive>
+    void serialize( Archive & ar )
+    {
+        ar( m_data[0], m_data[1], m_data[2], m_data[3] );
+    }
+};
+
+
+
+class MeshClipper {
+public:
+    void set_plane(const ClippingPlane& plane);
+    void set_mesh(const TriangleMesh& mesh);
+    void set_transformation(const Geometry::Transformation& trafo);
+
+    const std::vector<Vec3f>& get_triangles();
+
+private:
+    void recalculate_triangles();
+
+    Geometry::Transformation m_trafo;
+    const TriangleMesh* m_mesh = nullptr;
+    ClippingPlane m_plane;
+    std::vector<Vec2f> m_triangles2d;
+    std::vector<Vec3f> m_triangles3d;
+    bool m_triangles_valid = false;
+    std::unique_ptr<TriangleMeshSlicer> m_tms;
+};
+
+
+    
+} // namespace GUI
+} // namespace Slic3r
+
+
+#endif // slic3r_MeshUtils_hpp_