diff --git a/resources/icons/cancel.svg b/resources/icons/cancel.svg
new file mode 100644
index 000000000..da44606a0
--- /dev/null
+++ b/resources/icons/cancel.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
+<g id="resin">
+	<rect x="4" y="7" fill="#ED6B21" width="8" height="8"/>
+	<path fill="none" stroke="#808080" stroke-linecap="round" stroke-miterlimit="10" d="M4.5,15h6.99c0.28,0,0.5-0.23,0.5-0.5V6
+		c0-1-2-1-2-2s0-1,0-1h1V1.5C11,1.23,10.77,1,10.5,1H5.5C5.23,1,5,1.23,5,1.5V3h1v1c0,1-2,1-2,2v8.5C4,14.77,4.23,15,4.5,15z"/>
+</g>
+</svg>
diff --git a/resources/icons/cross_focus_large.svg b/resources/icons/cross_focus_large.svg
new file mode 100644
index 000000000..c246f2bd9
--- /dev/null
+++ b/resources/icons/cross_focus_large.svg
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
+   sodipodi:docname="cross_megafocus.svg"
+   xml:space="preserve"
+   enable-background="new 0 0 16 16"
+   viewBox="0 0 16 16"
+   y="0px"
+   x="0px"
+   id="Layer_1"
+   version="1.0"><metadata
+   id="metadata16"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+   id="defs14" /><sodipodi:namedview
+   inkscape:current-layer="Layer_1"
+   inkscape:window-maximized="1"
+   inkscape:window-y="-9"
+   inkscape:window-x="-9"
+   inkscape:cy="8"
+   inkscape:cx="8"
+   inkscape:zoom="47.0625"
+   showgrid="false"
+   id="namedview12"
+   inkscape:window-height="1721"
+   inkscape:window-width="3200"
+   inkscape:pageshadow="2"
+   inkscape:pageopacity="0"
+   guidetolerance="10"
+   gridtolerance="10"
+   objecttolerance="10"
+   borderopacity="1"
+   bordercolor="#666666"
+   pagecolor="#ffffff" />
+<g
+   style="opacity:1;fill-opacity:1"
+   transform="matrix(1.1,0,0,1.1,-0.8,-0.8)"
+   id="cross">
+	<g
+   style="fill-opacity:1"
+   id="g4">
+		
+			<line
+   style="fill-opacity:1"
+   id="line2"
+   y2="14"
+   x2="2"
+   y1="2"
+   x1="14"
+   stroke-miterlimit="10"
+   stroke-linecap="round"
+   stroke-width="3"
+   stroke="#ed6b21"
+   fill="none" />
+	</g>
+	<g
+   style="fill-opacity:1"
+   id="g8">
+		
+			<line
+   style="fill-opacity:1"
+   id="line6"
+   y2="14"
+   x2="14"
+   y1="2"
+   x1="2"
+   stroke-miterlimit="10"
+   stroke-linecap="round"
+   stroke-width="3"
+   stroke="#ed6b21"
+   fill="none" />
+	</g>
+</g>
+</svg>
diff --git a/resources/icons/ironing.svg b/resources/icons/ironing.svg
new file mode 100644
index 000000000..94917d6bf
--- /dev/null
+++ b/resources/icons/ironing.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
+<g id="ironing">
+	<g>
+		<path fill="#ED6B21" d="M14,9.42H2c-0.39,0-0.71-0.32-0.71-0.71C1.29,7.08,2.07,4,5,4h8c0.33,0,0.61,0.22,0.69,0.54l1,4
+			c0.05,0.21,0,0.44-0.13,0.61C14.42,9.32,14.22,9.42,14,9.42z M2.77,8h10.32l-0.65-2.58H5C3.39,5.42,2.91,7.03,2.77,8z"/>
+	</g>
+	<g>
+		<path fill="#ED6B21" d="M13,5.42c-0.39,0-0.71-0.32-0.71-0.71v-1c0-1.18-0.99-1.29-1.3-1.29H6c-0.39,0-0.71-0.32-0.71-0.71
+			S5.61,1,6,1h5c1.05,0,2.61,0.68,2.7,2.52c0,0.03,0,0.06,0,0.08v1.1C13.71,5.1,13.39,5.42,13,5.42z"/>
+	</g>
+	<g>
+		<path fill="#808080" d="M14.65,15H1.35C1.16,15,1,14.84,1,14.65s0.16-0.35,0.35-0.35h13.29c0.2,0,0.35,0.16,0.35,0.35
+			S14.84,15,14.65,15z"/>
+	</g>
+	<g>
+		<path fill="#808080" d="M14.65,13H1.35C1.16,13,1,12.84,1,12.65s0.16-0.35,0.35-0.35h13.29c0.2,0,0.35,0.16,0.35,0.35
+			S14.84,13,14.65,13z"/>
+	</g>
+	<g>
+		<path fill="#808080" d="M14.65,11H1.35C1.16,11,1,10.84,1,10.65s0.16-0.35,0.35-0.35h13.29c0.2,0,0.35,0.16,0.35,0.35
+			S14.84,11,14.65,11z"/>
+	</g>
+</g>
+</svg>
diff --git a/resources/icons/timer_dot.svg b/resources/icons/timer_dot.svg
new file mode 100644
index 000000000..3a77962b6
--- /dev/null
+++ b/resources/icons/timer_dot.svg
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
+   sodipodi:docname="timer_dot.svg"
+   xml:space="preserve"
+   enable-background="new 0 0 16 16"
+   viewBox="0 0 16 16"
+   y="0px"
+   x="0px"
+   id="Layer_1"
+   version="1.0"><metadata
+   id="metadata11"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
+   id="defs9"><linearGradient
+     id="linearGradient830"
+     inkscape:collect="always"><stop
+       id="stop826"
+       offset="0"
+       style="stop-color:#000000;stop-opacity:1;" /><stop
+       id="stop828"
+       offset="1"
+       style="stop-color:#000000;stop-opacity:0;" /></linearGradient><radialGradient
+     gradientUnits="userSpaceOnUse"
+     r="3.5"
+     fy="8"
+     fx="8"
+     cy="8"
+     cx="8"
+     id="radialGradient832"
+     xlink:href="#linearGradient830"
+     inkscape:collect="always" /></defs><sodipodi:namedview
+   inkscape:document-rotation="0"
+   inkscape:current-layer="Layer_1"
+   inkscape:window-maximized="1"
+   inkscape:window-y="-11"
+   inkscape:window-x="-11"
+   inkscape:cy="6.66147"
+   inkscape:cx="7.0304602"
+   inkscape:zoom="83.4386"
+   showgrid="false"
+   id="namedview7"
+   inkscape:window-height="2066"
+   inkscape:window-width="3840"
+   inkscape:pageshadow="2"
+   inkscape:pageopacity="0"
+   guidetolerance="10"
+   gridtolerance="10"
+   objecttolerance="10"
+   borderopacity="1"
+   bordercolor="#666666"
+   pagecolor="#ffffff" />
+<g
+   transform="matrix(0.7,0,0,0.7,2.4,2.4)"
+   style="fill:#bf6637;fill-opacity:1;stroke:none;stroke-opacity:1"
+   id="g4">
+	<circle
+   style="fill:#bf6637;fill-opacity:1;stroke:none;stroke-opacity:1"
+   id="circle2"
+   r="3"
+   cy="8"
+   cx="8" />
+</g>
+</svg>
diff --git a/resources/icons/timer_dot_empty.svg b/resources/icons/timer_dot_empty.svg
new file mode 100644
index 000000000..a8e776b49
--- /dev/null
+++ b/resources/icons/timer_dot_empty.svg
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.0"
+   id="Layer_1"
+   x="0px"
+   y="0px"
+   viewBox="0 0 16 16"
+   enable-background="new 0 0 16 16"
+   xml:space="preserve"
+   sodipodi:docname="timer_dot_empty.svg"
+   inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
+   id="metadata11"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+   id="defs9"><linearGradient
+     inkscape:collect="always"
+     id="linearGradient830"><stop
+       style="stop-color:#000000;stop-opacity:1;"
+       offset="0"
+       id="stop826" /><stop
+       style="stop-color:#000000;stop-opacity:0;"
+       offset="1"
+       id="stop828" /></linearGradient><radialGradient
+     inkscape:collect="always"
+     xlink:href="#linearGradient830"
+     id="radialGradient832"
+     cx="8"
+     cy="8"
+     fx="8"
+     fy="8"
+     r="3.5"
+     gradientUnits="userSpaceOnUse" /></defs><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="3840"
+   inkscape:window-height="2066"
+   id="namedview7"
+   showgrid="false"
+   inkscape:zoom="83.4386"
+   inkscape:cx="7.0304602"
+   inkscape:cy="6.66147"
+   inkscape:window-x="-11"
+   inkscape:window-y="-11"
+   inkscape:window-maximized="1"
+   inkscape:current-layer="Layer_1" />
+<g
+   id="g4"
+   style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1"
+   transform="matrix(0.7,0,0,0.7,2.4,2.4)">
+	<circle
+   cx="8"
+   cy="8"
+   r="3"
+   id="circle2"
+   style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1" />
+</g>
+</svg>
\ No newline at end of file
diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h
index d32f64aa4..feda857ae 100644
--- a/src/imgui/imconfig.h
+++ b/src/imgui/imconfig.h
@@ -113,7 +113,12 @@ namespace ImGui
     const char PrinterSlaIconMarker = 0x6; 
     const char FilamentIconMarker   = 0x7; 
     const char MaterialIconMarker   = 0x8;
-
+	const char CloseIconMarker      = 0xB;
+	const char CloseIconHoverMarker = 0xC;
+	const char TimerDotMarker       = 0xE;
+	const char TimerDotEmptyMarker  = 0xF;
+	const char WarningMarker        = 0x10;
+	const char ErrorMarker          = 0x11;
 //    void MyFunction(const char* name, const MyMatrix44& v);
 
 }
diff --git a/src/libnest2d/LICENSE.txt b/src/libnest2d/LICENSE.txt
index dba13ed2d..07b1d92c0 100644
--- a/src/libnest2d/LICENSE.txt
+++ b/src/libnest2d/LICENSE.txt
@@ -1,661 +1,165 @@
-                    GNU AFFERO GENERAL PUBLIC LICENSE
-                       Version 3, 19 November 2007
+    GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
 
  Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
-                            Preamble
-
-  The GNU Affero General Public License is a free, copyleft license for
-software and other kinds of works, specifically designed to ensure
-cooperation with the community in the case of network server software.
-
-  The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works.  By contrast,
-our General Public Licenses are intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
-  Developers that use our General Public Licenses protect your rights
-with two steps: (1) assert copyright on the software, and (2) offer
-you this License which gives you legal permission to copy, distribute
-and/or modify the software.
-
-  A secondary benefit of defending all users' freedom is that
-improvements made in alternate versions of the program, if they
-receive widespread use, become available for other developers to
-incorporate.  Many developers of free software are heartened and
-encouraged by the resulting cooperation.  However, in the case of
-software used on network servers, this result may fail to come about.
-The GNU General Public License permits making a modified version and
-letting the public access it on a server without ever releasing its
-source code to the public.
-
-  The GNU Affero General Public License is designed specifically to
-ensure that, in such cases, the modified source code becomes available
-to the community.  It requires the operator of a network server to
-provide the source code of the modified version running there to the
-users of that server.  Therefore, public use of a modified version, on
-a publicly accessible server, gives the public access to the source
-code of the modified version.
-
-  An older license, called the Affero General Public License and
-published by Affero, was designed to accomplish similar goals.  This is
-a different license, not a version of the Affero GPL, but Affero has
-released a new version of the Affero GPL which permits relicensing under
-this license.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                       TERMS AND CONDITIONS
-
-  0. Definitions.
-
-  "This License" refers to version 3 of the GNU Affero General Public License.
-
-  "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
-  "The Program" refers to any copyrightable work licensed under this
-License.  Each licensee is addressed as "you".  "Licensees" and
-"recipients" may be individuals or organizations.
-
-  To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy.  The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
-  A "covered work" means either the unmodified Program or a work based
-on the Program.
-
-  To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy.  Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
-  To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies.  Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
-  An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License.  If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
-  1. Source Code.
-
-  The "source code" for a work means the preferred form of the work
-for making modifications to it.  "Object code" means any non-source
-form of a work.
-
-  A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
-  The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form.  A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
-  The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities.  However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work.  For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
-  The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
-  The Corresponding Source for a work in source code form is that
-same work.
-
-  2. Basic Permissions.
-
-  All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met.  This License explicitly affirms your unlimited
-permission to run the unmodified Program.  The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work.  This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
-  You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force.  You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright.  Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
-  Conveying under any other circumstances is permitted solely under
-the conditions stated below.  Sublicensing is not allowed; section 10
-makes it unnecessary.
-
-  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
-  No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
-  When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
-  4. Conveying Verbatim Copies.
-
-  You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
-  You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
-  5. Conveying Modified Source Versions.
-
-  You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
-    a) The work must carry prominent notices stating that you modified
-    it, and giving a relevant date.
-
-    b) The work must carry prominent notices stating that it is
-    released under this License and any conditions added under section
-    7.  This requirement modifies the requirement in section 4 to
-    "keep intact all notices".
-
-    c) You must license the entire work, as a whole, under this
-    License to anyone who comes into possession of a copy.  This
-    License will therefore apply, along with any applicable section 7
-    additional terms, to the whole of the work, and all its parts,
-    regardless of how they are packaged.  This License gives no
-    permission to license the work in any other way, but it does not
-    invalidate such permission if you have separately received it.
-
-    d) If the work has interactive user interfaces, each must display
-    Appropriate Legal Notices; however, if the Program has interactive
-    interfaces that do not display Appropriate Legal Notices, your
-    work need not make them do so.
-
-  A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit.  Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
-  6. Conveying Non-Source Forms.
-
-  You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
-    a) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by the
-    Corresponding Source fixed on a durable physical medium
-    customarily used for software interchange.
-
-    b) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by a
-    written offer, valid for at least three years and valid for as
-    long as you offer spare parts or customer support for that product
-    model, to give anyone who possesses the object code either (1) a
-    copy of the Corresponding Source for all the software in the
-    product that is covered by this License, on a durable physical
-    medium customarily used for software interchange, for a price no
-    more than your reasonable cost of physically performing this
-    conveying of source, or (2) access to copy the
-    Corresponding Source from a network server at no charge.
-
-    c) Convey individual copies of the object code with a copy of the
-    written offer to provide the Corresponding Source.  This
-    alternative is allowed only occasionally and noncommercially, and
-    only if you received the object code with such an offer, in accord
-    with subsection 6b.
-
-    d) Convey the object code by offering access from a designated
-    place (gratis or for a charge), and offer equivalent access to the
-    Corresponding Source in the same way through the same place at no
-    further charge.  You need not require recipients to copy the
-    Corresponding Source along with the object code.  If the place to
-    copy the object code is a network server, the Corresponding Source
-    may be on a different server (operated by you or a third party)
-    that supports equivalent copying facilities, provided you maintain
-    clear directions next to the object code saying where to find the
-    Corresponding Source.  Regardless of what server hosts the
-    Corresponding Source, you remain obligated to ensure that it is
-    available for as long as needed to satisfy these requirements.
-
-    e) Convey the object code using peer-to-peer transmission, provided
-    you inform other peers where the object code and Corresponding
-    Source of the work are being offered to the general public at no
-    charge under subsection 6d.
-
-  A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
-  A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling.  In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage.  For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product.  A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
-  "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source.  The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
-  If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information.  But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
-  The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed.  Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
-  Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
-  7. Additional Terms.
-
-  "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law.  If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
-  When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it.  (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.)  You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
-  Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
-    a) Disclaiming warranty or limiting liability differently from the
-    terms of sections 15 and 16 of this License; or
-
-    b) Requiring preservation of specified reasonable legal notices or
-    author attributions in that material or in the Appropriate Legal
-    Notices displayed by works containing it; or
-
-    c) Prohibiting misrepresentation of the origin of that material, or
-    requiring that modified versions of such material be marked in
-    reasonable ways as different from the original version; or
-
-    d) Limiting the use for publicity purposes of names of licensors or
-    authors of the material; or
-
-    e) Declining to grant rights under trademark law for use of some
-    trade names, trademarks, or service marks; or
-
-    f) Requiring indemnification of licensors and authors of that
-    material by anyone who conveys the material (or modified versions of
-    it) with contractual assumptions of liability to the recipient, for
-    any liability that these contractual assumptions directly impose on
-    those licensors and authors.
-
-  All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10.  If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term.  If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
-  If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
-  Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
-  8. Termination.
-
-  You may not propagate or modify a covered work except as expressly
-provided under this License.  Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
-  However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
-  Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
-  Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License.  If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
-  9. Acceptance Not Required for Having Copies.
-
-  You are not required to accept this License in order to receive or
-run a copy of the Program.  Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance.  However,
-nothing other than this License grants you permission to propagate or
-modify any covered work.  These actions infringe copyright if you do
-not accept this License.  Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
-  10. Automatic Licensing of Downstream Recipients.
-
-  Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License.  You are not responsible
-for enforcing compliance by third parties with this License.
-
-  An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations.  If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
-  You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License.  For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
-  11. Patents.
-
-  A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based.  The
-work thus licensed is called the contributor's "contributor version".
-
-  A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version.  For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
-  Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
-  In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement).  To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
-  If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients.  "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
-  If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
-  A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License.  You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
-  Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
-  12. No Surrender of Others' Freedom.
-
-  If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all.  For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
-  13. Remote Network Interaction; Use with the GNU General Public License.
-
-  Notwithstanding any other provision of this License, if you modify the
-Program, your modified version must prominently offer all users
-interacting with it remotely through a computer network (if your version
-supports such interaction) an opportunity to receive the Corresponding
-Source of your version by providing access to the Corresponding Source
-from a network server at no charge, through some standard or customary
-means of facilitating copying of software.  This Corresponding Source
-shall include the Corresponding Source for any work covered by version 3
-of the GNU General Public License that is incorporated pursuant to the
-following paragraph.
-
-  Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU General Public License into a single
-combined work, and to convey the resulting work.  The terms of this
-License will continue to apply to the part which is the covered work,
-but the work with which it is combined will remain governed by version
-3 of the GNU General Public License.
-
-  14. Revised Versions of this License.
-
-  The Free Software Foundation may publish revised and/or new versions of
-the GNU Affero General Public License from time to time.  Such new versions
-will be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-  Each version is given a distinguishing version number.  If the
-Program specifies that a certain numbered version of the GNU Affero General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation.  If the Program does not specify a version number of the
-GNU Affero General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
-  If the Program specifies that a proxy can decide which future
-versions of the GNU Affero General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
-  Later license versions may give you additional or different
-permissions.  However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
-  15. Disclaimer of Warranty.
-
-  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. Limitation of Liability.
-
-  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
-  17. Interpretation of Sections 15 and 16.
-
-  If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
-                     END OF TERMS AND CONDITIONS
-
-            How to Apply These Terms to Your New Programs
-
-  If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
-  To do so, attach the following notices to the program.  It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-    <one line to give the program's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    This program is free software: you can redistribute it and/or modify
-    it under the terms of the GNU Affero General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU Affero General Public License for more details.
-
-    You should have received a copy of the GNU Affero General Public License
-    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-Also add information on how to contact you by electronic and paper mail.
-
-  If your software can interact with users remotely through a computer
-network, you should also make sure that it provides a way for users to
-get its source.  For example, if your program is a web application, its
-interface could display a "Source" link that leads users to an archive
-of the code.  There are many ways you could offer source, and different
-solutions will be better for different programs; see section 13 for the
-specific requirements.
-
-  You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU AGPL, see
-<http://www.gnu.org/licenses/>.
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
\ No newline at end of file
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index b4d82ab3e..f11a6e7c2 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -207,12 +207,13 @@ add_library(libslic3r STATIC
     SimplifyMeshImpl.hpp
     SimplifyMesh.cpp
     MarchingSquares.hpp
+    Optimizer.hpp
     ${OpenVDBUtils_SOURCES}
-    SLA/Common.hpp
-    SLA/Common.cpp
     SLA/Pad.hpp
     SLA/Pad.cpp
     SLA/SupportTreeBuilder.hpp
+    SLA/SupportTreeMesher.hpp
+    SLA/SupportTreeMesher.cpp
     SLA/SupportTreeBuildsteps.hpp
     SLA/SupportTreeBuildsteps.cpp
     SLA/SupportTreeBuilder.cpp
@@ -224,6 +225,7 @@ add_library(libslic3r STATIC
     SLA/Rotfinder.cpp
     SLA/BoostAdapter.hpp
     SLA/SpatIndex.hpp
+    SLA/SpatIndex.cpp
     SLA/RasterBase.hpp
     SLA/RasterBase.cpp
     SLA/AGGRaster.hpp
@@ -239,8 +241,10 @@ add_library(libslic3r STATIC
     SLA/SupportPointGenerator.cpp
     SLA/Contour3D.hpp
     SLA/Contour3D.cpp
-    SLA/EigenMesh3D.hpp
+    SLA/IndexedMesh.hpp
+    SLA/IndexedMesh.cpp
     SLA/Clustering.hpp
+    SLA/Clustering.cpp
     SLA/ReprojectPointsOnMesh.hpp
 )
 
diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp
index 3612e6898..59dc85a0a 100644
--- a/src/libslic3r/Format/3mf.cpp
+++ b/src/libslic3r/Format/3mf.cpp
@@ -1878,10 +1878,11 @@ namespace Slic3r {
             volume->calculate_convex_hull();
 
             // recreate custom supports from previously loaded attribute
-            assert(geometry.custom_supports.size() == triangles_count);
             for (unsigned i=0; i<triangles_count; ++i) {
-                if (! geometry.custom_supports[i].empty())
-                    volume->m_supported_facets.set_triangle_from_string(i, geometry.custom_supports[i]);
+                size_t index = src_start_id/3 + i;
+                assert(index < geometry.custom_supports.size());
+                if (! geometry.custom_supports[index].empty())
+                    volume->m_supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
             }
 
             // apply the remaining volume's metadata
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 35dc5a53b..7d8067718 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -686,6 +686,7 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
     std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) { return oi1.print_z < oi2.print_z; });
 
     std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print;
+
     // Merge numerically very close Z values.
     for (size_t i = 0; i < ordering.size();) {
         // Find the last layer with roughly the same print_z.
diff --git a/src/libslic3r/OpenVDBUtils.hpp b/src/libslic3r/OpenVDBUtils.hpp
index c493845a1..e35231d35 100644
--- a/src/libslic3r/OpenVDBUtils.hpp
+++ b/src/libslic3r/OpenVDBUtils.hpp
@@ -2,7 +2,6 @@
 #define OPENVDBUTILS_HPP
 
 #include <libslic3r/TriangleMesh.hpp>
-#include <libslic3r/SLA/Common.hpp>
 #include <libslic3r/SLA/Contour3D.hpp>
 #include <openvdb/openvdb.h>
 
diff --git a/src/libslic3r/Optimizer.hpp b/src/libslic3r/Optimizer.hpp
new file mode 100644
index 000000000..6495ae7ff
--- /dev/null
+++ b/src/libslic3r/Optimizer.hpp
@@ -0,0 +1,380 @@
+#ifndef NLOPTOPTIMIZER_HPP
+#define NLOPTOPTIMIZER_HPP
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable: 4244)
+#pragma warning(disable: 4267)
+#endif
+#include <nlopt.h>
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#include <utility>
+#include <tuple>
+#include <array>
+#include <cmath>
+#include <functional>
+#include <limits>
+#include <cassert>
+
+namespace Slic3r { namespace opt {
+
+// A type to hold the complete result of the optimization.
+template<size_t N> struct Result {
+    int resultcode;
+    std::array<double, N> optimum;
+    double score;
+};
+
+// An interval of possible input values for optimization
+class Bound {
+    double m_min, m_max;
+
+public:
+    Bound(double min = std::numeric_limits<double>::min(),
+          double max = std::numeric_limits<double>::max())
+        : m_min(min), m_max(max)
+    {}
+
+    double min() const noexcept { return m_min; }
+    double max() const noexcept { return m_max; }
+};
+
+// Helper types for optimization function input and bounds
+template<size_t N> using Input = std::array<double, N>;
+template<size_t N> using Bounds = std::array<Bound, N>;
+
+// A type for specifying the stop criteria. Setter methods can be concatenated
+class StopCriteria {
+
+    // If the absolute value difference between two scores.
+    double m_abs_score_diff = std::nan("");
+
+    // If the relative value difference between two scores.
+    double m_rel_score_diff = std::nan("");
+
+    // Stop if this value or better is found.
+    double m_stop_score = std::nan("");
+
+    // A predicate that if evaluates to true, the optimization should terminate
+    // and the best result found prior to termination should be returned.
+    std::function<bool()> m_stop_condition = [] { return false; };
+
+    // The max allowed number of iterations.
+    unsigned m_max_iterations = 0;
+
+public:
+
+    StopCriteria & abs_score_diff(double val)
+    {
+        m_abs_score_diff = val; return *this;
+    }
+
+    double abs_score_diff() const { return m_abs_score_diff; }
+
+    StopCriteria & rel_score_diff(double val)
+    {
+        m_rel_score_diff = val; return *this;
+    }
+
+    double rel_score_diff() const { return m_rel_score_diff; }
+
+    StopCriteria & stop_score(double val)
+    {
+        m_stop_score = val; return *this;
+    }
+
+    double stop_score() const { return m_stop_score; }
+
+    StopCriteria & max_iterations(double val)
+    {
+        m_max_iterations = val; return *this;
+    }
+
+    double max_iterations() const { return m_max_iterations; }
+
+    template<class Fn> StopCriteria & stop_condition(Fn &&cond)
+    {
+        m_stop_condition = cond; return *this;
+    }
+
+    bool stop_condition() { return m_stop_condition(); }
+};
+
+// Helper class to use optimization methods involving gradient.
+template<size_t N> struct ScoreGradient {
+    double score;
+    std::optional<std::array<double, N>> gradient;
+
+    ScoreGradient(double s, const std::array<double, N> &grad)
+        : score{s}, gradient{grad}
+    {}
+};
+
+// Helper to be used in static_assert.
+template<class T> struct always_false { enum { value = false }; };
+
+// Basic interface to optimizer object
+template<class Method, class Enable = void> class Optimizer {
+public:
+
+    Optimizer(const StopCriteria &)
+    {
+        static_assert (always_false<Method>::value,
+                       "Optimizer unimplemented for given method!");
+    }
+
+    Optimizer<Method> &to_min() { return *this; }
+    Optimizer<Method> &to_max() { return *this; }
+    Optimizer<Method> &set_criteria(const StopCriteria &) { return *this; }
+    StopCriteria get_criteria() const { return {}; };
+
+    template<class Func, size_t N>
+    Result<N> optimize(Func&& func,
+                       const Input<N> &initvals,
+                       const Bounds<N>& bounds) { return {}; }
+
+    // optional for randomized methods:
+    void seed(long /*s*/) {}
+};
+
+namespace detail {
+
+// Helper types for NLopt algorithm selection in template contexts
+template<nlopt_algorithm alg> struct NLoptAlg {};
+
+// NLopt can combine multiple algorithms if one is global an other is a local
+// method. This is how template specializations can be informed about this fact.
+template<nlopt_algorithm gl_alg, nlopt_algorithm lc_alg = NLOPT_LN_NELDERMEAD>
+struct NLoptAlgComb {};
+
+template<class M> struct IsNLoptAlg {
+    static const constexpr bool value = false;
+};
+
+template<nlopt_algorithm a> struct IsNLoptAlg<NLoptAlg<a>> {
+    static const constexpr bool value = true;
+};
+
+template<nlopt_algorithm a1, nlopt_algorithm a2>
+struct IsNLoptAlg<NLoptAlgComb<a1, a2>> {
+    static const constexpr bool value = true;
+};
+
+template<class M, class T = void>
+using NLoptOnly = std::enable_if_t<IsNLoptAlg<M>::value, T>;
+
+// Helper to convert C style array to std::array. The copy should be optimized
+// away with modern compilers.
+template<size_t N, class T> auto to_arr(const T *a)
+{
+    std::array<T, N> r;
+    std::copy(a, a + N, std::begin(r));
+    return r;
+}
+
+template<size_t N, class T> auto to_arr(const T (&a) [N])
+{
+    return to_arr<N>(static_cast<const T *>(a));
+}
+
+enum class OptDir { MIN, MAX }; // Where to optimize
+
+struct NLopt { // Helper RAII class for nlopt_opt
+    nlopt_opt ptr = nullptr;
+
+    template<class...A> explicit NLopt(A&&...a)
+    {
+        ptr = nlopt_create(std::forward<A>(a)...);
+    }
+
+    NLopt(const NLopt&) = delete;
+    NLopt(NLopt&&) = delete;
+    NLopt& operator=(const NLopt&) = delete;
+    NLopt& operator=(NLopt&&) = delete;
+
+    ~NLopt() { nlopt_destroy(ptr); }
+};
+
+template<class Method> class NLoptOpt {};
+
+// Optimizers based on NLopt.
+template<nlopt_algorithm alg> class NLoptOpt<NLoptAlg<alg>> {
+protected:
+    StopCriteria m_stopcr;
+    OptDir m_dir;
+
+    template<class Fn> using TOptData =
+        std::tuple<std::remove_reference_t<Fn>*, NLoptOpt*, nlopt_opt>;
+
+    template<class Fn, size_t N>
+    static double optfunc(unsigned n, const double *params,
+                          double *gradient,
+                          void *data)
+    {
+        assert(n >= N);
+
+        auto tdata = static_cast<TOptData<Fn>*>(data);
+
+        if (std::get<1>(*tdata)->m_stopcr.stop_condition())
+            nlopt_force_stop(std::get<2>(*tdata));
+
+        auto fnptr  = std::get<0>(*tdata);
+        auto funval = to_arr<N>(params);
+
+        double scoreval = 0.;
+        using RetT = decltype((*fnptr)(funval));
+        if constexpr (std::is_convertible_v<RetT, ScoreGradient<N>>) {
+            ScoreGradient<N> score = (*fnptr)(funval);
+            for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i];
+            scoreval = score.score;
+        } else {
+            scoreval = (*fnptr)(funval);
+        }
+
+        return scoreval;
+    }
+
+    template<size_t N>
+    void set_up(NLopt &nl, const Bounds<N>& bounds)
+    {
+        std::array<double, N> lb, ub;
+
+        for (size_t i = 0; i < N; ++i) {
+            lb[i] = bounds[i].min();
+            ub[i] = bounds[i].max();
+        }
+
+        nlopt_set_lower_bounds(nl.ptr, lb.data());
+        nlopt_set_upper_bounds(nl.ptr, ub.data());
+
+        double abs_diff = m_stopcr.abs_score_diff();
+        double rel_diff = m_stopcr.rel_score_diff();
+        double stopval = m_stopcr.stop_score();
+        if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff);
+        if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff);
+        if(!std::isnan(stopval))  nlopt_set_stopval(nl.ptr, stopval);
+
+        if(this->m_stopcr.max_iterations() > 0)
+            nlopt_set_maxeval(nl.ptr, this->m_stopcr.max_iterations());
+    }
+
+    template<class Fn, size_t N>
+    Result<N> optimize(NLopt &nl, Fn &&fn, const Input<N> &initvals)
+    {
+        Result<N> r;
+
+        TOptData<Fn> data = std::make_tuple(&fn, this, nl.ptr);
+
+        switch(m_dir) {
+        case OptDir::MIN:
+            nlopt_set_min_objective(nl.ptr, optfunc<Fn, N>, &data); break;
+        case OptDir::MAX:
+            nlopt_set_max_objective(nl.ptr, optfunc<Fn, N>, &data); break;
+        }
+
+        r.optimum = initvals;
+        r.resultcode = nlopt_optimize(nl.ptr, r.optimum.data(), &r.score);
+
+        return r;
+    }
+
+public:
+
+    template<class Func, size_t N>
+    Result<N> optimize(Func&& func,
+                       const Input<N> &initvals,
+                       const Bounds<N>& bounds)
+    {
+        NLopt nl{alg, N};
+        set_up(nl, bounds);
+
+        return optimize(nl, std::forward<Func>(func), initvals);
+    }
+
+    explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {}
+
+    void set_criteria(const StopCriteria &cr) { m_stopcr = cr; }
+    const StopCriteria &get_criteria() const noexcept { return m_stopcr; }
+    void set_dir(OptDir dir) noexcept { m_dir = dir; }
+
+    void seed(long s) { nlopt_srand(s); }
+};
+
+template<nlopt_algorithm glob, nlopt_algorithm loc>
+class NLoptOpt<NLoptAlgComb<glob, loc>>: public NLoptOpt<NLoptAlg<glob>>
+{
+    using Base = NLoptOpt<NLoptAlg<glob>>;
+public:
+
+    template<class Fn, size_t N>
+    Result<N> optimize(Fn&& f,
+                       const Input<N> &initvals,
+                       const Bounds<N>& bounds)
+    {
+        NLopt nl_glob{glob, N}, nl_loc{loc, N};
+
+        Base::set_up(nl_glob, bounds);
+        Base::set_up(nl_loc, bounds);
+        nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr);
+
+        return Base::optimize(nl_glob, std::forward<Fn>(f), initvals);
+    }
+
+    explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {}
+};
+
+} // namespace detail;
+
+// Optimizers based on NLopt.
+template<class M> class Optimizer<M, detail::NLoptOnly<M>> {
+    detail::NLoptOpt<M> m_opt;
+
+public:
+
+    Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; }
+    Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; }
+
+    template<class Func, size_t N>
+    Result<N> optimize(Func&& func,
+                       const Input<N> &initvals,
+                       const Bounds<N>& bounds)
+    {
+        return m_opt.optimize(std::forward<Func>(func), initvals, bounds);
+    }
+
+    explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {}
+
+    Optimizer &set_criteria(const StopCriteria &cr)
+    {
+        m_opt.set_criteria(cr); return *this;
+    }
+
+    const StopCriteria &get_criteria() const { return m_opt.get_criteria(); }
+
+    void seed(long s) { m_opt.seed(s); }
+};
+
+template<size_t N> Bounds<N> bounds(const Bound (&b) [N]) { return detail::to_arr(b); }
+template<size_t N> Input<N> initvals(const double (&a) [N]) { return detail::to_arr(a); }
+template<size_t N> auto score_gradient(double s, const double (&grad)[N])
+{
+    return ScoreGradient<N>(s, detail::to_arr(grad));
+}
+
+// Predefinded NLopt algorithms that are used in the codebase
+using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
+using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
+using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
+
+// TODO: define others if needed...
+
+// Helper defs for pre-crafted global and local optimizers that work well.
+using DefaultGlobalOptimizer = Optimizer<AlgNLoptGenetic>;
+using DefaultLocalOptimizer  = Optimizer<AlgNLoptSubplex>;
+
+}} // namespace Slic3r::opt
+
+#endif // NLOPTOPTIMIZER_HPP
diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp
index b818cd8be..8c1c69fde 100644
--- a/src/libslic3r/Point.hpp
+++ b/src/libslic3r/Point.hpp
@@ -60,10 +60,13 @@ inline int64_t cross2(const Vec2i64 &v1, const Vec2i64 &v2) { return v1(0) * v2(
 inline float   cross2(const Vec2f   &v1, const Vec2f   &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
 inline double  cross2(const Vec2d   &v1, const Vec2d   &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
 
-inline Vec2i32 to_2d(const Vec2i32 &pt3) { return Vec2i32(pt3(0), pt3(1)); }
-inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); }
-inline Vec2f   to_2d(const Vec3f   &pt3) { return Vec2f  (pt3(0), pt3(1)); }
-inline Vec2d   to_2d(const Vec3d   &pt3) { return Vec2d  (pt3(0), pt3(1)); }
+template<class T, int N> Eigen::Matrix<T,  2, 1, Eigen::DontAlign>
+to_2d(const Eigen::Matrix<T,  N, 1, Eigen::DontAlign> &ptN) { return {ptN(0), ptN(1)}; }
+
+//inline Vec2i32 to_2d(const Vec3i32 &pt3) { return Vec2i32(pt3(0), pt3(1)); }
+//inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); }
+//inline Vec2f   to_2d(const Vec3f   &pt3) { return Vec2f  (pt3(0), pt3(1)); }
+//inline Vec2d   to_2d(const Vec3d   &pt3) { return Vec2d  (pt3(0), pt3(1)); }
 
 inline Vec3d   to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); }
 inline Vec3f   to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); }
diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp
index c7acf8f49..44e6675d1 100644
--- a/src/libslic3r/Preset.cpp
+++ b/src/libslic3r/Preset.cpp
@@ -498,6 +498,7 @@ const std::vector<std::string>& Preset::sla_print_options()
             "support_head_penetration",
             "support_head_width",
             "support_pillar_diameter",
+            "support_small_pillar_diameter_percent",
             "support_max_bridges_on_pillar",
             "support_pillar_connection_mode",
             "support_buildplate_only",
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index fe3aec6b7..3401dcc02 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -2746,7 +2746,7 @@ void PrintConfigDef::init_sla_params()
     def->set_default_value(new ConfigOptionBool(true));
 
     def = this->add("support_head_front_diameter", coFloat);
-    def->label = L("Support head front diameter");
+    def->label = L("Pinhead front diameter");
     def->category = L("Supports");
     def->tooltip = L("Diameter of the pointing side of the head");
     def->sidetext = L("mm");
@@ -2755,7 +2755,7 @@ void PrintConfigDef::init_sla_params()
     def->set_default_value(new ConfigOptionFloat(0.4));
 
     def = this->add("support_head_penetration", coFloat);
-    def->label = L("Support head penetration");
+    def->label = L("Head penetration");
     def->category = L("Supports");
     def->tooltip = L("How much the pinhead has to penetrate the model surface");
     def->sidetext = L("mm");
@@ -2764,7 +2764,7 @@ void PrintConfigDef::init_sla_params()
     def->set_default_value(new ConfigOptionFloat(0.2));
 
     def = this->add("support_head_width", coFloat);
-    def->label = L("Support head width");
+    def->label = L("Pinhead width");
     def->category = L("Supports");
     def->tooltip = L("Width from the back sphere center to the front sphere center");
     def->sidetext = L("mm");
@@ -2774,7 +2774,7 @@ void PrintConfigDef::init_sla_params()
     def->set_default_value(new ConfigOptionFloat(1.0));
 
     def = this->add("support_pillar_diameter", coFloat);
-    def->label = L("Support pillar diameter");
+    def->label = L("Pillar diameter");
     def->category = L("Supports");
     def->tooltip = L("Diameter in mm of the support pillars");
     def->sidetext = L("mm");
@@ -2782,6 +2782,17 @@ void PrintConfigDef::init_sla_params()
     def->max = 15;
     def->mode = comSimple;
     def->set_default_value(new ConfigOptionFloat(1.0));
+
+    def = this->add("support_small_pillar_diameter_percent", coPercent);
+    def->label = L("Small pillar diameter percent");
+    def->category = L("Supports");
+    def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter "
+                     "which are used in problematic areas where a normal pilla cannot fit.");
+    def->sidetext = L("%");
+    def->min = 1;
+    def->max = 100;
+    def->mode = comExpert;
+    def->set_default_value(new ConfigOptionPercent(50));
     
     def = this->add("support_max_bridges_on_pillar", coInt);
     def->label = L("Max bridges on a pillar");
@@ -2794,7 +2805,7 @@ void PrintConfigDef::init_sla_params()
     def->set_default_value(new ConfigOptionInt(3));
 
     def = this->add("support_pillar_connection_mode", coEnum);
-    def->label = L("Support pillar connection mode");
+    def->label = L("Pillar connection mode");
     def->tooltip = L("Controls the bridge type between two neighboring pillars."
                      " Can be zig-zag, cross (double zig-zag) or dynamic which"
                      " will automatically switch between the first two depending"
diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp
index 9b5c47512..c4566c983 100644
--- a/src/libslic3r/PrintConfig.hpp
+++ b/src/libslic3r/PrintConfig.hpp
@@ -1031,6 +1031,10 @@ public:
 
     // Radius in mm of the support pillars.
     ConfigOptionFloat support_pillar_diameter /*= 0.8*/;
+
+    // The percentage of smaller pillars compared to the normal pillar diameter
+    // which are used in problematic areas where a normal pilla cannot fit.
+    ConfigOptionPercent support_small_pillar_diameter_percent;
     
     // How much bridge (supporting another pinhead) can be placed on a pillar.
     ConfigOptionInt   support_max_bridges_on_pillar;
@@ -1155,6 +1159,7 @@ protected:
         OPT_PTR(support_head_penetration);
         OPT_PTR(support_head_width);
         OPT_PTR(support_pillar_diameter);
+        OPT_PTR(support_small_pillar_diameter_percent);
         OPT_PTR(support_max_bridges_on_pillar);
         OPT_PTR(support_pillar_connection_mode);
         OPT_PTR(support_buildplate_only);
diff --git a/src/libslic3r/SLA/BoostAdapter.hpp b/src/libslic3r/SLA/BoostAdapter.hpp
index b7b3c63a6..13e0465b1 100644
--- a/src/libslic3r/SLA/BoostAdapter.hpp
+++ b/src/libslic3r/SLA/BoostAdapter.hpp
@@ -1,7 +1,9 @@
 #ifndef SLA_BOOSTADAPTER_HPP
 #define SLA_BOOSTADAPTER_HPP
 
-#include <libslic3r/SLA/Common.hpp>
+#include <libslic3r/Point.hpp>
+#include <libslic3r/BoundingBox.hpp>
+
 #include <boost/geometry.hpp>
 
 namespace boost {
diff --git a/src/libslic3r/SLA/Clustering.cpp b/src/libslic3r/SLA/Clustering.cpp
new file mode 100644
index 000000000..41ff1d4f0
--- /dev/null
+++ b/src/libslic3r/SLA/Clustering.cpp
@@ -0,0 +1,152 @@
+#include "Clustering.hpp"
+#include "boost/geometry/index/rtree.hpp"
+
+#include <libslic3r/SLA/SpatIndex.hpp>
+#include <libslic3r/SLA/BoostAdapter.hpp>
+
+namespace Slic3r { namespace sla {
+
+namespace bgi = boost::geometry::index;
+using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >;
+
+namespace {
+
+bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2)
+{
+    return e1.second < e2.second;
+};
+
+ClusteredPoints cluster(Index3D &sindex,
+                        unsigned max_points,
+                        std::function<std::vector<PointIndexEl>(
+                            const Index3D &, const PointIndexEl &)> qfn)
+{
+    using Elems = std::vector<PointIndexEl>;
+
+    // Recursive function for visiting all the points in a given distance to
+    // each other
+    std::function<void(Elems&, Elems&)> group =
+        [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster)
+    {
+        for(auto& p : pts) {
+            std::vector<PointIndexEl> tmp = qfn(sindex, p);
+
+            std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements);
+
+            Elems newpts;
+            std::set_difference(tmp.begin(), tmp.end(),
+                                cluster.begin(), cluster.end(),
+                                std::back_inserter(newpts), cmp_ptidx_elements);
+
+            int c = max_points && newpts.size() + cluster.size() > max_points?
+                        int(max_points - cluster.size()) : int(newpts.size());
+
+            cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c);
+            std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements);
+
+            if(!newpts.empty() && (!max_points || cluster.size() < max_points))
+                group(newpts, cluster);
+        }
+    };
+
+    std::vector<Elems> clusters;
+    for(auto it = sindex.begin(); it != sindex.end();) {
+        Elems cluster = {};
+        Elems pts = {*it};
+        group(pts, cluster);
+
+        for(auto& c : cluster) sindex.remove(c);
+        it = sindex.begin();
+
+        clusters.emplace_back(cluster);
+    }
+
+    ClusteredPoints result;
+    for(auto& cluster : clusters) {
+        result.emplace_back();
+        for(auto c : cluster) result.back().emplace_back(c.second);
+    }
+
+    return result;
+}
+
+std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
+                                           const PointIndexEl& p,
+                                           double dist,
+                                           unsigned max_points)
+{
+    std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
+    sindex.query(
+        bgi::nearest(p.first, max_points),
+        std::back_inserter(tmp)
+        );
+
+    for(auto it = tmp.begin(); it < tmp.end(); ++it)
+        if((p.first - it->first).norm() > dist) it = tmp.erase(it);
+
+    return tmp;
+}
+
+} // namespace
+
+// Clustering a set of points by the given criteria
+ClusteredPoints cluster(
+    const std::vector<unsigned>& indices,
+    std::function<Vec3d(unsigned)> pointfn,
+    double dist,
+    unsigned max_points)
+{
+    // A spatial index for querying the nearest points
+    Index3D sindex;
+
+    // Build the index
+    for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
+
+    return cluster(sindex, max_points,
+                   [dist, max_points](const Index3D& sidx, const PointIndexEl& p)
+                   {
+                       return distance_queryfn(sidx, p, dist, max_points);
+                   });
+}
+
+// Clustering a set of points by the given criteria
+ClusteredPoints cluster(
+    const std::vector<unsigned>& indices,
+    std::function<Vec3d(unsigned)> pointfn,
+    std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
+    unsigned max_points)
+{
+    // A spatial index for querying the nearest points
+    Index3D sindex;
+
+    // Build the index
+    for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
+
+    return cluster(sindex, max_points,
+                   [max_points, predicate](const Index3D& sidx, const PointIndexEl& p)
+                   {
+                       std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
+                       sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){
+                                      return predicate(p, e);
+                                  }), std::back_inserter(tmp));
+                       return tmp;
+                   });
+}
+
+ClusteredPoints cluster(const Eigen::MatrixXd& pts, double dist, unsigned max_points)
+{
+    // A spatial index for querying the nearest points
+    Index3D sindex;
+
+    // Build the index
+    for(Eigen::Index i = 0; i < pts.rows(); i++)
+        sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i)));
+
+    return cluster(sindex, max_points,
+                   [dist, max_points](const Index3D& sidx, const PointIndexEl& p)
+                   {
+                       return distance_queryfn(sidx, p, dist, max_points);
+                   });
+}
+
+}} // namespace Slic3r::sla
diff --git a/src/libslic3r/SLA/Clustering.hpp b/src/libslic3r/SLA/Clustering.hpp
index 1b0d47d95..269ec2882 100644
--- a/src/libslic3r/SLA/Clustering.hpp
+++ b/src/libslic3r/SLA/Clustering.hpp
@@ -2,7 +2,8 @@
 #define SLA_CLUSTERING_HPP
 
 #include <vector>
-#include <libslic3r/SLA/Common.hpp>
+
+#include <libslic3r/Point.hpp>
 #include <libslic3r/SLA/SpatIndex.hpp>
 
 namespace Slic3r { namespace sla {
@@ -16,7 +17,7 @@ ClusteredPoints cluster(const std::vector<unsigned>& indices,
                         double dist,
                         unsigned max_points);
 
-ClusteredPoints cluster(const PointSet& points,
+ClusteredPoints cluster(const Eigen::MatrixXd& points,
                         double dist,
                         unsigned max_points);
 
@@ -26,5 +27,56 @@ ClusteredPoints cluster(
     std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
     unsigned max_points);
 
-}}
+// This function returns the position of the centroid in the input 'clust'
+// vector of point indices.
+template<class DistFn, class PointFn>
+long cluster_centroid(const ClusterEl &clust, PointFn pointfn, DistFn df)
+{
+    switch(clust.size()) {
+    case 0: /* empty cluster */ return -1;
+    case 1: /* only one element */ return 0;
+    case 2: /* if two elements, there is no center */ return 0;
+    default: ;
+    }
+
+    // The function works by calculating for each point the average distance
+    // from all the other points in the cluster. We create a selector bitmask of
+    // the same size as the cluster. The bitmask will have two true bits and
+    // false bits for the rest of items and we will loop through all the
+    // permutations of the bitmask (combinations of two points). Get the
+    // distance for the two points and add the distance to the averages.
+    // The point with the smallest average than wins.
+
+    // The complexity should be O(n^2) but we will mostly apply this function
+    // for small clusters only (cca 3 elements)
+
+    std::vector<bool> sel(clust.size(), false);   // create full zero bitmask
+    std::fill(sel.end() - 2, sel.end(), true);    // insert the two ones
+    std::vector<double> avgs(clust.size(), 0.0);  // store the average distances
+
+    do {
+        std::array<size_t, 2> idx;
+        for(size_t i = 0, j = 0; i < clust.size(); i++)
+            if(sel[i]) idx[j++] = i;
+
+        double d = df(pointfn(clust[idx[0]]),
+                      pointfn(clust[idx[1]]));
+
+        // add the distance to the sums for both associated points
+        for(auto i : idx) avgs[i] += d;
+
+        // now continue with the next permutation of the bitmask with two 1s
+    } while(std::next_permutation(sel.begin(), sel.end()));
+
+    // Divide by point size in the cluster to get the average (may be redundant)
+    for(auto& a : avgs) a /= clust.size();
+
+    // get the lowest average distance and return the index
+    auto minit = std::min_element(avgs.begin(), avgs.end());
+    return long(minit - avgs.begin());
+}
+
+
+}} // namespace Slic3r::sla
+
 #endif // CLUSTERING_HPP
diff --git a/src/libslic3r/SLA/Common.hpp b/src/libslic3r/SLA/Common.hpp
deleted file mode 100644
index ca616cabc..000000000
--- a/src/libslic3r/SLA/Common.hpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#ifndef SLA_COMMON_HPP
-#define SLA_COMMON_HPP
-
-#include <memory>
-#include <vector>
-#include <numeric>
-#include <functional>
-#include <Eigen/Geometry>
-
-
-namespace Slic3r {
-    
-// Typedefs from Point.hpp
-typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
-typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
-typedef Eigen::Matrix<int, 3, 1, Eigen::DontAlign> Vec3i;
-typedef Eigen::Matrix<int, 4, 1, Eigen::DontAlign> Vec4i;
-
-namespace sla {
-
-using PointSet = Eigen::MatrixXd;
-
-} // namespace sla
-} // namespace Slic3r
-
-
-#endif // SLASUPPORTTREE_HPP
diff --git a/src/libslic3r/SLA/Contour3D.cpp b/src/libslic3r/SLA/Contour3D.cpp
index 408465d43..96d10af20 100644
--- a/src/libslic3r/SLA/Contour3D.cpp
+++ b/src/libslic3r/SLA/Contour3D.cpp
@@ -1,5 +1,5 @@
 #include <libslic3r/SLA/Contour3D.hpp>
-#include <libslic3r/SLA/EigenMesh3D.hpp>
+#include <libslic3r/SLA/IndexedMesh.hpp>
 
 #include <libslic3r/Format/objparser.hpp>
 
@@ -27,7 +27,7 @@ Contour3D::Contour3D(TriangleMesh &&trmesh)
     faces3.swap(trmesh.its.indices);
 }
 
-Contour3D::Contour3D(const EigenMesh3D &emesh) {
+Contour3D::Contour3D(const IndexedMesh &emesh) {
     points.reserve(emesh.vertices().size());
     faces3.reserve(emesh.indices().size());
     
diff --git a/src/libslic3r/SLA/Contour3D.hpp b/src/libslic3r/SLA/Contour3D.hpp
index 295612f19..3380cd6ab 100644
--- a/src/libslic3r/SLA/Contour3D.hpp
+++ b/src/libslic3r/SLA/Contour3D.hpp
@@ -1,13 +1,16 @@
 #ifndef SLA_CONTOUR3D_HPP
 #define SLA_CONTOUR3D_HPP
 
-#include <libslic3r/SLA/Common.hpp>
-
 #include <libslic3r/TriangleMesh.hpp>
 
-namespace Slic3r { namespace sla {
+namespace Slic3r {
 
-class EigenMesh3D;
+// Used for quads (TODO: remove this, and convert quads to triangles in OpenVDBUtils)
+using Vec4i = Eigen::Matrix<int, 4, 1, Eigen::DontAlign>;
+
+namespace sla {
+
+class IndexedMesh;
 
 /// Dumb vertex mesh consisting of triangles (or) quads. Capable of merging with
 /// other meshes of this type and converting to and from other mesh formats.
@@ -19,7 +22,7 @@ struct Contour3D {
     Contour3D() = default;
     Contour3D(const TriangleMesh &trmesh);
     Contour3D(TriangleMesh &&trmesh);
-    Contour3D(const EigenMesh3D  &emesh);
+    Contour3D(const IndexedMesh  &emesh);
     
     Contour3D& merge(const Contour3D& ctr);
     Contour3D& merge(const Pointf3s& triangles);
diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp
index 0dd9436a1..5334054a0 100644
--- a/src/libslic3r/SLA/Hollowing.cpp
+++ b/src/libslic3r/SLA/Hollowing.cpp
@@ -3,11 +3,10 @@
 #include <libslic3r/OpenVDBUtils.hpp>
 #include <libslic3r/TriangleMesh.hpp>
 #include <libslic3r/SLA/Hollowing.hpp>
-#include <libslic3r/SLA/Contour3D.hpp>
-#include <libslic3r/SLA/EigenMesh3D.hpp>
-#include <libslic3r/SLA/SupportTreeBuilder.hpp>
+#include <libslic3r/SLA/IndexedMesh.hpp>
 #include <libslic3r/ClipperUtils.hpp>
 #include <libslic3r/SimplifyMesh.hpp>
+#include <libslic3r/SLA/SupportTreeMesher.hpp>
 
 #include <boost/log/trivial.hpp>
 
@@ -160,7 +159,7 @@ bool DrainHole::get_intersections(const Vec3f& s, const Vec3f& dir,
     const Eigen::ParametrizedLine<float, 3> ray(s, dir.normalized());
 
     for (size_t i=0; i<2; ++i)
-        out[i] = std::make_pair(sla::EigenMesh3D::hit_result::infty(), Vec3d::Zero());
+        out[i] = std::make_pair(sla::IndexedMesh::hit_result::infty(), Vec3d::Zero());
 
     const float sqr_radius = pow(radius, 2.f);
 
diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp
index cc7d310ea..1f65fa8b7 100644
--- a/src/libslic3r/SLA/Hollowing.hpp
+++ b/src/libslic3r/SLA/Hollowing.hpp
@@ -2,7 +2,6 @@
 #define SLA_HOLLOWING_HPP
 
 #include <memory>
-#include <libslic3r/SLA/Common.hpp>
 #include <libslic3r/SLA/Contour3D.hpp>
 #include <libslic3r/SLA/JobController.hpp>
 
diff --git a/src/libslic3r/SLA/Common.cpp b/src/libslic3r/SLA/IndexedMesh.cpp
similarity index 53%
rename from src/libslic3r/SLA/Common.cpp
rename to src/libslic3r/SLA/IndexedMesh.cpp
index a7420a7fb..573b62b6d 100644
--- a/src/libslic3r/SLA/Common.cpp
+++ b/src/libslic3r/SLA/IndexedMesh.cpp
@@ -1,187 +1,18 @@
-#include <cmath>
-#include <libslic3r/SLA/Common.hpp>
-#include <libslic3r/SLA/Concurrency.hpp>
-#include <libslic3r/SLA/SpatIndex.hpp>
-#include <libslic3r/SLA/EigenMesh3D.hpp>
-#include <libslic3r/SLA/Contour3D.hpp>
-#include <libslic3r/SLA/Clustering.hpp>
+#include "IndexedMesh.hpp"
+#include "Concurrency.hpp"
+
 #include <libslic3r/AABBTreeIndirect.hpp>
+#include <libslic3r/TriangleMesh.hpp>
 
-// for concave hull merging decisions
-#include <libslic3r/SLA/BoostAdapter.hpp>
-#include "boost/geometry/index/rtree.hpp"
-
-#ifdef _MSC_VER
-#pragma warning(push)
-#pragma warning(disable: 4244)
-#pragma warning(disable: 4267)
-#endif
-
-
-#include <igl/remove_duplicate_vertices.h>
+#include <numeric>
 
 #ifdef SLIC3R_HOLE_RAYCASTER
-  #include <libslic3r/SLA/Hollowing.hpp>
+#include <libslic3r/SLA/Hollowing.hpp>
 #endif
 
+namespace Slic3r { namespace sla {
 
-#ifdef _MSC_VER
-#pragma warning(pop)
-#endif
-
-
-namespace Slic3r {
-namespace sla {
-
-
-/* **************************************************************************
- * PointIndex implementation
- * ************************************************************************** */
-
-class PointIndex::Impl {
-public:
-    using BoostIndex = boost::geometry::index::rtree< PointIndexEl,
-                                                     boost::geometry::index::rstar<16, 4> /* ? */ >;
-    
-    BoostIndex m_store;
-};
-
-PointIndex::PointIndex(): m_impl(new Impl()) {}
-PointIndex::~PointIndex() {}
-
-PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
-PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
-
-PointIndex& PointIndex::operator=(const PointIndex &cpy)
-{
-    m_impl.reset(new Impl(*cpy.m_impl));
-    return *this;
-}
-
-PointIndex& PointIndex::operator=(PointIndex &&cpy)
-{
-    m_impl.swap(cpy.m_impl);
-    return *this;
-}
-
-void PointIndex::insert(const PointIndexEl &el)
-{
-    m_impl->m_store.insert(el);
-}
-
-bool PointIndex::remove(const PointIndexEl& el)
-{
-    return m_impl->m_store.remove(el) == 1;
-}
-
-std::vector<PointIndexEl>
-PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const
-{
-    namespace bgi = boost::geometry::index;
-    
-    std::vector<PointIndexEl> ret;
-    m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret));
-    return ret;
-}
-
-std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const
-{
-    namespace bgi = boost::geometry::index;
-    std::vector<PointIndexEl> ret; ret.reserve(k);
-    m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret));
-    return ret;
-}
-
-size_t PointIndex::size() const
-{
-    return m_impl->m_store.size();
-}
-
-void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn)
-{
-    for(auto& el : m_impl->m_store) fn(el);
-}
-
-void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const
-{
-    for(const auto &el : m_impl->m_store) fn(el);
-}
-
-/* **************************************************************************
- * BoxIndex implementation
- * ************************************************************************** */
-
-class BoxIndex::Impl {
-public:
-    using BoostIndex = boost::geometry::index::
-        rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>;
-    
-    BoostIndex m_store;
-};
-
-BoxIndex::BoxIndex(): m_impl(new Impl()) {}
-BoxIndex::~BoxIndex() {}
-
-BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
-BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
-
-BoxIndex& BoxIndex::operator=(const BoxIndex &cpy)
-{
-    m_impl.reset(new Impl(*cpy.m_impl));
-    return *this;
-}
-
-BoxIndex& BoxIndex::operator=(BoxIndex &&cpy)
-{
-    m_impl.swap(cpy.m_impl);
-    return *this;
-}
-
-void BoxIndex::insert(const BoxIndexEl &el)
-{
-    m_impl->m_store.insert(el);
-}
-
-bool BoxIndex::remove(const BoxIndexEl& el)
-{
-    return m_impl->m_store.remove(el) == 1;
-}
-
-std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb,
-                                        BoxIndex::QueryType qt)
-{
-    namespace bgi = boost::geometry::index;
-    
-    std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size());
-    
-    switch (qt) {
-    case qtIntersects:
-        m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret));
-        break;
-    case qtWithin:
-        m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret));
-    }
-    
-    return ret;
-}
-
-size_t BoxIndex::size() const
-{
-    return m_impl->m_store.size();
-}
-
-void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn)
-{
-    for(auto& el : m_impl->m_store) fn(el);
-}
-
-
-/* ****************************************************************************
- * EigenMesh3D implementation
- * ****************************************************************************/
-
-
-class EigenMesh3D::AABBImpl {
+class IndexedMesh::AABBImpl {
 private:
     AABBTreeIndirect::Tree3f m_tree;
 
@@ -189,7 +20,7 @@ public:
     void init(const TriangleMesh& tm)
     {
         m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
-                    tm.its.vertices, tm.its.indices);
+            tm.its.vertices, tm.its.indices);
     }
 
     void intersect_ray(const TriangleMesh& tm,
@@ -215,9 +46,9 @@ public:
         size_t idx_unsigned = 0;
         Vec3d closest_vec3d(closest);
         double dist = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
-                          tm.its.vertices,
-                          tm.its.indices,
-                          m_tree, point, idx_unsigned, closest_vec3d);
+            tm.its.vertices,
+            tm.its.indices,
+            m_tree, point, idx_unsigned, closest_vec3d);
         i = int(idx_unsigned);
         closest = closest_vec3d;
         return dist;
@@ -226,72 +57,71 @@ public:
 
 static const constexpr double MESH_EPS = 1e-6;
 
-EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh)
+IndexedMesh::IndexedMesh(const TriangleMesh& tmesh)
     : m_aabb(new AABBImpl()), m_tm(&tmesh)
 {
     auto&& bb = tmesh.bounding_box();
     m_ground_level += bb.min(Z);
-    
+
     // Build the AABB accelaration tree
     m_aabb->init(tmesh);
 }
 
-EigenMesh3D::~EigenMesh3D() {}
+IndexedMesh::~IndexedMesh() {}
 
-EigenMesh3D::EigenMesh3D(const EigenMesh3D &other):
+IndexedMesh::IndexedMesh(const IndexedMesh &other):
     m_tm(other.m_tm), m_ground_level(other.m_ground_level),
     m_aabb( new AABBImpl(*other.m_aabb) ) {}
 
 
-EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
+IndexedMesh &IndexedMesh::operator=(const IndexedMesh &other)
 {
     m_tm = other.m_tm;
     m_ground_level = other.m_ground_level;
     m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this;
 }
 
-EigenMesh3D &EigenMesh3D::operator=(EigenMesh3D &&other) = default;
+IndexedMesh &IndexedMesh::operator=(IndexedMesh &&other) = default;
 
-EigenMesh3D::EigenMesh3D(EigenMesh3D &&other) = default;
+IndexedMesh::IndexedMesh(IndexedMesh &&other) = default;
 
 
 
-const std::vector<Vec3f>& EigenMesh3D::vertices() const
+const std::vector<Vec3f>& IndexedMesh::vertices() const
 {
     return m_tm->its.vertices;
 }
 
 
 
-const std::vector<Vec3i>& EigenMesh3D::indices()  const
+const std::vector<Vec3i>& IndexedMesh::indices()  const
 {
     return m_tm->its.indices;
 }
 
 
 
-const Vec3f& EigenMesh3D::vertices(size_t idx) const
+const Vec3f& IndexedMesh::vertices(size_t idx) const
 {
     return m_tm->its.vertices[idx];
 }
 
 
 
-const Vec3i& EigenMesh3D::indices(size_t idx) const
+const Vec3i& IndexedMesh::indices(size_t idx) const
 {
     return m_tm->its.indices[idx];
 }
 
 
 
-Vec3d EigenMesh3D::normal_by_face_id(int face_id) const {
+Vec3d IndexedMesh::normal_by_face_id(int face_id) const {
     return m_tm->stl.facet_start[face_id].normal.cast<double>();
 }
 
 
-
-EigenMesh3D::hit_result
-EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
+IndexedMesh::hit_result
+IndexedMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
 {
     assert(is_approx(dir.norm(), 1.));
     igl::Hit hit;
@@ -319,13 +149,13 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
     return ret;
 }
 
-std::vector<EigenMesh3D::hit_result>
-EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
+std::vector<IndexedMesh::hit_result>
+IndexedMesh::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
 {
-    std::vector<EigenMesh3D::hit_result> outs;
+    std::vector<IndexedMesh::hit_result> outs;
     std::vector<igl::Hit> hits;
     m_aabb->intersect_ray(*m_tm, s, dir, hits);
-    
+
     // The sort is necessary, the hits are not always sorted.
     std::sort(hits.begin(), hits.end(),
               [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
@@ -334,13 +164,13 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
     // along an axis of a cube due to floating-point approximations in igl (?)
     hits.erase(std::unique(hits.begin(), hits.end(),
                            [](const igl::Hit& a, const igl::Hit& b)
-                              { return a.t == b.t; }),
+                           { return a.t == b.t; }),
                hits.end());
 
     //  Convert the igl::Hit into hit_result
     outs.reserve(hits.size());
     for (const igl::Hit& hit : hits) {
-        outs.emplace_back(EigenMesh3D::hit_result(*this));
+        outs.emplace_back(IndexedMesh::hit_result(*this));
         outs.back().m_t = double(hit.t);
         outs.back().m_dir = dir;
         outs.back().m_source = s;
@@ -355,8 +185,8 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
 
 
 #ifdef SLIC3R_HOLE_RAYCASTER
-EigenMesh3D::hit_result EigenMesh3D::filter_hits(
-                     const std::vector<EigenMesh3D::hit_result>& object_hits) const
+IndexedMesh::hit_result IndexedMesh::filter_hits(
+    const std::vector<IndexedMesh::hit_result>& object_hits) const
 {
     assert(! m_holes.empty());
     hit_result out(*this);
@@ -377,7 +207,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits(
     };
     std::vector<HoleHit> hole_isects;
     hole_isects.reserve(m_holes.size());
-    
+
     auto sf = s.cast<float>();
     auto dirf = dir.cast<float>();
 
@@ -452,7 +282,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits(
 #endif
 
 
-double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
+double IndexedMesh::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
     double sqdst = 0;
     Eigen::Matrix<double, 1, 3> pp = p;
     Eigen::Matrix<double, 1, 3> cc;
@@ -461,31 +291,19 @@ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
     return sqdst;
 }
 
-/* ****************************************************************************
- * Misc functions
- * ****************************************************************************/
 
-namespace  {
-
-bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
-                   double eps = 0.05)
+static bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
+                          double eps = 0.05)
 {
     using Line3D = Eigen::ParametrizedLine<double, 3>;
-    
+
     auto line = Line3D::Through(e1, e2);
     double d = line.distance(p);
     return std::abs(d) < eps;
 }
 
-template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
-    auto p = pp2 - pp1;
-    return std::sqrt(p.transpose() * p);
-}
-
-}
-
 PointSet normals(const PointSet& points,
-                 const EigenMesh3D& mesh,
+                 const IndexedMesh& mesh,
                  double eps,
                  std::function<void()> thr, // throw on cancel
                  const std::vector<unsigned>& pt_indices)
@@ -531,11 +349,11 @@ PointSet normals(const PointSet& points,
             // ic will mark a single vertex.
             int ia = -1, ib = -1, ic = -1;
 
-            if (std::abs(distance(p, p1)) < eps) {
+            if (std::abs((p - p1).norm()) < eps) {
                 ic = trindex(0);
-            } else if (std::abs(distance(p, p2)) < eps) {
+            } else if (std::abs((p - p2).norm()) < eps) {
                 ic = trindex(1);
-            } else if (std::abs(distance(p, p3)) < eps) {
+            } else if (std::abs((p - p3).norm()) < eps) {
                 ic = trindex(2);
             } else if (point_on_edge(p, p1, p2, eps)) {
                 ia = trindex(0);
@@ -612,148 +430,4 @@ PointSet normals(const PointSet& points,
     return ret;
 }
 
-namespace bgi = boost::geometry::index;
-using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >;
-
-namespace {
-
-bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2)
-{
-    return e1.second < e2.second;
-};
-
-ClusteredPoints cluster(Index3D &sindex,
-                        unsigned max_points,
-                        std::function<std::vector<PointIndexEl>(
-                            const Index3D &, const PointIndexEl &)> qfn)
-{
-    using Elems = std::vector<PointIndexEl>;
-    
-    // Recursive function for visiting all the points in a given distance to
-    // each other
-    std::function<void(Elems&, Elems&)> group =
-        [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster)
-    {        
-        for(auto& p : pts) {
-            std::vector<PointIndexEl> tmp = qfn(sindex, p);
-            
-            std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements);
-            
-            Elems newpts;
-            std::set_difference(tmp.begin(), tmp.end(),
-                                cluster.begin(), cluster.end(),
-                                std::back_inserter(newpts), cmp_ptidx_elements);
-            
-            int c = max_points && newpts.size() + cluster.size() > max_points?
-                        int(max_points - cluster.size()) : int(newpts.size());
-            
-            cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c);
-            std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements);
-            
-            if(!newpts.empty() && (!max_points || cluster.size() < max_points))
-                group(newpts, cluster);
-        }
-    };
-    
-    std::vector<Elems> clusters;
-    for(auto it = sindex.begin(); it != sindex.end();) {
-        Elems cluster = {};
-        Elems pts = {*it};
-        group(pts, cluster);
-        
-        for(auto& c : cluster) sindex.remove(c);
-        it = sindex.begin();
-        
-        clusters.emplace_back(cluster);
-    }
-    
-    ClusteredPoints result;
-    for(auto& cluster : clusters) {
-        result.emplace_back();
-        for(auto c : cluster) result.back().emplace_back(c.second);
-    }
-    
-    return result;
-}
-
-std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
-                                           const PointIndexEl& p,
-                                           double dist,
-                                           unsigned max_points)
-{
-    std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
-    sindex.query(
-        bgi::nearest(p.first, max_points),
-        std::back_inserter(tmp)
-        );
-    
-    for(auto it = tmp.begin(); it < tmp.end(); ++it)
-        if(distance(p.first, it->first) > dist) it = tmp.erase(it);
-    
-    return tmp;
-}
-
-} // namespace
-
-// Clustering a set of points by the given criteria
-ClusteredPoints cluster(
-    const std::vector<unsigned>& indices,
-    std::function<Vec3d(unsigned)> pointfn,
-    double dist,
-    unsigned max_points)
-{
-    // A spatial index for querying the nearest points
-    Index3D sindex;
-    
-    // Build the index
-    for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
-    
-    return cluster(sindex, max_points,
-                   [dist, max_points](const Index3D& sidx, const PointIndexEl& p)
-                   {
-                       return distance_queryfn(sidx, p, dist, max_points);
-                   });
-}
-
-// Clustering a set of points by the given criteria
-ClusteredPoints cluster(
-    const std::vector<unsigned>& indices,
-    std::function<Vec3d(unsigned)> pointfn,
-    std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
-    unsigned max_points)
-{
-    // A spatial index for querying the nearest points
-    Index3D sindex;
-    
-    // Build the index
-    for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
-    
-    return cluster(sindex, max_points,
-                   [max_points, predicate](const Index3D& sidx, const PointIndexEl& p)
-                   {
-                       std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
-                       sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){
-                                      return predicate(p, e);
-                                  }), std::back_inserter(tmp));
-                       return tmp;
-                   });
-}
-
-ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points)
-{
-    // A spatial index for querying the nearest points
-    Index3D sindex;
-    
-    // Build the index
-    for(Eigen::Index i = 0; i < pts.rows(); i++)
-        sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i)));
-    
-    return cluster(sindex, max_points,
-                   [dist, max_points](const Index3D& sidx, const PointIndexEl& p)
-                   {
-                       return distance_queryfn(sidx, p, dist, max_points);
-                   });
-}
-
-} // namespace sla
-} // namespace Slic3r
+}} // namespace Slic3r::sla
diff --git a/src/libslic3r/SLA/EigenMesh3D.hpp b/src/libslic3r/SLA/IndexedMesh.hpp
similarity index 81%
rename from src/libslic3r/SLA/EigenMesh3D.hpp
rename to src/libslic3r/SLA/IndexedMesh.hpp
index b932c0c18..a72492b34 100644
--- a/src/libslic3r/SLA/EigenMesh3D.hpp
+++ b/src/libslic3r/SLA/IndexedMesh.hpp
@@ -1,8 +1,10 @@
-#ifndef SLA_EIGENMESH3D_H
-#define SLA_EIGENMESH3D_H
+#ifndef SLA_INDEXEDMESH_H
+#define SLA_INDEXEDMESH_H
 
-#include <libslic3r/SLA/Common.hpp>
+#include <memory>
+#include <vector>
 
+#include <libslic3r/Point.hpp>
 
 // There is an implementation of a hole-aware raycaster that was eventually
 // not used in production version. It is now hidden under following define
@@ -19,10 +21,12 @@ class TriangleMesh;
 
 namespace sla {
 
+using PointSet = Eigen::MatrixXd;
+
 /// An index-triangle structure for libIGL functions. Also serves as an
 /// alternative (raw) input format for the SLASupportTree.
 //  Implemented in libslic3r/SLA/Common.cpp
-class EigenMesh3D {
+class IndexedMesh {
     class AABBImpl;
     
     const TriangleMesh* m_tm;
@@ -38,15 +42,15 @@ class EigenMesh3D {
 
 public:
     
-    explicit EigenMesh3D(const TriangleMesh&);
+    explicit IndexedMesh(const TriangleMesh&);
     
-    EigenMesh3D(const EigenMesh3D& other);
-    EigenMesh3D& operator=(const EigenMesh3D&);
+    IndexedMesh(const IndexedMesh& other);
+    IndexedMesh& operator=(const IndexedMesh&);
     
-    EigenMesh3D(EigenMesh3D &&other);
-    EigenMesh3D& operator=(EigenMesh3D &&other);
+    IndexedMesh(IndexedMesh &&other);
+    IndexedMesh& operator=(IndexedMesh &&other);
     
-    ~EigenMesh3D();
+    ~IndexedMesh();
     
     inline double ground_level() const { return m_ground_level + m_gnd_offset; }
     inline void ground_level_offset(double o) { m_gnd_offset = o; }
@@ -62,15 +66,15 @@ public:
         // m_t holds a distance from m_source to the intersection.
         double m_t = infty();
         int m_face_id = -1;
-        const EigenMesh3D *m_mesh = nullptr;
+        const IndexedMesh *m_mesh = nullptr;
         Vec3d m_dir;
         Vec3d m_source;
         Vec3d m_normal;
-        friend class EigenMesh3D;
+        friend class IndexedMesh;
         
         // A valid object of this class can only be obtained from
-        // EigenMesh3D::query_ray_hit method.
-        explicit inline hit_result(const EigenMesh3D& em): m_mesh(&em) {}
+        // IndexedMesh::query_ray_hit method.
+        explicit inline hit_result(const IndexedMesh& em): m_mesh(&em) {}
     public:
         // This denotes no hit on the mesh.
         static inline constexpr double infty() { return std::numeric_limits<double>::infinity(); }
@@ -83,7 +87,7 @@ public:
         inline Vec3d position() const { return m_source + m_dir * m_t; }
         inline int face() const { return m_face_id; }
         inline bool is_valid() const { return m_mesh != nullptr; }
-        inline bool is_hit() const { return !std::isinf(m_t); }
+        inline bool is_hit() const { return m_face_id >= 0 && !std::isinf(m_t); }
 
         inline const Vec3d& normal() const {
             assert(is_valid());
@@ -107,7 +111,7 @@ public:
     // This function is currently not used anywhere, it was written when the
     // holes were subtracted on slices, that is, before we started using CGAL
     // to actually cut the holes into the mesh.
-    hit_result filter_hits(const std::vector<EigenMesh3D::hit_result>& obj_hits) const;
+    hit_result filter_hits(const std::vector<IndexedMesh::hit_result>& obj_hits) const;
 #endif
 
     // Casting a ray on the mesh, returns the distance where the hit occures.
@@ -125,16 +129,18 @@ public:
     }
 
     Vec3d normal_by_face_id(int face_id) const;
+
+    const TriangleMesh * get_triangle_mesh() const { return m_tm; }
 };
 
 // Calculate the normals for the selected points (from 'points' set) on the
 // mesh. This will call squared distance for each point.
 PointSet normals(const PointSet& points,
-    const EigenMesh3D& convert_mesh,
+    const IndexedMesh& convert_mesh,
     double eps = 0.05,  // min distance from edges
     std::function<void()> throw_on_cancel = [](){},
     const std::vector<unsigned>& selected_points = {});
 
 }} // namespace Slic3r::sla
 
-#endif // EIGENMESH3D_H
+#endif // INDEXEDMESH_H
diff --git a/src/libslic3r/SLA/JobController.hpp b/src/libslic3r/SLA/JobController.hpp
index 3baa3d12d..b815e4d6f 100644
--- a/src/libslic3r/SLA/JobController.hpp
+++ b/src/libslic3r/SLA/JobController.hpp
@@ -2,6 +2,7 @@
 #define SLA_JOBCONTROLLER_HPP
 
 #include <functional>
+#include <string>
 
 namespace Slic3r { namespace sla {
 
diff --git a/src/libslic3r/SLA/Pad.cpp b/src/libslic3r/SLA/Pad.cpp
index d933ef5ed..f2b189cd1 100644
--- a/src/libslic3r/SLA/Pad.cpp
+++ b/src/libslic3r/SLA/Pad.cpp
@@ -1,5 +1,4 @@
 #include <libslic3r/SLA/Pad.hpp>
-#include <libslic3r/SLA/Common.hpp>
 #include <libslic3r/SLA/SpatIndex.hpp>
 #include <libslic3r/SLA/BoostAdapter.hpp>
 #include <libslic3r/SLA/Contour3D.hpp>
diff --git a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp
index 702d1bce1..4737a6c21 100644
--- a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp
+++ b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp
@@ -4,7 +4,7 @@
 #include "libslic3r/Point.hpp"
 #include "SupportPoint.hpp"
 #include "Hollowing.hpp"
-#include "EigenMesh3D.hpp"
+#include "IndexedMesh.hpp"
 #include "libslic3r/Model.hpp"
 
 #include <tbb/parallel_for.h>
@@ -15,7 +15,7 @@ template<class Pt> Vec3d pos(const Pt &p) { return p.pos.template cast<double>()
 template<class Pt> void pos(Pt &p, const Vec3d &pp) { p.pos = pp.cast<float>(); }
 
 template<class PointType>
-void reproject_support_points(const EigenMesh3D &mesh, std::vector<PointType> &pts)
+void reproject_support_points(const IndexedMesh &mesh, std::vector<PointType> &pts)
 {
     tbb::parallel_for(size_t(0), pts.size(), [&mesh, &pts](size_t idx) {
         int junk;
@@ -40,7 +40,7 @@ inline void reproject_points_and_holes(ModelObject *object)
 
     TriangleMesh rmsh = object->raw_mesh();
     rmsh.require_shared_vertices();
-    EigenMesh3D emesh{rmsh};
+    IndexedMesh emesh{rmsh};
 
     if (has_sppoints)
         reproject_support_points(emesh, object->sla_support_points);
diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp
index fda8383b1..81ef00e6b 100644
--- a/src/libslic3r/SLA/Rotfinder.cpp
+++ b/src/libslic3r/SLA/Rotfinder.cpp
@@ -2,7 +2,6 @@
 #include <exception>
 
 #include <libnest2d/optimizers/nlopt/genetic.hpp>
-#include <libslic3r/SLA/Common.hpp>
 #include <libslic3r/SLA/Rotfinder.hpp>
 #include <libslic3r/SLA/SupportTree.hpp>
 #include "Model.hpp"
diff --git a/src/libslic3r/SLA/SpatIndex.cpp b/src/libslic3r/SLA/SpatIndex.cpp
new file mode 100644
index 000000000..d95ba55be
--- /dev/null
+++ b/src/libslic3r/SLA/SpatIndex.cpp
@@ -0,0 +1,161 @@
+#include "SpatIndex.hpp"
+
+// for concave hull merging decisions
+#include <libslic3r/SLA/BoostAdapter.hpp>
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable: 4244)
+#pragma warning(disable: 4267)
+#endif
+
+#include "boost/geometry/index/rtree.hpp"
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+namespace Slic3r { namespace sla {
+
+/* **************************************************************************
+ * PointIndex implementation
+ * ************************************************************************** */
+
+class PointIndex::Impl {
+public:
+    using BoostIndex = boost::geometry::index::rtree< PointIndexEl,
+                                                     boost::geometry::index::rstar<16, 4> /* ? */ >;
+
+    BoostIndex m_store;
+};
+
+PointIndex::PointIndex(): m_impl(new Impl()) {}
+PointIndex::~PointIndex() {}
+
+PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
+PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
+
+PointIndex& PointIndex::operator=(const PointIndex &cpy)
+{
+    m_impl.reset(new Impl(*cpy.m_impl));
+    return *this;
+}
+
+PointIndex& PointIndex::operator=(PointIndex &&cpy)
+{
+    m_impl.swap(cpy.m_impl);
+    return *this;
+}
+
+void PointIndex::insert(const PointIndexEl &el)
+{
+    m_impl->m_store.insert(el);
+}
+
+bool PointIndex::remove(const PointIndexEl& el)
+{
+    return m_impl->m_store.remove(el) == 1;
+}
+
+std::vector<PointIndexEl>
+PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const
+{
+    namespace bgi = boost::geometry::index;
+
+    std::vector<PointIndexEl> ret;
+    m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret));
+    return ret;
+}
+
+std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const
+{
+    namespace bgi = boost::geometry::index;
+    std::vector<PointIndexEl> ret; ret.reserve(k);
+    m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret));
+    return ret;
+}
+
+size_t PointIndex::size() const
+{
+    return m_impl->m_store.size();
+}
+
+void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn)
+{
+    for(auto& el : m_impl->m_store) fn(el);
+}
+
+void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const
+{
+    for(const auto &el : m_impl->m_store) fn(el);
+}
+
+/* **************************************************************************
+ * BoxIndex implementation
+ * ************************************************************************** */
+
+class BoxIndex::Impl {
+public:
+    using BoostIndex = boost::geometry::index::
+        rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>;
+
+    BoostIndex m_store;
+};
+
+BoxIndex::BoxIndex(): m_impl(new Impl()) {}
+BoxIndex::~BoxIndex() {}
+
+BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
+BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
+
+BoxIndex& BoxIndex::operator=(const BoxIndex &cpy)
+{
+    m_impl.reset(new Impl(*cpy.m_impl));
+    return *this;
+}
+
+BoxIndex& BoxIndex::operator=(BoxIndex &&cpy)
+{
+    m_impl.swap(cpy.m_impl);
+    return *this;
+}
+
+void BoxIndex::insert(const BoxIndexEl &el)
+{
+    m_impl->m_store.insert(el);
+}
+
+bool BoxIndex::remove(const BoxIndexEl& el)
+{
+    return m_impl->m_store.remove(el) == 1;
+}
+
+std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb,
+                                        BoxIndex::QueryType qt)
+{
+    namespace bgi = boost::geometry::index;
+
+    std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size());
+
+    switch (qt) {
+    case qtIntersects:
+        m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret));
+        break;
+    case qtWithin:
+        m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret));
+    }
+
+    return ret;
+}
+
+size_t BoxIndex::size() const
+{
+    return m_impl->m_store.size();
+}
+
+void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn)
+{
+    for(auto& el : m_impl->m_store) fn(el);
+}
+
+}} // namespace Slic3r::sla
diff --git a/src/libslic3r/SLA/SpatIndex.hpp b/src/libslic3r/SLA/SpatIndex.hpp
index 2955cdcdf..ef059d3ae 100644
--- a/src/libslic3r/SLA/SpatIndex.hpp
+++ b/src/libslic3r/SLA/SpatIndex.hpp
@@ -73,7 +73,7 @@ public:
     BoxIndex& operator=(BoxIndex&&);
     
     void insert(const BoxIndexEl&);
-    inline void insert(const BoundingBox& bb, unsigned idx)
+    void insert(const BoundingBox& bb, unsigned idx)
     {
         insert(std::make_pair(bb, unsigned(idx)));
     }
diff --git a/src/libslic3r/SLA/SupportPoint.hpp b/src/libslic3r/SLA/SupportPoint.hpp
index 202a614c3..2b973697b 100644
--- a/src/libslic3r/SLA/SupportPoint.hpp
+++ b/src/libslic3r/SLA/SupportPoint.hpp
@@ -2,7 +2,6 @@
 #define SLA_SUPPORTPOINT_HPP
 
 #include <vector>
-#include <libslic3r/SLA/Common.hpp>
 #include <libslic3r/ExPolygon.hpp>
 
 namespace Slic3r { namespace sla {
@@ -29,13 +28,13 @@ struct SupportPoint
                  float pos_y,
                  float pos_z,
                  float head_radius,
-                 bool  new_island)
+                 bool  new_island = false)
         : pos(pos_x, pos_y, pos_z)
         , head_front_radius(head_radius)
         , is_new_island(new_island)
     {}
     
-    SupportPoint(Vec3f position, float head_radius, bool new_island)
+    SupportPoint(Vec3f position, float head_radius, bool new_island = false)
         : pos(position)
         , head_front_radius(head_radius)
         , is_new_island(new_island)
diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp
index 78c2ced35..3cd075ae6 100644
--- a/src/libslic3r/SLA/SupportPointGenerator.cpp
+++ b/src/libslic3r/SLA/SupportPointGenerator.cpp
@@ -50,7 +50,7 @@ float SupportPointGenerator::distance_limit(float angle) const
 }*/
 
 SupportPointGenerator::SupportPointGenerator(
-        const sla::EigenMesh3D &emesh,
+        const sla::IndexedMesh &emesh,
         const std::vector<ExPolygons> &slices,
         const std::vector<float> &     heights,
         const Config &                 config,
@@ -64,7 +64,7 @@ SupportPointGenerator::SupportPointGenerator(
 }
 
 SupportPointGenerator::SupportPointGenerator(
-        const EigenMesh3D &emesh,
+        const IndexedMesh &emesh,
         const SupportPointGenerator::Config &config,
         std::function<void ()> throw_on_cancel, 
         std::function<void (int)> statusfn)
@@ -95,8 +95,8 @@ void SupportPointGenerator::project_onto_mesh(std::vector<sla::SupportPoint>& po
                     m_throw_on_cancel();
                 Vec3f& p = points[point_id].pos;
                 // Project the point upward and downward and choose the closer intersection with the mesh.
-                sla::EigenMesh3D::hit_result hit_up   = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., 1.));
-                sla::EigenMesh3D::hit_result hit_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.));
+                sla::IndexedMesh::hit_result hit_up   = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., 1.));
+                sla::IndexedMesh::hit_result hit_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.));
 
                 bool up   = hit_up.is_hit();
                 bool down = hit_down.is_hit();
@@ -104,7 +104,7 @@ void SupportPointGenerator::project_onto_mesh(std::vector<sla::SupportPoint>& po
                 if (!up && !down)
                     continue;
 
-                sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down;
+                sla::IndexedMesh::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down;
                 p = p + (hit.distance() * hit.direction()).cast<float>();
             }
         });
@@ -523,15 +523,12 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure
     }
 }
 
-void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance)
+void remove_bottom_points(std::vector<SupportPoint> &pts, float lvl)
 {
     // get iterator to the reorganized vector end
-    auto endit =
-        std::remove_if(pts.begin(), pts.end(),
-                       [tolerance, gnd_lvl](const sla::SupportPoint &sp) {
-        double diff = std::abs(gnd_lvl -
-                               double(sp.pos(Z)));
-        return diff <= tolerance;
+    auto endit = std::remove_if(pts.begin(), pts.end(), [lvl]
+                                (const sla::SupportPoint &sp) {
+        return sp.pos.z() <= lvl;
     });
 
     // erase all elements after the new end
diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp
index 2fe8e11fc..f1b377025 100644
--- a/src/libslic3r/SLA/SupportPointGenerator.hpp
+++ b/src/libslic3r/SLA/SupportPointGenerator.hpp
@@ -3,9 +3,8 @@
 
 #include <random>
 
-#include <libslic3r/SLA/Common.hpp>
 #include <libslic3r/SLA/SupportPoint.hpp>
-#include <libslic3r/SLA/EigenMesh3D.hpp>
+#include <libslic3r/SLA/IndexedMesh.hpp>
 
 #include <libslic3r/BoundingBox.hpp>
 #include <libslic3r/ClipperUtils.hpp>
@@ -28,10 +27,10 @@ public:
         inline float tear_pressure() const { return 1.f; }  // pressure that the display exerts    (the force unit per mm2)
     };
     
-    SupportPointGenerator(const EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
+    SupportPointGenerator(const IndexedMesh& emesh, const std::vector<ExPolygons>& slices,
                     const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
     
-    SupportPointGenerator(const EigenMesh3D& emesh, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
+    SupportPointGenerator(const IndexedMesh& emesh, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
     
     const std::vector<SupportPoint>& output() const { return m_output; }
     std::vector<SupportPoint>& output() { return m_output; }
@@ -207,14 +206,14 @@ private:
     static void output_structures(const std::vector<Structure> &structures);
 #endif // SLA_SUPPORTPOINTGEN_DEBUG
     
-    const EigenMesh3D& m_emesh;
+    const IndexedMesh& m_emesh;
     std::function<void(void)> m_throw_on_cancel;
     std::function<void(int)>  m_statusfn;
     
     std::mt19937 m_rng;
 };
 
-void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance);
+void remove_bottom_points(std::vector<SupportPoint> &pts, float lvl);
 
 }} // namespace Slic3r::sla
 
diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp
index 528778b68..1bb4cfab7 100644
--- a/src/libslic3r/SLA/SupportTree.cpp
+++ b/src/libslic3r/SLA/SupportTree.cpp
@@ -5,9 +5,9 @@
 
 #include <numeric>
 #include <libslic3r/SLA/SupportTree.hpp>
-#include <libslic3r/SLA/Common.hpp>
 #include <libslic3r/SLA/SpatIndex.hpp>
 #include <libslic3r/SLA/SupportTreeBuilder.hpp>
+#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
 
 #include <libslic3r/MTUtils.hpp>
 #include <libslic3r/ClipperUtils.hpp>
@@ -28,20 +28,6 @@
 namespace Slic3r {
 namespace sla {
 
-// Compile time configuration value definitions:
-
-// The max Z angle for a normal at which it will get completely ignored.
-const double SupportConfig::normal_cutoff_angle = 150.0 * M_PI / 180.0;
-
-// The shortest distance of any support structure from the model surface
-const double SupportConfig::safety_distance_mm = 0.5;
-
-const double SupportConfig::max_solo_pillar_height_mm = 15.0;
-const double SupportConfig::max_dual_pillar_height_mm = 35.0;
-const double   SupportConfig::optimizer_rel_score_diff = 1e-6;
-const unsigned SupportConfig::optimizer_max_iterations = 1000;
-const unsigned SupportConfig::pillar_cascade_neighbors = 3;
-
 void SupportTree::retrieve_full_mesh(TriangleMesh &outmesh) const {
     outmesh.merge(retrieve_mesh(MeshType::Support));
     outmesh.merge(retrieve_mesh(MeshType::Pad));
@@ -103,9 +89,11 @@ SupportTree::UPtr SupportTree::create(const SupportableMesh &sm,
     builder->m_ctl = ctl;
     
     if (sm.cfg.enabled) {
-        builder->build(sm);
+        // Execute takes care about the ground_level
+        SupportTreeBuildsteps::execute(*builder, sm);
         builder->merge_and_cleanup();   // clean metadata, leave only the meshes.
     } else {
+        // If a pad gets added later, it will be in the right Z level
         builder->ground_level = sm.emesh.ground_level();
     }
     
diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp
index c6255aa2f..4be90161d 100644
--- a/src/libslic3r/SLA/SupportTree.hpp
+++ b/src/libslic3r/SLA/SupportTree.hpp
@@ -5,9 +5,8 @@
 #include <memory>
 #include <Eigen/Geometry>
 
-#include <libslic3r/SLA/Common.hpp>
 #include <libslic3r/SLA/Pad.hpp>
-#include <libslic3r/SLA/EigenMesh3D.hpp>
+#include <libslic3r/SLA/IndexedMesh.hpp>
 #include <libslic3r/SLA/SupportPoint.hpp>
 #include <libslic3r/SLA/JobController.hpp>
 
@@ -32,7 +31,7 @@ enum class PillarConnectionMode
     dynamic
 };
 
-struct SupportConfig
+struct SupportTreeConfig
 {
     bool   enabled = true;
     
@@ -45,6 +44,8 @@ struct SupportConfig
     // Radius of the back side of the 3d arrow.
     double head_back_radius_mm = 0.5;
 
+    double head_fallback_radius_mm = 0.25;
+
     // Width in mm from the back sphere center to the front sphere center.
     double head_width_mm = 1.0;
 
@@ -95,36 +96,43 @@ struct SupportConfig
     // /////////////////////////////////////////////////////////////////////////
 
     // The max Z angle for a normal at which it will get completely ignored.
-    static const double normal_cutoff_angle;
+    static const double constexpr normal_cutoff_angle = 150.0 * M_PI / 180.0;
 
     // The shortest distance of any support structure from the model surface
-    static const double safety_distance_mm;
+    static const double constexpr safety_distance_mm = 0.5;
 
-    static const double max_solo_pillar_height_mm;
-    static const double max_dual_pillar_height_mm;
-    static const double   optimizer_rel_score_diff;
-    static const unsigned optimizer_max_iterations;
-    static const unsigned pillar_cascade_neighbors;
+    static const double constexpr max_solo_pillar_height_mm = 15.0;
+    static const double constexpr max_dual_pillar_height_mm = 35.0;
+    static const double constexpr optimizer_rel_score_diff = 1e-6;
+    static const unsigned constexpr optimizer_max_iterations = 1000;
+    static const unsigned constexpr pillar_cascade_neighbors = 3;
     
 };
 
+// TODO: Part of future refactor
+//class SupportConfig {
+//    std::optional<SupportTreeConfig> tree_cfg {std::in_place_t{}}; // fill up
+//    std::optional<PadConfig>         pad_cfg;
+//};
+
 enum class MeshType { Support, Pad };
 
 struct SupportableMesh
 {
-    EigenMesh3D   emesh;
+    IndexedMesh  emesh;
     SupportPoints pts;
-    SupportConfig cfg;
+    SupportTreeConfig cfg;
+    PadConfig     pad_cfg;
 
     explicit SupportableMesh(const TriangleMesh & trmsh,
                              const SupportPoints &sp,
-                             const SupportConfig &c)
+                             const SupportTreeConfig &c)
         : emesh{trmsh}, pts{sp}, cfg{c}
     {}
     
-    explicit SupportableMesh(const EigenMesh3D   &em,
+    explicit SupportableMesh(const IndexedMesh   &em,
                              const SupportPoints &sp,
-                             const SupportConfig &c)
+                             const SupportTreeConfig &c)
         : emesh{em}, pts{sp}, cfg{c}
     {}
 };
diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp
index cf6e7e020..daa01ef24 100644
--- a/src/libslic3r/SLA/SupportTreeBuilder.cpp
+++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp
@@ -1,336 +1,26 @@
+#define NOMINMAX
+
 #include <libslic3r/SLA/SupportTreeBuilder.hpp>
 #include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
+#include <libslic3r/SLA/SupportTreeMesher.hpp>
 #include <libslic3r/SLA/Contour3D.hpp>
 
 namespace Slic3r {
 namespace sla {
 
-Contour3D sphere(double rho, Portion portion, double fa) {
-    
-    Contour3D ret;
-    
-    // prohibit close to zero radius
-    if(rho <= 1e-6 && rho >= -1e-6) return ret;
-    
-    auto& vertices = ret.points;
-    auto& facets = ret.faces3;
-    
-    // Algorithm:
-    // Add points one-by-one to the sphere grid and form facets using relative
-    // coordinates. Sphere is composed effectively of a mesh of stacked circles.
-    
-    // adjust via rounding to get an even multiple for any provided angle.
-    double angle = (2*PI / floor(2*PI / fa));
-    
-    // Ring to be scaled to generate the steps of the sphere
-    std::vector<double> ring;
-    
-    for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i);
-    
-    const auto sbegin = size_t(2*std::get<0>(portion)/angle);
-    const auto send = size_t(2*std::get<1>(portion)/angle);
-    
-    const size_t steps = ring.size();
-    const double increment = 1.0 / double(steps);
-    
-    // special case: first ring connects to 0,0,0
-    // insert and form facets.
-    if(sbegin == 0)
-        vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho));
-    
-    auto id = coord_t(vertices.size());
-    for (size_t i = 0; i < ring.size(); i++) {
-        // Fixed scaling
-        const double z = -rho + increment*rho*2.0 * (sbegin + 1.0);
-        // radius of the circle for this step.
-        const double r = std::sqrt(std::abs(rho*rho - z*z));
-        Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
-        vertices.emplace_back(Vec3d(b(0), b(1), z));
-        
-        if (sbegin == 0)
-            (i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) :
-        			   facets.emplace_back(id - 1, 0, id);
-        ++id;
-    }
-    
-    // General case: insert and form facets for each step,
-    // joining it to the ring below it.
-    for (size_t s = sbegin + 2; s < send - 1; s++) {
-        const double z = -rho + increment*double(s*2.0*rho);
-        const double r = std::sqrt(std::abs(rho*rho - z*z));
-        
-        for (size_t i = 0; i < ring.size(); i++) {
-            Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
-            vertices.emplace_back(Vec3d(b(0), b(1), z));
-            auto id_ringsize = coord_t(id - int(ring.size()));
-            if (i == 0) {
-                // wrap around
-                facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) );
-                facets.emplace_back(id - 1, id_ringsize, id);
-            } else {
-                facets.emplace_back(id_ringsize - 1, id_ringsize, id);
-                facets.emplace_back(id - 1, id_ringsize - 1, id);
-            }
-            id++;
-        }
-    }
-    
-    // special case: last ring connects to 0,0,rho*2.0
-    // only form facets.
-    if(send >= size_t(2*PI / angle)) {
-        vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho));
-        for (size_t i = 0; i < ring.size(); i++) {
-            auto id_ringsize = coord_t(id - int(ring.size()));
-            if (i == 0) {
-                // third vertex is on the other side of the ring.
-                facets.emplace_back(id - 1, id_ringsize, id);
-            } else {
-                auto ci = coord_t(id_ringsize + coord_t(i));
-                facets.emplace_back(ci - 1, ci, id);
-            }
-        }
-    }
-    id++;
-    
-    return ret;
-}
-
-Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp)
-{
-    Contour3D ret;
-    
-    auto steps = int(ssteps);
-    auto& points = ret.points;
-    auto& indices = ret.faces3;
-    points.reserve(2*ssteps);
-    double a = 2*PI/steps;
-    
-    Vec3d jp = sp;
-    Vec3d endp = {sp(X), sp(Y), sp(Z) + h};
-    
-    // Upper circle points
-    for(int i = 0; i < steps; ++i) {
-        double phi = i*a;
-        double ex = endp(X) + r*std::cos(phi);
-        double ey = endp(Y) + r*std::sin(phi);
-        points.emplace_back(ex, ey, endp(Z));
-    }
-    
-    // Lower circle points
-    for(int i = 0; i < steps; ++i) {
-        double phi = i*a;
-        double x = jp(X) + r*std::cos(phi);
-        double y = jp(Y) + r*std::sin(phi);
-        points.emplace_back(x, y, jp(Z));
-    }
-    
-    // Now create long triangles connecting upper and lower circles
-    indices.reserve(2*ssteps);
-    auto offs = steps;
-    for(int i = 0; i < steps - 1; ++i) {
-        indices.emplace_back(i, i + offs, offs + i + 1);
-        indices.emplace_back(i, offs + i + 1, i + 1);
-    }
-    
-    // Last triangle connecting the first and last vertices
-    auto last = steps - 1;
-    indices.emplace_back(0, last, offs);
-    indices.emplace_back(last, offs + last, offs);
-    
-    // According to the slicing algorithms, we need to aid them with generating
-    // a watertight body. So we create a triangle fan for the upper and lower
-    // ending of the cylinder to close the geometry.
-    points.emplace_back(jp); int ci = int(points.size() - 1);
-    for(int i = 0; i < steps - 1; ++i)
-        indices.emplace_back(i + offs + 1, i + offs, ci);
-    
-    indices.emplace_back(offs, steps + offs - 1, ci);
-    
-    points.emplace_back(endp); ci = int(points.size() - 1);
-    for(int i = 0; i < steps - 1; ++i)
-        indices.emplace_back(ci, i, i + 1);
-    
-    indices.emplace_back(steps - 1, 0, ci);
-    
-    return ret;
-}
-
 Head::Head(double       r_big_mm,
            double       r_small_mm,
            double       length_mm,
            double       penetration,
            const Vec3d &direction,
-           const Vec3d &offset,
-           const size_t circlesteps)
-    : steps(circlesteps)
-    , dir(direction)
-    , tr(offset)
+           const Vec3d &offset)
+    : dir(direction)
+    , pos(offset)
     , r_back_mm(r_big_mm)
     , r_pin_mm(r_small_mm)
     , width_mm(length_mm)
     , penetration_mm(penetration)
 {
-    assert(width_mm > 0.);
-    assert(r_back_mm > 0.);
-    assert(r_pin_mm > 0.);
-    
-    // We create two spheres which will be connected with a robe that fits
-    // both circles perfectly.
-    
-    // Set up the model detail level
-    const double detail = 2*PI/steps;
-    
-    // We don't generate whole circles. Instead, we generate only the
-    // portions which are visible (not covered by the robe) To know the
-    // exact portion of the bottom and top circles we need to use some
-    // rules of tangent circles from which we can derive (using simple
-    // triangles the following relations:
-    
-    // The height of the whole mesh
-    const double h = r_big_mm + r_small_mm + width_mm;
-    double phi = PI/2 - std::acos( (r_big_mm - r_small_mm) / h );
-    
-    // To generate a whole circle we would pass a portion of (0, Pi)
-    // To generate only a half horizontal circle we can pass (0, Pi/2)
-    // The calculated phi is an offset to the half circles needed to smooth
-    // the transition from the circle to the robe geometry
-    
-    auto&& s1 = sphere(r_big_mm, make_portion(0, PI/2 + phi), detail);
-    auto&& s2 = sphere(r_small_mm, make_portion(PI/2 + phi, PI), detail);
-    
-    for(auto& p : s2.points) p.z() += h;
-    
-    mesh.merge(s1);
-    mesh.merge(s2);
-    
-    for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size();
-        idx1 < s1.points.size() - 1;
-        idx1++, idx2++)
-    {
-        coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2);
-        coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1;
-        
-        mesh.faces3.emplace_back(i1s1, i2s1, i2s2);
-        mesh.faces3.emplace_back(i1s1, i2s2, i1s2);
-    }
-    
-    auto i1s1 = coord_t(s1.points.size()) - coord_t(steps);
-    auto i2s1 = coord_t(s1.points.size()) - 1;
-    auto i1s2 = coord_t(s1.points.size());
-    auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1;
-    
-    mesh.faces3.emplace_back(i2s2, i2s1, i1s1);
-    mesh.faces3.emplace_back(i1s2, i2s2, i1s1);
-    
-    // To simplify further processing, we translate the mesh so that the
-    // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0)
-    for(auto& p : mesh.points) p.z() -= (h + r_small_mm - penetration_mm);
-}
-
-Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st):
-    r(radius), steps(st), endpt(endp), starts_from_head(false)
-{
-    assert(steps > 0);
-    
-    height = jp(Z) - endp(Z);
-    if(height > EPSILON) { // Endpoint is below the starting point
-        
-        // We just create a bridge geometry with the pillar parameters and
-        // move the data.
-        Contour3D body = cylinder(radius, height, st, endp);
-        mesh.points.swap(body.points);
-        mesh.faces3.swap(body.faces3);
-    }
-}
-
-Pillar &Pillar::add_base(double baseheight, double radius)
-{
-    if(baseheight <= 0) return *this;
-    if(baseheight > height) baseheight = height;
-    
-    assert(steps >= 0);
-    auto last = int(steps - 1);
-    
-    if(radius < r ) radius = r;
-    
-    double a = 2*PI/steps;
-    double z = endpt(Z) + baseheight;
-    
-    for(size_t i = 0; i < steps; ++i) {
-        double phi = i*a;
-        double x = endpt(X) + r*std::cos(phi);
-        double y = endpt(Y) + r*std::sin(phi);
-        base.points.emplace_back(x, y, z);
-    }
-    
-    for(size_t i = 0; i < steps; ++i) {
-        double phi = i*a;
-        double x = endpt(X) + radius*std::cos(phi);
-        double y = endpt(Y) + radius*std::sin(phi);
-        base.points.emplace_back(x, y, z - baseheight);
-    }
-    
-    auto ep = endpt; ep(Z) += baseheight;
-    base.points.emplace_back(endpt);
-    base.points.emplace_back(ep);
-    
-    auto& indices = base.faces3;
-    auto hcenter = int(base.points.size() - 1);
-    auto lcenter = int(base.points.size() - 2);
-    auto offs = int(steps);
-    for(int i = 0; i < last; ++i) {
-        indices.emplace_back(i, i + offs, offs + i + 1);
-        indices.emplace_back(i, offs + i + 1, i + 1);
-        indices.emplace_back(i, i + 1, hcenter);
-        indices.emplace_back(lcenter, offs + i + 1, offs + i);
-    }
-    
-    indices.emplace_back(0, last, offs);
-    indices.emplace_back(last, offs + last, offs);
-    indices.emplace_back(hcenter, last, 0);
-    indices.emplace_back(offs, offs + last, lcenter);
-    return *this;
-}
-
-Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps):
-    r(r_mm), startp(j1), endp(j2)
-{
-    using Quaternion = Eigen::Quaternion<double>;
-    Vec3d dir = (j2 - j1).normalized();
-    double d = distance(j2, j1);
-    
-    mesh = cylinder(r, d, steps);
-    
-    auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir);
-    for(auto& p : mesh.points) p = quater * p + j1;
-}
-
-CompactBridge::CompactBridge(const Vec3d &sp,
-                             const Vec3d &ep,
-                             const Vec3d &n,
-                             double       r,
-                             bool         endball,
-                             size_t       steps)
-{
-    Vec3d startp = sp + r * n;
-    Vec3d dir = (ep - startp).normalized();
-    Vec3d endp = ep - r * dir;
-    
-    Bridge br(startp, endp, r, steps);
-    mesh.merge(br.mesh);
-    
-    // now add the pins
-    double fa = 2*PI/steps;
-    auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa);
-    for(auto& p : upperball.points) p += startp;
-    
-    if(endball) {
-        auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa);
-        for(auto& p : lowerball.points) p += endp;
-        mesh.merge(lowerball);
-    }
-    
-    mesh.merge(upperball);
 }
 
 Pad::Pad(const TriangleMesh &support_mesh,
@@ -368,7 +58,6 @@ SupportTreeBuilder::SupportTreeBuilder(SupportTreeBuilder &&o)
     , m_pillars{std::move(o.m_pillars)}
     , m_bridges{std::move(o.m_bridges)}
     , m_crossbridges{std::move(o.m_crossbridges)}
-    , m_compact_bridges{std::move(o.m_compact_bridges)}
     , m_pad{std::move(o.m_pad)}
     , m_meshcache{std::move(o.m_meshcache)}
     , m_meshcache_valid{o.m_meshcache_valid}
@@ -382,7 +71,6 @@ SupportTreeBuilder::SupportTreeBuilder(const SupportTreeBuilder &o)
     , m_pillars{o.m_pillars}
     , m_bridges{o.m_bridges}
     , m_crossbridges{o.m_crossbridges}
-    , m_compact_bridges{o.m_compact_bridges}
     , m_pad{o.m_pad}
     , m_meshcache{o.m_meshcache}
     , m_meshcache_valid{o.m_meshcache_valid}
@@ -397,7 +85,6 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(SupportTreeBuilder &&o)
     m_pillars = std::move(o.m_pillars);
     m_bridges = std::move(o.m_bridges);
     m_crossbridges = std::move(o.m_crossbridges);
-    m_compact_bridges = std::move(o.m_compact_bridges);
     m_pad = std::move(o.m_pad);
     m_meshcache = std::move(o.m_meshcache);
     m_meshcache_valid = o.m_meshcache_valid;
@@ -413,7 +100,6 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o)
     m_pillars = o.m_pillars;
     m_bridges = o.m_bridges;
     m_crossbridges = o.m_crossbridges;
-    m_compact_bridges = o.m_compact_bridges;
     m_pad = o.m_pad;
     m_meshcache = o.m_meshcache;
     m_meshcache_valid = o.m_meshcache_valid;
@@ -422,7 +108,19 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o)
     return *this;
 }
 
-const TriangleMesh &SupportTreeBuilder::merged_mesh() const
+void SupportTreeBuilder::add_pillar_base(long pid, double baseheight, double radius)
+{
+    std::lock_guard<Mutex> lk(m_mutex);
+    assert(pid >= 0 && size_t(pid) < m_pillars.size());
+    Pillar& pll = m_pillars[size_t(pid)];
+    m_pedestals.emplace_back(pll.endpt, std::min(baseheight, pll.height),
+                             std::max(radius, pll.r), pll.r);
+
+    m_pedestals.back().id = m_pedestals.size() - 1;
+    m_meshcache_valid = false;
+}
+
+const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const
 {
     if (m_meshcache_valid) return m_meshcache;
     
@@ -430,35 +128,44 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const
     
     for (auto &head : m_heads) {
         if (ctl().stopcondition()) break;
-        if (head.is_valid()) merged.merge(head.mesh);
+        if (head.is_valid()) merged.merge(get_mesh(head, steps));
     }
     
-    for (auto &stick : m_pillars) {
+    for (auto &pill : m_pillars) {
         if (ctl().stopcondition()) break;
-        merged.merge(stick.mesh);
-        merged.merge(stick.base);
+        merged.merge(get_mesh(pill, steps));
+    }
+
+    for (auto &pedest : m_pedestals) {
+        if (ctl().stopcondition()) break;
+        merged.merge(get_mesh(pedest, steps));
     }
     
     for (auto &j : m_junctions) {
         if (ctl().stopcondition()) break;
-        merged.merge(j.mesh);
+        merged.merge(get_mesh(j, steps));
     }
-    
-    for (auto &cb : m_compact_bridges) {
-        if (ctl().stopcondition()) break;
-        merged.merge(cb.mesh);
-    }
-    
+
     for (auto &bs : m_bridges) {
         if (ctl().stopcondition()) break;
-        merged.merge(bs.mesh);
+        merged.merge(get_mesh(bs, steps));
     }
     
     for (auto &bs : m_crossbridges) {
         if (ctl().stopcondition()) break;
-        merged.merge(bs.mesh);
+        merged.merge(get_mesh(bs, steps));
     }
-    
+
+    for (auto &bs : m_diffbridges) {
+        if (ctl().stopcondition()) break;
+        merged.merge(get_mesh(bs, steps));
+    }
+
+    for (auto &anch : m_anchors) {
+        if (ctl().stopcondition()) break;
+        merged.merge(get_mesh(anch, steps));
+    }
+
     if (ctl().stopcondition()) {
         // In case of failure we have to return an empty mesh
         m_meshcache = TriangleMesh();
@@ -499,7 +206,6 @@ const TriangleMesh &SupportTreeBuilder::merge_and_cleanup()
     m_pillars = {};
     m_junctions = {};
     m_bridges = {};
-    m_compact_bridges = {};
     
     return ret;
 }
@@ -514,11 +220,4 @@ const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const
     return m_meshcache;
 }
 
-bool SupportTreeBuilder::build(const SupportableMesh &sm)
-{
-    ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm;
-    return SupportTreeBuildsteps::execute(*this, sm);
-}
-
-}
-}
+}} // namespace Slic3r::sla
diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp
index 90cf417c8..f29263ca3 100644
--- a/src/libslic3r/SLA/SupportTreeBuilder.hpp
+++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp
@@ -2,7 +2,6 @@
 #define SLA_SUPPORTTREEBUILDER_HPP
 
 #include <libslic3r/SLA/Concurrency.hpp>
-#include <libslic3r/SLA/Common.hpp>
 #include <libslic3r/SLA/SupportTree.hpp>
 #include <libslic3r/SLA/Contour3D.hpp>
 #include <libslic3r/SLA/Pad.hpp>
@@ -50,13 +49,6 @@ namespace sla {
  * nearby pillar.
  */
 
-using Coordf = double;
-using Portion = std::tuple<double, double>;
-
-inline Portion make_portion(double a, double b) {
-    return std::make_tuple(a, b);
-}
-
 template<class Vec> double distance(const Vec& p) {
     return std::sqrt(p.transpose() * p);
 }
@@ -66,33 +58,25 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
     return distance(p);
 }
 
-Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI),
-                 double fa=(2*PI/360));
+const Vec3d DOWN = {0.0, 0.0, -1.0};
 
-// Down facing cylinder in Z direction with arguments:
-// r: radius
-// h: Height
-// ssteps: how many edges will create the base circle
-// sp: starting point
-Contour3D cylinder(double r, double h, size_t ssteps = 45, const Vec3d &sp = {0,0,0});
+struct SupportTreeNode
+{
+    static const constexpr long ID_UNSET = -1;
 
-const constexpr long ID_UNSET = -1;
+    long id = ID_UNSET; // For identification withing a tree.
+};
 
-struct Head {
-    Contour3D mesh;
-    
-    size_t steps = 45;
-    Vec3d dir = {0, 0, -1};
-    Vec3d tr = {0, 0, 0};
+// A pinhead originating from a support point
+struct Head: public SupportTreeNode {
+    Vec3d dir = DOWN;
+    Vec3d pos = {0, 0, 0};
     
     double r_back_mm = 1;
     double r_pin_mm = 0.5;
     double width_mm = 2;
     double penetration_mm = 0.5;
-    
-    // For identification purposes. This will be used as the index into the
-    // container holding the head structures. See SLASupportTree::Impl
-    long id = ID_UNSET;
+
     
     // If there is a pillar connecting to this head, then the id will be set.
     long pillar_id = ID_UNSET;
@@ -106,31 +90,23 @@ struct Head {
          double r_small_mm,
          double length_mm,
          double penetration,
-         const Vec3d &direction = {0, 0, -1},  // direction (normal to the dull end)
-         const Vec3d &offset = {0, 0, 0},      // displacement
-         const size_t circlesteps = 45);
-    
-    void transform()
+         const Vec3d &direction = DOWN,  // direction (normal to the dull end)
+         const Vec3d &offset = {0, 0, 0}      // displacement
+         );
+
+    inline double real_width() const
     {
-        using Quaternion = Eigen::Quaternion<double>;
-        
-        // We rotate the head to the specified direction The head's pointing
-        // side is facing upwards so this means that it would hold a support
-        // point with a normal pointing straight down. This is the reason of
-        // the -1 z coordinate
-        auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, dir);
-        
-        for(auto& p : mesh.points) p = quatern * p + tr;
+        return 2 * r_pin_mm + width_mm + 2 * r_back_mm ;
     }
-    
+
     inline double fullwidth() const
     {
-        return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm;
+        return real_width() - penetration_mm;
     }
     
     inline Vec3d junction_point() const
     {
-        return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir;
+        return pos + (fullwidth() - r_back_mm) * dir;
     }
     
     inline double request_pillar_radius(double radius) const
@@ -140,31 +116,17 @@ struct Head {
     }
 };
 
-struct Junction {
-    Contour3D mesh;
+// A junction connecting bridges and pillars
+struct Junction: public SupportTreeNode {
     double r = 1;
-    size_t steps = 45;
     Vec3d pos;
-    
-    long id = ID_UNSET;
-    
-    Junction(const Vec3d& tr, double r_mm, size_t stepnum = 45):
-        r(r_mm), steps(stepnum), pos(tr)
-    {
-        mesh = sphere(r_mm, make_portion(0, PI), 2*PI/steps);
-        for(auto& p : mesh.points) p += tr;
-    }
+
+    Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {}
 };
 
-struct Pillar {
-    Contour3D mesh;
-    Contour3D base;
-    double r = 1;
-    size_t steps = 0;
+struct Pillar: public SupportTreeNode {
+    double height, r;
     Vec3d endpt;
-    double height = 0;
-    
-    long id = ID_UNSET;
     
     // If the pillar connects to a head, this is the id of that head
     bool starts_from_head = true; // Could start from a junction as well
@@ -175,54 +137,52 @@ struct Pillar {
     
     // How many pillars are cascaded with this one
     unsigned links = 0;
-    
-    Pillar(const Vec3d& jp, const Vec3d& endp,
-           double radius = 1, size_t st = 45);
-    
-    Pillar(const Junction &junc, const Vec3d &endp)
-        : Pillar(junc.pos, endp, junc.r, junc.steps)
-    {}
-    
-    Pillar(const Head &head, const Vec3d &endp, double radius = 1)
-        : Pillar(head.junction_point(), endp,
-                 head.request_pillar_radius(radius), head.steps)
-    {}
-    
-    inline Vec3d startpoint() const
+
+    Pillar(const Vec3d &endp, double h, double radius = 1.):
+        height{h}, r(radius), endpt(endp), starts_from_head(false) {}
+
+    Vec3d startpoint() const
     {
-        return {endpt(X), endpt(Y), endpt(Z) + height};
+        return {endpt.x(), endpt.y(), endpt.z() + height};
     }
     
-    inline const Vec3d& endpoint() const { return endpt; }
-    
-    Pillar& add_base(double baseheight = 3, double radius = 2);
+    const Vec3d& endpoint() const { return endpt; }
 };
 
+// A base for pillars or bridges that end on the ground
+struct Pedestal: public SupportTreeNode {
+    Vec3d pos;
+    double height, r_bottom, r_top;
+
+    Pedestal(const Vec3d &p, double h, double rbottom, double rtop)
+        : pos{p}, height{h}, r_bottom{rbottom}, r_top{rtop}
+    {}
+};
+
+// This is the thing that anchors a pillar or bridge to the model body.
+// It is actually a reverse pinhead.
+struct Anchor: public Head { using Head::Head; };
+
 // A Bridge between two pillars (with junction endpoints)
-struct Bridge {
-    Contour3D mesh;
+struct Bridge: public SupportTreeNode {
     double r = 0.8;
-    long id = ID_UNSET;
     Vec3d startp = Vec3d::Zero(), endp = Vec3d::Zero();
     
     Bridge(const Vec3d &j1,
            const Vec3d &j2,
-           double       r_mm  = 0.8,
-           size_t       steps = 45);
+           double       r_mm  = 0.8): r{r_mm}, startp{j1}, endp{j2}
+    {}
+
+    double get_length() const { return (endp - startp).norm(); }
+    Vec3d  get_dir() const { return (endp - startp).normalized(); }
 };
 
-// A bridge that spans from model surface to model surface with small connecting
-// edges on the endpoints. Used for headless support points.
-struct CompactBridge {
-    Contour3D mesh;
-    long id = ID_UNSET;
-    
-    CompactBridge(const Vec3d& sp,
-                  const Vec3d& ep,
-                  const Vec3d& n,
-                  double r,
-                  bool endball = true,
-                  size_t steps = 45);
+struct DiffBridge: public Bridge {
+    double end_r;
+
+    DiffBridge(const Vec3d &p_s, const Vec3d &p_e, double r_s, double r_e)
+        : Bridge{p_s, p_e, r_s}, end_r{r_e}
+    {}
 };
 
 // A wrapper struct around the pad
@@ -258,13 +218,16 @@ struct Pad {
 // merged mesh. It can be retrieved using a dedicated method (pad())
 class SupportTreeBuilder: public SupportTree {
     // For heads it is beneficial to use the same IDs as for the support points.
-    std::vector<Head> m_heads;
-    std::vector<size_t> m_head_indices;
-    std::vector<Pillar> m_pillars;
-    std::vector<Junction> m_junctions;
-    std::vector<Bridge> m_bridges;
-    std::vector<Bridge> m_crossbridges;
-    std::vector<CompactBridge> m_compact_bridges;    
+    std::vector<Head>       m_heads;
+    std::vector<size_t>     m_head_indices;
+    std::vector<Pillar>     m_pillars;
+    std::vector<Junction>   m_junctions;
+    std::vector<Bridge>     m_bridges;
+    std::vector<Bridge>     m_crossbridges;
+    std::vector<DiffBridge> m_diffbridges;
+    std::vector<Pedestal>   m_pedestals;
+    std::vector<Anchor>     m_anchors;
+
     Pad m_pad;
     
     using Mutex = ccr::SpinningMutex;
@@ -274,8 +237,8 @@ class SupportTreeBuilder: public SupportTree {
     mutable bool m_meshcache_valid = false;
     mutable double m_model_height = 0; // the full height of the model
     
-    template<class...Args>
-    const Bridge& _add_bridge(std::vector<Bridge> &br, Args&&... args)
+    template<class BridgeT, class...Args>
+    const BridgeT& _add_bridge(std::vector<BridgeT> &br, Args&&... args)
     {
         std::lock_guard<Mutex> lk(m_mutex);
         br.emplace_back(std::forward<Args>(args)...);
@@ -306,7 +269,7 @@ public:
         return m_heads.back();
     }
     
-    template<class...Args> long add_pillar(long headid, Args&&... args)
+    template<class...Args> long add_pillar(long headid, double length)
     {
         std::lock_guard<Mutex> lk(m_mutex);
         if (m_pillars.capacity() < m_heads.size())
@@ -315,7 +278,9 @@ public:
         assert(headid >= 0 && size_t(headid) < m_head_indices.size());
         Head &head = m_heads[m_head_indices[size_t(headid)]];
         
-        m_pillars.emplace_back(head, std::forward<Args>(args)...);
+        Vec3d hjp = head.junction_point() - Vec3d{0, 0, length};
+        m_pillars.emplace_back(hjp, length, head.r_back_mm);
+
         Pillar& pillar = m_pillars.back();
         pillar.id = long(m_pillars.size() - 1);
         head.pillar_id = pillar.id;
@@ -326,11 +291,15 @@ public:
         return pillar.id;
     }
     
-    void add_pillar_base(long pid, double baseheight = 3, double radius = 2)
+    void add_pillar_base(long pid, double baseheight = 3, double radius = 2);
+
+    template<class...Args> const Anchor& add_anchor(Args&&...args)
     {
         std::lock_guard<Mutex> lk(m_mutex);
-        assert(pid >= 0 && size_t(pid) < m_pillars.size());
-        m_pillars[size_t(pid)].add_base(baseheight, radius);
+        m_anchors.emplace_back(std::forward<Args>(args)...);
+        m_anchors.back().id = long(m_junctions.size() - 1);
+        m_meshcache_valid = false;
+        return m_anchors.back();
     }
     
     void increment_bridges(const Pillar& pillar)
@@ -371,17 +340,6 @@ public:
         return pillar.id;
     }
     
-    const Pillar& head_pillar(unsigned headid) const
-    {
-        std::lock_guard<Mutex> lk(m_mutex);
-        assert(headid < m_head_indices.size());
-        
-        const Head& h = m_heads[m_head_indices[headid]];
-        assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size()));
-        
-        return m_pillars[size_t(h.pillar_id)];
-    }
-    
     template<class...Args> const Junction& add_junction(Args&&... args)
     {
         std::lock_guard<Mutex> lk(m_mutex);
@@ -391,18 +349,18 @@ public:
         return m_junctions.back();
     }
     
-    const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r, size_t n = 45)
+    const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r)
     {
-        return _add_bridge(m_bridges, s, e, r, n);
+        return _add_bridge(m_bridges, s, e, r);
     }
     
-    const Bridge& add_bridge(long headid, const Vec3d &endp, size_t s = 45)
+    const Bridge& add_bridge(long headid, const Vec3d &endp)
     {
         std::lock_guard<Mutex> lk(m_mutex);
         assert(headid >= 0 && size_t(headid) < m_head_indices.size());
         
         Head &h = m_heads[m_head_indices[size_t(headid)]];
-        m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm, s);
+        m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm);
         m_bridges.back().id = long(m_bridges.size() - 1);
         
         h.bridge_id = m_bridges.back().id;
@@ -414,14 +372,10 @@ public:
     {
         return _add_bridge(m_crossbridges, std::forward<Args>(args)...);
     }
-    
-    template<class...Args> const CompactBridge& add_compact_bridge(Args&&...args)
+
+    template<class...Args> const DiffBridge& add_diffbridge(Args&&... args)
     {
-        std::lock_guard<Mutex> lk(m_mutex);
-        m_compact_bridges.emplace_back(std::forward<Args>(args)...);
-        m_compact_bridges.back().id = long(m_compact_bridges.size() - 1);
-        m_meshcache_valid = false;
-        return m_compact_bridges.back();
+        return _add_bridge(m_diffbridges, std::forward<Args>(args)...);
     }
     
     Head &head(unsigned id)
@@ -439,7 +393,7 @@ public:
     }
     
     inline const std::vector<Pillar> &pillars() const { return m_pillars; }
-    inline const std::vector<Head> &heads() const { return m_heads; }
+    inline const std::vector<Head>   &heads() const { return m_heads; }
     inline const std::vector<Bridge> &bridges() const { return m_bridges; }
     inline const std::vector<Bridge> &crossbridges() const { return m_crossbridges; }
     
@@ -464,7 +418,7 @@ public:
     const Pad& pad() const { return m_pad; }
     
     // WITHOUT THE PAD!!!
-    const TriangleMesh &merged_mesh() const;
+    const TriangleMesh &merged_mesh(size_t steps = 45) const;
     
     // WITH THE PAD
     double full_height() const;
@@ -488,8 +442,6 @@ public:
     
     virtual const TriangleMesh &retrieve_mesh(
         MeshType meshtype = MeshType::Support) const override;
-
-    bool build(const SupportableMesh &supportable_mesh);
 };
 
 }} // namespace Slic3r::sla
diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp
index 29ad6057f..2b40f0082 100644
--- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp
+++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp
@@ -1,19 +1,36 @@
 #include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
 
-#include <libnest2d/optimizers/nlopt/genetic.hpp>
-#include <libnest2d/optimizers/nlopt/subplex.hpp>
+#include <libslic3r/SLA/SpatIndex.hpp>
+#include <libslic3r/Optimizer.hpp>
 #include <boost/log/trivial.hpp>
 
 namespace Slic3r {
 namespace sla {
 
-static const Vec3d DOWN = {0.0, 0.0, -1.0};
+using Slic3r::opt::initvals;
+using Slic3r::opt::bounds;
+using Slic3r::opt::StopCriteria;
+using Slic3r::opt::Optimizer;
+using Slic3r::opt::AlgNLoptSubplex;
+using Slic3r::opt::AlgNLoptGenetic;
 
-using libnest2d::opt::initvals;
-using libnest2d::opt::bound;
-using libnest2d::opt::StopCriteria;
-using libnest2d::opt::GeneticOptimizer;
-using libnest2d::opt::SubplexOptimizer;
+StopCriteria get_criteria(const SupportTreeConfig &cfg)
+{
+    return StopCriteria{}
+        .rel_score_diff(cfg.optimizer_rel_score_diff)
+        .max_iterations(cfg.optimizer_max_iterations);
+}
+
+template<class C, class Hit = IndexedMesh::hit_result>
+static Hit min_hit(const C &hits)
+{
+    auto mit = std::min_element(hits.begin(), hits.end(),
+                                [](const Hit &h1, const Hit &h2) {
+                                    return h1.distance() < h2.distance();
+                                });
+
+    return *mit;
+}
 
 SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder &   builder,
                                              const SupportableMesh &sm)
@@ -27,7 +44,7 @@ SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder &   builder,
 {
     // Prepare the support points in Eigen/IGL format as well, we will use
     // it mostly in this form.
-    
+
     long i = 0;
     for (const SupportPoint &sp : m_support_pts) {
         m_points.row(i)(X) = double(sp.pos(X));
@@ -41,9 +58,11 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder &   builder,
                                     const SupportableMesh &sm)
 {
     if(sm.pts.empty()) return false;
-    
+
+    builder.ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm;
+
     SupportTreeBuildsteps alg(builder, sm);
-    
+
     // Let's define the individual steps of the processing. We can experiment
     // later with the ordering and the dependencies between them.
     enum Steps {
@@ -54,59 +73,52 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder &   builder,
         ROUTING_GROUND,
         ROUTING_NONGROUND,
         CASCADE_PILLARS,
-        HEADLESS,
         MERGE_RESULT,
         DONE,
         ABORT,
         NUM_STEPS
         //...
     };
-    
+
     // Collect the algorithm steps into a nice sequence
     std::array<std::function<void()>, NUM_STEPS> program = {
         [] () {
             // Begin...
             // Potentially clear up the shared data (not needed for now)
         },
-        
+
         std::bind(&SupportTreeBuildsteps::filter, &alg),
-        
+
         std::bind(&SupportTreeBuildsteps::add_pinheads, &alg),
-        
+
         std::bind(&SupportTreeBuildsteps::classify, &alg),
-        
+
         std::bind(&SupportTreeBuildsteps::routing_to_ground, &alg),
-        
+
         std::bind(&SupportTreeBuildsteps::routing_to_model, &alg),
-        
+
         std::bind(&SupportTreeBuildsteps::interconnect_pillars, &alg),
-        
-        std::bind(&SupportTreeBuildsteps::routing_headless, &alg),
-        
+
         std::bind(&SupportTreeBuildsteps::merge_result, &alg),
-        
+
         [] () {
             // Done
         },
-        
+
         [] () {
             // Abort
         }
     };
-    
+
     Steps pc = BEGIN;
-    
+
     if(sm.cfg.ground_facing_only) {
         program[ROUTING_NONGROUND] = []() {
             BOOST_LOG_TRIVIAL(info)
                 << "Skipping model-facing supports as requested.";
         };
-        program[HEADLESS] = []() {
-            BOOST_LOG_TRIVIAL(info) << "Skipping headless stick generation as"
-                                       " requested.";
-        };
     }
-    
+
     // Let's define a simple automaton that will run our program.
     auto progress = [&builder, &pc] () {
         static const std::array<std::string, NUM_STEPS> stepstr {
@@ -117,12 +129,11 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder &   builder,
             "Routing to ground",
             "Routing supports to model surface",
             "Interconnecting pillars",
-            "Processing small holes",
             "Merging support mesh",
             "Done",
             "Abort"
         };
-        
+
         static const std::array<unsigned, NUM_STEPS> stepstate {
             0,
             10,
@@ -131,14 +142,13 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder &   builder,
             60,
             70,
             80,
-            85,
             99,
             100,
             0
         };
-        
+
         if(builder.ctl().stopcondition()) pc = ABORT;
-        
+
         switch(pc) {
         case BEGIN: pc = FILTER; break;
         case FILTER: pc = PINHEADS; break;
@@ -146,143 +156,76 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder &   builder,
         case CLASSIFY: pc = ROUTING_GROUND; break;
         case ROUTING_GROUND: pc = ROUTING_NONGROUND; break;
         case ROUTING_NONGROUND: pc = CASCADE_PILLARS; break;
-        case CASCADE_PILLARS: pc = HEADLESS; break;
-        case HEADLESS: pc = MERGE_RESULT; break;
+        case CASCADE_PILLARS: pc = MERGE_RESULT; break;
         case MERGE_RESULT: pc = DONE; break;
         case DONE:
         case ABORT: break;
         default: ;
         }
-        
+
         builder.ctl().statuscb(stepstate[pc], stepstr[pc]);
     };
-    
+
     // Just here we run the computation...
     while(pc < DONE) {
         progress();
         program[pc]();
     }
-    
+
     return pc == ABORT;
 }
 
-// Give points on a 3D ring with given center, radius and orientation
-// method based on:
-// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
-template<size_t N>
-class PointRing {
-    std::array<double, N> m_phis;
-    
-    // Two vectors that will be perpendicular to each other and to the
-    // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
-    // placeholder.
-    // a and b vectors are perpendicular to the ring direction and to each other.
-    // Together they define the plane where we have to iterate with the
-    // given angles in the 'm_phis' vector
-    Vec3d a = {0, 1, 0}, b;
-    double m_radius = 0.;
-    
-    static inline bool constexpr is_one(double val) 
-    { 
-        return std::abs(std::abs(val) - 1) < 1e-20;
-    }
-    
-public:
-    
-    PointRing(const Vec3d &n)
-    {
-        m_phis = linspace_array<N>(0., 2 * PI);
-    
-        // We have to address the case when the direction vector v (same as
-        // dir) is coincident with one of the world axes. In this case two of
-        // its components will be completely zero and one is 1.0. Our method
-        // becomes dangerous here due to division with zero. Instead, vector
-        // 'a' can be an element-wise rotated version of 'v'
-        if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) {
-            a = {n(Z), n(X), n(Y)};
-            b = {n(Y), n(Z), n(X)};
-        }
-        else {
-            a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize();
-            b = a.cross(n);
-        }
-    }
-    
-    Vec3d get(size_t idx, const Vec3d src, double r) const
-    {
-        double phi = m_phis[idx];
-        double sinphi = std::sin(phi);
-        double cosphi = std::cos(phi);
-     
-        double rpscos = r * cosphi;
-        double rpssin = r * sinphi;
-     
-        // Point on the sphere
-        return {src(X) + rpscos * a(X) + rpssin * b(X),
-                src(Y) + rpscos * a(Y) + rpssin * b(Y),
-                src(Z) + rpscos * a(Z) + rpssin * b(Z)};
-    }
-};
-
-template<class C, class Hit = EigenMesh3D::hit_result> 
-static Hit min_hit(const C &hits)
-{
-    auto mit = std::min_element(hits.begin(), hits.end(), 
-                                [](const Hit &h1, const Hit &h2) { 
-        return h1.distance() < h2.distance(); 
-    });
-    
-    return *mit;
-}
-
-EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect(
-    const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width)
+IndexedMesh::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect(
+    const Vec3d &s,
+    const Vec3d &dir,
+    double       r_pin,
+    double       r_back,
+    double       width,
+    double       sd)
 {
     static const size_t SAMPLES = 8;
-    
+
     // Move away slightly from the touching point to avoid raycasting on the
     // inner surface of the mesh.
-    
-    const double& sd = m_cfg.safety_distance_mm;
-    
+
     auto& m = m_mesh;
-    using HitResult = EigenMesh3D::hit_result;
-    
+    using HitResult = IndexedMesh::hit_result;
+
     // Hit results
     std::array<HitResult, SAMPLES> hits;
-    
+
     struct Rings {
         double rpin;
         double rback;
         Vec3d  spin;
         Vec3d  sback;
         PointRing<SAMPLES> ring;
-        
+
         Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); }
         Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); }
     } rings {r_pin + sd, r_back + sd, s, s + width * dir, dir};
-    
+
     // We will shoot multiple rays from the head pinpoint in the direction
     // of the pinhead robe (side) surface. The result will be the smallest
     // hit distance.
-    
-    ccr::enumerate(hits.begin(), hits.end(), 
+
+    ccr::enumerate(hits.begin(), hits.end(),
                    [&m, &rings, sd](HitResult &hit, size_t i) {
-    
+
        // Point on the circle on the pin sphere
        Vec3d ps = rings.pinring(i);
        // This is the point on the circle on the back sphere
        Vec3d p = rings.backring(i);
-       
+
        // Point ps is not on mesh but can be inside or
        // outside as well. This would cause many problems
        // with ray-casting. To detect the position we will
        // use the ray-casting result (which has an is_inside
-       // predicate).       
-    
+       // predicate).
+
        Vec3d n = (p - ps).normalized();
        auto  q = m.query_ray_hit(ps + sd * n, n);
-    
+
        if (q.is_inside()) { // the hit is inside the model
            if (q.distance() > rings.rpin) {
                // If we are inside the model and the hit
@@ -307,40 +250,38 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect(
        } else
            hit = q;
     });
-    
+
     return min_hit(hits);
 }
 
-EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect(
-    const Vec3d &src, const Vec3d &dir, double r, bool ins_check)
+IndexedMesh::hit_result SupportTreeBuildsteps::bridge_mesh_intersect(
+    const Vec3d &src, const Vec3d &dir, double r, double sd)
 {
     static const size_t SAMPLES = 8;
     PointRing<SAMPLES> ring{dir};
-    
-    using Hit = EigenMesh3D::hit_result;
-    
+
+    using Hit = IndexedMesh::hit_result;
+
     // Hit results
     std::array<Hit, SAMPLES> hits;
-    
-    ccr::enumerate(hits.begin(), hits.end(), 
-                   [this, r, src, ins_check, &ring, dir] (Hit &hit, size_t i) {
-        
-        const double sd = m_cfg.safety_distance_mm;
-        
+
+    ccr::enumerate(hits.begin(), hits.end(),
+                [this, r, src, /*ins_check,*/ &ring, dir, sd] (Hit &hit, size_t i) {
+
         // Point on the circle on the pin sphere
         Vec3d p = ring.get(i, src, r + sd);
-        
-        auto hr = m_mesh.query_ray_hit(p + sd * dir, dir);
-        
-        if(ins_check && hr.is_inside()) {
+
+        auto hr = m_mesh.query_ray_hit(p + r * dir, dir);
+
+        if(/*ins_check && */hr.is_inside()) {
             if(hr.distance() > 2 * r + sd) hit = Hit(0.0);
             else {
                 // re-cast the ray from the outside of the object
-                hit = m_mesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir);
+                hit = m_mesh.query_ray_hit(p + (hr.distance() + EPSILON) * dir, dir);
             }
         } else hit = hr;
     });
-    
+
     return min_hit(hits);
 }
 
@@ -354,61 +295,61 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
     // shorter pillar is too short to start a new bridge but the taller
     // pillar could still be bridged with the shorter one.
     bool was_connected = false;
-    
+
     Vec3d supper = pillar.startpoint();
     Vec3d slower = nextpillar.startpoint();
     Vec3d eupper = pillar.endpoint();
     Vec3d elower = nextpillar.endpoint();
-    
+
     double zmin = m_builder.ground_level + m_cfg.base_height_mm;
     eupper(Z) = std::max(eupper(Z), zmin);
     elower(Z) = std::max(elower(Z), zmin);
-    
+
     // The usable length of both pillars should be positive
     if(slower(Z) - elower(Z) < 0) return false;
     if(supper(Z) - eupper(Z) < 0) return false;
-    
+
     double pillar_dist = distance(Vec2d{slower(X), slower(Y)},
                                   Vec2d{supper(X), supper(Y)});
     double bridge_distance = pillar_dist / std::cos(-m_cfg.bridge_slope);
     double zstep = pillar_dist * std::tan(-m_cfg.bridge_slope);
-    
+
     if(pillar_dist < 2 * m_cfg.head_back_radius_mm ||
         pillar_dist > m_cfg.max_pillar_link_distance_mm) return false;
-    
+
     if(supper(Z) < slower(Z)) supper.swap(slower);
     if(eupper(Z) < elower(Z)) eupper.swap(elower);
-    
+
     double startz = 0, endz = 0;
-    
+
     startz = slower(Z) - zstep < supper(Z) ? slower(Z) - zstep : slower(Z);
     endz = eupper(Z) + zstep > elower(Z) ? eupper(Z) + zstep : eupper(Z);
-    
+
     if(slower(Z) - eupper(Z) < std::abs(zstep)) {
         // no space for even one cross
-        
+
         // Get max available space
         startz = std::min(supper(Z), slower(Z) - zstep);
         endz = std::max(eupper(Z) + zstep, elower(Z));
-        
+
         // Align to center
         double available_dist = (startz - endz);
         double rounds = std::floor(available_dist / std::abs(zstep));
         startz -= 0.5 * (available_dist - rounds * std::abs(zstep));
     }
-    
+
     auto pcm = m_cfg.pillar_connection_mode;
     bool docrosses =
         pcm == PillarConnectionMode::cross ||
         (pcm == PillarConnectionMode::dynamic &&
          pillar_dist > 2*m_cfg.base_radius_mm);
-    
+
     // 'sj' means starting junction, 'ej' is the end junction of a bridge.
     // They will be swapped in every iteration thus the zig-zag pattern.
     // According to a config parameter, a second bridge may be added which
     // results in a cross connection between the pillars.
     Vec3d sj = supper, ej = slower; sj(Z) = startz; ej(Z) = sj(Z) + zstep;
-    
+
     // TODO: This is a workaround to not have a faulty last bridge
     while(ej(Z) >= eupper(Z) /*endz*/) {
         if(bridge_mesh_distance(sj, dirv(sj, ej), pillar.r) >= bridge_distance)
@@ -416,7 +357,7 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
             m_builder.add_crossbridge(sj, ej, pillar.r);
             was_connected = true;
         }
-        
+
         // double bridging: (crosses)
         if(docrosses) {
             Vec3d sjback(ej(X), ej(Y), sj(Z));
@@ -429,11 +370,11 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
                 was_connected = true;
             }
         }
-        
+
         sj.swap(ej);
         ej(Z) = sj(Z) + zstep;
     }
-    
+
     return was_connected;
 }
 
@@ -443,228 +384,242 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head,
     auto nearpillar = [this, nearpillar_id]() -> const Pillar& {
         return m_builder.pillar(nearpillar_id);
     };
-    
-    if (m_builder.bridgecount(nearpillar()) > m_cfg.max_bridges_on_pillar) 
+
+    if (m_builder.bridgecount(nearpillar()) > m_cfg.max_bridges_on_pillar)
         return false;
-    
+
     Vec3d headjp = head.junction_point();
     Vec3d nearjp_u = nearpillar().startpoint();
     Vec3d nearjp_l = nearpillar().endpoint();
-    
+
     double r = head.r_back_mm;
     double d2d = distance(to_2d(headjp), to_2d(nearjp_u));
     double d3d = distance(headjp, nearjp_u);
-    
+
     double hdiff = nearjp_u(Z) - headjp(Z);
     double slope = std::atan2(hdiff, d2d);
-    
+
     Vec3d bridgestart = headjp;
     Vec3d bridgeend = nearjp_u;
-    double max_len = m_cfg.max_bridge_length_mm;
+    double max_len = r * m_cfg.max_bridge_length_mm / m_cfg.head_back_radius_mm;
     double max_slope = m_cfg.bridge_slope;
     double zdiff = 0.0;
-    
+
     // check the default situation if feasible for a bridge
     if(d3d > max_len || slope > -max_slope) {
         // not feasible to connect the two head junctions. We have to search
         // for a suitable touch point.
-        
+
         double Zdown = headjp(Z) + d2d * std::tan(-max_slope);
         Vec3d touchjp = bridgeend; touchjp(Z) = Zdown;
         double D = distance(headjp, touchjp);
         zdiff = Zdown - nearjp_u(Z);
-        
+
         if(zdiff > 0) {
             Zdown -= zdiff;
             bridgestart(Z) -= zdiff;
             touchjp(Z) = Zdown;
-            
+
             double t = bridge_mesh_distance(headjp, DOWN, r);
-            
+
             // We can't insert a pillar under the source head to connect
             // with the nearby pillar's starting junction
             if(t < zdiff) return false;
         }
-        
+
         if(Zdown <= nearjp_u(Z) && Zdown >= nearjp_l(Z) && D < max_len)
             bridgeend(Z) = Zdown;
         else
             return false;
     }
-    
+
     // There will be a minimum distance from the ground where the
     // bridge is allowed to connect. This is an empiric value.
-    double minz = m_builder.ground_level + 2 * m_cfg.head_width_mm;
+    double minz = m_builder.ground_level + 4 * head.r_back_mm;
     if(bridgeend(Z) < minz) return false;
-    
+
     double t = bridge_mesh_distance(bridgestart, dirv(bridgestart, bridgeend), r);
-    
+
     // Cannot insert the bridge. (further search might not worth the hassle)
     if(t < distance(bridgestart, bridgeend)) return false;
-    
+
     std::lock_guard<ccr::BlockingMutex> lk(m_bridge_mutex);
-    
+
     if (m_builder.bridgecount(nearpillar()) < m_cfg.max_bridges_on_pillar) {
         // A partial pillar is needed under the starting head.
         if(zdiff > 0) {
-            m_builder.add_pillar(head.id, bridgestart, r);
+            m_builder.add_pillar(head.id, headjp.z() - bridgestart.z());
             m_builder.add_junction(bridgestart, r);
-            m_builder.add_bridge(bridgestart, bridgeend, head.r_back_mm);
+            m_builder.add_bridge(bridgestart, bridgeend, r);
         } else {
             m_builder.add_bridge(head.id, bridgeend);
         }
-        
+
         m_builder.increment_bridges(nearpillar());
     } else return false;
-    
+
     return true;
 }
 
-bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &head)
-{
-    PointIndex spindex = m_pillar_index.guarded_clone();
-    
-    long nearest_id = ID_UNSET;
-    
-    Vec3d querypoint = head.junction_point();
-    
-    while(nearest_id < 0 && !spindex.empty()) { m_thr();
-        // loop until a suitable head is not found
-        // if there is a pillar closer than the cluster center
-        // (this may happen as the clustering is not perfect)
-        // than we will bridge to this closer pillar
-        
-        Vec3d qp(querypoint(X), querypoint(Y), m_builder.ground_level);
-        auto qres = spindex.nearest(qp, 1);
-        if(qres.empty()) break;
-        
-        auto ne = qres.front();
-        nearest_id = ne.second;
-        
-        if(nearest_id >= 0) {
-            if(size_t(nearest_id) < m_builder.pillarcount()) {
-                if(!connect_to_nearpillar(head, nearest_id)) {
-                    nearest_id = ID_UNSET;    // continue searching
-                    spindex.remove(ne);       // without the current pillar
-                }
-            }
-        }
-    }
-    
-    return nearest_id >= 0;
-}
-
-void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
+bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &hjp,
                                                  const Vec3d &sourcedir,
                                                  double       radius,
                                                  long         head_id)
 {
-    const double SLOPE = 1. / std::cos(m_cfg.bridge_slope);
-    
-    double gndlvl       = m_builder.ground_level;
-    Vec3d  endp         = {jp(X), jp(Y), gndlvl};
-    double sd           = m_cfg.pillar_base_safety_distance_mm;
-    long   pillar_id    = ID_UNSET;
-    double min_dist     = sd + m_cfg.base_radius_mm + EPSILON;
-    double dist         = 0;
-    bool   can_add_base = true;
-    bool   normal_mode  = true;
-    
-    // If in zero elevation mode and the pillar is too close to the model body,
-    // the support pillar can not be placed in the gap between the model and
-    // the pad, and the pillar bases must not touch the model body either.
-    // To solve this, a corrector bridge is inserted between the starting point
-    // (jp) and the new pillar.
-    if (m_cfg.object_elevation_mm < EPSILON
-        && (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) {
-        // Get the distance from the mesh. This can be later optimized
-        // to get the distance in 2D plane because we are dealing with
-        // the ground level only.
+    Vec3d  jp           = hjp, endp = jp, dir = sourcedir;
+    long   pillar_id    = SupportTreeNode::ID_UNSET;
+    bool   can_add_base = false, non_head = false;
 
-        normal_mode = false;
+    double gndlvl = 0.; // The Z level where pedestals should be
+    double jp_gnd = 0.; // The lowest Z where a junction center can be
+    double gap_dist = 0.; // The gap distance between the model and the pad
 
-        // The min distance needed to move away from the model in XY plane.
-        double current_d = min_dist - dist;
-        double current_bride_d = SLOPE * current_d;
+    auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; };
 
+    auto eval_limits = [this, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd]
+        (bool base_en = true)
+    {
+        can_add_base  = base_en && radius >= m_cfg.head_back_radius_mm;
+        double base_r = can_add_base ? m_cfg.base_radius_mm : 0.;
+        gndlvl        = m_builder.ground_level;
+        if (!can_add_base) gndlvl -= m_mesh.ground_level_offset();
+        jp_gnd   = gndlvl + (can_add_base ? 0. : m_cfg.head_back_radius_mm);
+        gap_dist = m_cfg.pillar_base_safety_distance_mm + base_r + EPSILON;
+    };
+
+    eval_limits();
+
+    // We are dealing with a mini pillar that's potentially too long
+    if (radius < m_cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius)
+    {
+        std::optional<DiffBridge> diffbr =
+            search_widening_path(jp, dir, radius, m_cfg.head_back_radius_mm);
+
+        if (diffbr && diffbr->endp.z() > jp_gnd) {
+            auto &br = m_builder.add_diffbridge(*diffbr);
+            if (head_id >= 0) m_builder.head(head_id).bridge_id = br.id;
+            endp = diffbr->endp;
+            radius = diffbr->end_r;
+            m_builder.add_junction(endp, radius);
+            non_head = true;
+            dir = diffbr->get_dir();
+            eval_limits();
+        } else return false;
+    }
+
+    if (m_cfg.object_elevation_mm < EPSILON)
+    {
         // get a suitable direction for the corrector bridge. It is the
         // original sourcedir's azimuth but the polar angle is saturated to the
         // configured bridge slope.
-        auto [polar, azimuth] = dir_to_spheric(sourcedir);
+        auto [polar, azimuth] = dir_to_spheric(dir);
         polar = PI - m_cfg.bridge_slope;
-        auto dir = spheric_to_dir(polar, azimuth).normalized();
-        
-        StopCriteria scr;
-        scr.stop_score = min_dist;
-        SubplexOptimizer solver(scr);
-        
-        // Search for a distance along the corrector bridge to move the endpoint
-        // sufficiently away form the model body. The first few optimization
-        // cycles should succeed here.
-        auto result = solver.optimize_max(
-            [this, dir, jp, gndlvl](double mv) {
-                Vec3d endpt = jp + mv * dir;
-                endpt(Z)    = gndlvl;
-                return std::sqrt(m_mesh.squared_distance(endpt));
-            },
-            initvals(current_bride_d), 
-            bound(0.0, m_cfg.max_bridge_length_mm - current_bride_d));
-        
-        endp         = jp + std::get<0>(result.optimum) * dir;
-        Vec3d pgnd   = {endp(X), endp(Y), gndlvl};
-        can_add_base = result.score > min_dist;
-        
-        double gnd_offs = m_mesh.ground_level_offset();
-        auto abort_in_shame =
-            [gnd_offs, &normal_mode, &can_add_base, &endp, jp, gndlvl]()
-        {
-            normal_mode  = true;
-            can_add_base = false;   // Nothing left to do, hope for the best
-            endp         = {jp(X), jp(Y), gndlvl - gnd_offs };
-        };
-        
-        // We have to check if the bridge is feasible.
-        if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm())
-            abort_in_shame();
-        else {
-            // If the new endpoint is below ground, do not make a pillar
-            if (endp(Z) < gndlvl)
-                endp = endp - SLOPE * (gndlvl - endp(Z)) * dir; // back off
-            else {
-                
-                auto hit = bridge_mesh_intersect(endp, DOWN, radius);
-                if (!std::isinf(hit.distance())) abort_in_shame();
-                
-                pillar_id = m_builder.add_pillar(endp, pgnd, radius);
-                
-                if (can_add_base)
-                    m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm,
-                                              m_cfg.base_radius_mm);
+        Vec3d d = spheric_to_dir(polar, azimuth).normalized();
+        double t = bridge_mesh_distance(endp, dir, radius);
+        double tmax = std::min(m_cfg.max_bridge_length_mm, t);
+        t = 0.;
+
+        double zd = endp.z() - jp_gnd;
+        double tmax2 = zd / std::sqrt(1 - m_cfg.bridge_slope * m_cfg.bridge_slope);
+        tmax = std::min(tmax, tmax2);
+
+        Vec3d nexp = endp;
+        double dlast = 0.;
+        while (((dlast = std::sqrt(m_mesh.squared_distance(to_floor(nexp)))) < gap_dist ||
+                !std::isinf(bridge_mesh_distance(nexp, DOWN, radius))) && t < tmax) {
+            t += radius;
+            nexp = endp + t * d;
+        }
+
+        if (dlast < gap_dist && can_add_base) {
+            nexp         = endp;
+            t            = 0.;
+            can_add_base = false;
+            eval_limits(can_add_base);
+
+            zd = endp.z() - jp_gnd;
+            tmax2 = zd / std::sqrt(1 - m_cfg.bridge_slope * m_cfg.bridge_slope);
+            tmax = std::min(tmax, tmax2);
+
+            while (((dlast = std::sqrt(m_mesh.squared_distance(to_floor(nexp)))) < gap_dist ||
+                    !std::isinf(bridge_mesh_distance(nexp, DOWN, radius))) && t < tmax) {
+                t += radius;
+                nexp = endp + t * d;
             }
-            
-            m_builder.add_bridge(jp, endp, radius);
-            m_builder.add_junction(endp, radius);
-            
-            // Add a degenerated pillar and the bridge.
-            // The degenerate pillar will have zero length and it will
-            // prevent from queries of head_pillar() to have non-existing
-            // pillar when the head should have one.
-            if (head_id >= 0)
-                m_builder.add_pillar(head_id, jp, radius);
+        }
+
+        // Could not find a path to avoid the pad gap
+        if (dlast < gap_dist) return false;
+
+        if (t > 0.) { // Need to make additional bridge
+            const Bridge& br = m_builder.add_bridge(endp, nexp, radius);
+            if (head_id >= 0) m_builder.head(head_id).bridge_id = br.id;
+
+            m_builder.add_junction(nexp, radius);
+            endp = nexp;
+            non_head = true;
         }
     }
-    
-    if (normal_mode) {
-        pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, endp, radius) :
-                                   m_builder.add_pillar(jp, endp, radius);
 
-        if (can_add_base)
-            m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm,
-                                      m_cfg.base_radius_mm);
-    }
-    
+    Vec3d gp = to_floor(endp);
+    double h = endp.z() - gp.z();
+
+    pillar_id = head_id >= 0 && !non_head ? m_builder.add_pillar(head_id, h) :
+                                            m_builder.add_pillar(gp, h, radius);
+
+    if (can_add_base)
+        add_pillar_base(pillar_id);
+
     if(pillar_id >= 0) // Save the pillar endpoint in the spatial index
-        m_pillar_index.guarded_insert(endp, unsigned(pillar_id));
+        m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt,
+                                      unsigned(pillar_id));
+
+    return true;
+}
+
+std::optional<DiffBridge> SupportTreeBuildsteps::search_widening_path(
+    const Vec3d &jp, const Vec3d &dir, double radius, double new_radius)
+{
+    double w = radius + 2 * m_cfg.head_back_radius_mm;
+    double stopval = w + jp.z() - m_builder.ground_level;
+    Optimizer<AlgNLoptSubplex> solver(get_criteria(m_cfg).stop_score(stopval));
+
+    auto [polar, azimuth] = dir_to_spheric(dir);
+
+    double fallback_ratio = radius / m_cfg.head_back_radius_mm;
+
+    auto oresult = solver.to_max().optimize(
+        [this, jp, radius, new_radius](const opt::Input<3> &input) {
+            auto &[plr, azm, t] = input;
+
+            auto   d   = spheric_to_dir(plr, azm).normalized();
+            double ret = pinhead_mesh_intersect(jp, d, radius, new_radius, t)
+                             .distance();
+            double down = bridge_mesh_distance(jp + t * d, d, new_radius);
+
+            if (ret > t && std::isinf(down))
+                ret += jp.z() - m_builder.ground_level;
+
+            return ret;
+        },
+        initvals({polar, azimuth, w}), // start with what we have
+        bounds({
+            {PI - m_cfg.bridge_slope, PI}, // Must not exceed the slope limit
+            {-PI, PI}, // azimuth can be a full search
+            {radius + m_cfg.head_back_radius_mm,
+                  fallback_ratio * m_cfg.max_bridge_length_mm}
+        }));
+
+    if (oresult.score >= stopval) {
+        polar       = std::get<0>(oresult.optimum);
+        azimuth     = std::get<1>(oresult.optimum);
+        double t    = std::get<2>(oresult.optimum);
+        Vec3d  endp = jp + t * spheric_to_dir(polar, azimuth);
+
+        return DiffBridge(jp, endp, radius, m_cfg.head_back_radius_mm);
+    }
+
+    return {};
 }
 
 void SupportTreeBuildsteps::filter()
@@ -672,7 +627,7 @@ void SupportTreeBuildsteps::filter()
     // Get the points that are too close to each other and keep only the
     // first one
     auto aliases = cluster(m_points, D_SP, 2);
-    
+
     PtIndices filtered_indices;
     filtered_indices.reserve(aliases.size());
     m_iheads.reserve(aliases.size());
@@ -681,136 +636,130 @@ void SupportTreeBuildsteps::filter()
         // Here we keep only the front point of the cluster.
         filtered_indices.emplace_back(a.front());
     }
-    
+
     // calculate the normals to the triangles for filtered points
     auto nmls = sla::normals(m_points, m_mesh, m_cfg.head_front_radius_mm,
                              m_thr, filtered_indices);
-    
+
     // Not all of the support points have to be a valid position for
     // support creation. The angle may be inappropriate or there may
     // not be enough space for the pinhead. Filtering is applied for
     // these reasons.
-    
-    ccr::SpinningMutex mutex;
-    auto addfn = [&mutex](PtIndices &container, unsigned val) {
-        std::lock_guard<ccr::SpinningMutex> lk(mutex);
-        container.emplace_back(val);
-    };
-    
-    auto filterfn = [this, &nmls, addfn](unsigned fidx, size_t i) {
+
+    std::vector<Head> heads; heads.reserve(m_support_pts.size());
+    for (const SupportPoint &sp : m_support_pts) {
         m_thr();
-        
+        heads.emplace_back(
+            std::nan(""),
+            sp.head_front_radius,
+            0.,
+            m_cfg.head_penetration_mm,
+            Vec3d::Zero(),         // dir
+            sp.pos.cast<double>() // displacement
+            );
+    }
+
+    std::function<void(unsigned, size_t, double)> filterfn;
+    filterfn = [this, &nmls, &heads, &filterfn](unsigned fidx, size_t i, double back_r) {
+        m_thr();
+
         auto n = nmls.row(Eigen::Index(i));
-        
+
         // for all normals we generate the spherical coordinates and
         // saturate the polar angle to 45 degrees from the bottom then
         // convert back to standard coordinates to get the new normal.
         // Then we just create a quaternion from the two normals
         // (Quaternion::FromTwoVectors) and apply the rotation to the
         // arrow head.
-        
+
         auto [polar, azimuth] = dir_to_spheric(n);
-        
+
         // skip if the tilt is not sane
-        if(polar >= PI - m_cfg.normal_cutoff_angle) {
-            
-            // We saturate the polar angle to 3pi/4
-            polar = std::max(polar, 3*PI / 4);
-            
-            // save the head (pinpoint) position
-            Vec3d hp = m_points.row(fidx);
-            
-            double w = m_cfg.head_width_mm +
-                       m_cfg.head_back_radius_mm +
-                       2*m_cfg.head_front_radius_mm;
-            
-            double pin_r = double(m_support_pts[fidx].head_front_radius);
-            
-            // Reassemble the now corrected normal
-            auto nn = spheric_to_dir(polar, azimuth).normalized();
-            
-            // check available distance
-            EigenMesh3D::hit_result t
-                = pinhead_mesh_intersect(hp, // touching point
-                                         nn, // normal
-                                         pin_r,
-                                         m_cfg.head_back_radius_mm,
-                                         w);
-            
-            if(t.distance() <= w) {
-                
-                // Let's try to optimize this angle, there might be a
-                // viable normal that doesn't collide with the model
-                // geometry and its very close to the default.
-                
-                StopCriteria stc;
-                stc.max_iterations = m_cfg.optimizer_max_iterations;
-                stc.relative_score_difference = m_cfg.optimizer_rel_score_diff;
-                stc.stop_score = w; // space greater than w is enough
-                GeneticOptimizer solver(stc);
-                solver.seed(0); // we want deterministic behavior
-                
-                auto oresult = solver.optimize_max(
-                    [this, pin_r, w, hp](double plr, double azm)
-                    {
-                        auto dir = spheric_to_dir(plr, azm).normalized();
-                        
-                        double score = pinhead_mesh_distance(
-                            hp, dir, pin_r, m_cfg.head_back_radius_mm, w);
-                        
-                        return score;
-                    },
-                    initvals(polar, azimuth), // start with what we have
-                    bound(3 * PI / 4, PI),    // Must not exceed the tilt limit
-                    bound(-PI, PI) // azimuth can be a full search
-                    );
-                
-                if(oresult.score > w) {
-                    polar = std::get<0>(oresult.optimum);
-                    azimuth = std::get<1>(oresult.optimum);
-                    nn = spheric_to_dir(polar, azimuth).normalized();
-                    t = EigenMesh3D::hit_result(oresult.score);
-                }
-            }
-            
-            // save the verified and corrected normal
-            m_support_nmls.row(fidx) = nn;
-            
-            if (t.distance() > w) {
-                // Check distance from ground, we might have zero elevation.
-                if (hp(Z) + w * nn(Z) < m_builder.ground_level) {
-                    addfn(m_iheadless, fidx);
-                } else {
-                    // mark the point for needing a head.
-                    addfn(m_iheads, fidx);
-                }
-            } else if (polar >= 3 * PI / 4) {
-                // Headless supports do not tilt like the headed ones
-                // so the normal should point almost to the ground.
-                addfn(m_iheadless, fidx);
+        if (polar < PI - m_cfg.normal_cutoff_angle) return;
+
+        // We saturate the polar angle to 3pi/4
+        polar = std::max(polar, PI - m_cfg.bridge_slope);
+
+        // save the head (pinpoint) position
+        Vec3d hp = m_points.row(fidx);
+
+        double lmin = m_cfg.head_width_mm, lmax = lmin;
+
+        if (back_r < m_cfg.head_back_radius_mm) {
+            lmin = 0., lmax = m_cfg.head_penetration_mm;
+        }
+
+        // The distance needed for a pinhead to not collide with model.
+        double w = lmin + 2 * back_r + 2 * m_cfg.head_front_radius_mm -
+                   m_cfg.head_penetration_mm;
+
+        double pin_r = double(m_support_pts[fidx].head_front_radius);
+
+        // Reassemble the now corrected normal
+        auto nn = spheric_to_dir(polar, azimuth).normalized();
+
+        // check available distance
+        IndexedMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r,
+                                                           back_r, w);
+
+        if (t.distance() < w) {
+            // Let's try to optimize this angle, there might be a
+            // viable normal that doesn't collide with the model
+            // geometry and its very close to the default.
+
+            Optimizer<AlgNLoptGenetic> solver(get_criteria(m_cfg));
+            solver.seed(0); // we want deterministic behavior
+
+            auto oresult = solver.to_max().optimize(
+                [this, pin_r, back_r, hp](const opt::Input<3> &input)
+                {
+                    auto &[plr, azm, l] = input;
+
+                    auto dir = spheric_to_dir(plr, azm).normalized();
+
+                    return pinhead_mesh_intersect(
+                        hp, dir, pin_r, back_r, l).distance();
+                },
+                initvals({polar, azimuth, (lmin + lmax) / 2.}), // start with what we have
+                bounds({
+                    {PI - m_cfg.bridge_slope, PI},    // Must not exceed the slope limit
+                    {-PI, PI}, // azimuth can be a full search
+                    {lmin, lmax}
+                }));
+
+            if(oresult.score > w) {
+                polar = std::get<0>(oresult.optimum);
+                azimuth = std::get<1>(oresult.optimum);
+                nn = spheric_to_dir(polar, azimuth).normalized();
+                lmin = std::get<2>(oresult.optimum);
+                t = IndexedMesh::hit_result(oresult.score);
             }
         }
+
+        if (t.distance() > w && hp(Z) + w * nn(Z) >= m_builder.ground_level) {
+            Head &h = heads[fidx];
+            h.id = fidx; h.dir = nn; h.width_mm = lmin; h.r_back_mm = back_r;
+        } else if (back_r > m_cfg.head_fallback_radius_mm) {
+            filterfn(fidx, i, m_cfg.head_fallback_radius_mm);
+        }
     };
-    
-    ccr::enumerate(filtered_indices.begin(), filtered_indices.end(), filterfn);
-    
+
+    ccr::enumerate(filtered_indices.begin(), filtered_indices.end(),
+                   [this, &filterfn](unsigned fidx, size_t i) {
+                       filterfn(fidx, i, m_cfg.head_back_radius_mm);
+                   });
+
+    for (size_t i = 0; i < heads.size(); ++i)
+        if (heads[i].is_valid()) {
+            m_builder.add_head(i, heads[i]);
+            m_iheads.emplace_back(i);
+        }
+
     m_thr();
 }
 
 void SupportTreeBuildsteps::add_pinheads()
 {
-    for (unsigned i : m_iheads) {
-        m_thr();
-        m_builder.add_head(
-            i,
-            m_cfg.head_back_radius_mm,
-            m_support_pts[i].head_front_radius,
-            m_cfg.head_width_mm,
-            m_cfg.head_penetration_mm,
-            m_support_nmls.row(i),         // dir
-            m_support_pts[i].pos.cast<double>() // displacement
-            );
-    }
 }
 
 void SupportTreeBuildsteps::classify()
@@ -819,37 +768,37 @@ void SupportTreeBuildsteps::classify()
     PtIndices ground_head_indices;
     ground_head_indices.reserve(m_iheads.size());
     m_iheads_onmodel.reserve(m_iheads.size());
-    
+
     // First we decide which heads reach the ground and can be full
     // pillars and which shall be connected to the model surface (or
     // search a suitable path around the surface that leads to the
     // ground -- TODO)
     for(unsigned i : m_iheads) {
         m_thr();
-        
-        auto& head = m_builder.head(i);
+
+        Head &head = m_builder.head(i);
         double r = head.r_back_mm;
         Vec3d headjp = head.junction_point();
-        
+
         // collision check
         auto hit = bridge_mesh_intersect(headjp, DOWN, r);
-        
+
         if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i);
         else if(m_cfg.ground_facing_only)  head.invalidate();
         else m_iheads_onmodel.emplace_back(i);
-        
+
         m_head_to_ground_scans[i] = hit;
     }
-    
+
     // We want to search for clusters of points that are far enough
     // from each other in the XY plane to not cross their pillar bases
     // These clusters of support points will join in one pillar,
     // possibly in their centroid support point.
-    
+
     auto pointfn = [this](unsigned i) {
         return m_builder.head(i).junction_point();
     };
-    
+
     auto predicate = [this](const PointIndexEl &e1,
                             const PointIndexEl &e2) {
         double d2d = distance(to_2d(e1.first), to_2d(e2.first));
@@ -864,14 +813,12 @@ void SupportTreeBuildsteps::classify()
 
 void SupportTreeBuildsteps::routing_to_ground()
 {
-    const double pradius = m_cfg.head_back_radius_mm;
-    
     ClusterEl cl_centroids;
     cl_centroids.reserve(m_pillar_clusters.size());
-    
+
     for (auto &cl : m_pillar_clusters) {
         m_thr();
-        
+
         // place all the centroid head positions into the index. We
         // will query for alternative pillar positions. If a sidehead
         // cannot connect to the cluster centroid, we have to search
@@ -879,9 +826,9 @@ void SupportTreeBuildsteps::routing_to_ground()
         // elements in the cluster, the centroid is arbitrary and the
         // sidehead is allowed to connect to a nearby pillar to
         // increase structural stability.
-        
+
         if (cl.empty()) continue;
-        
+
         // get the current cluster centroid
         auto &      thr    = m_thr;
         const auto &points = m_points;
@@ -895,43 +842,44 @@ void SupportTreeBuildsteps::routing_to_ground()
 
         assert(lcid >= 0);
         unsigned hid = cl[size_t(lcid)]; // Head ID
-        
+
         cl_centroids.emplace_back(hid);
-        
+
         Head &h = m_builder.head(hid);
-        h.transform();
-        
-        create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id);
+
+        if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) {
+            BOOST_LOG_TRIVIAL(warning)
+                << "Pillar cannot be created for support point id: " << hid;
+            m_iheads_onmodel.emplace_back(h.id);
+            continue;
+        }
     }
-    
+
     // now we will go through the clusters ones again and connect the
     // sidepoints with the cluster centroid (which is a ground pillar)
     // or a nearby pillar if the centroid is unreachable.
     size_t ci = 0;
     for (auto cl : m_pillar_clusters) {
         m_thr();
-        
+
         auto cidx = cl_centroids[ci++];
-        
-        // TODO: don't consider the cluster centroid but calculate a
-        // central position where the pillar can be placed. this way
-        // the weight is distributed more effectively on the pillar.
-        
-        auto centerpillarID = m_builder.head_pillar(cidx).id;
-        
-        for (auto c : cl) {
-            m_thr();
-            if (c == cidx) continue;
-            
-            auto &sidehead = m_builder.head(c);
-            sidehead.transform();
-            
-            if (!connect_to_nearpillar(sidehead, centerpillarID) &&
-                !search_pillar_and_connect(sidehead)) {
-                Vec3d pstart = sidehead.junction_point();
-                // Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl};
-                // Could not find a pillar, create one
-                create_ground_pillar(pstart, sidehead.dir, pradius, sidehead.id);
+
+        auto q = m_pillar_index.query(m_builder.head(cidx).junction_point(), 1);
+        if (!q.empty()) {
+            long centerpillarID = q.front().second;
+            for (auto c : cl) {
+                m_thr();
+                if (c == cidx) continue;
+
+                auto &sidehead = m_builder.head(c);
+
+                if (!connect_to_nearpillar(sidehead, centerpillarID) &&
+                    !search_pillar_and_connect(sidehead)) {
+                    Vec3d pstart = sidehead.junction_point();
+                    // Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl};
+                    // Could not find a pillar, create one
+                    create_ground_pillar(pstart, sidehead.dir, sidehead.r_back_mm, sidehead.id);
+                }
             }
         }
     }
@@ -943,62 +891,66 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir)
     double r = head.r_back_mm;
     double t = bridge_mesh_distance(hjp, dir, head.r_back_mm);
     double d = 0, tdown = 0;
-    t = std::min(t, m_cfg.max_bridge_length_mm);
+    t = std::min(t, m_cfg.max_bridge_length_mm * r / m_cfg.head_back_radius_mm);
 
     while (d < t && !std::isinf(tdown = bridge_mesh_distance(hjp + d * dir, DOWN, r)))
         d += r;
-    
+
     if(!std::isinf(tdown)) return false;
-    
+
     Vec3d endp = hjp + d * dir;
-    m_builder.add_bridge(head.id, endp);
-    m_builder.add_junction(endp, head.r_back_mm);
-    
-    this->create_ground_pillar(endp, dir, head.r_back_mm);
-    
-    return true;
+    bool ret = false;
+
+    if ((ret = create_ground_pillar(endp, dir, head.r_back_mm))) {
+        m_builder.add_bridge(head.id, endp);
+        m_builder.add_junction(endp, head.r_back_mm);
+    }
+
+    return ret;
 }
 
 bool SupportTreeBuildsteps::connect_to_ground(Head &head)
 {
     if (connect_to_ground(head, head.dir)) return true;
-    
+
     // Optimize bridge direction:
     // Straight path failed so we will try to search for a suitable
     // direction out of the cavity.
     auto [polar, azimuth] = dir_to_spheric(head.dir);
-    
-    StopCriteria stc;
-    stc.max_iterations = m_cfg.optimizer_max_iterations;
-    stc.relative_score_difference = m_cfg.optimizer_rel_score_diff;
-    stc.stop_score = 1e6;
-    GeneticOptimizer solver(stc);
+
+    Optimizer<AlgNLoptGenetic> solver(get_criteria(m_cfg).stop_score(1e6));
     solver.seed(0); // we want deterministic behavior
-    
+
     double r_back = head.r_back_mm;
-    Vec3d hjp = head.junction_point();    
-    auto oresult = solver.optimize_max(
-        [this, hjp, r_back](double plr, double azm) {
+    Vec3d hjp = head.junction_point();
+    auto oresult = solver.to_max().optimize(
+        [this, hjp, r_back](const opt::Input<2> &input) {
+            auto &[plr, azm] = input;
             Vec3d n = spheric_to_dir(plr, azm).normalized();
             return bridge_mesh_distance(hjp, n, r_back);
         },
-        initvals(polar, azimuth),  // let's start with what we have
-        bound(3*PI/4, PI),  // Must not exceed the slope limit
-        bound(-PI, PI)      // azimuth can be a full range search
-        );
-    
+        initvals({polar, azimuth}),  // let's start with what we have
+        bounds({ {PI - m_cfg.bridge_slope, PI}, {-PI, PI} })
+    );
+
     Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
     return connect_to_ground(head, bridgedir);
 }
 
 bool SupportTreeBuildsteps::connect_to_model_body(Head &head)
 {
-    if (head.id <= ID_UNSET) return false;
-    
+    if (head.id <= SupportTreeNode::ID_UNSET) return false;
+
     auto it = m_head_to_ground_scans.find(unsigned(head.id));
     if (it == m_head_to_ground_scans.end()) return false;
-    
+
     auto &hit = it->second;
+
+    if (!hit.is_hit()) {
+        // TODO scan for potential anchor points on model surface
+        return false;
+    }
+
     Vec3d hjp = head.junction_point();
     double zangle = std::asin(hit.direction()(Z));
     zangle = std::max(zangle, PI/4);
@@ -1006,9 +958,11 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head)
 
     // The width of the tail head that we would like to have...
     h = std::min(hit.distance() - head.r_back_mm, h);
-    
-    if(h <= 0.) return false;
-    
+
+    // If this is a mini pillar dont bother with the tail width, can be 0.
+    if (head.r_back_mm < m_cfg.head_back_radius_mm) h = std::max(h, 0.);
+    else if (h <= 0.) return false;
+
     Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h};
     auto center_hit = m_mesh.query_ray_hit(hjp, DOWN);
 
@@ -1016,13 +970,11 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head)
     Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm?
                      center_hit.position() : hit.position();
 
-    head.transform();
-
-    long pillar_id = m_builder.add_pillar(head.id, endp, head.r_back_mm);
+    long pillar_id = m_builder.add_pillar(head.id, hjp.z() - endp.z());
     Pillar &pill = m_builder.pillar(pillar_id);
 
     Vec3d taildir = endp - hitp;
-    double dist = distance(endp, hitp) + m_cfg.head_penetration_mm;
+    double dist = (hitp - endp).norm() + m_cfg.head_penetration_mm;
     double w = dist - 2 * head.r_pin_mm - head.r_back_mm;
 
     if (w < 0.) {
@@ -1030,19 +982,53 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head)
         w = 0.;
     }
 
-    Head tailhead(head.r_back_mm, head.r_pin_mm, w,
-                  m_cfg.head_penetration_mm, taildir, hitp);
+    m_builder.add_anchor(head.r_back_mm, head.r_pin_mm, w,
+                         m_cfg.head_penetration_mm, taildir, hitp);
 
-    tailhead.transform();
-    pill.base = tailhead.mesh;
-    
     m_pillar_index.guarded_insert(pill.endpoint(), pill.id);
-    
+
     return true;
 }
 
+bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &source)
+{
+    // Hope that a local copy takes less time than the whole search loop.
+    // We also need to remove elements progressively from the copied index.
+    PointIndex spindex = m_pillar_index.guarded_clone();
+
+    long nearest_id = SupportTreeNode::ID_UNSET;
+
+    Vec3d querypt = source.junction_point();
+
+    while(nearest_id < 0 && !spindex.empty()) { m_thr();
+        // loop until a suitable head is not found
+        // if there is a pillar closer than the cluster center
+        // (this may happen as the clustering is not perfect)
+        // than we will bridge to this closer pillar
+
+        Vec3d qp(querypt(X), querypt(Y), m_builder.ground_level);
+        auto qres = spindex.nearest(qp, 1);
+        if(qres.empty()) break;
+
+        auto ne = qres.front();
+        nearest_id = ne.second;
+
+        if(nearest_id >= 0) {
+            if (size_t(nearest_id) < m_builder.pillarcount()) {
+                if(!connect_to_nearpillar(source, nearest_id) ||
+                    m_builder.pillar(nearest_id).r < source.r_back_mm) {
+                    nearest_id = SupportTreeNode::ID_UNSET;    // continue searching
+                    spindex.remove(ne);       // without the current pillar
+                }
+            }
+        }
+    }
+
+    return nearest_id >= 0;
+}
+
 void SupportTreeBuildsteps::routing_to_model()
-{   
+{
     // We need to check if there is an easy way out to the bed surface.
     // If it can be routed there with a bridge shorter than
     // min_bridge_distance.
@@ -1050,23 +1036,23 @@ void SupportTreeBuildsteps::routing_to_model()
     ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(),
                    [this] (const unsigned idx, size_t) {
         m_thr();
-        
+
         auto& head = m_builder.head(idx);
-        
+
         // Search nearby pillar
-        if(search_pillar_and_connect(head)) { head.transform(); return; }
-        
+        if (search_pillar_and_connect(head)) { return; }
+
         // Cannot connect to nearby pillar. We will try to search for
         // a route to the ground.
-        if(connect_to_ground(head)) { head.transform(); return; }
-        
+        if (connect_to_ground(head)) { return; }
+
         // No route to the ground, so connect to the model body as a last resort
         if (connect_to_model_body(head)) { return; }
-        
+
         // We have failed to route this head.
         BOOST_LOG_TRIVIAL(warning)
-            << "Failed to route model facing support point. ID: " << idx;
-        
+                << "Failed to route model facing support point. ID: " << idx;
+
         head.invalidate();
     });
 }
@@ -1076,19 +1062,19 @@ void SupportTreeBuildsteps::interconnect_pillars()
     // Now comes the algorithm that connects pillars with each other.
     // Ideally every pillar should be connected with at least one of its
     // neighbors if that neighbor is within max_pillar_link_distance
-    
+
     // Pillars with height exceeding H1 will require at least one neighbor
     // to connect with. Height exceeding H2 require two neighbors.
     double H1 = m_cfg.max_solo_pillar_height_mm;
     double H2 = m_cfg.max_dual_pillar_height_mm;
     double d = m_cfg.max_pillar_link_distance_mm;
-    
+
     //A connection between two pillars only counts if the height ratio is
     // bigger than 50%
     double min_height_ratio = 0.5;
-    
+
     std::set<unsigned long> pairs;
-    
+
     // A function to connect one pillar with its neighbors. THe number of
     // neighbors is given in the configuration. This function if called
     // for every pillar in the pillar index. A pair of pillar will not
@@ -1098,66 +1084,68 @@ void SupportTreeBuildsteps::interconnect_pillars()
         [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el)
     {
         Vec3d qp = el.first;    // endpoint of the pillar
-        
+
         const Pillar& pillar = m_builder.pillar(el.second); // actual pillar
-        
+
         // Get the max number of neighbors a pillar should connect to
         unsigned neighbors = m_cfg.pillar_cascade_neighbors;
-        
+
         // connections are already enough for the pillar
         if(pillar.links >= neighbors) return;
-        
+
+        double max_d = d * pillar.r / m_cfg.head_back_radius_mm;
         // Query all remaining points within reach
-        auto qres = m_pillar_index.query([qp, d](const PointIndexEl& e){
-            return distance(e.first, qp) < d;
+        auto qres = m_pillar_index.query([qp, max_d](const PointIndexEl& e){
+            return distance(e.first, qp) < max_d;
         });
-        
+
         // sort the result by distance (have to check if this is needed)
         std::sort(qres.begin(), qres.end(),
                   [qp](const PointIndexEl& e1, const PointIndexEl& e2){
                       return distance(e1.first, qp) < distance(e2.first, qp);
                   });
-        
+
         for(auto& re : qres) { // process the queried neighbors
-            
+
             if(re.second == el.second) continue; // Skip self
-            
+
             auto a = el.second, b = re.second;
-            
+
             // Get unique hash for the given pair (order doesn't matter)
             auto hashval = pairhash(a, b);
-            
+
             // Search for the pair amongst the remembered pairs
             if(pairs.find(hashval) != pairs.end()) continue;
-            
+
             const Pillar& neighborpillar = m_builder.pillar(re.second);
-            
+
             // this neighbor is occupied, skip
-            if(neighborpillar.links >= neighbors) continue;
-            
+            if (neighborpillar.links >= neighbors) continue;
+            if (neighborpillar.r < pillar.r) continue;
+
             if(interconnect(pillar, neighborpillar)) {
                 pairs.insert(hashval);
-                
+
                 // If the interconnection length between the two pillars is
                 // less than 50% of the longer pillar's height, don't count
                 if(pillar.height < H1 ||
                     neighborpillar.height / pillar.height > min_height_ratio)
                     m_builder.increment_links(pillar);
-                
+
                 if(neighborpillar.height < H1 ||
                     pillar.height / neighborpillar.height > min_height_ratio)
                     m_builder.increment_links(neighborpillar);
-                
+
             }
-            
+
             // connections are enough for one pillar
             if(pillar.links >= neighbors) break;
         }
     };
-    
+
     // Run the cascade for the pillars in the index
     m_pillar_index.foreach(cascadefn);
-    
+
     // We would be done here if we could allow some pillars to not be
     // connected with any neighbors. But this might leave the support tree
     // unprintable.
@@ -1165,16 +1153,16 @@ void SupportTreeBuildsteps::interconnect_pillars()
     // The current solution is to insert additional pillars next to these
     // lonely pillars. One or even two additional pillar might get inserted
     // depending on the length of the lonely pillar.
-    
+
     size_t pillarcount = m_builder.pillarcount();
-    
+
     // Again, go through all pillars, this time in the whole support tree
     // not just the index.
     for(size_t pid = 0; pid < pillarcount; pid++) {
         auto pillar = [this, pid]() { return m_builder.pillar(pid); };
-        
+
         // Decide how many additional pillars will be needed:
-        
+
         unsigned needpillars = 0;
         if (pillar().bridges > m_cfg.max_bridges_on_pillar)
             needpillars = 3;
@@ -1185,28 +1173,28 @@ void SupportTreeBuildsteps::interconnect_pillars()
             // No neighbors could be found and the pillar is too long.
             needpillars = 1;
         }
-        
+
         needpillars = std::max(pillar().links, needpillars) - pillar().links;
         if (needpillars == 0) continue;
-        
+
         // Search for new pillar locations:
-        
+
         bool   found    = false;
         double alpha    = 0; // goes to 2Pi
         double r        = 2 * m_cfg.base_radius_mm;
         Vec3d  pillarsp = pillar().startpoint();
-        
+
         // temp value for starting point detection
         Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r);
-        
+
         // A vector of bool for placement feasbility
         std::vector<bool>  canplace(needpillars, false);
         std::vector<Vec3d> spts(needpillars); // vector of starting points
-        
+
         double gnd      = m_builder.ground_level;
         double min_dist = m_cfg.pillar_base_safety_distance_mm +
                           m_cfg.base_radius_mm + EPSILON;
-        
+
         while(!found && alpha < 2*PI) {
             for (unsigned n = 0;
                  n < needpillars && (!n || canplace[n - 1]);
@@ -1217,36 +1205,38 @@ void SupportTreeBuildsteps::interconnect_pillars()
                 s(X) += std::cos(a) * r;
                 s(Y) += std::sin(a) * r;
                 spts[n] = s;
-                
+
                 // Check the path vertically down
                 Vec3d check_from = s + Vec3d{0., 0., pillar().r};
                 auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r);
                 Vec3d gndsp{s(X), s(Y), gnd};
-                
+
                 // If the path is clear, check for pillar base collisions
                 canplace[n] = std::isinf(hr.distance()) &&
                               std::sqrt(m_mesh.squared_distance(gndsp)) >
                                   min_dist;
             }
-            
+
             found = std::all_of(canplace.begin(), canplace.end(),
                                 [](bool v) { return v; });
-            
+
             // 20 angles will be tried...
             alpha += 0.1 * PI;
         }
-        
+
         std::vector<long> newpills;
         newpills.reserve(needpillars);
 
         if (found)
             for (unsigned n = 0; n < needpillars; n++) {
-                Vec3d  s = spts[n];
-                Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r);
-                p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm);
+                Vec3d s = spts[n];
+                Pillar p(Vec3d{s.x(), s.y(), gnd}, s.z() - gnd, pillar().r);
 
                 if (interconnect(pillar(), p)) {
                     Pillar &pp = m_builder.pillar(m_builder.add_pillar(p));
+
+                    add_pillar_base(pp.id);
+
                     m_pillar_index.insert(pp.endpoint(), unsigned(pp.id));
 
                     m_builder.add_junction(s, pillar().r);
@@ -1255,9 +1245,8 @@ void SupportTreeBuildsteps::interconnect_pillars()
                     if (distance(pillarsp, s) < t)
                         m_builder.add_bridge(pillarsp, s, pillar().r);
 
-                    if (pillar().endpoint()(Z) > m_builder.ground_level)
-                        m_builder.add_junction(pillar().endpoint(),
-                                               pillar().r);
+                    if (pillar().endpoint()(Z) > m_builder.ground_level + pillar().r)
+                        m_builder.add_junction(pillar().endpoint(), pillar().r);
 
                     newpills.emplace_back(pp.id);
                     m_builder.increment_links(pillar());
@@ -1275,51 +1264,10 @@ void SupportTreeBuildsteps::interconnect_pillars()
                     m_builder.increment_links(nxpll);
                 }
             }
-            
+
             m_pillar_index.foreach(cascadefn);
         }
     }
 }
 
-void SupportTreeBuildsteps::routing_headless()
-{
-    // For now we will just generate smaller headless sticks with a sharp
-    // ending point that connects to the mesh surface.
-    
-    // We will sink the pins into the model surface for a distance of 1/3 of
-    // the pin radius
-    for(unsigned i : m_iheadless) {
-        m_thr();
-        
-        const auto R = double(m_support_pts[i].head_front_radius);
-        const double HWIDTH_MM = std::min(R, m_cfg.head_penetration_mm);
-        
-        // Exact support position
-        Vec3d sph = m_support_pts[i].pos.cast<double>();
-        Vec3d n = m_support_nmls.row(i);   // mesh outward normal
-        Vec3d sp = sph - n * HWIDTH_MM;     // stick head start point
-        
-        Vec3d sj = sp + R * n;              // stick start point
-        
-        // This is only for checking
-        double idist = bridge_mesh_distance(sph, DOWN, R, true);
-        double realdist = ray_mesh_intersect(sj, DOWN).distance();
-        double dist = realdist;
-        
-        if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level;
-        
-        if(std::isnan(idist) || idist < 2*R || std::isnan(dist) || dist < 2*R) {
-            BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless"
-                                       << " support stick at: "
-                                       << sj.transpose();
-            continue;
-        }
-        
-        bool use_endball = !std::isinf(realdist);
-        Vec3d ej = sj + (dist + HWIDTH_MM) * DOWN ;
-        m_builder.add_compact_bridge(sp, ej, n, R, use_endball);
-    }
-}
-
-}
-}
+}} // namespace Slic3r::sla
diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp
index cfe78fe97..013666f07 100644
--- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp
+++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp
@@ -5,6 +5,7 @@
 
 #include <libslic3r/SLA/SupportTreeBuilder.hpp>
 #include <libslic3r/SLA/Clustering.hpp>
+#include <libslic3r/SLA/SpatIndex.hpp>
 
 namespace Slic3r {
 namespace sla {
@@ -16,9 +17,7 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers
     X, Y, Z
 };
 
-inline Vec2d to_vec2(const Vec3d& v3) {
-    return {v3(X), v3(Y)};
-}
+inline Vec2d to_vec2(const Vec3d &v3) { return {v3(X), v3(Y)}; }
 
 inline std::pair<double, double> dir_to_spheric(const Vec3d &n, double norm = 1.)
 {
@@ -46,55 +45,71 @@ inline Vec3d spheric_to_dir(const std::pair<double, double> &v)
     return spheric_to_dir(v.first, v.second);
 }
 
-// This function returns the position of the centroid in the input 'clust'
-// vector of point indices.
-template<class DistFn>
-long cluster_centroid(const ClusterEl& clust,
-                      const std::function<Vec3d(size_t)> &pointfn,
-                      DistFn df)
+inline Vec3d spheric_to_dir(const std::array<double, 2> &v)
 {
-    switch(clust.size()) {
-    case 0: /* empty cluster */ return ID_UNSET;
-    case 1: /* only one element */ return 0;
-    case 2: /* if two elements, there is no center */ return 0;
-    default: ;
+    return spheric_to_dir(v[0], v[1]);
+}
+
+// Give points on a 3D ring with given center, radius and orientation
+// method based on:
+// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
+template<size_t N>
+class PointRing {
+    std::array<double, N> m_phis;
+
+    // Two vectors that will be perpendicular to each other and to the
+    // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
+    // placeholder.
+    // a and b vectors are perpendicular to the ring direction and to each other.
+    // Together they define the plane where we have to iterate with the
+    // given angles in the 'm_phis' vector
+    Vec3d a = {0, 1, 0}, b;
+    double m_radius = 0.;
+
+    static inline bool constexpr is_one(double val)
+    {
+        return std::abs(std::abs(val) - 1) < 1e-20;
     }
 
-    // The function works by calculating for each point the average distance
-    // from all the other points in the cluster. We create a selector bitmask of
-    // the same size as the cluster. The bitmask will have two true bits and
-    // false bits for the rest of items and we will loop through all the
-    // permutations of the bitmask (combinations of two points). Get the
-    // distance for the two points and add the distance to the averages.
-    // The point with the smallest average than wins.
+public:
 
-    // The complexity should be O(n^2) but we will mostly apply this function
-    // for small clusters only (cca 3 elements)
+    PointRing(const Vec3d &n)
+    {
+        m_phis = linspace_array<N>(0., 2 * PI);
 
-    std::vector<bool> sel(clust.size(), false);   // create full zero bitmask
-    std::fill(sel.end() - 2, sel.end(), true);    // insert the two ones
-    std::vector<double> avgs(clust.size(), 0.0);  // store the average distances
+        // We have to address the case when the direction vector v (same as
+        // dir) is coincident with one of the world axes. In this case two of
+        // its components will be completely zero and one is 1.0. Our method
+        // becomes dangerous here due to division with zero. Instead, vector
+        // 'a' can be an element-wise rotated version of 'v'
+        if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) {
+            a = {n(Z), n(X), n(Y)};
+            b = {n(Y), n(Z), n(X)};
+        }
+        else {
+            a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize();
+            b = a.cross(n);
+        }
+    }
 
-    do {
-        std::array<size_t, 2> idx;
-        for(size_t i = 0, j = 0; i < clust.size(); i++) if(sel[i]) idx[j++] = i;
+    Vec3d get(size_t idx, const Vec3d src, double r) const
+    {
+        double phi = m_phis[idx];
+        double sinphi = std::sin(phi);
+        double cosphi = std::cos(phi);
 
-        double d = df(pointfn(clust[idx[0]]),
-                      pointfn(clust[idx[1]]));
+        double rpscos = r * cosphi;
+        double rpssin = r * sinphi;
 
-        // add the distance to the sums for both associated points
-        for(auto i : idx) avgs[i] += d;
+        // Point on the sphere
+        return {src(X) + rpscos * a(X) + rpssin * b(X),
+                src(Y) + rpscos * a(Y) + rpssin * b(Y),
+                src(Z) + rpscos * a(Z) + rpssin * b(Z)};
+    }
+};
 
-        // now continue with the next permutation of the bitmask with two 1s
-    } while(std::next_permutation(sel.begin(), sel.end()));
-
-    // Divide by point size in the cluster to get the average (may be redundant)
-    for(auto& a : avgs) a /= clust.size();
-
-    // get the lowest average distance and return the index
-    auto minit = std::min_element(avgs.begin(), avgs.end());
-    return long(minit - avgs.begin());
-}
+//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan(""));
+//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan(""));
 
 inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) {
     return (endp - startp).normalized();
@@ -170,8 +185,8 @@ IntegerOnly<DoubleI> pairhash(I a, I b)
 }
 
 class SupportTreeBuildsteps {
-    const SupportConfig& m_cfg;
-    const EigenMesh3D& m_mesh;
+    const SupportTreeConfig& m_cfg;
+    const IndexedMesh& m_mesh;
     const std::vector<SupportPoint>& m_support_pts;
 
     using PtIndices = std::vector<unsigned>;
@@ -180,7 +195,7 @@ class SupportTreeBuildsteps {
     PtIndices m_iheads_onmodel;
     PtIndices m_iheadless;         // headless support points
     
-    std::map<unsigned, EigenMesh3D::hit_result> m_head_to_ground_scans;
+    std::map<unsigned, IndexedMesh::hit_result> m_head_to_ground_scans;
 
     // normals for support points from model faces.
     PointSet  m_support_nmls;
@@ -206,7 +221,7 @@ class SupportTreeBuildsteps {
     // When bridging heads to pillars... TODO: find a cleaner solution
     ccr::BlockingMutex m_bridge_mutex;
 
-    inline EigenMesh3D::hit_result ray_mesh_intersect(const Vec3d& s, 
+    inline IndexedMesh::hit_result ray_mesh_intersect(const Vec3d& s, 
                                                       const Vec3d& dir)
     {
         return m_mesh.query_ray_hit(s, dir);
@@ -223,16 +238,24 @@ class SupportTreeBuildsteps {
     // point was inside the model, an "invalid" hit_result will be returned
     // with a zero distance value instead of a NAN. This way the result can
     // be used safely for comparison with other distances.
-    EigenMesh3D::hit_result pinhead_mesh_intersect(
+    IndexedMesh::hit_result pinhead_mesh_intersect(
         const Vec3d& s,
         const Vec3d& dir,
         double r_pin,
         double r_back,
-        double width);
-    
-    template<class...Args>
-    inline double pinhead_mesh_distance(Args&&...args) {
-        return pinhead_mesh_intersect(std::forward<Args>(args)...).distance();
+        double width,
+        double safety_d);
+
+    IndexedMesh::hit_result pinhead_mesh_intersect(
+        const Vec3d& s,
+        const Vec3d& dir,
+        double r_pin,
+        double r_back,
+        double width)
+    {
+        return pinhead_mesh_intersect(s, dir, r_pin, r_back, width,
+                                      r_back * m_cfg.safety_distance_mm /
+                                          m_cfg.head_back_radius_mm);
     }
 
     // Checking bridge (pillar and stick as well) intersection with the model.
@@ -243,11 +266,21 @@ class SupportTreeBuildsteps {
     // point was inside the model, an "invalid" hit_result will be returned
     // with a zero distance value instead of a NAN. This way the result can
     // be used safely for comparison with other distances.
-    EigenMesh3D::hit_result bridge_mesh_intersect(
+    IndexedMesh::hit_result bridge_mesh_intersect(
         const Vec3d& s,
         const Vec3d& dir,
         double r,
-        bool ins_check = false);
+        double safety_d);
+
+    IndexedMesh::hit_result bridge_mesh_intersect(
+        const Vec3d& s,
+        const Vec3d& dir,
+        double r)
+    {
+        return bridge_mesh_intersect(s, dir, r,
+                                     r * m_cfg.safety_distance_mm /
+                                         m_cfg.head_back_radius_mm);
+    }
     
     template<class...Args>
     inline double bridge_mesh_distance(Args&&...args) {
@@ -268,20 +301,29 @@ class SupportTreeBuildsteps {
     inline bool connect_to_ground(Head& head);
     
     bool connect_to_model_body(Head &head);
-    
-    bool search_pillar_and_connect(const Head& head);
+
+    bool search_pillar_and_connect(const Head& source);
     
     // This is a proxy function for pillar creation which will mind the gap
     // between the pad and the model bottom in zero elevation mode.
     // jp is the starting junction point which needs to be routed down.
     // sourcedir is the allowed direction of an optional bridge between the
     // jp junction and the final pillar.
-    void create_ground_pillar(const Vec3d &jp,
+    bool create_ground_pillar(const Vec3d &jp,
                               const Vec3d &sourcedir,
                               double       radius,
-                              long         head_id = ID_UNSET);
-    
-    
+                              long         head_id = SupportTreeNode::ID_UNSET);
+
+    void add_pillar_base(long pid)
+    {
+        m_builder.add_pillar_base(pid, m_cfg.base_height_mm, m_cfg.base_radius_mm);
+    }
+
+    std::optional<DiffBridge> search_widening_path(const Vec3d &jp,
+                                                   const Vec3d &dir,
+                                                   double       radius,
+                                                   double       new_radius);
+
 public:
     SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
 
@@ -324,11 +366,6 @@ public:
 
     void interconnect_pillars();
 
-    // Step: process the support points where there is not enough space for a
-    // full pinhead. In this case we will use a rounded sphere as a touching
-    // point and use a thinner bridge (let's call it a stick).
-    void routing_headless ();
-
     inline void merge_result() { m_builder.merged_mesh(); }
 
     static bool execute(SupportTreeBuilder & builder, const SupportableMesh &sm);
diff --git a/src/libslic3r/SLA/SupportTreeMesher.cpp b/src/libslic3r/SLA/SupportTreeMesher.cpp
new file mode 100644
index 000000000..15491775b
--- /dev/null
+++ b/src/libslic3r/SLA/SupportTreeMesher.cpp
@@ -0,0 +1,266 @@
+#include "SupportTreeMesher.hpp"
+
+namespace Slic3r { namespace sla {
+
+Contour3D sphere(double rho, Portion portion, double fa) {
+
+    Contour3D ret;
+
+    // prohibit close to zero radius
+    if(rho <= 1e-6 && rho >= -1e-6) return ret;
+
+    auto& vertices = ret.points;
+    auto& facets = ret.faces3;
+
+    // Algorithm:
+    // Add points one-by-one to the sphere grid and form facets using relative
+    // coordinates. Sphere is composed effectively of a mesh of stacked circles.
+
+    // adjust via rounding to get an even multiple for any provided angle.
+    double angle = (2*PI / floor(2*PI / fa));
+
+    // Ring to be scaled to generate the steps of the sphere
+    std::vector<double> ring;
+
+    for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i);
+
+    const auto sbegin = size_t(2*std::get<0>(portion)/angle);
+    const auto send = size_t(2*std::get<1>(portion)/angle);
+
+    const size_t steps = ring.size();
+    const double increment = 1.0 / double(steps);
+
+    // special case: first ring connects to 0,0,0
+    // insert and form facets.
+    if(sbegin == 0)
+        vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho));
+
+    auto id = coord_t(vertices.size());
+    for (size_t i = 0; i < ring.size(); i++) {
+        // Fixed scaling
+        const double z = -rho + increment*rho*2.0 * (sbegin + 1.0);
+        // radius of the circle for this step.
+        const double r = std::sqrt(std::abs(rho*rho - z*z));
+        Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
+        vertices.emplace_back(Vec3d(b(0), b(1), z));
+
+        if (sbegin == 0)
+            (i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) :
+                       facets.emplace_back(id - 1, 0, id);
+        ++id;
+    }
+
+    // General case: insert and form facets for each step,
+    // joining it to the ring below it.
+    for (size_t s = sbegin + 2; s < send - 1; s++) {
+        const double z = -rho + increment*double(s*2.0*rho);
+        const double r = std::sqrt(std::abs(rho*rho - z*z));
+
+        for (size_t i = 0; i < ring.size(); i++) {
+            Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
+            vertices.emplace_back(Vec3d(b(0), b(1), z));
+            auto id_ringsize = coord_t(id - int(ring.size()));
+            if (i == 0) {
+                // wrap around
+                facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) );
+                facets.emplace_back(id - 1, id_ringsize, id);
+            } else {
+                facets.emplace_back(id_ringsize - 1, id_ringsize, id);
+                facets.emplace_back(id - 1, id_ringsize - 1, id);
+            }
+            id++;
+        }
+    }
+
+    // special case: last ring connects to 0,0,rho*2.0
+    // only form facets.
+    if(send >= size_t(2*PI / angle)) {
+        vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho));
+        for (size_t i = 0; i < ring.size(); i++) {
+            auto id_ringsize = coord_t(id - int(ring.size()));
+            if (i == 0) {
+                // third vertex is on the other side of the ring.
+                facets.emplace_back(id - 1, id_ringsize, id);
+            } else {
+                auto ci = coord_t(id_ringsize + coord_t(i));
+                facets.emplace_back(ci - 1, ci, id);
+            }
+        }
+    }
+    id++;
+
+    return ret;
+}
+
+Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp)
+{
+    assert(ssteps > 0);
+
+    Contour3D ret;
+
+    auto steps = int(ssteps);
+    auto& points = ret.points;
+    auto& indices = ret.faces3;
+    points.reserve(2*ssteps);
+    double a = 2*PI/steps;
+
+    Vec3d jp = sp;
+    Vec3d endp = {sp(X), sp(Y), sp(Z) + h};
+
+    // Upper circle points
+    for(int i = 0; i < steps; ++i) {
+        double phi = i*a;
+        double ex = endp(X) + r*std::cos(phi);
+        double ey = endp(Y) + r*std::sin(phi);
+        points.emplace_back(ex, ey, endp(Z));
+    }
+
+    // Lower circle points
+    for(int i = 0; i < steps; ++i) {
+        double phi = i*a;
+        double x = jp(X) + r*std::cos(phi);
+        double y = jp(Y) + r*std::sin(phi);
+        points.emplace_back(x, y, jp(Z));
+    }
+
+    // Now create long triangles connecting upper and lower circles
+    indices.reserve(2*ssteps);
+    auto offs = steps;
+    for(int i = 0; i < steps - 1; ++i) {
+        indices.emplace_back(i, i + offs, offs + i + 1);
+        indices.emplace_back(i, offs + i + 1, i + 1);
+    }
+
+    // Last triangle connecting the first and last vertices
+    auto last = steps - 1;
+    indices.emplace_back(0, last, offs);
+    indices.emplace_back(last, offs + last, offs);
+
+    // According to the slicing algorithms, we need to aid them with generating
+    // a watertight body. So we create a triangle fan for the upper and lower
+    // ending of the cylinder to close the geometry.
+    points.emplace_back(jp); int ci = int(points.size() - 1);
+    for(int i = 0; i < steps - 1; ++i)
+        indices.emplace_back(i + offs + 1, i + offs, ci);
+
+    indices.emplace_back(offs, steps + offs - 1, ci);
+
+    points.emplace_back(endp); ci = int(points.size() - 1);
+    for(int i = 0; i < steps - 1; ++i)
+        indices.emplace_back(ci, i, i + 1);
+
+    indices.emplace_back(steps - 1, 0, ci);
+
+    return ret;
+}
+
+Contour3D pinhead(double r_pin, double r_back, double length, size_t steps)
+{
+    assert(steps > 0);
+    assert(length >= 0.);
+    assert(r_back > 0.);
+    assert(r_pin > 0.);
+
+    Contour3D mesh;
+
+    // We create two spheres which will be connected with a robe that fits
+    // both circles perfectly.
+
+    // Set up the model detail level
+    const double detail = 2 * PI / steps;
+
+    // We don't generate whole circles. Instead, we generate only the
+    // portions which are visible (not covered by the robe) To know the
+    // exact portion of the bottom and top circles we need to use some
+    // rules of tangent circles from which we can derive (using simple
+    // triangles the following relations:
+
+    // The height of the whole mesh
+    const double h   = r_back + r_pin + length;
+    double       phi = PI / 2. - std::acos((r_back - r_pin) / h);
+
+    // To generate a whole circle we would pass a portion of (0, Pi)
+    // To generate only a half horizontal circle we can pass (0, Pi/2)
+    // The calculated phi is an offset to the half circles needed to smooth
+    // the transition from the circle to the robe geometry
+
+    auto &&s1 = sphere(r_back, make_portion(0, PI / 2 + phi), detail);
+    auto &&s2 = sphere(r_pin, make_portion(PI / 2 + phi, PI), detail);
+
+    for (auto &p : s2.points) p.z() += h;
+
+    mesh.merge(s1);
+    mesh.merge(s2);
+
+    for (size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size();
+         idx1 < s1.points.size() - 1; idx1++, idx2++) {
+        coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2);
+        coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1;
+
+        mesh.faces3.emplace_back(i1s1, i2s1, i2s2);
+        mesh.faces3.emplace_back(i1s1, i2s2, i1s2);
+    }
+
+    auto i1s1 = coord_t(s1.points.size()) - coord_t(steps);
+    auto i2s1 = coord_t(s1.points.size()) - 1;
+    auto i1s2 = coord_t(s1.points.size());
+    auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1;
+
+    mesh.faces3.emplace_back(i2s2, i2s1, i1s1);
+    mesh.faces3.emplace_back(i1s2, i2s2, i1s1);
+
+    return mesh;
+}
+
+Contour3D halfcone(double       baseheight,
+                   double       r_bottom,
+                   double       r_top,
+                   const Vec3d &pos,
+                   size_t       steps)
+{
+    assert(steps > 0);
+
+    if (baseheight <= 0 || steps <= 0) return {};
+
+    Contour3D base;
+
+    double a    = 2 * PI / steps;
+    auto   last = int(steps - 1);
+    Vec3d  ep{pos.x(), pos.y(), pos.z() + baseheight};
+    for (size_t i = 0; i < steps; ++i) {
+        double phi = i * a;
+        double x   = pos.x() + r_top * std::cos(phi);
+        double y   = pos.y() + r_top * std::sin(phi);
+        base.points.emplace_back(x, y, ep.z());
+    }
+
+    for (size_t i = 0; i < steps; ++i) {
+        double phi = i * a;
+        double x   = pos.x() + r_bottom * std::cos(phi);
+        double y   = pos.y() + r_bottom * std::sin(phi);
+        base.points.emplace_back(x, y, pos.z());
+    }
+
+    base.points.emplace_back(pos);
+    base.points.emplace_back(ep);
+
+    auto &indices = base.faces3;
+    auto  hcenter = int(base.points.size() - 1);
+    auto  lcenter = int(base.points.size() - 2);
+    auto  offs    = int(steps);
+    for (int i = 0; i < last; ++i) {
+        indices.emplace_back(i, i + offs, offs + i + 1);
+        indices.emplace_back(i, offs + i + 1, i + 1);
+        indices.emplace_back(i, i + 1, hcenter);
+        indices.emplace_back(lcenter, offs + i + 1, offs + i);
+    }
+
+    indices.emplace_back(0, last, offs);
+    indices.emplace_back(last, offs + last, offs);
+    indices.emplace_back(hcenter, last, 0);
+    indices.emplace_back(offs, offs + last, lcenter);
+
+    return base;
+}
+
+}} // namespace Slic3r::sla
diff --git a/src/libslic3r/SLA/SupportTreeMesher.hpp b/src/libslic3r/SLA/SupportTreeMesher.hpp
new file mode 100644
index 000000000..63182745d
--- /dev/null
+++ b/src/libslic3r/SLA/SupportTreeMesher.hpp
@@ -0,0 +1,117 @@
+#ifndef SUPPORTTREEMESHER_HPP
+#define SUPPORTTREEMESHER_HPP
+
+#include "libslic3r/Point.hpp"
+
+#include "libslic3r/SLA/SupportTreeBuilder.hpp"
+#include "libslic3r/SLA/Contour3D.hpp"
+
+namespace Slic3r { namespace sla {
+
+using Portion = std::tuple<double, double>;
+
+inline Portion make_portion(double a, double b)
+{
+    return std::make_tuple(a, b);
+}
+
+Contour3D sphere(double  rho,
+                 Portion portion = make_portion(0., 2. * PI),
+                 double  fa      = (2. * PI / 360.));
+
+// Down facing cylinder in Z direction with arguments:
+// r: radius
+// h: Height
+// ssteps: how many edges will create the base circle
+// sp: starting point
+Contour3D cylinder(double       r,
+                   double       h,
+                   size_t       steps = 45,
+                   const Vec3d &sp    = Vec3d::Zero());
+
+Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45);
+
+Contour3D halfcone(double       baseheight,
+                   double       r_bottom,
+                   double       r_top,
+                   const Vec3d &pt    = Vec3d::Zero(),
+                   size_t       steps = 45);
+
+inline Contour3D get_mesh(const Head &h, size_t steps)
+{
+    Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, steps);
+
+    for(auto& p : mesh.points) p.z() -= (h.fullwidth() - h.r_back_mm);
+
+    using Quaternion = Eigen::Quaternion<double>;
+
+    // We rotate the head to the specified direction. The head's pointing
+    // side is facing upwards so this means that it would hold a support
+    // point with a normal pointing straight down. This is the reason of
+    // the -1 z coordinate
+    auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, h.dir);
+
+    for(auto& p : mesh.points) p = quatern * p + h.pos;
+
+    return mesh;
+}
+
+inline Contour3D get_mesh(const Pillar &p, size_t steps)
+{
+    if(p.height > EPSILON) { // Endpoint is below the starting point
+        // We just create a bridge geometry with the pillar parameters and
+        // move the data.
+        return cylinder(p.r, p.height, steps, p.endpoint());
+    }
+
+    return {};
+}
+
+inline Contour3D get_mesh(const Pedestal &p, size_t steps)
+{
+    return halfcone(p.height, p.r_bottom, p.r_top, p.pos, steps);
+}
+
+inline Contour3D get_mesh(const Junction &j, size_t steps)
+{
+    Contour3D mesh = sphere(j.r, make_portion(0, PI), 2 *PI / steps);
+    for(auto& p : mesh.points) p += j.pos;
+    return mesh;
+}
+
+inline Contour3D get_mesh(const Bridge &br, size_t steps)
+{
+    using Quaternion = Eigen::Quaternion<double>;
+    Vec3d v = (br.endp - br.startp);
+    Vec3d dir = v.normalized();
+    double d = v.norm();
+
+    Contour3D mesh = cylinder(br.r, d, steps);
+
+    auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir);
+    for(auto& p : mesh.points) p = quater * p + br.startp;
+
+    return mesh;
+}
+
+inline Contour3D get_mesh(const DiffBridge &br, size_t steps)
+{
+    double h = br.get_length();
+    Contour3D mesh = halfcone(h, br.r, br.end_r, Vec3d::Zero(), steps);
+
+    using Quaternion = Eigen::Quaternion<double>;
+
+    // We rotate the head to the specified direction. The head's pointing
+    // side is facing upwards so this means that it would hold a support
+    // point with a normal pointing straight down. This is the reason of
+    // the -1 z coordinate
+    auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, 1}, br.get_dir());
+
+    for(auto& p : mesh.points) p = quatern * p + br.startp;
+
+    return mesh;
+}
+
+}} // namespace Slic3r::sla
+
+#endif // SUPPORTTREEMESHER_HPP
diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp
index 2402207a8..4395bea46 100644
--- a/src/libslic3r/SLAPrint.cpp
+++ b/src/libslic3r/SLAPrint.cpp
@@ -35,13 +35,16 @@ bool is_zero_elevation(const SLAPrintObjectConfig &c)
 }
 
 // Compile the argument for support creation from the static print config.
-sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c)
+sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c)
 {
-    sla::SupportConfig scfg;
+    sla::SupportTreeConfig scfg;
     
     scfg.enabled = c.supports_enable.getBool();
     scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat();
-    scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat();
+    double pillar_r = 0.5 * c.support_pillar_diameter.getFloat();
+    scfg.head_back_radius_mm = pillar_r;
+    scfg.head_fallback_radius_mm =
+        0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r;
     scfg.head_penetration_mm = c.support_head_penetration.getFloat();
     scfg.head_width_mm = c.support_head_width.getFloat();
     scfg.object_elevation_mm = is_zero_elevation(c) ?
@@ -616,7 +619,7 @@ std::string SLAPrint::validate() const
             return L("Cannot proceed without support points! "
                      "Add support points or disable support generation.");
 
-        sla::SupportConfig cfg = make_support_cfg(po->config());
+        sla::SupportTreeConfig cfg = make_support_cfg(po->config());
 
         double elv = cfg.object_elevation_mm;
         
@@ -925,6 +928,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
             || opt_key == "support_head_penetration"
             || opt_key == "support_head_width"
             || opt_key == "support_pillar_diameter"
+            || opt_key == "support_small_pillar_diameter_percent"
             || opt_key == "support_max_bridges_on_pillar"
             || opt_key == "support_pillar_connection_mode"
             || opt_key == "support_buildplate_only"
diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp
index 9d41586ee..f4b220c58 100644
--- a/src/libslic3r/SLAPrint.hpp
+++ b/src/libslic3r/SLAPrint.hpp
@@ -544,7 +544,7 @@ private:
 
 bool is_zero_elevation(const SLAPrintObjectConfig &c);
 
-sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c);
+sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c);
 
 sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c);
 
diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp
index e421e9c1d..76bbf498d 100644
--- a/src/libslic3r/SLAPrintSteps.cpp
+++ b/src/libslic3r/SLAPrintSteps.cpp
@@ -360,18 +360,6 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
         // removed them on purpose. No calculation will be done.
         po.m_supportdata->pts = po.transformed_support_points();
     }
-
-    // If the zero elevation mode is engaged, we have to filter out all the
-    // points that are on the bottom of the object
-    if (is_zero_elevation(po.config())) {
-        double tolerance = po.config().pad_enable.getBool() ?
-                               po.m_config.pad_wall_thickness.getFloat() :
-                               po.m_config.support_base_height.getFloat();
-
-        remove_bottom_points(po.m_supportdata->pts,
-                             po.m_supportdata->emesh.ground_level(),
-                             tolerance);
-    }
 }
 
 void SLAPrint::Steps::support_tree(SLAPrintObject &po)
@@ -382,6 +370,13 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po)
     
     if (pcfg.embed_object)
         po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm);
+
+    // If the zero elevation mode is engaged, we have to filter out all the
+    // points that are on the bottom of the object
+    if (is_zero_elevation(po.config())) {
+        remove_bottom_points(po.m_supportdata->pts,
+                             float(po.m_supportdata->emesh.ground_level() + EPSILON));
+    }
     
     po.m_supportdata->cfg = make_support_cfg(po.m_config);
 //    po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp
index c6991c057..e4b71697d 100644
--- a/src/libslic3r/Technologies.hpp
+++ b/src/libslic3r/Technologies.hpp
@@ -54,8 +54,5 @@
 // Enable built-in DPI changed event handler of wxWidgets 3.1.3
 #define ENABLE_WX_3_1_3_DPI_CHANGED_EVENT (1 && ENABLE_2_3_0_ALPHA1)
 
-// Enable changing application layout without the need to restart
-#define ENABLE_LAYOUT_NO_RESTART (1 && ENABLE_2_3_0_ALPHA1)
-
 
 #endif // _prusaslicer_technologies_h_
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 20ea4e33a..849460792 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -163,6 +163,8 @@ set(SLIC3R_GUI_SOURCES
     GUI/InstanceCheck.hpp
     GUI/Search.cpp
     GUI/Search.hpp
+    GUI/NotificationManager.cpp
+    GUI/NotificationManager.hpp
     Utils/Http.cpp
     Utils/Http.hpp
     Utils/FixModelByWin10.cpp
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
index 8d50998c4..7309654a8 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
@@ -89,11 +89,13 @@ void BackgroundSlicingProcess::process_fff()
 {
 	assert(m_print == m_fff_print);
     m_print->process();
-	wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id));
+	wxCommandEvent evt(m_event_slicing_completed_id);
+	evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psBrim).timestamp));
+	wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
     m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb);
-
 	if (this->set_step_started(bspsGCodeFinalize)) {
 	    if (! m_export_path.empty()) {
+			wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
 	    	//FIXME localize the messages
 	    	// Perform the final post-processing of the export path by applying the print statistics over the file name.
 	    	std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
@@ -124,6 +126,7 @@ void BackgroundSlicingProcess::process_fff()
 	    	run_post_process_scripts(export_path, m_fff_print->config());
 	    	m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());
 	    } else if (! m_upload_job.empty()) {
+			wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
 			prepare_upload();
 	    } else {
 			m_print->set_status(100, _utf8(L("Slicing complete")));
@@ -149,6 +152,8 @@ void BackgroundSlicingProcess::process_sla()
     m_print->process();
     if (this->set_step_started(bspsGCodeFinalize)) {
         if (! m_export_path.empty()) {
+			wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
+
             const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
 
             Zipper zipper(export_path);
@@ -170,6 +175,7 @@ void BackgroundSlicingProcess::process_sla()
 
             m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str());
         } else if (! m_upload_job.empty()) {
+			wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
             prepare_upload();
         } else {
 			m_print->set_status(100, _utf8(L("Slicing complete")));
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
index 38e9e1075..c4672f1b4 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
@@ -60,6 +60,10 @@ public:
 	// The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code export is finished.
 	// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
 	void set_finished_event(int event_id) { m_event_finished_id = event_id; }
+	// The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code is being exported to
+	// specified path or uploaded.
+	// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
+	void set_export_began_event(int event_id) { m_event_export_began_id = event_id; }
 
 	// Activate either m_fff_print or m_sla_print.
 	// Return true if changed.
@@ -190,6 +194,9 @@ private:
 	int 						m_event_slicing_completed_id 	= 0;
 	// wxWidgets command ID to be sent to the plater to inform that the task finished.
 	int 						m_event_finished_id  			= 0;
+	// wxWidgets command ID to be sent to the plater to inform that the G-code is being exported.
+	int                         m_event_export_began_id         = 0;
+
 };
 
 }; // namespace Slic3r
diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp
index cd8463a77..5836b8a2c 100644
--- a/src/slic3r/GUI/ConfigManipulation.cpp
+++ b/src/slic3r/GUI/ConfigManipulation.cpp
@@ -353,6 +353,7 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
     toggle_field("support_head_penetration", supports_en);
     toggle_field("support_head_width", supports_en);
     toggle_field("support_pillar_diameter", supports_en);
+    toggle_field("support_small_pillar_diameter_percent", supports_en);
     toggle_field("support_max_bridges_on_pillar", supports_en);
     toggle_field("support_pillar_connection_mode", supports_en);
     toggle_field("support_buildplate_only", supports_en);
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index b1367233d..9bed5fde7 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -31,6 +31,7 @@
 #include "GUI_ObjectManipulation.hpp"
 #include "Mouse3DController.hpp"
 #include "I18N.hpp"
+#include "NotificationManager.hpp"
 
 #if ENABLE_RETINA_GL
 #include "slic3r/Utils/RetinaHelper.hpp"
@@ -225,56 +226,44 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
     static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
 
     const Size& cnv_size = canvas.get_canvas_size();
-    float canvas_w = (float)cnv_size.get_width();
-    float canvas_h = (float)cnv_size.get_height();
 
     ImGuiWrapper& imgui = *wxGetApp().imgui();
-    imgui.set_next_window_pos(canvas_w - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, canvas_h, ImGuiCond_Always, 1.0f, 1.0f);
+    imgui.set_next_window_pos(static_cast<float>(cnv_size.get_width()) - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, 
+        static_cast<float>(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f);
 
-    imgui.begin(_(L("Variable layer height")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
+    imgui.begin(_L("Variable layer height"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
 
-    ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
-    imgui.text(_(L("Left mouse button:")));
-    ImGui::PopStyleColor();
+    imgui.text_colored(ORANGE, _L("Left mouse button:"));
     ImGui::SameLine();
-    imgui.text(_(L("Add detail")));
+    imgui.text(_L("Add detail"));
 
-    ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
-    imgui.text(_(L("Right mouse button:")));
-    ImGui::PopStyleColor();
+    imgui.text_colored(ORANGE, _L("Right mouse button:"));
     ImGui::SameLine();
-    imgui.text(_(L("Remove detail")));
+    imgui.text(_L("Remove detail"));
 
-    ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
-    imgui.text(_(L("Shift + Left mouse button:")));
-    ImGui::PopStyleColor();
+    imgui.text_colored(ORANGE, _L("Shift + Left mouse button:"));
     ImGui::SameLine();
-    imgui.text(_(L("Reset to base")));
+    imgui.text(_L("Reset to base"));
 
-    ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
-    imgui.text(_(L("Shift + Right mouse button:")));
-    ImGui::PopStyleColor();
+    imgui.text_colored(ORANGE, _L("Shift + Right mouse button:"));
     ImGui::SameLine();
-    imgui.text(_(L("Smoothing")));
+    imgui.text(_L("Smoothing"));
 
-    ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
-    imgui.text(_(L("Mouse wheel:")));
-    ImGui::PopStyleColor();
+    imgui.text_colored(ORANGE, _L("Mouse wheel:"));
     ImGui::SameLine();
-    imgui.text(_(L("Increase/decrease edit area")));
+    imgui.text(_L("Increase/decrease edit area"));
     
     ImGui::Separator();
-    if (imgui.button(_(L("Adaptive"))))
+    if (imgui.button(_L("Adaptive")))
         wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event<float>(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_quality));
 
     ImGui::SameLine();
     float text_align = ImGui::GetCursorPosX();
     ImGui::AlignTextToFramePadding();
-    imgui.text(_(L("Quality / Speed")));
-    if (ImGui::IsItemHovered())
-    {
+    imgui.text(_L("Quality / Speed"));
+    if (ImGui::IsItemHovered()) {
         ImGui::BeginTooltip();
-        ImGui::TextUnformatted(_(L("Higher print quality versus higher print speed.")).ToUTF8());
+        ImGui::TextUnformatted(_L("Higher print quality versus higher print speed.").ToUTF8());
         ImGui::EndTooltip();
     }
 
@@ -285,13 +274,13 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
     ImGui::SliderFloat("", &m_adaptive_quality, 0.0f, 1.f, "%.2f");
 
     ImGui::Separator();
-    if (imgui.button(_(L("Smooth"))))
+    if (imgui.button(_L("Smooth")))
         wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params));
 
     ImGui::SameLine();
     ImGui::SetCursorPosX(text_align);
     ImGui::AlignTextToFramePadding();
-    imgui.text(_(L("Radius")));
+    imgui.text(_L("Radius"));
     ImGui::SameLine();
     ImGui::SetCursorPosX(widget_align);
     ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f);
@@ -301,7 +290,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
 
     ImGui::SetCursorPosX(text_align);
     ImGui::AlignTextToFramePadding();
-    imgui.text(_(L("Keep min")));
+    imgui.text(_L("Keep min"));
     ImGui::SameLine();
     if (ImGui::GetCursorPosX() < widget_align)  // because of line lenght after localization
         ImGui::SetCursorPosX(widget_align);
@@ -310,7 +299,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
     imgui.checkbox("##2", m_smooth_params.keep_min);
 
     ImGui::Separator();
-    if (imgui.button(_(L("Reset"))))
+    if (imgui.button(_L("Reset")))
         wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE));
 
     imgui.end();
@@ -663,19 +652,45 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
 
         m_warnings.emplace_back(warning);
         std::sort(m_warnings.begin(), m_warnings.end());
+
+		std::string text;
+		switch (warning) {
+			case ObjectOutside: text = L("An object outside the print area was detected."); break;
+			case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break;
+			case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break;
+			case SomethingNotShown: text = L("Some objects are not visible."); break;
+			case ObjectClashed: wxGetApp().plater()->get_notification_manager()->push_plater_error_notification(L("An object outside the print area was detected.\n"
+																												 "Resolve the current problem to continue slicing."), 
+				                                                                                                *(wxGetApp().plater()->get_current_canvas3D()));
+				break;
+		}
+		if (!text.empty())
+			wxGetApp().plater()->get_notification_manager()->push_plater_warning_notification(text, *(wxGetApp().plater()->get_current_canvas3D()));
     }
     else {
         if (it == m_warnings.end()) // deactivating something that is not active is an easy task
             return;
 
         m_warnings.erase(it);
-        if (m_warnings.empty()) { // nothing remains to be shown
+
+		std::string text;
+		switch (warning) {
+		case ObjectOutside: text = L("An object outside the print area was detected."); break;
+		case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break;
+		case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break;
+		case SomethingNotShown: text = L("Some objects are not visibl.e"); break;
+		case ObjectClashed: wxGetApp().plater()->get_notification_manager()->close_plater_error_notification(); break;
+		}
+		if (!text.empty())
+			wxGetApp().plater()->get_notification_manager()->close_plater_warning_notification(text);
+
+        /*if (m_warnings.empty()) { // nothing remains to be shown
             reset();
             m_msg_text = "";// save information for rescaling
             return;
-        }
+        }*/
     }
-
+	/*
     // Look at the end of our vector and generate proper texture.
     std::string text;
     bool red_colored = false;
@@ -697,6 +712,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
     // save information for rescaling
     m_msg_text = text;
     m_is_colored_red = red_colored;
+	*/
 }
 
 
@@ -1430,8 +1446,7 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas
 #if ENABLE_SLOPE_RENDERING
 void GLCanvas3D::Slope::render() const
 {
-    if (m_dialog_shown)
-    {
+    if (m_dialog_shown) {
         const std::array<float, 2>& z_range = m_volumes.get_slope_z_range();
         std::array<float, 2> angle_range = { Geometry::rad2deg(::acos(z_range[0])) - 90.0f, Geometry::rad2deg(::acos(z_range[1])) - 90.0f };
         bool modified = false;
@@ -1439,9 +1454,9 @@ void GLCanvas3D::Slope::render() const
         ImGuiWrapper& imgui = *wxGetApp().imgui();
         const Size& cnv_size = m_canvas.get_canvas_size();
         imgui.set_next_window_pos((float)cnv_size.get_width(), (float)cnv_size.get_height(), ImGuiCond_Always, 1.0f, 1.0f);
-        imgui.begin(_(L("Slope visualization")), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
+        imgui.begin(_L("Slope visualization"), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
 
-        imgui.text(_(L("Facets' slope range (degrees)")) + ":");
+        imgui.text(_L("Facets' slope range (degrees)") + ":");
 
         ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.0f, 0.0f, 0.5f));
         ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 0.0f, 0.0f, 0.5f));
@@ -1453,8 +1468,7 @@ void GLCanvas3D::Slope::render() const
         float slope_bound = 90.f - angle_range[1];
         bool mod = ImGui::SliderFloat("##red", &slope_bound, 0.0f, 90.0f, "%.1f");
         angle_range[1] = 90.f - slope_bound;
-        if (mod)
-        {
+        if (mod) {
             modified = true;
             if (angle_range[0] > angle_range[1])
                 angle_range[0] = angle_range[1];
@@ -1462,15 +1476,14 @@ void GLCanvas3D::Slope::render() const
 
         ImGui::PopStyleColor(4);
         ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.75f, 0.0f, 0.5f));
-                ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 1.0f, 0.0f, 0.5f));
-                ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.85f, 0.0f, 0.5f));
-                ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.25f, 0.0f, 1.0f));
+        ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 1.0f, 0.0f, 0.5f));
+        ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.85f, 0.0f, 0.5f));
+        ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.25f, 0.0f, 1.0f));
 
         slope_bound = 90.f - angle_range[0];
         mod = ImGui::SliderFloat("##yellow", &slope_bound, 0.0f, 90.0f, "%.1f");
         angle_range[0] = 90.f - slope_bound;
-        if (mod)
-        {
+        if (mod) {
             modified = true;
             if (angle_range[1] < angle_range[0])
                 angle_range[1] = angle_range[0];
@@ -2089,6 +2102,8 @@ void GLCanvas3D::render()
 
     std::string tooltip;
 
+	
+
 	// Negative coordinate means out of the window, likely because the window was deactivated.
 	// In that case the tooltip should be hidden.
     if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) 
@@ -2118,6 +2133,8 @@ void GLCanvas3D::render()
         m_tooltip.render(m_mouse.position, *this);
 
     wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this);
+	
+	wxGetApp().plater()->get_notification_manager()->render_notifications(*this);
 
     wxGetApp().imgui()->render();
 
@@ -3433,6 +3450,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 #ifdef SLIC3R_DEBUG_MOUSE_EVENTS
         printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
 #endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
+		 m_dirty = true;
         // do not return if dragging or tooltip not empty to allow for tooltip update
         if (!m_mouse.dragging && m_tooltip.is_empty())
             return;
@@ -3826,7 +3844,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             m_gizmos.reset_all_states();
 
         // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over.
-        if (m_picking_enabled)
+        //if (m_picking_enabled)
             m_dirty = true;
     }
     else
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 410614b94..38bda5f5e 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -57,6 +57,7 @@
 #include "Mouse3DController.hpp"
 #include "RemovableDriveManager.hpp"
 #include "InstanceCheck.hpp"
+#include "NotificationManager.hpp"
 
 #ifdef __WXMSW__
 #include <dbt.h>
@@ -387,7 +388,7 @@ bool GUI_App::on_init_inner()
     // supplied as argument to --datadir; in that case we should still run the wizard
     preset_bundle->setup_directories();
 
-#ifdef __WXMSW__
+#ifdef __WXMSW__ 
     associate_3mf_files();
 #endif // __WXMSW__
 
@@ -395,6 +396,11 @@ bool GUI_App::on_init_inner()
     Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) {
         app_config->set("version_online", into_u8(evt.GetString()));
         app_config->save();
+		if(this->plater_ != nullptr) {
+			if (*Semver::parse(SLIC3R_VERSION) < * Semver::parse(into_u8(evt.GetString()))) {
+				this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAviable, *(this->plater_->get_current_canvas3D()));
+			}
+		}
     });
 
     // initialize label colors and fonts
@@ -1085,34 +1091,21 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
             break;
         case ConfigMenuPreferences:
         {
-#if ENABLE_LAYOUT_NO_RESTART
             bool app_layout_changed = false;
-#else
-            bool recreate_app = false;
-#endif // ENABLE_LAYOUT_NO_RESTART
             {
                 // the dialog needs to be destroyed before the call to recreate_GUI()
                 // or sometimes the application crashes into wxDialogBase() destructor
                 // so we put it into an inner scope
                 PreferencesDialog dlg(mainframe);
                 dlg.ShowModal();
-#if ENABLE_LAYOUT_NO_RESTART
                 app_layout_changed = dlg.settings_layout_changed();
-#else
-                recreate_app = dlg.settings_layout_changed();
-#endif // ENABLE_LAYOUT_NO_RESTART
             }
-#if ENABLE_LAYOUT_NO_RESTART
             if (app_layout_changed) {
                 mainframe->GetSizer()->Hide((size_t)0);
                 mainframe->update_layout();
                 mainframe->select_tab(0);
                 mainframe->GetSizer()->Show((size_t)0);
             }
-#else
-            if (recreate_app)
-                recreate_GUI(_L("Changing of the settings layout") + dots);
-#endif // ENABLE_LAYOUT_NO_RESTART
             break;
         }
         case ConfigMenuLanguage:
@@ -1480,7 +1473,7 @@ void GUI_App::check_updates(const bool verbose)
 	
 	PresetUpdater::UpdateResult updater_result;
 	try {
-		updater_result = preset_updater->config_update(app_config->orig_version());
+		updater_result = preset_updater->config_update(app_config->orig_version(), verbose);
 		if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
 			mainframe->Close();
 		}
diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp
index db551610b..82073c549 100644
--- a/src/slic3r/GUI/GUI_App.hpp
+++ b/src/slic3r/GUI/GUI_App.hpp
@@ -195,12 +195,15 @@ public:
     Plater*             plater();
     Model&      		model();
 
+
     AppConfig*      app_config{ nullptr };
     PresetBundle*   preset_bundle{ nullptr };
     PresetUpdater*  preset_updater{ nullptr };
     MainFrame*      mainframe{ nullptr };
     Plater*         plater_{ nullptr };
 
+	PresetUpdater* get_preset_updater() { return preset_updater; }
+
     wxNotebook*     tab_panel() const ;
     int             extruders_cnt() const;
     int             extruders_edited_cnt() const;
diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp
index 9d6b2b9cb..a326eea7b 100644
--- a/src/slic3r/GUI/GUI_ObjectList.cpp
+++ b/src/slic3r/GUI/GUI_ObjectList.cpp
@@ -91,7 +91,7 @@ ObjectList::ObjectList(wxWindow* parent) :
         // ptFFF
         CATEGORY_ICON[L("Layers and Perimeters")]    = create_scaled_bitmap("layers");
         CATEGORY_ICON[L("Infill")]                   = create_scaled_bitmap("infill");
-        CATEGORY_ICON[L("Ironing")]                  = create_scaled_bitmap("infill"); // FIXME when the ironing icon is available
+        CATEGORY_ICON[L("Ironing")]                  = create_scaled_bitmap("ironing");
         CATEGORY_ICON[L("Support material")]         = create_scaled_bitmap("support");
         CATEGORY_ICON[L("Speed")]                    = create_scaled_bitmap("time");
         CATEGORY_ICON[L("Extruders")]                = create_scaled_bitmap("funnel");
@@ -642,7 +642,7 @@ void ObjectList::msw_rescale_icons()
         // ptFFF
         CATEGORY_ICON[L("Layers and Perimeters")]    = create_scaled_bitmap("layers");
         CATEGORY_ICON[L("Infill")]                   = create_scaled_bitmap("infill");
-        CATEGORY_ICON[L("Ironing")]                  = create_scaled_bitmap("infill"); // FIXME when the ironing icon is available
+        CATEGORY_ICON[L("Ironing")]                  = create_scaled_bitmap("ironing");
         CATEGORY_ICON[L("Support material")]         = create_scaled_bitmap("support");
         CATEGORY_ICON[L("Speed")]                    = create_scaled_bitmap("time");
         CATEGORY_ICON[L("Extruders")]                = create_scaled_bitmap("funnel");
diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp
index 2737b3edb..6ce3f62a6 100644
--- a/src/slic3r/GUI/GUI_Utils.hpp
+++ b/src/slic3r/GUI/GUI_Utils.hpp
@@ -110,13 +110,8 @@ public:
             if (!m_can_rescale)
                 return;
 
-#if ENABLE_LAYOUT_NO_RESTART
             if (m_force_rescale || is_new_scale_factor())
                 rescale(wxRect());
-#else
-            if (is_new_scale_factor())
-                rescale(wxRect());
-#endif // ENABLE_LAYOUT_NO_RESTART
             });
 #else
         this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent& evt) {
@@ -127,13 +122,8 @@ public:
             if (!m_can_rescale)
                 return;
 
-#if ENABLE_LAYOUT_NO_RESTART
             if (m_force_rescale || is_new_scale_factor())
                 rescale(evt.rect);
-#else
-            if (is_new_scale_factor())
-                rescale(evt.rect);
-#endif // ENABLE_LAYOUT_NO_RESTART
             });
 #endif // wxVERSION_EQUAL_OR_GREATER_THAN
 
@@ -175,9 +165,7 @@ public:
     int     em_unit() const             { return m_em_unit; }
 //    int     font_size() const           { return m_font_size; }
     const wxFont& normal_font() const   { return m_normal_font; }
-#if ENABLE_LAYOUT_NO_RESTART
     void enable_force_rescale()         { m_force_rescale = true; }
-#endif // ENABLE_LAYOUT_NO_RESTART
 
 protected:
     virtual void on_dpi_changed(const wxRect &suggested_rect) = 0;
@@ -191,9 +179,7 @@ private:
     wxFont m_normal_font;
     float m_prev_scale_factor;
     bool  m_can_rescale{ true };
-#if ENABLE_LAYOUT_NO_RESTART
     bool m_force_rescale{ false };
-#endif // ENABLE_LAYOUT_NO_RESTART
 
     int   m_new_font_point_size;
 
@@ -233,17 +219,17 @@ private:
     {
         this->Freeze();
 
-#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
+#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
         if (m_force_rescale) {
-#endif // ENABLE_LAYOUT_NO_RESTART
+#endif // wxVERSION_EQUAL_OR_GREATER_THAN
             // rescale fonts of all controls
             scale_controls_fonts(this, m_new_font_point_size);
             // rescale current window font
             scale_win_font(this, m_new_font_point_size);
-#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
+#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
             m_force_rescale = false;
         }
-#endif // ENABLE_LAYOUT_NO_RESTART
+#endif // wxVERSION_EQUAL_OR_GREATER_THAN
 
         // set normal application font as a current window font
         m_normal_font = this->GetFont();
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
index d43ac1947..309c7cf42 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
@@ -513,9 +513,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
 
         auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
             static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
-            ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
-            m_imgui->text(caption);
-            ImGui::PopStyleColor();
+            m_imgui->text_colored(ORANGE, caption);
             ImGui::SameLine(caption_max);
             m_imgui->text(text);
         };
diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp
index 88dd02ccb..266472eca 100644
--- a/src/slic3r/GUI/ImGuiWrapper.cpp
+++ b/src/slic3r/GUI/ImGuiWrapper.cpp
@@ -37,11 +37,17 @@ namespace GUI {
 
 
 static const std::map<const char, std::string> font_icons = {
-    {ImGui::PrintIconMarker     , "cog"        },
-    {ImGui::PrinterIconMarker   , "printer"    },
-    {ImGui::PrinterSlaIconMarker, "sla_printer"},
-    {ImGui::FilamentIconMarker  , "spool"      },
-    {ImGui::MaterialIconMarker  , "resin"      }
+    {ImGui::PrintIconMarker     , "cog"               },
+    {ImGui::PrinterIconMarker   , "printer"           },
+    {ImGui::PrinterSlaIconMarker, "sla_printer"       },
+    {ImGui::FilamentIconMarker  , "spool"             },
+    {ImGui::MaterialIconMarker  , "resin"             },
+	{ImGui::CloseIconMarker     , "cross"             },
+	{ImGui::CloseIconHoverMarker, "cross_focus_large" },
+	{ImGui::TimerDotMarker      , "timer_dot"         },
+    {ImGui::TimerDotEmptyMarker , "timer_dot_empty"   },
+	{ImGui::WarningMarker       , "flag_green"        },
+    {ImGui::ErrorMarker         , "flag_red"          }
 };
 
 ImGuiWrapper::ImGuiWrapper()
@@ -265,6 +271,11 @@ void ImGuiWrapper::set_next_window_bg_alpha(float alpha)
     ImGui::SetNextWindowBgAlpha(alpha);
 }
 
+void ImGuiWrapper::set_next_window_size(float x, float y, ImGuiCond cond)
+{
+	ImGui::SetNextWindowSize(ImVec2(x, y), cond);
+}
+
 bool ImGuiWrapper::begin(const std::string &name, int flags)
 {
     return ImGui::Begin(name.c_str(), nullptr, (ImGuiWindowFlags)flags);
@@ -296,12 +307,23 @@ bool ImGuiWrapper::button(const wxString &label)
     return ImGui::Button(label_utf8.c_str());
 }
 
+bool ImGuiWrapper::button(const wxString& label, float width, float height)
+{
+	auto label_utf8 = into_u8(label);
+	return ImGui::Button(label_utf8.c_str(), ImVec2(width, height));
+}
+
 bool ImGuiWrapper::radio_button(const wxString &label, bool active)
 {
     auto label_utf8 = into_u8(label);
     return ImGui::RadioButton(label_utf8.c_str(), active);
 }
 
+bool ImGuiWrapper::image_button()
+{
+	return false;
+}
+
 bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format)
 {
     return ImGui::InputDouble(label.c_str(), const_cast<double*>(&value), 0.0f, 0.0f, format.c_str());
@@ -354,6 +376,22 @@ void ImGuiWrapper::text(const wxString &label)
     this->text(label_utf8.c_str());
 }
 
+void ImGuiWrapper::text_colored(const ImVec4& color, const char* label)
+{
+    ImGui::TextColored(color, label);
+}
+
+void ImGuiWrapper::text_colored(const ImVec4& color, const std::string& label)
+{
+    this->text_colored(color, label.c_str());
+}
+
+void ImGuiWrapper::text_colored(const ImVec4& color, const wxString& label)
+{
+    auto label_utf8 = into_u8(label);
+    this->text_colored(color, label_utf8.c_str());
+}
+
 bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/)
 {
     return ImGui::SliderFloat(label, v, v_min, v_max, format, power);
diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp
index bf542e138..ee553c4b6 100644
--- a/src/slic3r/GUI/ImGuiWrapper.hpp
+++ b/src/slic3r/GUI/ImGuiWrapper.hpp
@@ -57,6 +57,7 @@ public:
 
     void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f);
     void set_next_window_bg_alpha(float alpha);
+	void set_next_window_size(float x, float y, ImGuiCond cond);
 
     bool begin(const std::string &name, int flags = 0);
     bool begin(const wxString &name, int flags = 0);
@@ -65,7 +66,9 @@ public:
     void end();
 
     bool button(const wxString &label);
+	bool button(const wxString& label, float width, float height);
     bool radio_button(const wxString &label, bool active);
+	bool image_button();
     bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f");
     bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f");
     bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f");
@@ -73,6 +76,9 @@ public:
     void text(const char *label);
     void text(const std::string &label);
     void text(const wxString &label);
+    void text_colored(const ImVec4& color, const char* label);
+    void text_colored(const ImVec4& color, const std::string& label);
+    void text_colored(const ImVec4& color, const wxString& label);
     bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
     bool slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
     bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index aa0ee24e6..084280300 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -42,7 +42,6 @@
 namespace Slic3r {
 namespace GUI {
 
-#if ENABLE_LAYOUT_NO_RESTART
 enum class ERescaleTarget
 {
     Mainframe,
@@ -71,15 +70,12 @@ static void rescale_dialog_after_dpi_change(MainFrame& mainframe, SettingsDialog
         }
     }
 }
-#endif // ENABLE_LAYOUT_NO_RESTART
 
 MainFrame::MainFrame() :
 DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, "mainframe"),
     m_printhost_queue_dlg(new PrintHostQueueDialog(this))
     , m_recent_projects(9)
-#if ENABLE_LAYOUT_NO_RESTART
     , m_settings_dialog(this)
-#endif // ENABLE_LAYOUT_NO_RESTART
 {
     // Fonts were created by the DPIFrame constructor for the monitor, on which the window opened.
     wxGetApp().update_fonts(this);
@@ -119,43 +115,15 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
 
     m_loaded = true;
 
-#if !ENABLE_LAYOUT_NO_RESTART
-#ifdef __APPLE__
-    // Using SetMinSize() on Mac messes up the window position in some cases
-    // cf. https://groups.google.com/forum/#!topic/wx-users/yUKPBBfXWO0
-    // So, if we haven't possibility to set MinSize() for the MainFrame, 
-    // set the MinSize() as a half of regular  for the m_plater and m_tabpanel, when settings layout is in slNew mode
-    // Otherwise, MainFrame will be maximized by height
-    if (slNew) {
-        wxSize size = wxGetApp().get_min_size();
-        size.SetHeight(int(0.5*size.GetHeight()));
-        m_plater->SetMinSize(size);
-        m_tabpanel->SetMinSize(size);
-    }
-#endif
-#endif // !ENABLE_LAYOUT_NO_RESTART
-
     // initialize layout
     m_main_sizer = new wxBoxSizer(wxVERTICAL);
     wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
     sizer->Add(m_main_sizer, 1, wxEXPAND);
-#if ENABLE_LAYOUT_NO_RESTART
     SetSizer(sizer);
     // initialize layout from config
     update_layout();
     sizer->SetSizeHints(this);
     Fit();
-#else
-    if (m_plater && m_layout != slOld)
-        sizer->Add(m_plater, 1, wxEXPAND);
-
-    if (m_tabpanel && m_layout != slDlg)
-        sizer->Add(m_tabpanel, 1, wxEXPAND);
-
-    sizer->SetSizeHints(this);
-    SetSizer(sizer);
-    Fit();
-#endif // !ENABLE_LAYOUT_NO_RESTART
 
     const wxSize min_size = wxGetApp().get_min_size(); //wxSize(76*wxGetApp().em_unit(), 49*wxGetApp().em_unit());
 #ifdef __APPLE__
@@ -247,12 +215,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
     });
 
     wxGetApp().persist_window_geometry(this, true);
-#if ENABLE_LAYOUT_NO_RESTART
     wxGetApp().persist_window_geometry(&m_settings_dialog, true);
-#else
-    if (m_settings_dialog != nullptr)
-        wxGetApp().persist_window_geometry(m_settings_dialog, true);
-#endif // ENABLE_LAYOUT_NO_RESTART
 
     update_ui_from_settings();    // FIXME (?)
 
@@ -260,7 +223,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
         m_plater->show_action_buttons(true);
 }
 
-#if ENABLE_LAYOUT_NO_RESTART
 void MainFrame::update_layout()
 {
     auto restore_to_creation = [this]() {
@@ -375,7 +337,6 @@ void MainFrame::update_layout()
     Layout();
     Thaw();
 }
-#endif // ENABLE_LAYOUT_NO_RESTART
 
 // Called when closing the application and when switching the application language.
 void MainFrame::shutdown()
@@ -409,20 +370,9 @@ void MainFrame::shutdown()
     // In addition, there were some crashes due to the Paint events sent to already destructed windows.
     this->Show(false);
 
-#if ENABLE_LAYOUT_NO_RESTART
     if (m_settings_dialog.IsShown())
         // call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry()
         m_settings_dialog.Close();
-#else
-    if (m_settings_dialog != nullptr)
-    {
-        if (m_settings_dialog->IsShown())
-            // call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry()
-            m_settings_dialog->Close();
-
-        m_settings_dialog->Destroy();
-    }
-#endif // ENABLE_LAYOUT_NO_RESTART
 
 	// Stop the background thread (Windows and Linux).
 	// Disconnect from a 3DConnextion driver (OSX).
@@ -481,7 +431,6 @@ void MainFrame::update_title()
 
 void MainFrame::init_tabpanel()
 {
-#if ENABLE_LAYOUT_NO_RESTART
     // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10
     // with multiple high resolution displays connected.
     m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
@@ -490,27 +439,6 @@ void MainFrame::init_tabpanel()
 #endif
     m_tabpanel->Hide();
     m_settings_dialog.set_tabpanel(m_tabpanel);
-#else
-    m_layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? slOld :
-        wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? slNew :
-        wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? slDlg : slOld;
-
-    // From the very beginning the Print settings should be selected
-    m_last_selected_tab = m_layout == slDlg ? 0 : 1;
-
-    if (m_layout == slDlg) {
-        m_settings_dialog = new SettingsDialog(this);
-        m_tabpanel = m_settings_dialog->get_tabpanel();
-    }
-    else {
-        // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10
-        // with multiple high resolution displays connected.
-        m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
-#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList
-        m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font());
-#endif
-    }
-#endif // ENABLE_LAYOUT_NO_RESTART
 
     m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) {
         wxWindow* panel = m_tabpanel->GetCurrentPage();
@@ -531,20 +459,9 @@ void MainFrame::init_tabpanel()
             select_tab(0); // select Plater
     });
 
-#if ENABLE_LAYOUT_NO_RESTART
     m_plater = new Plater(this, this);
     m_plater->Hide();
-#else
-    if (m_layout == slOld) {
-        m_plater = new Plater(m_tabpanel, this);
-        m_tabpanel->AddPage(m_plater, _L("Plater"));
-    }
-    else {
-        m_plater = new Plater(this, this);
-        if (m_layout == slNew)
-            m_tabpanel->AddPage(new wxPanel(m_tabpanel), _L("Plater")); // empty panel just for Plater tab
-    }
-#endif // ENABLE_LAYOUT_NO_RESTART
+
     wxGetApp().plater_ = m_plater;
 
     wxGetApp().obj_list()->create_popup_menus();
@@ -686,7 +603,6 @@ bool MainFrame::can_slice() const
 
 bool MainFrame::can_change_view() const
 {
-#if ENABLE_LAYOUT_NO_RESTART
     switch (m_layout)
     {
     default:                   { return false; }
@@ -697,15 +613,6 @@ bool MainFrame::can_change_view() const
         return page_id != wxNOT_FOUND && dynamic_cast<const Slic3r::GUI::Plater*>(m_tabpanel->GetPage((size_t)page_id)) != nullptr;
     }
     }
-#else
-    if (m_layout == slNew)
-        return m_plater->IsShown();
-    if (m_layout == slDlg)
-        return true;
-    // slOld layout mode
-    int page_id = m_tabpanel->GetSelection();
-    return page_id != wxNOT_FOUND && dynamic_cast<const Slic3r::GUI::Plater*>(m_tabpanel->GetPage((size_t)page_id)) != nullptr;
-#endif // ENABLE_LAYOUT_NO_RESTART
 }
 
 bool MainFrame::can_select() const
@@ -746,11 +653,7 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
     wxGetApp().plater()->msw_rescale();
 
     // update Tabs
-#if ENABLE_LAYOUT_NO_RESTART
     if (m_layout != ESettingsLayout::Dlg) // Do not update tabs if the Settings are in the separated dialog
-#else
-    if (m_layout != slDlg) // Update tabs later, from the SettingsDialog, when the Settings are in the separated dialog
-#endif // ENABLE_LAYOUT_NO_RESTART
         for (auto tab : wxGetApp().tabs_list)
             tab->msw_rescale();
 
@@ -779,10 +682,8 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
 
     this->Maximize(is_maximized);
 
-#if ENABLE_LAYOUT_NO_RESTART
     if (m_layout == ESettingsLayout::Dlg)
         rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog);
-#endif // ENABLE_LAYOUT_NO_RESTART
 }
 
 void MainFrame::on_sys_color_changed()
@@ -1516,25 +1417,15 @@ void MainFrame::load_config(const DynamicPrintConfig& config)
 void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
 {
     bool tabpanel_was_hidden = false;
-#if ENABLE_LAYOUT_NO_RESTART
     if (m_layout == ESettingsLayout::Dlg) {
-#else
-    if (m_layout == slDlg) {
-#endif // ENABLE_LAYOUT_NO_RESTART
         if (tab==0) {
-#if ENABLE_LAYOUT_NO_RESTART
             if (m_settings_dialog.IsShown())
                 this->SetFocus();
-#else
-            if (m_settings_dialog->IsShown())
-                this->SetFocus();
-#endif // ENABLE_LAYOUT_NO_RESTART
             // plater should be focused for correct navigation inside search window
             if (m_plater->canvas3D()->is_search_pressed())
                 m_plater->SetFocus();
             return;
         }
-#if ENABLE_LAYOUT_NO_RESTART
         // Show/Activate Settings Dialog
 #ifdef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList
         if (m_settings_dialog.IsShown())
@@ -1551,28 +1442,11 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
             m_settings_dialog.Show();
         }
 #endif
-#else
-        // Show/Activate Settings Dialog
-        if (m_settings_dialog->IsShown())
-#ifdef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList
-            m_settings_dialog->Hide();
-#else
-            m_settings_dialog->SetFocus();
-        else
-#endif
-        m_settings_dialog->Show();
-#endif // ENABLE_LAYOUT_NO_RESTART
     }
-#if ENABLE_LAYOUT_NO_RESTART
     else if (m_layout == ESettingsLayout::New) {
         m_main_sizer->Show(m_plater, tab == 0);
         tabpanel_was_hidden = !m_main_sizer->IsShown(m_tabpanel);
         m_main_sizer->Show(m_tabpanel, tab != 0);
-#else
-    else if (m_layout == slNew) {
-        m_plater->Show(tab == 0);
-        m_tabpanel->Show(tab != 0);
-#endif // ENABLE_LAYOUT_NO_RESTART
 
         // plater should be focused for correct navigation inside search window
         if (tab == 0 && m_plater->canvas3D()->is_search_pressed())
@@ -1589,11 +1463,7 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
             tab->update_changed_tree_ui();
 
     // when tab == -1, it means we should show the last selected tab
-#if ENABLE_LAYOUT_NO_RESTART
     m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab);
-#else
-    m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == slDlg && tab != 0) ? tab-1 : tab);
-#endif // ENABLE_LAYOUT_NO_RESTART
 }
 
 // Set a camera direction, zoom to all objects.
@@ -1722,34 +1592,6 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe)
     SetIcon(wxIcon(var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG));
 #endif // _WIN32
 
-#if !ENABLE_LAYOUT_NO_RESTART
-    // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10
-    // with multiple high resolution displays connected.
-    m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxGetApp().get_min_size(), wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
-#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList
-    m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font());
-#endif
-
-    m_tabpanel->Bind(wxEVT_KEY_UP, [this](wxKeyEvent& evt) {
-        if ((evt.GetModifiers() & wxMOD_CONTROL) != 0) {
-            switch (evt.GetKeyCode()) {
-            case '1': { m_main_frame->select_tab(0); break; }
-            case '2': { m_main_frame->select_tab(1); break; }
-            case '3': { m_main_frame->select_tab(2); break; }
-            case '4': { m_main_frame->select_tab(3); break; }
-#ifdef __APPLE__
-            case 'f':
-#else /* __APPLE__ */
-            case WXK_CONTROL_F:
-#endif /* __APPLE__ */
-            case 'F': { m_main_frame->plater()->search(false); break; }
-            default:break;
-            }
-        }
-    });
-#endif // !ENABLE_LAYOUT_NO_RESTART
-
-#if ENABLE_LAYOUT_NO_RESTART
     this->Bind(wxEVT_SHOW, [this](wxShowEvent& evt) {
 
         auto key_up_handker = [this](wxKeyEvent& evt) {
@@ -1779,13 +1621,9 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe)
                 m_tabpanel->Unbind(wxEVT_KEY_UP, key_up_handker);
         }
         });
-#endif // ENABLE_LAYOUT_NO_RESTART
 
     // initialize layout
     auto sizer = new wxBoxSizer(wxVERTICAL);
-#if !ENABLE_LAYOUT_NO_RESTART
-    sizer->Add(m_tabpanel, 1, wxEXPAND);
-#endif // !ENABLE_LAYOUT_NO_RESTART
     sizer->SetSizeHints(this);
     SetSizer(sizer);
     Fit();
diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp
index 4514b8f50..3c93f6b58 100644
--- a/src/slic3r/GUI/MainFrame.hpp
+++ b/src/slic3r/GUI/MainFrame.hpp
@@ -55,11 +55,7 @@ class SettingsDialog : public DPIDialog
 public:
     SettingsDialog(MainFrame* mainframe);
     ~SettingsDialog() {}
-#if ENABLE_LAYOUT_NO_RESTART
     void set_tabpanel(wxNotebook* tabpanel) { m_tabpanel = tabpanel; }
-#else
-    wxNotebook* get_tabpanel() { return m_tabpanel; }
-#endif // ENABLE_LAYOUT_NO_RESTART
 
 protected:
     void on_dpi_changed(const wxRect& suggested_rect) override;
@@ -119,7 +115,6 @@ class MainFrame : public DPIFrame
 
     wxFileHistory m_recent_projects;
 
-#if ENABLE_LAYOUT_NO_RESTART
     enum class ESettingsLayout
     {
         Unknown,
@@ -129,13 +124,6 @@ class MainFrame : public DPIFrame
     };
     
     ESettingsLayout m_layout{ ESettingsLayout::Unknown };
-#else
-    enum SettingsLayout {
-        slOld = 0,
-        slNew,
-        slDlg,
-    }               m_layout;
-#endif // ENABLE_LAYOUT_NO_RESTART
 
 protected:
     virtual void on_dpi_changed(const wxRect &suggested_rect);
@@ -145,9 +133,7 @@ public:
     MainFrame();
     ~MainFrame() = default;
 
-#if ENABLE_LAYOUT_NO_RESTART
     void update_layout();
-#endif // ENABLE_LAYOUT_NO_RESTART
 
 	// Called when closing the application and when switching the application language.
 	void 		shutdown();
@@ -190,12 +176,8 @@ public:
 
     Plater*             m_plater { nullptr };
     wxNotebook*         m_tabpanel { nullptr };
-#if ENABLE_LAYOUT_NO_RESTART
     SettingsDialog      m_settings_dialog;
     wxWindow*           m_plater_page{ nullptr };
-#else
-    SettingsDialog*     m_settings_dialog { nullptr };
-#endif // ENABLE_LAYOUT_NO_RESTART
     wxProgressDialog*   m_progress_dialog { nullptr };
     std::shared_ptr<ProgressStatusBar>  m_statusbar;
 
diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp
index 581f50a88..ee0abe76f 100644
--- a/src/slic3r/GUI/MeshUtils.cpp
+++ b/src/slic3r/GUI/MeshUtils.cpp
@@ -134,7 +134,7 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
     Vec3d direction;
     line_from_mouse_pos(mouse_pos, trafo, camera, point, direction);
 
-    std::vector<sla::EigenMesh3D::hit_result> hits = m_emesh.query_ray_hits(point, direction);
+    std::vector<sla::IndexedMesh::hit_result> hits = m_emesh.query_ray_hits(point, direction);
 
     if (hits.empty())
         return false; // no intersection found
@@ -184,7 +184,7 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
 
         bool is_obscured = false;
         // Cast a ray in the direction of the camera and look for intersection with the mesh:
-        std::vector<sla::EigenMesh3D::hit_result> hits;
+        std::vector<sla::IndexedMesh::hit_result> hits;
         // Offset the start of the ray by EPSILON to account for numerical inaccuracies.
         hits = m_emesh.query_ray_hits((inverse_trafo * pt + direction_to_camera_mesh * EPSILON).cast<double>(),
                                       direction_to_camera.cast<double>());
diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp
index 2758577a2..60dcb30c8 100644
--- a/src/slic3r/GUI/MeshUtils.hpp
+++ b/src/slic3r/GUI/MeshUtils.hpp
@@ -3,7 +3,7 @@
 
 #include "libslic3r/Point.hpp"
 #include "libslic3r/Geometry.hpp"
-#include "libslic3r/SLA/EigenMesh3D.hpp"
+#include "libslic3r/SLA/IndexedMesh.hpp"
 #include "admesh/stl.h"
 
 #include "slic3r/GUI/3DScene.hpp"
@@ -147,7 +147,7 @@ public:
     Vec3f get_triangle_normal(size_t facet_idx) const;
 
 private:
-    sla::EigenMesh3D m_emesh;
+    sla::IndexedMesh m_emesh;
     std::vector<stl_normal> m_normals;
 };
 
diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp
index 91d2414d5..9bbbf92a0 100644
--- a/src/slic3r/GUI/Mouse3DController.cpp
+++ b/src/slic3r/GUI/Mouse3DController.cpp
@@ -7,6 +7,7 @@
 #include "AppConfig.hpp"
 #include "GLCanvas3D.hpp"
 #include "Plater.hpp"
+#include "NotificationManager.hpp"
 
 #include <wx/glcanvas.h>
 
@@ -239,8 +240,7 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
 
     // when the user clicks on [X] or [Close] button we need to trigger
     // an extra frame to let the dialog disappear
-    if (m_settings_dialog_closed_by_user)
-    {
+    if (m_settings_dialog_closed_by_user) {
         m_show_settings_dialog = false;
         m_settings_dialog_closed_by_user = false;
         canvas.request_extra_frame();
@@ -261,13 +261,10 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
 
     static ImVec2 last_win_size(0.0f, 0.0f);
     bool shown = true;
-    if (imgui.begin(_(L("3Dconnexion settings")), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse))
-    {
-        if (shown)
-        {
+    if (imgui.begin(_L("3Dconnexion settings"), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse)) {
+        if (shown) {
             ImVec2 win_size = ImGui::GetWindowSize();
-            if ((last_win_size.x != win_size.x) || (last_win_size.y != win_size.y))
-            {
+            if (last_win_size.x != win_size.x || last_win_size.y != win_size.y) {
                 // when the user clicks on [X] button, the next time the dialog is shown 
                 // has a dummy size, so we trigger an extra frame to let it have the correct size
                 last_win_size = win_size;
@@ -275,59 +272,51 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
             }
 
             const ImVec4& color = ImGui::GetStyleColorVec4(ImGuiCol_Separator);
-            ImGui::PushStyleColor(ImGuiCol_Text, color);
-            imgui.text(_(L("Device:")));
-            ImGui::PopStyleColor();
+            imgui.text_colored(color, _L("Device:"));
             ImGui::SameLine();
             imgui.text(m_device_str);
 
             ImGui::Separator();
-            ImGui::PushStyleColor(ImGuiCol_Text, color);
-            imgui.text(_(L("Speed:")));
-            ImGui::PopStyleColor();
+            imgui.text_colored(color, _L("Speed:"));
 
             float translation_scale = (float)params_copy.translation.scale / Params::DefaultTranslationScale;
-            if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) {
+            if (imgui.slider_float(_L("Translation") + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) {
             	params_copy.translation.scale = Params::DefaultTranslationScale * (double)translation_scale;
             	params_changed = true;
             }
 
             float rotation_scale = params_copy.rotation.scale / Params::DefaultRotationScale;
-            if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) {
+            if (imgui.slider_float(_L("Rotation") + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) {
             	params_copy.rotation.scale = Params::DefaultRotationScale * rotation_scale;
             	params_changed = true;
             }
 
             float zoom_scale = params_copy.zoom.scale / Params::DefaultZoomScale;
-            if (imgui.slider_float(_(L("Zoom")), &zoom_scale, 0.1f, 10.0f, "%.1f")) {
+            if (imgui.slider_float(_L("Zoom"), &zoom_scale, 0.1f, 10.0f, "%.1f")) {
             	params_copy.zoom.scale = Params::DefaultZoomScale * zoom_scale;
             	params_changed = true;
             }
 
             ImGui::Separator();
-            ImGui::PushStyleColor(ImGuiCol_Text, color);
-            imgui.text(_(L("Deadzone:")));
-            ImGui::PopStyleColor();
+            imgui.text_colored(color, _L("Deadzone:"));
 
             float translation_deadzone = (float)params_copy.translation.deadzone;
-            if (imgui.slider_float(_(L("Translation")) + "/" + _(L("Zoom")), &translation_deadzone, 0.0f, (float)Params::MaxTranslationDeadzone, "%.2f")) {
+            if (imgui.slider_float(_L("Translation") + "/" + _L("Zoom"), &translation_deadzone, 0.0f, (float)Params::MaxTranslationDeadzone, "%.2f")) {
             	params_copy.translation.deadzone = (double)translation_deadzone;
             	params_changed = true;
             }
 
             float rotation_deadzone = params_copy.rotation.deadzone;
-            if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, Params::MaxRotationDeadzone, "%.2f")) {
+            if (imgui.slider_float(_L("Rotation") + "##2", &rotation_deadzone, 0.0f, Params::MaxRotationDeadzone, "%.2f")) {
             	params_copy.rotation.deadzone = rotation_deadzone;
             	params_changed = true;
             }
 
             ImGui::Separator();
-            ImGui::PushStyleColor(ImGuiCol_Text, color);
-            imgui.text(_(L("Options:")));
-            ImGui::PopStyleColor();
+            imgui.text_colored(color, _L("Options:"));
 
             bool swap_yz = params_copy.swap_yz;
-            if (imgui.checkbox("Swap Y/Z axes", swap_yz)) {
+            if (imgui.checkbox(_L("Swap Y/Z axes"), swap_yz)) {
                 params_copy.swap_yz = swap_yz;
                 params_changed = true;
             }
@@ -335,25 +324,20 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
             ImGui::Separator();
             ImGui::Separator();
-            ImGui::PushStyleColor(ImGuiCol_Text, color);
-            imgui.text("DEBUG:");
-            imgui.text("Vectors:");
-            ImGui::PopStyleColor();
+            imgui.text_colored(color, "DEBUG:");
+            imgui.text_colored(color, "Vectors:");
             Vec3f translation = m_state.get_first_vector_of_type(State::QueueItem::TranslationType).cast<float>();
             Vec3f rotation = m_state.get_first_vector_of_type(State::QueueItem::RotationType).cast<float>();
             ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
             ImGui::InputFloat3("Rotation##3", rotation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
 
-            ImGui::PushStyleColor(ImGuiCol_Text, color);
-            imgui.text("Queue size:");
-            ImGui::PopStyleColor();
+            imgui.text_colored(color, "Queue size:");
 
             int input_queue_size_current[2] = { int(m_state.input_queue_size_current()), int(m_state.input_queue_max_size_achieved) };
             ImGui::InputInt2("Current##4", input_queue_size_current, ImGuiInputTextFlags_ReadOnly);
 
             int input_queue_size_param = int(params_copy.input_queue_max_size);
-            if (ImGui::InputInt("Max size", &input_queue_size_param, 1, 1, ImGuiInputTextFlags_ReadOnly))
-            {
+            if (ImGui::InputInt("Max size", &input_queue_size_param, 1, 1, ImGuiInputTextFlags_ReadOnly)) {
                 if (input_queue_size_param > 0) {
 	            	params_copy.input_queue_max_size = input_queue_size_param;
     	        	params_changed = true;
@@ -361,23 +345,19 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
             }
 
             ImGui::Separator();
-            ImGui::PushStyleColor(ImGuiCol_Text, color);
-            imgui.text("Camera:");
-            ImGui::PopStyleColor();
+            imgui.text_colored(color, "Camera:");
             Vec3f target = wxGetApp().plater()->get_camera().get_target().cast<float>();
             ImGui::InputFloat3("Target", target.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
 
             ImGui::Separator();
-            if (imgui.button(_(L("Close"))))
-            {
+            if (imgui.button(_L("Close"))) {
                 // the user clicked on the [Close] button
                 m_settings_dialog_closed_by_user = true;
                 canvas.set_as_dirty();
             }
         }
-        else
-        {
+        else {
             // the user clicked on the [X] button
             m_settings_dialog_closed_by_user = true;
             canvas.set_as_dirty();
@@ -424,6 +404,8 @@ void Mouse3DController::disconnected()
         m_params_by_device[m_device_str] = m_params_ui;
 	    m_device_str.clear();
 	    m_connected = false;
+		wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::Mouse3dDisconnected, *(wxGetApp().plater()->get_current_canvas3D()));
+
         wxGetApp().plater()->CallAfter([]() {
         	Plater *plater = wxGetApp().plater();
         	if (plater != nullptr) {
diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp
new file mode 100644
index 000000000..b7301f3d8
--- /dev/null
+++ b/src/slic3r/GUI/NotificationManager.cpp
@@ -0,0 +1,918 @@
+#include "NotificationManager.hpp"
+
+#include "GUI_App.hpp"
+#include "Plater.hpp"
+#include "GLCanvas3D.hpp"
+#include "ImGuiWrapper.hpp"
+
+#include "wxExtensions.hpp"
+
+#include <boost/algorithm/string.hpp>
+#include <boost/log/trivial.hpp>
+#include <wx/glcanvas.h>
+#include <iostream>
+
+
+
+
+#define NOTIFICATION_MAX_MOVE 3.0f
+
+#define GAP_WIDTH 10.0f
+#define SPACE_RIGHT_PANEL 10.0f
+
+namespace Slic3r {
+namespace GUI {
+
+wxDEFINE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent);
+wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
+wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent);
+
+namespace Notifications_Internal{
+	void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity)
+	{
+		if (fading_out)
+			ImGui::PushStyleColor(idx, ImVec4(col.x, col.y, col.z, col.w * current_fade_opacity));
+		else
+			ImGui::PushStyleColor(idx, col);
+	}
+}
+//ScalableBitmap bmp_icon;
+//------PopNotification--------
+NotificationManager::PopNotification::PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler) :
+	  m_data                (n)
+	, m_id                  (id)    
+	, m_remaining_time      (n.duration)
+	, m_last_remaining_time (n.duration)
+	, m_counting_down       (n.duration != 0)
+	, m_text1               (n.text1)
+    , m_hypertext           (n.hypertext)
+    , m_text2               (n.text2)
+	, m_evt_handler         (evt_handler)
+{
+	init();
+}
+NotificationManager::PopNotification::~PopNotification()
+{
+}
+NotificationManager::PopNotification::RenderResult NotificationManager::PopNotification::render(GLCanvas3D& canvas, const float& initial_y)
+{
+	if (m_finished)
+		return RenderResult::Finished;
+	if (m_close_pending) {
+		// request of extra frame will be done in caller function by ret val ClosePending
+		m_finished = true;
+		return RenderResult::ClosePending;
+	}
+	if (m_hidden) {
+		m_top_y = initial_y - GAP_WIDTH;
+		return RenderResult::Static;
+	}
+	RenderResult    ret_val = m_counting_down ? RenderResult::Countdown : RenderResult::Static;
+	Size            cnv_size = canvas.get_canvas_size();
+	ImGuiWrapper&   imgui = *wxGetApp().imgui();
+	bool            shown = true;
+	std::string     name;
+	ImVec2          mouse_pos = ImGui::GetMousePos();
+
+	if (m_line_height != ImGui::CalcTextSize("A").y)
+		init();
+	
+	set_next_window_size(imgui);
+
+	//top y of window
+	m_top_y = initial_y + m_window_height;
+	//top right position
+	ImVec2 win_pos(1.0f * (float)cnv_size.get_width() - SPACE_RIGHT_PANEL, 1.0f * (float)cnv_size.get_height() - m_top_y);
+	imgui.set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f);
+	imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always);
+
+	//find if hovered
+	if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y&& mouse_pos.y < win_pos.y + m_window_height)
+	{
+		ImGui::SetNextWindowFocus();
+		ret_val = RenderResult::Hovered;
+		//reset fading
+		m_fading_out = false;
+		m_current_fade_opacity = 1.f;
+		m_remaining_time = m_data.duration;
+		m_countdown_frame = 0;
+	}
+
+	if (m_counting_down && m_remaining_time < 0)
+		m_close_pending = true;
+
+	if (m_close_pending) {
+		// request of extra frame will be done in caller function by ret val ClosePending
+		m_finished = true;
+		return RenderResult::ClosePending;
+	}
+
+	// color change based on fading out
+	bool fading_pop = false;
+	if (m_fading_out) {
+		if (!m_paused)
+			m_current_fade_opacity -= 1.f / ((m_fading_time + 1.f) * 60.f);
+		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
+		Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity);
+		fading_pop = true;
+	}
+	// background color
+	if (m_is_gray) {
+		ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f);
+		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
+	} else if (m_data.level == NotificationLevel::ErrorNotification) {
+		ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
+		backcolor.x += 0.3f;
+		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
+	} else if (m_data.level == NotificationLevel::WarningNotification) {
+		ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
+		backcolor.x += 0.3f;
+		backcolor.y += 0.15f;
+		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
+	}
+
+	//name of window - probably indentifies window and is shown so last_end add whitespaces according to id
+	for (size_t i = 0; i < m_id; i++)
+		name += " ";
+	if (imgui.begin(name, &shown, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar )) {
+		if (shown) {
+			
+			ImVec2 win_size = ImGui::GetWindowSize();
+			
+			
+			//FIXME: dont forget to us this for texts
+			//GUI::format(_utf8(L()));
+			
+			/*
+			//countdown numbers
+			ImGui::SetCursorPosX(15);
+			ImGui::SetCursorPosY(15);
+			imgui.text(std::to_string(m_remaining_time).c_str());
+			*/
+			if(m_counting_down)
+				render_countdown(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
+			render_left_sign(imgui);
+			render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
+			render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
+			if (m_multiline && m_lines_count > 3)
+				render_minimize_button(imgui, win_pos.x, win_pos.y);
+		} else {
+			// the user clicked on the [X] button ( ImGuiWindowFlags_NoTitleBar means theres no [X] button)
+			m_close_pending = true;
+			canvas.set_as_dirty();
+		}
+	}
+	imgui.end();
+	
+	if (fading_pop) {
+		ImGui::PopStyleColor();
+		ImGui::PopStyleColor();
+	}
+	if (m_is_gray)
+		ImGui::PopStyleColor();
+	else if (m_data.level == NotificationLevel::ErrorNotification)
+		ImGui::PopStyleColor();
+	else if (m_data.level == NotificationLevel::WarningNotification)
+		ImGui::PopStyleColor();
+	return ret_val;
+}
+void NotificationManager::PopNotification::init()
+{
+	std::string text          = m_text1 + " " + m_hypertext;
+	int         last_end      = 0;
+	            m_lines_count = 0;
+
+	//determine line width 
+	m_line_height = ImGui::CalcTextSize("A").y;
+
+	m_left_indentation = m_line_height;
+	if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) {
+		std::string text;
+		text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker);
+		float picture_width = ImGui::CalcTextSize(text.c_str()).x;
+		m_left_indentation = picture_width + m_line_height / 2;
+	}
+	m_window_width_offset = m_left_indentation + m_line_height * 2;
+	m_window_width = m_line_height * 25;
+	
+	// count lines
+	m_endlines.clear();
+	while (last_end < text.length() - 1)
+	{
+		int next_hard_end = text.find_first_of('\n', last_end);
+		if (next_hard_end > 0 && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) {
+			//next line is ended by '/n'
+			m_endlines.push_back(next_hard_end);
+			last_end = next_hard_end + 1;
+		}
+		else {
+			// find next suitable endline
+			if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - 3.5f * m_line_height) {// m_window_width_offset) {
+				// more than one line till end
+				int next_space = text.find_first_of(' ', last_end);
+				if (next_space > 0) {
+					int next_space_candidate = text.find_first_of(' ', next_space + 1);
+					while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) {
+						next_space = next_space_candidate;
+						next_space_candidate = text.find_first_of(' ', next_space + 1);
+					}
+					m_endlines.push_back(next_space);
+					last_end = next_space + 1;
+				}
+			}
+			else {
+				m_endlines.push_back(text.length());
+				last_end = text.length();
+			}
+
+		}
+		m_lines_count++;
+	}
+}
+void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui)
+{ 
+	if (m_multiline) {
+		m_window_height = m_lines_count * m_line_height;
+	}else
+	{
+		m_window_height = 2 * m_line_height;
+	}
+	m_window_height += 1 * m_line_height; // top and bottom
+}
+
+void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
+{
+	ImVec2      win_size(win_size_x, win_size_y);
+	ImVec2      win_pos(win_pos_x, win_pos_y);
+	float       x_offset = m_left_indentation;
+	std::string fulltext = m_text1 + m_hypertext; //+ m_text2;
+	ImVec2      text_size = ImGui::CalcTextSize(fulltext.c_str());
+	// text posistions are calculated by lines count
+	// large texts has "more" button or are displayed whole
+	// smaller texts are divided as one liners and two liners
+	if (m_lines_count > 2) {
+		if (m_multiline) {
+			
+			int last_end = 0;
+			float starting_y = m_line_height/2;//10;
+			float shift_y = m_line_height;// -m_line_height / 20;
+			for (size_t i = 0; i < m_lines_count; i++) {
+			    std::string line = m_text1.substr(last_end , m_endlines[i] - last_end);
+				last_end = m_endlines[i] + 1;
+				ImGui::SetCursorPosX(x_offset);
+				ImGui::SetCursorPosY(starting_y + i * shift_y);
+				imgui.text(line.c_str());
+			}
+			//hyperlink text
+			if (!m_hypertext.empty())
+			{
+				render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(m_endlines[m_lines_count - 2] + 1, m_endlines[m_lines_count - 1] - m_endlines[m_lines_count - 2] - 1).c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext);
+			}
+			
+			
+		} else {
+			// line1
+			ImGui::SetCursorPosX(x_offset);
+			ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
+			imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
+			// line2
+			std::string line = m_text1.substr(m_endlines[0] + 1, m_endlines[1] - m_endlines[0] - 1);
+			if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x)
+			{
+				line = line.substr(0, line.length() - 6);
+				line += "..";
+			}else
+				line += "  ";
+			ImGui::SetCursorPosX(x_offset);
+			ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2);
+			imgui.text(line.c_str());
+			// "More" hypertext
+			render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true);
+		}
+	} else {
+		//text 1
+		float cursor_y = win_size.y / 2 - text_size.y / 2;
+		float cursor_x = x_offset;
+		if(m_lines_count > 1) {
+			// line1
+			ImGui::SetCursorPosX(x_offset);
+			ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
+			imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
+			// line2
+			std::string line = m_text1.substr(m_endlines[0] + 1);
+			cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2;
+			ImGui::SetCursorPosX(x_offset);
+			ImGui::SetCursorPosY(cursor_y);
+			imgui.text(line.c_str());
+			cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x;
+		} else {
+			ImGui::SetCursorPosX(x_offset);
+			ImGui::SetCursorPosY(cursor_y);
+			imgui.text(m_text1.c_str());
+			cursor_x = x_offset + ImGui::CalcTextSize(m_text1.c_str()).x;
+		}
+		//hyperlink text
+		if (!m_hypertext.empty())
+		{
+			render_hypertext(imgui, cursor_x + 4, cursor_y, m_hypertext);
+		}
+
+		//notification text 2
+		//text 2 is suposed to be after the hyperlink - currently it is not used
+		/*
+		if (!m_text2.empty())
+		{
+			ImVec2 part_size = ImGui::CalcTextSize(m_hypertext.c_str());
+			ImGui::SetCursorPosX(win_size.x / 2 + text_size.x / 2 - part_size.x + 8 - x_offset);
+			ImGui::SetCursorPosY(cursor_y);
+			imgui.text(m_text2.c_str());
+		}
+		*/
+	}
+}
+
+void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, const float text_x, const float text_y, const std::string text, bool more)
+{
+	//invisible button
+	ImVec2 part_size = ImGui::CalcTextSize(text.c_str());
+	ImGui::SetCursorPosX(text_x -4);
+	ImGui::SetCursorPosY(text_y -5);
+	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
+	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
+	ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
+	if (imgui.button("   ", part_size.x + 6, part_size.y + 10))
+	{
+		if (more)
+		{
+			m_multiline = true;
+			set_next_window_size(imgui);
+		}
+		else {
+			on_text_click();
+			m_close_pending = true;
+		}
+	}
+	ImGui::PopStyleColor();
+	ImGui::PopStyleColor();
+	ImGui::PopStyleColor();
+
+	//hover color
+	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
+	if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
+		orange_color.y += 0.2f;
+
+	//text
+	Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity);
+	ImGui::SetCursorPosX(text_x);
+	ImGui::SetCursorPosY(text_y);
+	imgui.text(text.c_str());
+	ImGui::PopStyleColor();
+
+	//underline
+	ImVec2 lineEnd = ImGui::GetItemRectMax();
+	lineEnd.y -= 2;
+	ImVec2 lineStart = lineEnd;
+	lineStart.x = ImGui::GetItemRectMin().x;
+	ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.w * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))));
+
+}
+
+void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
+{
+	ImVec2 win_size(win_size_x, win_size_y);
+	ImVec2 win_pos(win_pos_x, win_pos_y);
+	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
+	orange_color.w = 0.8f;
+	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
+	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
+	Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
+	Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
+	ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
+
+
+	//button - if part if treggered
+	std::string button_text;
+	button_text = ImGui::CloseIconMarker;
+	
+	if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y),
+		                           ImVec2(win_pos.x, win_pos.y + win_size.y - (m_multiline? 2 * m_line_height : 0)),
+		                           true))
+	{
+		button_text = ImGui::CloseIconHoverMarker;
+	}
+	ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
+	ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
+	ImGui::SetCursorPosX(win_size.x - m_line_height * 2.25f);
+	ImGui::SetCursorPosY(win_size.y / 2 - button_size.y/2);
+	if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
+	{
+		m_close_pending = true;
+	}
+
+	//invisible large button
+	ImGui::SetCursorPosX(win_size.x - win_size.x / 10.f);
+	ImGui::SetCursorPosY(0);
+	if (imgui.button(" ", win_size.x / 10.f, win_size.y - (m_multiline ? 2 * m_line_height : 0)))
+	{
+		m_close_pending = true;
+	}
+	ImGui::PopStyleColor();
+	ImGui::PopStyleColor();
+	ImGui::PopStyleColor();
+	ImGui::PopStyleColor();
+	ImGui::PopStyleColor();
+}
+void NotificationManager::PopNotification::render_countdown(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
+{
+	/*
+	ImVec2 win_size(win_size_x, win_size_y);
+	ImVec2 win_pos(win_pos_x, win_pos_y);
+	
+	//countdown dots
+	std::string dot_text;
+	dot_text = m_remaining_time <= (float)m_data.duration / 4 * 3 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
+	ImGui::SetCursorPosX(win_size.x - m_line_height);
+	//ImGui::SetCursorPosY(win_size.y / 2 - 24);
+	ImGui::SetCursorPosY(0);
+	imgui.text(dot_text.c_str());
+
+	dot_text = m_remaining_time < m_data.duration / 2 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
+	ImGui::SetCursorPosX(win_size.x - m_line_height);
+	//ImGui::SetCursorPosY(win_size.y / 2 - 9);
+	ImGui::SetCursorPosY(win_size.y / 2 - m_line_height / 2);
+	imgui.text(dot_text.c_str());
+
+	dot_text = m_remaining_time <= m_data.duration / 4 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
+	ImGui::SetCursorPosX(win_size.x - m_line_height);
+	//ImGui::SetCursorPosY(win_size.y / 2 + 6);
+	ImGui::SetCursorPosY(win_size.y - m_line_height);
+	imgui.text(dot_text.c_str());
+	*/
+	if (!m_fading_out && m_remaining_time <= m_data.duration / 4) {
+		m_fading_out = true;
+		m_fading_time = m_remaining_time;
+	}
+	
+	if (m_last_remaining_time != m_remaining_time) {
+		m_last_remaining_time = m_remaining_time;
+		m_countdown_frame = 0;
+	}
+	/*
+	//countdown line
+	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
+	float  invisible_length = ((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x);
+	invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame);
+	ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length, win_pos_y + win_size_y - 5);
+	ImVec2 lineStart = ImVec2(win_pos_x - win_size_x, win_pos_y + win_size_y - 5);
+	ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.picture_width * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))), 2.f);
+	if (!m_paused)
+		m_countdown_frame++;
+		*/
+}
+void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui)
+{
+	if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) {
+		std::string text;
+		text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker);
+		ImGui::SetCursorPosX(m_line_height / 3);
+		ImGui::SetCursorPosY(m_window_height / 2 - m_line_height / 2);
+		imgui.text(text.c_str());
+	} 
+}
+void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y)
+{
+	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
+	orange_color.w = 0.8f;
+	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
+	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
+	Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
+	Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
+	Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
+
+	
+	//button - if part if treggered
+	std::string button_text;
+	button_text = ImGui::CloseIconMarker;
+	if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 10.f, win_pos_y + m_window_height - 2 * m_line_height + 1),
+		ImVec2(win_pos_x, win_pos_y + m_window_height),
+		true)) 
+	{
+		button_text = ImGui::CloseIconHoverMarker;
+	}
+	ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
+	ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
+	ImGui::SetCursorPosX(m_window_width - m_line_height * 2.25f);
+	ImGui::SetCursorPosY(m_window_height - button_size.y - 5);
+	if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
+	{
+		m_multiline = false;
+	}
+	
+	ImGui::PopStyleColor();
+	ImGui::PopStyleColor();
+	ImGui::PopStyleColor();
+	ImGui::PopStyleColor();
+	ImGui::PopStyleColor();
+}
+void NotificationManager::PopNotification::on_text_click()
+{
+	switch (m_data.type) {
+	case NotificationType::ExportToRemovableFinished :
+		assert(m_evt_handler != nullptr);
+		if (m_evt_handler != nullptr)
+			wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED));
+		break;
+	case NotificationType::SlicingComplete :
+		//wxGetApp().plater()->export_gcode(false);
+		assert(m_evt_handler != nullptr);
+		if (m_evt_handler != nullptr)
+			wxPostEvent(m_evt_handler, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED));
+		break;
+	case NotificationType::PresetUpdateAviable :
+		//wxGetApp().plater()->export_gcode(false);
+		assert(m_evt_handler != nullptr);
+		if (m_evt_handler != nullptr)
+			wxPostEvent(m_evt_handler, PresetUpdateAviableClickedEvent(EVT_PRESET_UPDATE_AVIABLE_CLICKED));
+		break;
+	case NotificationType::NewAppAviable:
+		wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases");
+		break;
+	default:
+		break;
+	}
+}
+void NotificationManager::PopNotification::update(const NotificationData& n)
+{
+	m_text1          = n.text1;
+	m_hypertext      = n.hypertext;
+    m_text2          = n.text2;
+	init();
+}
+bool NotificationManager::PopNotification::compare_text(const std::string& text)
+{
+	std::string t1(m_text1);
+	std::string t2(text);
+	t1.erase(std::remove_if(t1.begin(), t1.end(), ::isspace), t1.end());
+	t2.erase(std::remove_if(t2.begin(), t2.end(), ::isspace), t2.end());
+	if (t1.compare(t2) == 0)
+		return true;
+	return false;
+}
+
+NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool large) :
+	  NotificationManager::PopNotification(n, id, evt_handler)
+{
+	set_large(large);
+}
+void NotificationManager::SlicingCompleteLargeNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
+{
+	if (!m_is_large)
+		PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
+	else {
+		ImVec2 win_size(win_size_x, win_size_y);
+		ImVec2 win_pos(win_pos_x, win_pos_y);
+
+		ImVec2 text1_size = ImGui::CalcTextSize(m_text1.c_str());
+		float x_offset = m_left_indentation;
+		std::string fulltext = m_text1 + m_hypertext + m_text2;
+		ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str());
+		float cursor_y = win_size.y / 2 - text_size.y / 2;
+		if (m_has_print_info) {
+			x_offset = 20;
+			cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2;
+			ImGui::SetCursorPosX(x_offset);
+			ImGui::SetCursorPosY(cursor_y);
+			imgui.text(m_print_info.c_str());
+			cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2;
+		}
+		ImGui::SetCursorPosX(x_offset);
+		ImGui::SetCursorPosY(cursor_y);
+		imgui.text(m_text1.c_str());
+
+		render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext);
+		
+	}
+}
+void NotificationManager::SlicingCompleteLargeNotification::set_print_info(std::string info)
+{
+	m_print_info = info;
+	m_has_print_info = true;
+	if(m_is_large)
+		m_lines_count = 2;
+}
+void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l)
+{
+	m_is_large = l;
+	m_counting_down = !l;
+	m_hypertext = l ? _u8L("Export G-Code.") : std::string();
+	m_hidden = !l;
+}
+//------NotificationManager--------
+NotificationManager::NotificationManager(wxEvtHandler* evt_handler) :
+	m_evt_handler(evt_handler)
+{
+}
+NotificationManager::~NotificationManager()
+{
+	for (PopNotification* notification : m_pop_notifications)
+	{
+		delete notification;
+	}
+}
+void NotificationManager::push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp)
+{
+	auto it = std::find_if(basic_notifications.begin(), basic_notifications.end(),
+		boost::bind(&NotificationData::type, _1) == type);	
+	if (it != basic_notifications.end())
+		push_notification_data( *it, canvas, timestamp);
+}
+void NotificationManager::push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp)
+{
+	push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, canvas, timestamp );
+}
+void NotificationManager::push_notification(const std::string& text, NotificationManager::NotificationLevel level, GLCanvas3D& canvas, int timestamp)
+{
+	switch (level)
+	{
+	case Slic3r::GUI::NotificationManager::NotificationLevel::RegularNotification:
+		push_notification_data({ NotificationType::CustomNotification, level, 10, text }, canvas, timestamp);
+		break;
+	case Slic3r::GUI::NotificationManager::NotificationLevel::ErrorNotification:
+		push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp);
+
+		break;
+	case Slic3r::GUI::NotificationManager::NotificationLevel::ImportantNotification:
+		push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp);
+		break;
+	default:
+		break;
+	}
+}
+void NotificationManager::push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas)
+{
+	set_all_slicing_errors_gray(false);
+	push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotification, 0,  _u8L("ERROR:") + "\n" + text }, canvas, 0);
+	close_notification_of_type(NotificationType::SlicingComplete);
+}
+void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step)
+{
+	NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotification, 0,  _u8L("WARNING:") + "\n" + text };
+
+	NotificationManager::SlicingWarningNotification* notification = new NotificationManager::SlicingWarningNotification(data, m_next_id++, m_evt_handler);
+	notification->set_object_id(oid);
+	notification->set_warning_step(warning_step);
+	if 
+		(push_notification_data(notification, canvas, 0)) {
+		notification->set_gray(gray);		
+	}
+	else {
+		delete notification;
+	}
+	
+}
+void NotificationManager::push_plater_error_notification(const std::string& text, GLCanvas3D& canvas)
+{
+	push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0,  _u8L("ERROR:") + "\n" + text }, canvas, 0);
+}
+void NotificationManager::push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas)
+{
+	push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0,  _u8L("WARNING:") + "\n" + text }, canvas, 0);
+}
+void NotificationManager::close_plater_error_notification()
+{
+	for (PopNotification* notification : m_pop_notifications) {
+		if (notification->get_type() == NotificationType::PlaterError) {
+			notification->close();
+		}
+	}
+}
+void NotificationManager::close_plater_warning_notification(const std::string& text)
+{
+	for (PopNotification* notification : m_pop_notifications) {
+		if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) {
+			notification->close();
+		}
+	}
+}
+void NotificationManager::set_all_slicing_errors_gray(bool g)
+{
+	for (PopNotification* notification : m_pop_notifications) {
+		if (notification->get_type() == NotificationType::SlicingError) {
+			notification->set_gray(g);
+		}
+	}
+}
+void NotificationManager::set_all_slicing_warnings_gray(bool g)
+{
+	for (PopNotification* notification : m_pop_notifications) {
+		if (notification->get_type() == NotificationType::SlicingWarning) {
+			notification->set_gray(g);
+		}
+	}
+}
+void NotificationManager::set_slicing_warning_gray(const std::string& text, bool g)
+{
+	for (PopNotification* notification : m_pop_notifications) {
+		if (notification->get_type() == NotificationType::SlicingWarning && notification->compare_text(text)) {
+			notification->set_gray(g);
+		}
+	}
+}
+void NotificationManager::close_slicing_errors_and_warnings()
+{
+	for (PopNotification* notification : m_pop_notifications) {
+		if (notification->get_type() == NotificationType::SlicingError || notification->get_type() == NotificationType::SlicingWarning) {
+			notification->close();
+		}
+	}
+}
+void NotificationManager::push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large)
+{
+	std::string hypertext;
+	int         time = 10;
+	if(large)
+	{
+		hypertext = _u8L("Export G-Code.");
+		time = 0;
+	}
+	NotificationData data{ NotificationType::SlicingComplete, NotificationLevel::RegularNotification, time,  _u8L("Slicing finished."), hypertext };
+
+	NotificationManager::SlicingCompleteLargeNotification* notification = new NotificationManager::SlicingCompleteLargeNotification(data, m_next_id++, m_evt_handler, large);
+	if (push_notification_data(notification, canvas, timestamp)) {
+	} else {
+		delete notification;
+	}	
+}
+void NotificationManager::set_slicing_complete_print_time(std::string info)
+{
+	for (PopNotification* notification : m_pop_notifications) {
+		if (notification->get_type() == NotificationType::SlicingComplete) {
+			dynamic_cast<SlicingCompleteLargeNotification*>(notification)->set_print_info(info);
+			break;
+		}
+	}
+}
+void NotificationManager::set_slicing_complete_large(bool large)
+{
+	for (PopNotification* notification : m_pop_notifications) {
+		if (notification->get_type() == NotificationType::SlicingComplete) {
+			dynamic_cast<SlicingCompleteLargeNotification*>(notification)->set_large(large);
+			break;
+		}
+	}
+}
+void NotificationManager::close_notification_of_type(const NotificationType type)
+{
+	for (PopNotification* notification : m_pop_notifications) {
+		if (notification->get_type() == type) {
+			notification->close();
+		}
+	}
+}
+void NotificationManager::compare_warning_oids(const std::vector<size_t>& living_oids)
+{
+	for (PopNotification* notification : m_pop_notifications) {
+		if (notification->get_type() == NotificationType::SlicingWarning) {
+			auto w = dynamic_cast<SlicingWarningNotification*>(notification);
+			bool found = false;
+			for (size_t oid : living_oids) {
+				if (w->get_object_id() == oid) {
+					found = true;
+					break;
+				}
+			}
+			if (!found)
+				notification->close();
+		}
+	}
+}
+bool NotificationManager::push_notification_data(const NotificationData &notification_data,  GLCanvas3D& canvas, int timestamp)
+{
+	PopNotification* n = new PopNotification(notification_data, m_next_id++, m_evt_handler);
+	bool r = push_notification_data(n, canvas, timestamp);
+	if (!r)
+		delete n;
+	return r;
+}
+bool NotificationManager::push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp)
+{
+	// if timestamped notif, push only new one
+	if (timestamp != 0) {
+		if (m_used_timestamps.find(timestamp) == m_used_timestamps.end()) {
+			m_used_timestamps.insert(timestamp);
+		} else {
+			return false;
+		}
+	}
+	if (!this->find_older(notification)) {
+			m_pop_notifications.emplace_back(notification);
+		canvas.request_extra_frame();
+		return true;
+	} else {
+		m_pop_notifications.back()->update(notification->get_data());
+		canvas.request_extra_frame();
+		return false;
+	}
+}
+void NotificationManager::render_notifications(GLCanvas3D& canvas)
+{
+	float    last_x = 0.0f;
+	float    current_height = 0.0f;
+	bool     request_next_frame = false;
+	bool     render_main = false;
+	bool     hovered = false;	
+	sort_notifications();
+	// iterate thru notifications and render them / erease them
+	for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) {
+		if ((*it)->get_finished()) {
+			delete (*it);
+			it = m_pop_notifications.erase(it);
+		} else {
+			(*it)->set_paused(m_hovered);
+			PopNotification::RenderResult res = (*it)->render(canvas, last_x);
+			if (res != PopNotification::RenderResult::Finished) {
+				last_x = (*it)->get_top() + GAP_WIDTH;
+				current_height = std::max(current_height, (*it)->get_current_top());
+				render_main = true;
+			}
+			if (res == PopNotification::RenderResult::Countdown || res == PopNotification::RenderResult::ClosePending || res == PopNotification::RenderResult::Finished)
+				request_next_frame = true;
+			if (res == PopNotification::RenderResult::Hovered)
+				hovered = true;
+			++it;
+		}
+	}
+	m_hovered = hovered;
+
+	//actualizate timers and request frame if needed
+	wxWindow* p = dynamic_cast<wxWindow*> (wxGetApp().plater());
+	while (p->GetParent())
+		p = p->GetParent();
+	wxTopLevelWindow* top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
+	if (!top_level_wnd->IsActive())
+		return;
+
+	if (!m_hovered && m_last_time < wxGetLocalTime())
+	{
+		if (wxGetLocalTime() - m_last_time == 1)
+		{
+			for(auto notification : m_pop_notifications)
+			{
+				notification->substract_remaining_time();
+			}
+		}
+		m_last_time = wxGetLocalTime();
+	}
+
+	if (request_next_frame)
+		canvas.request_extra_frame();
+}
+
+
+void NotificationManager::sort_notifications()
+{
+	std::sort(m_pop_notifications.begin(), m_pop_notifications.end(), [](PopNotification* n1, PopNotification* n2) {
+		int n1l = (int)n1->get_data().level;
+		int n2l = (int)n2->get_data().level;
+		if (n1l == n2l && n1->get_is_gray() && !n2->get_is_gray())
+			return true;
+		return (n1l < n2l);
+		});
+}
+
+bool NotificationManager::find_older(NotificationManager::PopNotification* notification)
+{
+	NotificationType type = notification->get_type();
+	std::string text = notification->get_data().text1;
+	for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) {
+		if((*it)->get_type() == type && !(*it)->get_finished()) {
+			if (type == NotificationType::CustomNotification || type == NotificationType::PlaterWarning) {
+				if (!(*it)->compare_text(text))
+					continue;
+			}else if (type == NotificationType::SlicingWarning) {
+				auto w1 = dynamic_cast<SlicingWarningNotification*>(notification);
+				auto w2 = dynamic_cast<SlicingWarningNotification*>(*it);
+				if (w1 != nullptr && w2 != nullptr) {
+					if (!(*it)->compare_text(text) || w1->get_object_id() != w2->get_object_id()) {
+						continue;
+					}
+				} else {
+					continue;
+				}
+			}
+
+			if (it != m_pop_notifications.end() - 1)
+				std::rotate(it, it + 1, m_pop_notifications.end());
+			return true;
+		}
+	}
+	return false;
+}
+
+void NotificationManager::dpi_changed()
+{
+
+}
+
+}//namespace GUI
+}//namespace Slic3r
diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp
new file mode 100644
index 000000000..d7037c53e
--- /dev/null
+++ b/src/slic3r/GUI/NotificationManager.hpp
@@ -0,0 +1,268 @@
+#ifndef slic3r_GUI_NotificationManager_hpp_
+#define slic3r_GUI_NotificationManager_hpp_
+
+#include "Event.hpp"
+#include "I18N.hpp"
+
+#include <string>
+#include <vector>
+#include <deque>
+#include <unordered_set>
+
+namespace Slic3r {
+namespace GUI {
+
+using EjectDriveNotificationClickedEvent = SimpleEvent;
+wxDECLARE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent);
+using ExportGcodeNotificationClickedEvent = SimpleEvent;
+wxDECLARE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
+using PresetUpdateAviableClickedEvent = SimpleEvent;
+wxDECLARE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent);
+
+class GLCanvas3D;
+class ImGuiWrapper;
+
+enum class NotificationType
+{
+	CustomNotification,
+	SlicingComplete,
+	SlicingNotPossible,
+	ExportToRemovableFinished,
+	Mouse3dDisconnected,
+	Mouse3dConnected,
+	NewPresetsAviable,
+	NewAppAviable,
+	PresetUpdateAviable,
+	LoadingFailed,
+	ValidateError, // currently not used - instead Slicing error is used for both slicing and validate errors
+	SlicingError,
+	SlicingWarning,
+	PlaterError,
+	PlaterWarning,
+	ApplyError
+
+};
+class NotificationManager
+{
+public:
+	enum class NotificationLevel : int
+	{
+		ErrorNotification =     4,
+		WarningNotification =   3,
+		ImportantNotification = 2,
+		RegularNotification   = 1,
+	};
+	// duration 0 means not disapearing
+	struct NotificationData {
+		NotificationType    type;
+		NotificationLevel   level;
+		const int           duration;
+		const std::string   text1;
+		const std::string   hypertext = std::string();
+		const std::string   text2     = std::string();
+	};
+
+	//Pop notification - shows only once to user.
+	class PopNotification
+	{
+	public:
+		enum class RenderResult
+		{
+			Finished,
+			ClosePending,
+			Static,
+			Countdown,
+			Hovered
+		};
+		 PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler);
+		virtual ~PopNotification();
+		RenderResult           render(GLCanvas3D& canvas, const float& initial_y);
+		// close will dissapear notification on next render
+		void                   close() { m_close_pending = true; }
+		// data from newer notification of same type
+		void                   update(const NotificationData& n);
+		bool                   get_finished() const { return m_finished; }
+		// returns top after movement
+		float                  get_top() const { return m_top_y; }
+		//returns top in actual frame
+		float                  get_current_top() const { return m_top_y; }
+		const NotificationType get_type() const { return m_data.type; }
+		const NotificationData get_data() const { return m_data;  }
+		const bool             get_is_gray() const { return m_is_gray; }
+		// Call equals one second down
+		void                   substract_remaining_time() { m_remaining_time--; }
+		void                   set_gray(bool g) { m_is_gray = g; }
+		void                   set_paused(bool p) { m_paused = p; }
+		bool                   compare_text(const std::string& text);
+	protected:
+		// Call after every size change
+		void         init();
+		// Calculetes correct size but not se it in imgui!
+		virtual void set_next_window_size(ImGuiWrapper& imgui);
+		virtual void render_text(ImGuiWrapper& imgui,
+			                     const float win_size_x, const float win_size_y,
+			                     const float win_pos_x , const float win_pos_y);
+		void         render_close_button(ImGuiWrapper& imgui,
+			                             const float win_size_x, const float win_size_y,
+			                             const float win_pos_x , const float win_pos_y);
+		void         render_countdown(ImGuiWrapper& imgui,
+			                          const float win_size_x, const float win_size_y,
+			                          const float win_pos_x , const float win_pos_y);
+		void         render_hypertext(ImGuiWrapper& imgui,
+			                          const float text_x, const float text_y,
+		                              const std::string text,
+		                              bool more = false);
+		void         render_left_sign(ImGuiWrapper& imgui);
+		void         render_minimize_button(ImGuiWrapper& imgui,
+			                                const float win_pos_x, const float win_pos_y);
+		void         on_text_click();
+
+		const NotificationData m_data;
+
+		int              m_id;
+		// Main text
+		std::string      m_text1;
+		// Clickable text
+		std::string      m_hypertext;
+		// Aditional text after hypertext - currently not used
+		std::string      m_text2;
+		// Countdown variables
+		long             m_remaining_time;
+		bool             m_counting_down;
+		long             m_last_remaining_time;
+		bool             m_paused{ false };
+		int              m_countdown_frame{ 0 };
+		bool             m_fading_out{ false };
+		// total time left when fading beggins
+		float            m_fading_time{ 0.0f }; 
+		float            m_current_fade_opacity{ 1.f };
+		// If hidden the notif is alive but not visible to user
+		bool             m_hidden               { false };
+		//  m_finished = true - does not render, marked to delete
+		bool             m_finished             { false }; 
+		// Will go to m_finished next render
+		bool             m_close_pending        { false }; 
+		// variables to count positions correctly
+		float            m_window_width_offset;
+		float            m_left_indentation;
+		// Total size of notification window - varies based on monitor
+		float            m_window_height        { 56.0f };  
+		float            m_window_width         { 450.0f };
+		//Distance from bottom of notifications to top of this notification
+		float            m_top_y                { 0.0f };  
+		
+		// Height of text
+		// Used as basic scaling unit!
+		float            m_line_height;
+		std::vector<int> m_endlines;
+		// Gray are f.e. eorrors when its uknown if they are still valid
+		bool             m_is_gray              { false };
+		//if multiline = true, notification is showing all lines(>2)
+		bool             m_multiline            { false };
+		int              m_lines_count{ 1 };
+		wxEvtHandler*    m_evt_handler;
+	};
+
+	class SlicingCompleteLargeNotification : public PopNotification
+	{
+	public:
+		SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool largeds);
+		void set_large(bool l);
+		bool get_large() { return m_is_large; }
+
+		void set_print_info(std::string info);
+	protected:
+		virtual void render_text(ImGuiWrapper& imgui,
+			                     const float win_size_x, const float win_size_y,
+			                     const float win_pos_x, const float win_pos_y) 
+			                     override;
+
+		bool        m_is_large;
+		bool        m_has_print_info { false };
+		std::string m_print_info { std::string() };
+	};
+
+	class SlicingWarningNotification : public PopNotification
+	{
+	public:
+		SlicingWarningNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler) : PopNotification(n, id, evt_handler) {}
+		void         set_object_id(size_t id) { object_id = id; }
+		const size_t get_object_id() { return object_id; }
+		void         set_warning_step(int ws) { warning_step = ws; }
+		const int    get_warning_step() { return warning_step; }
+	protected:
+		size_t object_id;
+		int    warning_step;
+	};
+
+	NotificationManager(wxEvtHandler* evt_handler);
+	~NotificationManager();
+
+	
+	// only type means one of basic_notification (see below)
+	void push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp = 0);
+	// only text means Undefined type
+	void push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp = 0);
+	void push_notification(const std::string& text, NotificationLevel level, GLCanvas3D& canvas, int timestamp = 0);
+	// creates Slicing Error notification with custom text
+	void push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas);
+	// creates Slicing Warning notification with custom text
+	void push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step);
+	// marks slicing errors as gray
+	void set_all_slicing_errors_gray(bool g);
+	// marks slicing warings as gray
+	void set_all_slicing_warnings_gray(bool g);
+	void set_slicing_warning_gray(const std::string& text, bool g);
+	// imidietly stops showing slicing errors
+	void close_slicing_errors_and_warnings();
+	void compare_warning_oids(const std::vector<size_t>& living_oids);
+	void push_plater_error_notification(const std::string& text, GLCanvas3D& canvas);
+	void push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas);
+	void close_plater_error_notification();
+	void close_plater_warning_notification(const std::string& text);
+	// creates special notification slicing complete
+	// if large = true prints printing time and export button 
+	void push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large);
+	void set_slicing_complete_print_time(std::string info);
+	void set_slicing_complete_large(bool large);
+	// renders notifications in queue and deletes expired ones
+	void render_notifications(GLCanvas3D& canvas);
+	// finds and closes all notifications of given type
+	void close_notification_of_type(const NotificationType type);
+	void dpi_changed();
+private:
+	//pushes notification into the queue of notifications that are rendered
+	//can be used to create custom notification
+	bool push_notification_data(const NotificationData& notification_data, GLCanvas3D& canvas, int timestamp);
+	bool push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp);
+	//finds older notification of same type and moves it to the end of queue. returns true if found
+	bool find_older(NotificationManager::PopNotification* notification);
+	void sort_notifications();
+
+	wxEvtHandler*                m_evt_handler;
+	std::deque<PopNotification*> m_pop_notifications;
+	int                          m_next_id { 1 };
+	long                         m_last_time { 0 };
+	bool                         m_hovered { false };
+	//timestamps used for slining finished - notification could be gone so it needs to be stored here
+	std::unordered_set<int>      m_used_timestamps;
+
+	//prepared (basic) notifications
+	const std::vector<NotificationData> basic_notifications = {
+		{NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10,  _u8L("Slicing is not possible.")},
+		{NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0,  _u8L("Exporting finished."),  _u8L("Eject drive.") },
+		{NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10,  _u8L("3D Mouse disconnected.") },
+		{NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5,  _u8L("3D Mouse connected.") },
+		{NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20,  _u8L("New Presets are available."),  _u8L("See here.") },
+		{NotificationType::PresetUpdateAviable, NotificationLevel::ImportantNotification, 20,  _u8L("Configuration update is available."),  _u8L("See more.")},
+		{NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20,  _u8L("New version is available."),  _u8L("See Releases page.")},
+		//{NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20,  _u8L("New vesion of PrusaSlicer is available.",  _u8L("Download page.") },
+		//{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20,  _u8L("Loading of model has Failed") },
+		//{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10,  _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
+	};
+};
+
+}//namespace GUI
+}//namespace Slic3r
+
+#endif //slic3r_GUI_NotificationManager_hpp_
\ No newline at end of file
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 5a19215bb..2c330b60e 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -74,8 +74,10 @@
 #include "../Utils/PrintHost.hpp"
 #include "../Utils/FixModelByWin10.hpp"
 #include "../Utils/UndoRedo.hpp"
+#include "../Utils/PresetUpdater.hpp"
 #include "RemovableDriveManager.hpp"
 #include "InstanceCheck.hpp"
+#include "NotificationManager.hpp"
 #include "PresetComboBoxes.hpp"
 
 #ifdef __APPLE__
@@ -102,6 +104,7 @@ wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS,     SimpleEvent);
 wxDEFINE_EVENT(EVT_SLICING_UPDATE,                  SlicingStatusEvent);
 wxDEFINE_EVENT(EVT_SLICING_COMPLETED,               wxCommandEvent);
 wxDEFINE_EVENT(EVT_PROCESS_COMPLETED,               wxCommandEvent);
+wxDEFINE_EVENT(EVT_EXPORT_BEGAN,                    wxCommandEvent);
 
 // Sidebar widgets
 
@@ -569,7 +572,7 @@ struct Sidebar::priv
     wxButton *btn_export_gcode;
     wxButton *btn_reslice;
     ScalableButton *btn_send_gcode;
-    ScalableButton *btn_remove_device;
+    ScalableButton *btn_eject_device;
 	ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected)
 
     bool                is_collapsed {false};
@@ -742,12 +745,12 @@ Sidebar::Sidebar(Plater *parent)
     };
 
     init_scalable_btn(&p->btn_send_gcode   , "export_gcode", _L("Send to printer") + "\tCtrl+Shift+G");
-    init_scalable_btn(&p->btn_remove_device, "eject_sd"       , _L("Remove device") + "\tCtrl+T");
+    init_scalable_btn(&p->btn_eject_device, "eject_sd"       , _L("Remove device") + "\tCtrl+T");
 	init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export to SD card / Flash drive") + "\tCtrl+U");
 
     // regular buttons "Slice now" and "Export G-code" 
 
-    const int scaled_height = p->btn_remove_device->GetBitmapHeight() + 4;
+    const int scaled_height = p->btn_eject_device->GetBitmapHeight() + 4;
     auto init_btn = [this](wxButton **btn, wxString label, const int button_height) {
         *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition,
                             wxSize(-1, button_height), wxBU_EXACTFIT);
@@ -765,7 +768,7 @@ Sidebar::Sidebar(Plater *parent)
     complect_btns_sizer->Add(p->btn_export_gcode, 1, wxEXPAND);
     complect_btns_sizer->Add(p->btn_send_gcode);
 	complect_btns_sizer->Add(p->btn_export_gcode_removable);
-    complect_btns_sizer->Add(p->btn_remove_device);
+    complect_btns_sizer->Add(p->btn_eject_device);
 	
 
     btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, margin_5);
@@ -788,7 +791,7 @@ Sidebar::Sidebar(Plater *parent)
         p->plater->select_view_3D("Preview");
     });
     p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); });
-    p->btn_remove_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); });
+    p->btn_eject_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); });
 	p->btn_export_gcode_removable->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(true); });
 }
 
@@ -931,9 +934,9 @@ void Sidebar::msw_rescale()
     p->object_info->msw_rescale();
 
     p->btn_send_gcode->msw_rescale();
-    p->btn_remove_device->msw_rescale();
+    p->btn_eject_device->msw_rescale();
 	p->btn_export_gcode_removable->msw_rescale();
-    const int scaled_height = p->btn_remove_device->GetBitmap().GetHeight() + 4;
+    const int scaled_height = p->btn_eject_device->GetBitmap().GetHeight() + 4;
     p->btn_export_gcode->SetMinSize(wxSize(-1, scaled_height));
     p->btn_reslice     ->SetMinSize(wxSize(-1, scaled_height));
 
@@ -956,7 +959,7 @@ void Sidebar::sys_color_changed()
 
     // btn...->msw_rescale() updates icon on button, so use it
     p->btn_send_gcode->msw_rescale();
-    p->btn_remove_device->msw_rescale();
+    p->btn_eject_device->msw_rescale();
     p->btn_export_gcode_removable->msw_rescale();
 
     p->scrolled->Layout();
@@ -1192,6 +1195,12 @@ void Sidebar::update_sliced_info_sizer()
                     new_label += format_wxstr("\n   - %1%", _L("normal mode"));
                     info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time);
                     fill_labels(ps.estimated_normal_custom_gcode_print_times, new_label, info_text);
+
+					// uncomment next line to not disappear slicing finished notif when colapsing sidebar before time estimate
+					//if (p->plater->is_sidebar_collapsed())
+					p->plater->get_notification_manager()->set_slicing_complete_large(p->plater->is_sidebar_collapsed());
+					p->plater->get_notification_manager()->set_slicing_complete_print_time("Estimated printing time: " + ps.estimated_normal_print_time);
+
                 }
                 if (ps.estimated_silent_print_time != "N/A") {
                     new_label += format_wxstr("\n   - %1%", _L("stealth mode"));
@@ -1227,15 +1236,16 @@ void Sidebar::enable_buttons(bool enable)
     p->btn_reslice->Enable(enable);
     p->btn_export_gcode->Enable(enable);
     p->btn_send_gcode->Enable(enable);
-    p->btn_remove_device->Enable(enable);
+    p->btn_eject_device->Enable(enable);
 	p->btn_export_gcode_removable->Enable(enable);
 }
 
-bool Sidebar::show_reslice(bool show)         const { return p->btn_reslice->Show(show); }
-bool Sidebar::show_export(bool show)          const { return p->btn_export_gcode->Show(show); }
-bool Sidebar::show_send(bool show)            const { return p->btn_send_gcode->Show(show); }
-bool Sidebar::show_disconnect(bool show)      const { return p->btn_remove_device->Show(show); }
-bool Sidebar::show_export_removable(bool show)const { return p->btn_export_gcode_removable->Show(show); }
+bool Sidebar::show_reslice(bool show)          const { return p->btn_reslice->Show(show); }
+bool Sidebar::show_export(bool show)           const { return p->btn_export_gcode->Show(show); }
+bool Sidebar::show_send(bool show)             const { return p->btn_send_gcode->Show(show); }
+bool Sidebar::show_export_removable(bool show) const { return p->btn_export_gcode_removable->Show(show); }
+bool Sidebar::show_eject(bool show)            const { return p->btn_eject_device->Show(show); }
+bool Sidebar::get_eject_shown()                const { return p->btn_eject_device->IsShown(); }
 
 bool Sidebar::is_multifilament()
 {
@@ -1433,6 +1443,7 @@ struct Plater::priv
     GLToolbar view_toolbar;
     GLToolbar collapse_toolbar;
     Preview *preview;
+	NotificationManager* notification_manager;
 
     BackgroundSlicingProcess    background_process;
     bool suppressed_backround_processing_update { false };
@@ -1617,7 +1628,17 @@ struct Plater::priv
     void on_slicing_update(SlicingStatusEvent&);
     void on_slicing_completed(wxCommandEvent&);
     void on_process_completed(wxCommandEvent&);
+	void on_export_began(wxCommandEvent&);
     void on_layer_editing_toggled(bool enable);
+	void on_slicing_began();
+
+	void clear_warnings();
+	void add_warning(const Slic3r::PrintStateBase::Warning &warning, size_t oid);
+	void actualizate_warnings(const Model& model, size_t print_oid);
+	// Displays dialog window with list of warnings. 
+	// Returns true if user clicks OK.
+	// Returns true if current_warnings vector is empty without showning the dialog
+	bool warnings_dialog();
 
     void on_action_add(SimpleEvent&);
     void on_action_split_objects(SimpleEvent&);
@@ -1668,7 +1689,7 @@ struct Plater::priv
     // Flag indicating that the G-code export targets a removable device, therefore the show_action_buttons() needs to be called at any case when the background processing finishes.
     bool 						writing_to_removable_device = { false };
     bool                        inside_snapshot_capture() { return m_prevent_snapshots != 0; }
-
+	bool                        process_completed_with_error { false };
 private:
     bool init_object_menu();
     bool init_common_menu(wxMenu* menu, const bool is_part = false);
@@ -1696,6 +1717,11 @@ private:
                                                               * */
     std::string 				m_last_fff_printer_profile_name;
     std::string 				m_last_sla_printer_profile_name;
+
+	// vector of all warnings generated by last slicing
+	std::vector<std::pair<Slic3r::PrintStateBase::Warning, size_t>> current_warnings;
+	bool show_warning_dialog { false };
+	
 };
 
 const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase);
@@ -1741,6 +1767,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
         });
     background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
     background_process.set_finished_event(EVT_PROCESS_COMPLETED);
+	background_process.set_export_began_event(EVT_EXPORT_BEGAN);
     // Default printer technology for default config.
     background_process.select_technology(this->printer_technology);
     // Register progress callback from the Print class to the Plater.
@@ -1852,8 +1879,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
     preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); });
     preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); });
 
-    q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this);
+	q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this);
     q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this);
+	q->Bind(EVT_EXPORT_BEGAN, &priv::on_export_began, this);
     q->Bind(EVT_GLVIEWTOOLBAR_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); });
     q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [q](SimpleEvent&) { q->select_view_3D("Preview"); });
 
@@ -1880,16 +1908,27 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
     });
 #endif /* _WIN32 */
 
-    this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this](RemovableDriveEjectEvent &evt) {
+	notification_manager = new NotificationManager(this->q);
+	this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); });
+	this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); });
+	this->q->Bind(EVT_PRESET_UPDATE_AVIABLE_CLICKED, [this](PresetUpdateAviableClickedEvent&) {  wxGetApp().get_preset_updater()->on_update_notification_confirm(); });
+
+	this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this, q](RemovableDriveEjectEvent &evt) {
 		if (evt.data.second) {
 			this->show_action_buttons(this->ready_to_slice);
-			Slic3r::GUI::show_info(this->q, format_wxstr(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."),
-				evt.data.first.name, evt.data.first.path));
-		} else
-			Slic3r::GUI::show_info(this->q, format_wxstr(_L("Ejecting of device %s(%s) has failed."),
-				evt.data.first.name, evt.data.first.path));
+			notification_manager->push_notification(format(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."),evt.data.first.name, evt.data.first.path),
+				                                    NotificationManager::NotificationLevel::RegularNotification, *q->get_current_canvas3D());
+		} else {
+			notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path),
+				                                    NotificationManager::NotificationLevel::ErrorNotification, *q->get_current_canvas3D());
+		}
+	});
+    this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) {
+		this->show_action_buttons(this->ready_to_slice); 
+		if (!this->sidebar->get_eject_shown()) {
+			notification_manager->close_notification_of_type(NotificationType::ExportToRemovableFinished);
+		}
 	});
-    this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this](RemovableDrivesChangedEvent &) { this->show_action_buttons(this->ready_to_slice); });
     // Start the background thread and register this window as a target for update events.
     wxGetApp().removable_drive_manager()->init(this->q);
 #ifdef _WIN32
@@ -2519,6 +2558,8 @@ void Plater::priv::reset()
 {
     Plater::TakeSnapshot snapshot(q, _L("Reset Project"));
 
+	clear_warnings();
+
     set_project_filename(wxEmptyString);
 
     // Prevent toolpaths preview from rendering while we modify the Print object
@@ -2688,22 +2729,13 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
 		// The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors.
         std::string err = this->background_process.validate();
         if (err.empty()) {
+			notification_manager->set_all_slicing_errors_gray(true);
             if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled())
                 return_state |= UPDATE_BACKGROUND_PROCESS_RESTART;
         } else {
-            // The print is not valid.
-            // Only show the error message immediately, if the top level parent of this window is active.
-            auto p = dynamic_cast<wxWindow*>(this->q);
-            while (p->GetParent())
-                p = p->GetParent();
-            auto *top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
-            if (! postpone_error_messages && top_level_wnd && top_level_wnd->IsActive()) {
-                // The error returned from the Print needs to be translated into the local language.
-                GUI::show_error(this->q, err);
-            } else {
-                // Show the error message once the main window gets activated.
-                this->delayed_error_message = err;
-            }
+			// The print is not valid.
+			// Show error as notification.
+			notification_manager->push_slicing_error_notification(err, *q->get_current_canvas3D());
             return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
         }
     } else if (! this->delayed_error_message.empty()) {
@@ -2711,6 +2743,14 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
         return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
     }
 
+	//actualizate warnings
+	if (invalidated != Print::APPLY_STATUS_UNCHANGED) {
+		actualizate_warnings(this->q->model(), this->background_process.current_print()->id().id);
+		notification_manager->set_all_slicing_warnings_gray(true);
+		show_warning_dialog = false;
+		process_completed_with_error = false;
+	}
+
     if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() &&
         (return_state & UPDATE_BACKGROUND_PROCESS_RESTART) == 0) {
         // The background processing was killed and it will not be restarted.
@@ -2773,6 +2813,8 @@ bool Plater::priv::restart_background_process(unsigned int state)
                 this->statusbar()->set_status_text(_L("Cancelling"));
                 this->background_process.stop();
             });
+			if (!show_warning_dialog)
+				on_slicing_began();
             return true;
         }
     }
@@ -2799,6 +2841,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova
     if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
         return;
 
+	show_warning_dialog = true;
     if (! output_path.empty()) {
         background_process.schedule_export(output_path.string(), output_path_on_removable_media);
     } else {
@@ -3285,11 +3328,20 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
                 state = print_object->step_state_with_warnings(static_cast<SLAPrintObjectStep>(warning_step));
         }
         // Now process state.warnings.
+		for (auto const& warning : state.warnings) {
+			if (warning.current) {
+				notification_manager->push_slicing_warning_notification(warning.message, false, *q->get_current_canvas3D(), object_id.id, warning_step);
+				add_warning(warning, object_id.id);
+			}
+		}
     }
 }
 
-void Plater::priv::on_slicing_completed(wxCommandEvent &)
+void Plater::priv::on_slicing_completed(wxCommandEvent & evt)
 {
+	//notification_manager->push_notification(NotificationType::SlicingComplete, *q->get_current_canvas3D(), evt.GetInt());
+	notification_manager->push_slicing_complete_notification(*q->get_current_canvas3D(), evt.GetInt(), is_sidebar_collapsed());
+
     switch (this->printer_technology) {
     case ptFFF:
         this->update_fff_scene();
@@ -3302,8 +3354,63 @@ void Plater::priv::on_slicing_completed(wxCommandEvent &)
         break;
     default: break;
     }
-} 
 
+}
+void Plater::priv::on_export_began(wxCommandEvent& evt)
+{
+	if (show_warning_dialog)
+		warnings_dialog();
+}
+void Plater::priv::on_slicing_began()
+{
+	clear_warnings();
+	notification_manager->close_notification_of_type(NotificationType::SlicingComplete);
+}
+void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid)
+{
+	for (auto const& it : current_warnings) {
+		if (warning.message_id == it.first.message_id) {
+			if (warning.message_id != 0 || (warning.message_id == 0 && warning.message == it.first.message))
+				return;
+		} 
+	}
+	current_warnings.emplace_back(std::pair<Slic3r::PrintStateBase::Warning, size_t>(warning, oid));
+}
+void Plater::priv::actualizate_warnings(const Model& model, size_t print_oid)
+{
+	std::vector<size_t> living_oids;
+	living_oids.push_back(model.id().id);
+	living_oids.push_back(print_oid);
+	for (auto it = model.objects.begin(); it != model.objects.end(); ++it) {
+		living_oids.push_back((*it)->id().id);
+	}
+	notification_manager->compare_warning_oids(living_oids);
+}
+void Plater::priv::clear_warnings()
+{
+	notification_manager->close_slicing_errors_and_warnings();
+	this->current_warnings.clear();
+}
+bool Plater::priv::warnings_dialog()
+{
+	if (current_warnings.empty())
+		return true;
+	std::string text = _u8L("There are active warnings concerning sliced models:\n");
+	bool empt = true;
+	for (auto const& it : current_warnings) {
+		int next_n = it.first.message.find_first_of('\n', 0);
+		text += "\n";
+		if (next_n != std::string::npos)
+			text += it.first.message.substr(0, next_n);
+		else
+			text += it.first.message;
+	}
+	//text += "\n\nDo you still wish to export?";
+	wxMessageDialog msg_wingow(this->q, text, wxString(SLIC3R_APP_NAME " ") + _L("generated warnings"), wxOK);
+	const auto res = msg_wingow.ShowModal();
+	return res == wxID_OK;
+
+}
 void Plater::priv::on_process_completed(wxCommandEvent &evt)
 {
     // Stop the background task, wait until the thread goes into the "Idle" state.
@@ -3322,14 +3429,13 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt)
     if (error) {
         wxString message = evt.GetString();
         if (message.IsEmpty())
-            message = _L("Export failed");
-        if (q->m_tracking_popup_menu)
-        	// We don't want to pop-up a message box when tracking a pop-up menu.
-        	// We postpone the error message instead.
-            q->m_tracking_popup_menu_error_message = message;
-        else
-	        show_error(q, message);
+            message = _L("Export failed.");
+		notification_manager->push_slicing_error_notification(boost::nowide::narrow(message), *q->get_current_canvas3D());
         this->statusbar()->set_status_text(message);
+		const wxString invalid_str = _L("Invalid data");
+		for (auto btn : { ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport })
+			sidebar->set_btn_label(btn, invalid_str);
+		process_completed_with_error = true;
     }
     if (canceled)
         this->statusbar()->set_status_text(_L("Cancelled"));
@@ -3355,18 +3461,21 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt)
     default: break;
     }
 	
-
     if (canceled) {
         if (wxGetApp().get_mode() == comSimple)
             sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now");
         show_action_buttons(true);
     }
-    else if (this->writing_to_removable_device || wxGetApp().get_mode() == comSimple)
+    else if (wxGetApp().get_mode() == comSimple)
 	{
-		wxGetApp().removable_drive_manager()->set_exporting_finished(true);
 		show_action_buttons(false);
 	}
-    this->writing_to_removable_device = false;
+	else if (this->writing_to_removable_device)
+	{
+		show_action_buttons(false);
+		notification_manager->push_notification(NotificationType::ExportToRemovableFinished, *q->get_current_canvas3D());
+	}
+	this->writing_to_removable_device = false;
 }
 
 void Plater::priv::on_layer_editing_toggled(bool enable)
@@ -4013,7 +4122,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const
 			sidebar->show_export(true) |
 			sidebar->show_send(send_gcode_shown) |
 			sidebar->show_export_removable(removable_media_status.has_removable_drives) |
-			sidebar->show_disconnect(removable_media_status.has_eject))
+			sidebar->show_eject(removable_media_status.has_eject))
             sidebar->Layout();
     }
     else
@@ -4025,7 +4134,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const
             sidebar->show_export(!ready_to_slice) |
             sidebar->show_send(send_gcode_shown && !ready_to_slice) |
 			sidebar->show_export_removable(!ready_to_slice && removable_media_status.has_removable_drives) |
-            sidebar->show_disconnect(!ready_to_slice && removable_media_status.has_eject))
+            sidebar->show_eject(!ready_to_slice && removable_media_status.has_eject))
             sidebar->Layout();
     }
 }
@@ -4588,6 +4697,9 @@ void Plater::export_gcode(bool prefer_removable)
     if (p->model.objects.empty())
         return;
 
+	if (p->process_completed_with_error)//here
+		return;
+
     // If possible, remove accents from accented latin characters.
     // This function is useful for generating file names to be processed by legacy firmwares.
     fs::path default_output_file;
@@ -4847,7 +4959,6 @@ void Plater::export_toolpaths_to_obj() const
     p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str());
 }
 
-
 void Plater::reslice()
 {
     // Stop arrange and (or) optimize rotation tasks.
@@ -5535,6 +5646,16 @@ Mouse3DController& Plater::get_mouse3d_controller()
     return p->mouse3d_controller;
 }
 
+const NotificationManager* Plater::get_notification_manager() const
+{
+	return p->notification_manager;
+}
+
+NotificationManager* Plater::get_notification_manager()
+{
+	return p->notification_manager;
+}
+
 bool Plater::can_delete() const { return p->can_delete(); }
 bool Plater::can_delete_all() const { return p->can_delete_all(); }
 bool Plater::can_increase_instances() const { return p->can_increase_instances(); }
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index 916397f7d..9b8d41cd3 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -45,6 +45,7 @@ class ObjectLayers;
 class ObjectList;
 class GLCanvas3D;
 class Mouse3DController;
+class NotificationManager;
 struct Camera;
 class Bed3D;
 class GLToolbar;
@@ -95,8 +96,9 @@ public:
     bool                    show_reslice(bool show) const;
 	bool                    show_export(bool show) const;
 	bool                    show_send(bool show) const;
-    bool                    show_disconnect(bool show)const;
+    bool                    show_eject(bool show)const;
 	bool                    show_export_removable(bool show) const;
+	bool                    get_eject_shown() const;
     bool                    is_multifilament();
     void                    update_mode();
     bool                    is_collapsed();
@@ -303,6 +305,9 @@ public:
     Mouse3DController& get_mouse3d_controller();
 
 	void set_bed_shape() const;
+    
+	const NotificationManager* get_notification_manager() const;
+	NotificationManager* get_notification_manager();
 
     // ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
 	class SuppressSnapshots
diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp
index 02e4a899d..4b5808e16 100644
--- a/src/slic3r/GUI/Preferences.cpp
+++ b/src/slic3r/GUI/Preferences.cpp
@@ -234,30 +234,6 @@ void PreferencesDialog::accept()
 	    }
 	}
 
-#if !ENABLE_LAYOUT_NO_RESTART
-	if (m_settings_layout_changed) {
-		// the dialog needs to be destroyed before the call to recreate_gui()
-		// or sometimes the application crashes into wxDialogBase() destructor
-		// so we put it into an inner scope
-		wxMessageDialog dialog(nullptr,
-			            _L("Switching the settings layout mode will trigger application restart.\n"
-				                  "You will lose content of the plater.") + "\n\n" +
-			                   _L("Do you want to proceed?"),
-			wxString(SLIC3R_APP_NAME) + " - " + _L("Switching the settings layout mode"),
-			wxICON_QUESTION | wxOK | wxCANCEL);
-
-		if (dialog.ShowModal() == wxID_CANCEL)
-		{
-			int selection = app_config->get("old_settings_layout_mode") == "1" ? 0 :
-				            app_config->get("new_settings_layout_mode") == "1" ? 1 :
-				            app_config->get("dlg_settings_layout_mode") == "1" ? 2 : 0;
-
-			m_layout_mode_box->SetSelection(selection);
-			return;
-		}
-	}
-#endif // !ENABLE_LAYOUT_NO_RESTART
-
 	for (std::map<std::string, std::string>::iterator it = m_values.begin(); it != m_values.end(); ++it)
 		app_config->set(it->first, it->second);
 
diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp
index 9d37362ba..4f4beb202 100644
--- a/src/slic3r/GUI/Tab.cpp
+++ b/src/slic3r/GUI/Tab.cpp
@@ -3959,6 +3959,7 @@ void TabSLAPrint::build()
 
     optgroup = page->new_optgroup(L("Support pillar"));
     optgroup->append_single_option_line("support_pillar_diameter");
+    optgroup->append_single_option_line("support_small_pillar_diameter_percent");
     optgroup->append_single_option_line("support_max_bridges_on_pillar");
     
     optgroup->append_single_option_line("support_pillar_connection_mode");
diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp
index dec251858..14b9fb0c4 100644
--- a/src/slic3r/Utils/PresetUpdater.cpp
+++ b/src/slic3r/Utils/PresetUpdater.cpp
@@ -27,6 +27,7 @@
 #include "slic3r/GUI/GUI_App.hpp"
 #include "slic3r/GUI/Plater.hpp"
 #include "slic3r/GUI/format.hpp"
+#include "slic3r/GUI/NotificationManager.hpp"
 #include "slic3r/Utils/Http.hpp"
 #include "slic3r/Config/Version.hpp"
 #include "slic3r/Config/Snapshot.hpp"
@@ -154,6 +155,9 @@ struct PresetUpdater::priv
 	bool cancel;
 	std::thread thread;
 
+	bool has_waiting_updates { false };
+	Updates waiting_updates;
+
 	priv();
 
 	void set_download_prefs(AppConfig *app_config);
@@ -165,6 +169,7 @@ struct PresetUpdater::priv
 	void check_install_indices() const;
 	Updates get_config_updates(const Semver& old_slic3r_version) const;
 	void perform_updates(Updates &&updates, bool snapshot = true) const;
+	void set_waiting_updates(Updates u);
 };
 
 PresetUpdater::priv::priv()
@@ -326,7 +331,15 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors)
 				continue;
 			}
 			Slic3r::rename_file(idx_path_temp, idx_path);
-			index = std::move(new_index);
+			//if we rename path we need to change it in Index object too or create the object again
+			//index = std::move(new_index);
+			try {
+				index.load(idx_path);
+			}
+			catch (const std::exception& /* err */) {
+				BOOST_LOG_TRIVIAL(error) << format("Could not load downloaded index %1% for vendor %2%: invalid index?", idx_path, vendor.name);
+				continue;
+			}
 			if (cancel)
 				return;
 		}
@@ -632,6 +645,12 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
 	}
 }
 
+void PresetUpdater::priv::set_waiting_updates(Updates u)
+{
+	waiting_updates = u;
+	has_waiting_updates = true;
+}
+
 PresetUpdater::PresetUpdater() :
 	p(new priv())
 {}
@@ -690,9 +709,9 @@ void PresetUpdater::slic3r_update_notify()
 	}
 }
 
-PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver &old_slic3r_version) const
+PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const
 {
-	if (! p->enabled_config_update) { return R_NOOP; }
+ 	if (! p->enabled_config_update) { return R_NOOP; }
 
 	auto updates = p->get_config_updates(old_slic3r_version);
 	if (updates.incompats.size() > 0) {
@@ -779,30 +798,38 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver &old_slic3
 		}
 
 		// regular update
-		BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", updates.updates.size());
+		if (no_notification) {
+			BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size());
 
-		std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
-		for (const auto &update : updates.updates) {
-			std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string();
-			updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
-		}
+			std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
+			for (const auto& update : updates.updates) {
+				std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string();
+				updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
+			}
 
-		GUI::MsgUpdateConfig dlg(updates_msg);
+			GUI::MsgUpdateConfig dlg(updates_msg);
 
-		const auto res = dlg.ShowModal();
-		if (res == wxID_OK) {
-			BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
-			p->perform_updates(std::move(updates));
+			const auto res = dlg.ShowModal();
+			if (res == wxID_OK) {
+				BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
+				p->perform_updates(std::move(updates));
 
-			// Reload global configuration
-			auto *app_config = GUI::wxGetApp().app_config;
-			GUI::wxGetApp().preset_bundle->load_presets(*app_config);
-			GUI::wxGetApp().load_current_presets();
-			return R_UPDATE_INSTALLED;
+				// Reload global configuration
+				auto* app_config = GUI::wxGetApp().app_config;
+				GUI::wxGetApp().preset_bundle->load_presets(*app_config);
+				GUI::wxGetApp().load_current_presets();
+				return R_UPDATE_INSTALLED;
+			}
+			else {
+				BOOST_LOG_TRIVIAL(info) << "User refused the update";
+				return R_UPDATE_REJECT;
+			}
 		} else {
-			BOOST_LOG_TRIVIAL(info) << "User refused the update";
-			return R_UPDATE_REJECT;
+			p->set_waiting_updates(updates);
+			GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAviable, *(GUI::wxGetApp().plater()->get_current_canvas3D()));
 		}
+		
+		// MsgUpdateConfig will show after the notificaation is clicked
 	} else {
 		BOOST_LOG_TRIVIAL(info) << "No configuration updates available.";
 	}
@@ -825,5 +852,37 @@ void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool
 	p->perform_updates(std::move(updates), snapshot);
 }
 
+void PresetUpdater::on_update_notification_confirm()
+{
+	if (!p->has_waiting_updates)
+		return;
+	BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size());
+
+	std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
+	for (const auto& update : p->waiting_updates.updates) {
+		std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string();
+		updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
+	}
+
+	GUI::MsgUpdateConfig dlg(updates_msg);
+
+	const auto res = dlg.ShowModal();
+	if (res == wxID_OK) {
+		BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
+		p->perform_updates(std::move(p->waiting_updates));
+
+		// Reload global configuration
+		auto* app_config = GUI::wxGetApp().app_config;
+		GUI::wxGetApp().preset_bundle->load_presets(*app_config);
+		GUI::wxGetApp().load_current_presets();
+		p->has_waiting_updates = false;
+		//return R_UPDATE_INSTALLED;
+	}
+	else {
+		BOOST_LOG_TRIVIAL(info) << "User refused the update";
+		//return R_UPDATE_REJECT;
+	}
+	
+}
 
 }
diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp
index e18695828..0ca363c61 100644
--- a/src/slic3r/Utils/PresetUpdater.hpp
+++ b/src/slic3r/Utils/PresetUpdater.hpp
@@ -35,16 +35,20 @@ public:
 		R_INCOMPAT_CONFIGURED,
 		R_UPDATE_INSTALLED,
 		R_UPDATE_REJECT,
+		R_UPDATE_NOTIFICATION
 	};
 
 	// If updating is enabled, check if updates are available in cache, if so, ask about installation.
 	// A false return value implies Slic3r should exit due to incompatibility of configuration.
 	// Providing old slic3r version upgrade profiles on upgrade of an application even in case
 	// that the config index installed from the Internet is equal to the index contained in the installation package.
-	UpdateResult config_update(const Semver &old_slic3r_version) const;
+	// no_notification = force modal textbox, otherwise some cases only shows notification
+	UpdateResult config_update(const Semver &old_slic3r_version, bool no_notification) const;
 
 	// "Update" a list of bundles from resources (behaves like an online update).
 	void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
+
+	void on_update_notification_confirm();
 private:
 	struct priv;
 	std::unique_ptr<priv> p;
diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt
index 9d47f3ae4..f6b261fda 100644
--- a/tests/sla_print/CMakeLists.txt
+++ b/tests/sla_print/CMakeLists.txt
@@ -1,7 +1,7 @@
 get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
 add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp 
     sla_print_tests.cpp 
-    sla_test_utils.hpp sla_test_utils.cpp
+    sla_test_utils.hpp sla_test_utils.cpp sla_treebuilder_tests.cpp
     sla_raycast_tests.cpp)
 target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
 set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp
index 82df2c1a6..dad2b9097 100644
--- a/tests/sla_print/sla_print_tests.cpp
+++ b/tests/sla_print/sla_print_tests.cpp
@@ -4,6 +4,8 @@
 
 #include "sla_test_utils.hpp"
 
+#include <libslic3r/SLA/SupportTreeMesher.hpp>
+
 namespace {
 
 const char *const BELOW_PAD_TEST_OBJECTS[] = {
@@ -37,9 +39,9 @@ TEST_CASE("Support point generator should be deterministic if seeded",
           "[SLASupportGeneration], [SLAPointGen]") {
     TriangleMesh mesh = load_model("A_upsidedown.obj");
     
-    sla::EigenMesh3D emesh{mesh};
+    sla::IndexedMesh emesh{mesh};
     
-    sla::SupportConfig supportcfg;
+    sla::SupportTreeConfig supportcfg;
     sla::SupportPointGenerator::Config autogencfg;
     autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm);
     sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}};
@@ -124,14 +126,14 @@ TEST_CASE("WingedPadAroundObjectIsValid", "[SLASupportGeneration]") {
 }
 
 TEST_CASE("ElevatedSupportGeometryIsValid", "[SLASupportGeneration]") {
-    sla::SupportConfig supportcfg;
+    sla::SupportTreeConfig supportcfg;
     supportcfg.object_elevation_mm = 5.;
     
     for (auto fname : SUPPORT_TEST_MODELS) test_supports(fname);
 }
 
 TEST_CASE("FloorSupportGeometryIsValid", "[SLASupportGeneration]") {
-    sla::SupportConfig supportcfg;
+    sla::SupportTreeConfig supportcfg;
     supportcfg.object_elevation_mm = 0;
     
     for (auto &fname: SUPPORT_TEST_MODELS) test_supports(fname, supportcfg);
@@ -139,7 +141,7 @@ TEST_CASE("FloorSupportGeometryIsValid", "[SLASupportGeneration]") {
 
 TEST_CASE("ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration]") {
     
-    sla::SupportConfig supportcfg;
+    sla::SupportTreeConfig supportcfg;
     
     for (auto fname : SUPPORT_TEST_MODELS)
         test_support_model_collision(fname, supportcfg);
@@ -147,7 +149,7 @@ TEST_CASE("ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration]") {
 
 TEST_CASE("FloorSupportsDoNotPierceModel", "[SLASupportGeneration]") {
     
-    sla::SupportConfig supportcfg;
+    sla::SupportTreeConfig supportcfg;
     supportcfg.object_elevation_mm = 0;
     
     for (auto fname : SUPPORT_TEST_MODELS)
@@ -228,3 +230,12 @@ TEST_CASE("Triangle mesh conversions should be correct", "[SLAConversions]")
         cntr.from_obj(infile);
     }
 }
+
+TEST_CASE("halfcone test", "[halfcone]") {
+    sla::DiffBridge br{Vec3d{1., 1., 1.}, Vec3d{10., 10., 10.}, 0.25, 0.5};
+
+    TriangleMesh m = sla::to_triangle_mesh(sla::get_mesh(br, 45));
+
+    m.require_shared_vertices();
+    m.WriteOBJFile("Halfcone.obj");
+}
diff --git a/tests/sla_print/sla_raycast_tests.cpp b/tests/sla_print/sla_raycast_tests.cpp
index c82e4569a..b56909280 100644
--- a/tests/sla_print/sla_raycast_tests.cpp
+++ b/tests/sla_print/sla_raycast_tests.cpp
@@ -1,7 +1,7 @@
 #include <catch2/catch.hpp>
 #include <test_utils.hpp>
 
-#include <libslic3r/SLA/EigenMesh3D.hpp>
+#include <libslic3r/SLA/IndexedMesh.hpp>
 #include <libslic3r/SLA/Hollowing.hpp>
 
 #include "sla_test_utils.hpp"
@@ -65,7 +65,7 @@ TEST_CASE("Raycaster with loaded drillholes", "[sla_raycast]")
     cube.merge(*cube_inside);
     cube.require_shared_vertices();
     
-    sla::EigenMesh3D emesh{cube};
+    sla::IndexedMesh emesh{cube};
     emesh.load_holes(holes);
     
     Vec3d s = center.cast<double>();
diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp
index 1eaf796c0..8978281d8 100644
--- a/tests/sla_print/sla_test_utils.cpp
+++ b/tests/sla_print/sla_test_utils.cpp
@@ -2,13 +2,13 @@
 #include "libslic3r/SLA/AGGRaster.hpp"
 
 void test_support_model_collision(const std::string          &obj_filename,
-                                  const sla::SupportConfig   &input_supportcfg,
+                                  const sla::SupportTreeConfig   &input_supportcfg,
                                   const sla::HollowingConfig &hollowingcfg,
                                   const sla::DrainHoles      &drainholes)
 {
     SupportByproducts byproducts;
     
-    sla::SupportConfig supportcfg = input_supportcfg;
+    sla::SupportTreeConfig supportcfg = input_supportcfg;
     
     // Set head penetration to a small negative value which should ensure that
     // the supports will not touch the model body.
@@ -69,11 +69,12 @@ void export_failed_case(const std::vector<ExPolygons> &support_slices, const Sup
     m.merge(byproducts.input_mesh);
     m.repair();
     m.require_shared_vertices();
-    m.WriteOBJFile(byproducts.obj_fname.c_str());
+    m.WriteOBJFile((Catch::getResultCapture().getCurrentTestName() + "_" +
+                    byproducts.obj_fname).c_str());
 }
 
 void test_supports(const std::string          &obj_filename,
-                   const sla::SupportConfig   &supportcfg,
+                   const sla::SupportTreeConfig   &supportcfg,
                    const sla::HollowingConfig &hollowingcfg,
                    const sla::DrainHoles      &drainholes,
                    SupportByproducts          &out)
@@ -104,7 +105,7 @@ void test_supports(const std::string          &obj_filename,
     
     // Create the special index-triangle mesh with spatial indexing which
     // is the input of the support point and support mesh generators
-    sla::EigenMesh3D emesh{mesh};
+    sla::IndexedMesh emesh{mesh};
 
 #ifdef SLIC3R_HOLE_RAYCASTER
     if (hollowingcfg.enabled) 
@@ -129,8 +130,7 @@ void test_supports(const std::string          &obj_filename,
     // If there is no elevation, support points shall be removed from the
     // bottom of the object.
     if (std::abs(supportcfg.object_elevation_mm) < EPSILON) {
-        sla::remove_bottom_points(support_points, zmin,
-                                  supportcfg.base_height_mm);
+        sla::remove_bottom_points(support_points, zmin + supportcfg.base_height_mm);
     } else {
         // Should be support points at least on the bottom of the model
         REQUIRE_FALSE(support_points.empty());
@@ -141,7 +141,8 @@ void test_supports(const std::string          &obj_filename,
     
     // Generate the actual support tree
     sla::SupportTreeBuilder treebuilder;
-    treebuilder.build(sla::SupportableMesh{emesh, support_points, supportcfg});
+    sla::SupportableMesh    sm{emesh, support_points, supportcfg};
+    sla::SupportTreeBuildsteps::execute(treebuilder, sm);
     
     check_support_tree_integrity(treebuilder, supportcfg);
     
@@ -157,8 +158,8 @@ void test_supports(const std::string          &obj_filename,
     if (std::abs(supportcfg.object_elevation_mm) < EPSILON)
         allowed_zmin = zmin - 2 * supportcfg.head_back_radius_mm;
     
-    REQUIRE(obb.min.z() >= allowed_zmin);
-    REQUIRE(obb.max.z() <= zmax);
+    REQUIRE(obb.min.z() >= Approx(allowed_zmin));
+    REQUIRE(obb.max.z() <= Approx(zmax));
     
     // Move out the support tree into the byproducts, we can examine it further
     // in various tests.
@@ -168,15 +169,15 @@ void test_supports(const std::string          &obj_filename,
 }
 
 void check_support_tree_integrity(const sla::SupportTreeBuilder &stree, 
-                                  const sla::SupportConfig &cfg)
+                                  const sla::SupportTreeConfig &cfg)
 {
     double gnd  = stree.ground_level;
     double H1   = cfg.max_solo_pillar_height_mm;
     double H2   = cfg.max_dual_pillar_height_mm;
     
     for (const sla::Head &head : stree.heads()) {
-        REQUIRE((!head.is_valid() || head.pillar_id != sla::ID_UNSET ||
-                head.bridge_id != sla::ID_UNSET));
+        REQUIRE((!head.is_valid() || head.pillar_id != sla::SupportTreeNode::ID_UNSET ||
+                head.bridge_id != sla::SupportTreeNode::ID_UNSET));
     }
     
     for (const sla::Pillar &pillar : stree.pillars()) {
diff --git a/tests/sla_print/sla_test_utils.hpp b/tests/sla_print/sla_test_utils.hpp
index 3652b1f81..fdd883ed8 100644
--- a/tests/sla_print/sla_test_utils.hpp
+++ b/tests/sla_print/sla_test_utils.hpp
@@ -67,16 +67,16 @@ struct SupportByproducts
 const constexpr float CLOSING_RADIUS = 0.005f;
 
 void check_support_tree_integrity(const sla::SupportTreeBuilder &stree,
-                                  const sla::SupportConfig &cfg);
+                                  const sla::SupportTreeConfig &cfg);
 
 void test_supports(const std::string          &obj_filename,
-                   const sla::SupportConfig   &supportcfg,
+                   const sla::SupportTreeConfig   &supportcfg,
                    const sla::HollowingConfig &hollowingcfg,
                    const sla::DrainHoles      &drainholes,
                    SupportByproducts          &out);
 
 inline void test_supports(const std::string &obj_filename,
-                   const sla::SupportConfig &supportcfg,
+                   const sla::SupportTreeConfig &supportcfg,
                    SupportByproducts        &out) 
 {
     sla::HollowingConfig hcfg;
@@ -85,7 +85,7 @@ inline void test_supports(const std::string &obj_filename,
 }
 
 inline void test_supports(const std::string &obj_filename,
-                   const sla::SupportConfig &supportcfg = {})
+                   const sla::SupportTreeConfig &supportcfg = {})
 {
     SupportByproducts byproducts;
     test_supports(obj_filename, supportcfg, byproducts);
@@ -97,13 +97,13 @@ void export_failed_case(const std::vector<ExPolygons> &support_slices,
 
 void test_support_model_collision(
     const std::string          &obj_filename,
-    const sla::SupportConfig   &input_supportcfg,
+    const sla::SupportTreeConfig   &input_supportcfg,
     const sla::HollowingConfig &hollowingcfg,
     const sla::DrainHoles      &drainholes);
 
 inline void test_support_model_collision(
     const std::string        &obj_filename,
-    const sla::SupportConfig &input_supportcfg = {}) 
+    const sla::SupportTreeConfig &input_supportcfg = {}) 
 {
     sla::HollowingConfig hcfg;
     hcfg.enabled = false;
diff --git a/tests/sla_print/sla_treebuilder_tests.cpp b/tests/sla_print/sla_treebuilder_tests.cpp
new file mode 100644
index 000000000..91c2ea6f8
--- /dev/null
+++ b/tests/sla_print/sla_treebuilder_tests.cpp
@@ -0,0 +1,99 @@
+//#include <catch2/catch.hpp>
+//#include <test_utils.hpp>
+
+//#include "libslic3r/TriangleMesh.hpp"
+//#include "libslic3r/SLA/SupportTreeBuildsteps.hpp"
+//#include "libslic3r/SLA/SupportTreeMesher.hpp"
+
+//TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]") {
+//    using namespace Slic3r;
+
+//    TriangleMesh cube = make_cube(10., 10., 10.);
+
+//    sla::SupportConfig cfg = {}; // use default config
+//    sla::SupportPoints pts = {{10.f, 5.f, 5.f, float(cfg.head_front_radius_mm), false}};
+//    sla::SupportableMesh sm{cube, pts, cfg};
+
+//    size_t steps = 45;
+//    SECTION("Bridge is straight horizontal and pointing away from the cube") {
+
+//        sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{15., 5., 5.},
+//                           pts[0].head_front_radius);
+
+//        auto hit = sla::query_hit(sm, bridge);
+
+//        REQUIRE(std::isinf(hit.distance()));
+
+//        cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
+//        cube.require_shared_vertices();
+//        cube.WriteOBJFile("cube1.obj");
+//    }
+
+//    SECTION("Bridge is tilted down in 45 degrees, pointing away from the cube") {
+//        sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{15., 5., 0.},
+//                           pts[0].head_front_radius);
+
+//        auto hit = sla::query_hit(sm, bridge);
+
+//        REQUIRE(std::isinf(hit.distance()));
+
+//        cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
+//        cube.require_shared_vertices();
+//        cube.WriteOBJFile("cube2.obj");
+//    }
+//}
+
+
+//TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") {
+//    using namespace Slic3r;
+
+//    TriangleMesh sphere = make_sphere(1.);
+
+//    sla::SupportConfig cfg = {}; // use default config
+//    cfg.head_back_radius_mm = cfg.head_front_radius_mm;
+//    sla::SupportPoints pts = {{1.f, 0.f, 0.f, float(cfg.head_front_radius_mm), false}};
+//    sla::SupportableMesh sm{sphere, pts, cfg};
+
+//    size_t steps = 45;
+//    SECTION("Bridge is straight horizontal and pointing away from the sphere") {
+
+//        sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{2., 0., 0.},
+//                           pts[0].head_front_radius);
+
+//        auto hit = sla::query_hit(sm, bridge);
+
+//        sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
+//        sphere.require_shared_vertices();
+//        sphere.WriteOBJFile("sphere1.obj");
+
+//        REQUIRE(std::isinf(hit.distance()));
+//    }
+
+//    SECTION("Bridge is tilted down 45 deg and pointing away from the sphere") {
+
+//        sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{2., 0., -2.},
+//                           pts[0].head_front_radius);
+
+//        auto hit = sla::query_hit(sm, bridge);
+
+//        sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
+//        sphere.require_shared_vertices();
+//        sphere.WriteOBJFile("sphere2.obj");
+
+//        REQUIRE(std::isinf(hit.distance()));
+//    }
+
+//    SECTION("Bridge is tilted down 90 deg and pointing away from the sphere") {
+
+//        sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{1., 0., -2.},
+//                           pts[0].head_front_radius);
+
+//        auto hit = sla::query_hit(sm, bridge);
+
+//        sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
+//        sphere.require_shared_vertices();
+//        sphere.WriteOBJFile("sphere3.obj");
+
+//        REQUIRE(std::isinf(hit.distance()));
+//    }
+//}