diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp
index 4b8dfa234..2cfb0740c 100644
--- a/src/libslic3r/Config.cpp
+++ b/src/libslic3r/Config.cpp
@@ -402,6 +402,42 @@ std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, s
     return out;
 }
 
+std::string ConfigBase::SetDeserializeItem::format(std::initializer_list<int> values)
+{
+    std::string out;
+    int i = 0;
+    for (int v : values) {
+        if (i ++ > 0)
+            out += ", ";
+        out += std::to_string(v);
+    }
+    return out;
+}
+
+std::string ConfigBase::SetDeserializeItem::format(std::initializer_list<float> values)
+{
+    std::string out;
+    int i = 0;
+    for (float v : values) {
+        if (i ++ > 0)
+            out += ", ";
+        out += float_to_string_decimal_point(double(v));
+    }
+    return out;
+}
+
+std::string ConfigBase::SetDeserializeItem::format(std::initializer_list<double> values)
+{
+    std::string out;
+    int i = 0;
+    for (float v : values) {
+        if (i ++ > 0)
+            out += ", ";
+        out += float_to_string_decimal_point(v);
+    }
+    return out;
+}
+
 void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent)
 {
     // loop through options and apply them
diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp
index bfd307de3..b8c046ceb 100644
--- a/src/libslic3r/Config.hpp
+++ b/src/libslic3r/Config.hpp
@@ -1950,6 +1950,11 @@ public:
             throw BadOptionTypeException("Conversion to a wrong type");
         return static_cast<TYPE*>(opt);
     }
+
+    template<class T> T*       opt(const t_config_option_key &opt_key, bool create = false)
+        { return dynamic_cast<T*>(this->optptr(opt_key, create)); }
+    template<class T> const T* opt(const t_config_option_key &opt_key) const
+        { return dynamic_cast<const T*>(this->optptr(opt_key)); }
     
     // Apply all keys of other ConfigBase defined by this->def() to this ConfigBase.
     // An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(),
@@ -1995,11 +2000,23 @@ public:
     	SetDeserializeItem(const std::string &opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {}
     	SetDeserializeItem(const char *opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {}
     	SetDeserializeItem(const std::string &opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {}
+        SetDeserializeItem(const char *opt_key, const std::initializer_list<int> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
+        SetDeserializeItem(const std::string &opt_key, const std::initializer_list<int> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
         SetDeserializeItem(const char *opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {}
         SetDeserializeItem(const std::string &opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {}
         SetDeserializeItem(const char *opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {}
         SetDeserializeItem(const std::string &opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {}
+        SetDeserializeItem(const char *opt_key, const std::initializer_list<float> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
+        SetDeserializeItem(const std::string &opt_key, const std::initializer_list<float> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
+        SetDeserializeItem(const char *opt_key, const std::initializer_list<double> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
+        SetDeserializeItem(const std::string &opt_key, const std::initializer_list<double> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
+
     	std::string opt_key; std::string opt_value; bool append = false;
+
+    private:
+        static std::string format(std::initializer_list<int> values);
+        static std::string format(std::initializer_list<float> values);
+        static std::string format(std::initializer_list<double> values);
     };
 	// May throw BadOptionTypeException() if the operation fails.
     void set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions);
@@ -2008,7 +2025,31 @@ public:
 
     double get_abs_value(const t_config_option_key &opt_key) const;
     double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const;
-    void setenv_() const;
+
+    std::string&        opt_string(const t_config_option_key &opt_key, bool create = false)     { return this->option<ConfigOptionString>(opt_key, create)->value; }
+    const std::string&  opt_string(const t_config_option_key &opt_key) const                    { return const_cast<ConfigBase*>(this)->opt_string(opt_key); }
+    std::string&        opt_string(const t_config_option_key &opt_key, unsigned int idx)        { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); }
+    const std::string&  opt_string(const t_config_option_key &opt_key, unsigned int idx) const  { return const_cast<ConfigBase*>(this)->opt_string(opt_key, idx); }
+
+    double&             opt_float(const t_config_option_key &opt_key)                           { return this->option<ConfigOptionFloat>(opt_key)->value; }
+    const double&       opt_float(const t_config_option_key &opt_key) const                     { return dynamic_cast<const ConfigOptionFloat*>(this->option(opt_key))->value; }
+    double&             opt_float(const t_config_option_key &opt_key, unsigned int idx)         { return this->option<ConfigOptionFloats>(opt_key)->get_at(idx); }
+    const double&       opt_float(const t_config_option_key &opt_key, unsigned int idx) const   { return dynamic_cast<const ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); }
+
+    int&                opt_int(const t_config_option_key &opt_key)                             { return this->option<ConfigOptionInt>(opt_key)->value; }
+    int                 opt_int(const t_config_option_key &opt_key) const                       { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; }
+    int&                opt_int(const t_config_option_key &opt_key, unsigned int idx)           { return this->option<ConfigOptionInts>(opt_key)->get_at(idx); }
+    int                 opt_int(const t_config_option_key &opt_key, unsigned int idx) const     { return dynamic_cast<const ConfigOptionInts*>(this->option(opt_key))->get_at(idx); }
+
+    // In ConfigManipulation::toggle_print_fff_options, it is called on option with type ConfigOptionEnumGeneric* and also ConfigOptionEnum*.
+    // Thus the virtual method getInt() is used to retrieve the enum value.
+    template<typename ENUM>
+    ENUM                opt_enum(const t_config_option_key &opt_key) const                      { return static_cast<ENUM>(this->option(opt_key)->getInt()); }
+
+    bool                opt_bool(const t_config_option_key &opt_key) const                      { return this->option<ConfigOptionBool>(opt_key)->value != 0; }
+    bool                opt_bool(const t_config_option_key &opt_key, unsigned int idx) const    { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; }
+
+    void                setenv_() const;
     ConfigSubstitutions load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
     ConfigSubstitutions load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
     ConfigSubstitutions load_from_ini_string(const std::string &data, ForwardCompatibilitySubstitutionRule compatibility_rule);
@@ -2017,10 +2058,10 @@ public:
     ConfigSubstitutions load_from_ini_string_commented(std::string &&data, ForwardCompatibilitySubstitutionRule compatibility_rule);
     ConfigSubstitutions load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
     ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);
-    void save(const std::string &file) const;
+    void                save(const std::string &file) const;
 
 	// Set all the nullable values to nils.
-    void null_nullables();
+    void                null_nullables();
 
     static size_t load_from_gcode_string_legacy(ConfigBase& config, const char* str, ConfigSubstitutionContext& substitutions);
 
@@ -2129,10 +2170,6 @@ public:
     // Allow DynamicConfig to be instantiated on ints own without a definition.
     // If the definition is not defined, the method requiring the definition will throw NoDefinitionException.
     const ConfigDef*        def() const override { return nullptr; }
-    template<class T> T*    opt(const t_config_option_key &opt_key, bool create = false)
-        { return dynamic_cast<T*>(this->option(opt_key, create)); }
-    template<class T> const T* opt(const t_config_option_key &opt_key) const
-        { return dynamic_cast<const T*>(this->option(opt_key)); }
     // Overrides ConfigResolver::optptr().
     const ConfigOption*     optptr(const t_config_option_key &opt_key) const override;
     // Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name.
@@ -2163,29 +2200,6 @@ public:
     // Returns options being equal in the two configs, ignoring options not present in both configs.
     t_config_option_keys equal(const DynamicConfig &other) const;
 
-    std::string&        opt_string(const t_config_option_key &opt_key, bool create = false)     { return this->option<ConfigOptionString>(opt_key, create)->value; }
-    const std::string&  opt_string(const t_config_option_key &opt_key) const                    { return const_cast<DynamicConfig*>(this)->opt_string(opt_key); }
-    std::string&        opt_string(const t_config_option_key &opt_key, unsigned int idx)        { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); }
-    const std::string&  opt_string(const t_config_option_key &opt_key, unsigned int idx) const  { return const_cast<DynamicConfig*>(this)->opt_string(opt_key, idx); }
-
-    double&             opt_float(const t_config_option_key &opt_key)                           { return this->option<ConfigOptionFloat>(opt_key)->value; }
-    const double&       opt_float(const t_config_option_key &opt_key) const                     { return dynamic_cast<const ConfigOptionFloat*>(this->option(opt_key))->value; }
-    double&             opt_float(const t_config_option_key &opt_key, unsigned int idx)         { return this->option<ConfigOptionFloats>(opt_key)->get_at(idx); }
-    const double&       opt_float(const t_config_option_key &opt_key, unsigned int idx) const   { return dynamic_cast<const ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); }
-
-    int&                opt_int(const t_config_option_key &opt_key)                             { return this->option<ConfigOptionInt>(opt_key)->value; }
-    int                 opt_int(const t_config_option_key &opt_key) const                       { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; }
-    int&                opt_int(const t_config_option_key &opt_key, unsigned int idx)           { return this->option<ConfigOptionInts>(opt_key)->get_at(idx); }
-    int                 opt_int(const t_config_option_key &opt_key, unsigned int idx) const     { return dynamic_cast<const ConfigOptionInts*>(this->option(opt_key))->get_at(idx); }
-
-    // In ConfigManipulation::toggle_print_fff_options, it is called on option with type ConfigOptionEnumGeneric* and also ConfigOptionEnum*.
-    // Thus the virtual method getInt() is used to retrieve the enum value.
-    template<typename ENUM>
-    ENUM                opt_enum(const t_config_option_key &opt_key) const                      { return static_cast<ENUM>(this->option(opt_key)->getInt()); }
-
-    bool                opt_bool(const t_config_option_key &opt_key) const                      { return this->option<ConfigOptionBool>(opt_key)->value != 0; }
-    bool                opt_bool(const t_config_option_key &opt_key, unsigned int idx) const    { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; }
-
     // Command line processing
     bool                read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys = nullptr);
 
diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp
index 344450c4a..7eccf2ec8 100644
--- a/src/libslic3r/ExPolygon.hpp
+++ b/src/libslic3r/ExPolygon.hpp
@@ -67,6 +67,8 @@ public:
     void simplify(double tolerance, ExPolygons* expolygons) const;
     void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const;
     void medial_axis(double max_width, double min_width, Polylines* polylines) const;
+    Polylines medial_axis(double max_width, double min_width) const 
+        { Polylines out; this->medial_axis(max_width, min_width, &out); return out; }
     Lines lines() const;
 
     // Number of contours (outer contour with holes).
diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp
index 1fe040518..91a81c7f3 100644
--- a/src/libslic3r/GCode/CoolingBuffer.hpp
+++ b/src/libslic3r/GCode/CoolingBuffer.hpp
@@ -26,6 +26,8 @@ public:
     void        reset(const Vec3d &position);
     void        set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; }
     std::string process_layer(std::string &&gcode, size_t layer_id, bool flush);
+    std::string process_layer(const std::string &gcode, size_t layer_id, bool flush)
+        { return this->process_layer(std::string(gcode), layer_id, flush); }
 
 private:
 	CoolingBuffer& operator=(const CoolingBuffer&) = delete;
diff --git a/src/libslic3r/Geometry/ConvexHull.hpp b/src/libslic3r/Geometry/ConvexHull.hpp
index 9e9088f1e..94f4e4cf2 100644
--- a/src/libslic3r/Geometry/ConvexHull.hpp
+++ b/src/libslic3r/Geometry/ConvexHull.hpp
@@ -1,6 +1,8 @@
 #ifndef slic3r_Geometry_ConvexHull_hpp_
 #define slic3r_Geometry_ConvexHull_hpp_
 
+#include <vector>
+
 #include "../Polygon.hpp"
 
 namespace Slic3r {
diff --git a/t/clean_polylines.t b/t/clean_polylines.t
deleted file mode 100644
index 50c6f5bbd..000000000
--- a/t/clean_polylines.t
+++ /dev/null
@@ -1,83 +0,0 @@
-use Test::More;
-use strict;
-use warnings;
-
-plan tests => 6;
-
-BEGIN {
-    use FindBin;
-    use lib "$FindBin::Bin/../lib";
-    use local::lib "$FindBin::Bin/../local-lib";
-}
-
-use Slic3r;
-
-{
-    my $polyline = Slic3r::Polyline->new(
-        [0,0],[1,0],[2,0],[2,1],[2,2],[1,2],[0,2],[0,1],[0,0],
-    );
-    $polyline->simplify(1);
-    is_deeply $polyline->pp, [ [0, 0], [2, 0], [2, 2], [0, 2], [0, 0] ], 'Douglas-Peucker';
-}
-
-{
-    my $polyline = Slic3r::Polyline->new(
-        [0,0], [50,50], [100,0], [125,-25], [150,50],
-    );
-    $polyline->simplify(25);
-    is_deeply $polyline->pp, [ [0, 0], [50, 50], [125, -25], [150, 50] ], 'Douglas-Peucker';
-}
-
-{
-    my $gear = Slic3r::Polygon->new_scale(
-        [144.9694,317.1543], [145.4181,301.5633], [146.3466,296.921], [131.8436,294.1643], [131.7467,294.1464], 
-        [121.7238,291.5082], [117.1631,290.2776], [107.9198,308.2068], [100.1735,304.5101], [104.9896,290.3672], 
-        [106.6511,286.2133], [93.453,279.2327], [81.0065,271.4171], [67.7886,286.5055], [60.7927,280.1127], 
-        [69.3928,268.2566], [72.7271,264.9224], [61.8152,253.9959], [52.2273,242.8494], [47.5799,245.7224], 
-        [34.6577,252.6559], [30.3369,245.2236], [42.1712,236.3251], [46.1122,233.9605], [43.2099,228.4876], 
-        [35.0862,211.5672], [33.1441,207.0856], [13.3923,212.1895], [10.6572,203.3273], [6.0707,204.8561], 
-        [7.2775,204.4259], [29.6713,196.3631], [25.9815,172.1277], [25.4589,167.2745], [19.8337,167.0129], 
-        [5.0625,166.3346], [5.0625,156.9425], [5.3701,156.9282], [21.8636,156.1628], [25.3713,156.4613], 
-        [25.4243,155.9976], [29.3432,155.8157], [30.3838,149.3549], [26.3596,147.8137], [27.1085,141.2604], 
-        [29.8466,126.8337], [24.5841,124.9201], [10.6664,119.8989], [13.4454,110.9264], [33.1886,116.0691], 
-        [38.817,103.1819], [45.8311,89.8133], [30.4286,76.81], [35.7686,70.0812], [48.0879,77.6873], 
-        [51.564,81.1635], [61.9006,69.1791], [72.3019,58.7916], [60.5509,42.5416], [68.3369,37.1532], 
-        [77.9524,48.1338], [80.405,52.2215], [92.5632,44.5992], [93.0123,44.3223], [106.3561,37.2056], 
-        [100.8631,17.4679], [108.759,14.3778], [107.3148,11.1283], [117.0002,32.8627], [140.9109,27.3974], 
-        [145.7004,26.4994], [145.1346,6.1011], [154.502,5.4063], [156.9398,25.6501], [171.0557,26.2017], 
-        [181.3139,27.323], [186.2377,27.8532], [191.6031,8.5474], [200.6724,11.2756], [197.2362,30.2334], 
-        [220.0789,39.1906], [224.3261,41.031], [236.3506,24.4291], [243.6897,28.6723], [234.2956,46.7747], 
-        [245.6562,55.1643], [257.2523,65.0901], [261.4374,61.5679], [273.1709,52.8031], [278.555,59.5164], 
-        [268.4334,69.8001], [264.1615,72.3633], [268.2763,77.9442], [278.8488,93.5305], [281.4596,97.6332], 
-        [286.4487,95.5191], [300.2821,90.5903], [303.4456,98.5849], [286.4523,107.7253], [293.7063,131.1779], 
-        [294.9748,135.8787], [314.918,133.8172], [315.6941,143.2589], [300.9234,146.1746], [296.6419,147.0309], 
-        [297.1839,161.7052], [296.6136,176.3942], [302.1147,177.4857], [316.603,180.3608], [317.1658,176.7341], 
-        [315.215,189.6589], [315.1749,189.6548], [294.9411,187.5222], [291.13,201.7233], [286.2615,215.5916], 
-        [291.1944,218.2545], [303.9158,225.1271], [299.2384,233.3694], [285.7165,227.6001], [281.7091,225.1956], 
-        [273.8981,237.6457], [268.3486,245.2248], [267.4538,246.4414], [264.8496,250.0221], [268.6392,253.896], 
-        [278.5017,265.2131], [272.721,271.4403], [257.2776,258.3579], [234.4345,276.5687], [242.6222,294.8315], 
-        [234.9061,298.5798], [227.0321,286.2841], [225.2505,281.8301], [211.5387,287.8187], [202.3025,291.0935], 
-        [197.307,292.831], [199.808,313.1906], [191.5298,315.0787], [187.3082,299.8172], [186.4201,295.3766], 
-        [180.595,296.0487], [161.7854,297.4248], [156.8058,297.6214], [154.3395,317.8592],
-    );
-    
-    my $num_points = scalar @$gear;
-    my $simplified = $gear->simplify(1000);
-    ok @$simplified == 1, 'gear simplified to a single polygon';
-    ###note sprintf "original points: %d\nnew points: %d", $num_points, scalar(@{$simplified->[0]});
-    ok @{$simplified->[0]} < $num_points, 'gear was further simplified using Douglas-Peucker';
-}
-
-{
-
-    my $hole_in_square = Slic3r::Polygon->new(  # cw
-        [140, 140],
-        [140, 160],
-        [160, 160],
-        [160, 140],
-    );
-    my $simplified = $hole_in_square->simplify(2);
-    is scalar(@$simplified), 1, 'hole simplification returns one polygon';
-    ok $simplified->[0]->is_counter_clockwise, 'hole simplification turns cw polygon into ccw polygon';
-}
-
diff --git a/t/combineinfill.t b/t/combineinfill.t
index a19e817a1..ebb430419 100644
--- a/t/combineinfill.t
+++ b/t/combineinfill.t
@@ -107,60 +107,4 @@ plan tests => 8;
             'infill combination is idempotent';
 }
 
-# the following needs to be adapted to the new API
-if (0) {
-    my $config = Slic3r::Config::new_from_defaults;
-    $config->set('skirts', 0);
-    $config->set('solid_layers', 0);
-    $config->set('bottom_solid_layers', 0);
-    $config->set('top_solid_layers', 0);
-    $config->set('infill_every_layers', 6);
-    $config->set('layer_height', 0.06);
-    $config->set('perimeters', 1);
-    
-    my $test = sub {
-        my ($shift) = @_;
-        
-        my $self = Slic3r::Test::init_print('20mm_cube', config => $config);
-        
-        $shift /= &Slic3r::SCALING_FACTOR;
-        my $scale = 4; # make room for fat infill lines with low layer height
-
-        # Put a slope on the box's sides by shifting x and y coords by $tilt * (z / boxheight).
-        # The test here is to put such a slight slope on the walls that it should
-        # not trigger any extra fill on fill layers that should be empty when 
-        # combine infill is enabled.
-        $_->[0] += $shift * ($_->[2] / (20 / &Slic3r::SCALING_FACTOR)) for @{$self->objects->[0]->meshes->[0]->vertices};
-        $_->[1] += $shift * ($_->[2] / (20 / &Slic3r::SCALING_FACTOR)) for @{$self->objects->[0]->meshes->[0]->vertices};
-        $_ = [$_->[0]*$scale, $_->[1]*$scale, $_->[2]] for @{$self->objects->[0]->meshes->[0]->vertices};
-                
-        # copy of Print::export_gcode() up to the point 
-        # after fill surfaces are combined
-        $_->slice for @{$self->objects};
-        $_->make_perimeters for @{$self->objects};
-        $_->detect_surfaces_type for @{$self->objects};
-        $_->prepare_fill_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects};
-        $_->process_external_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects};
-        $_->discover_horizontal_shells for @{$self->objects};
-        $_->combine_infill for @{$self->objects};
-
-        # Only layers with id % 6 == 0 should have fill.
-        my $spurious_infill = 0;
-        foreach my $layer (map @{$_->layers}, @{$self->objects}) {
-            ++$spurious_infill if ($layer->id % 6 && grep @{$_->fill_surfaces} > 0, @{$layer->regions});
-        }
-
-        $spurious_infill -= scalar(@{$self->objects->[0]->layers} - 1) % 6;
-        
-        fail "spurious fill surfaces found on layers that should have none (walls " . sprintf("%.4f", Slic3r::Geometry::rad2deg(atan2($shift, 20/&Slic3r::SCALING_FACTOR))) . " degrees off vertical)"
-            unless $spurious_infill == 0;
-        1;
-    };
-    
-    # Test with mm skew offsets for the top of the 20mm-high box
-    for my $shift (0, 0.0001, 1) {
-        ok $test->($shift), "no spurious fill surfaces with box walls " . sprintf("%.4f",Slic3r::Geometry::rad2deg(atan2($shift, 20))) . " degrees off of vertical";
-    }
-}
-
 __END__
diff --git a/t/config.t b/t/config.t
deleted file mode 100644
index f4a1867de..000000000
--- a/t/config.t
+++ /dev/null
@@ -1,20 +0,0 @@
-use Test::More tests => 1;
-use strict;
-use warnings;
-
-BEGIN {
-    use FindBin;
-    use lib "$FindBin::Bin/../lib";
-    use local::lib "$FindBin::Bin/../local-lib";
-}
-
-use Slic3r;
-use Slic3r::Test;
-
-{
-    my $config = Slic3r::Config::new_from_defaults;
-    $config->set('perimeter_extrusion_width', '250%');
-    ok $config->validate, 'percent extrusion width is validated';
-}
-
-__END__
diff --git a/t/cooling.t b/t/cooling.t
deleted file mode 100644
index e46cfa2f7..000000000
--- a/t/cooling.t
+++ /dev/null
@@ -1,214 +0,0 @@
-use Test::More;
-use strict;
-use warnings;
-
-plan tests => 14;
-
-BEGIN {
-    use FindBin;
-    use lib "$FindBin::Bin/../lib";
-    use local::lib "$FindBin::Bin/../local-lib";
-}
-
-use List::Util qw(none all);
-use Slic3r;
-use Slic3r::Test;
-
-my $gcodegen;
-sub buffer {
-    my $config = shift;
-    if (defined($config)) {
-        $config = $config->clone();
-    } else {
-        $config = Slic3r::Config->new;
-    }
-    my $config_override = shift;
-    foreach my $key (keys %{$config_override}) {
-        $config->set($key, ${$config_override}{$key});
-    }
-
-    my $print_config = Slic3r::Config::Print->new;
-    $print_config->apply_dynamic($config);
-    
-    $gcodegen = Slic3r::GCode->new;
-    $gcodegen->apply_print_config($print_config);
-    $gcodegen->set_layer_count(10);
-
-    my $extruders_ref = shift;
-    $extruders_ref = [ 0 ] if !defined $extruders_ref;
-    $gcodegen->set_extruders($extruders_ref);
-    return Slic3r::GCode::CoolingBuffer->new($gcodegen);
-}
-
-my $gcode1      = "G1 X100 E1 F3000\n";
-my $print_time1 = 100 / (3000 / 60); # 2 sec
-my $gcode2      = $gcode1 . "G1 X0 E1 F3000\n";
-my $print_time2 = 2 * $print_time1; # 4 sec
-
-my $config = Slic3r::Config::new_from_defaults;
-# Default cooling settings.
-$config->set('bridge_fan_speed',            [ 100 ]);
-$config->set('cooling',                     [ 1 ]);
-$config->set('fan_always_on',               [ 0 ]);
-$config->set('fan_below_layer_time',        [ 60 ]);
-$config->set('max_fan_speed',               [ 100 ]);
-$config->set('min_print_speed',             [ 10 ]);
-$config->set('slowdown_below_layer_time',   [ 5 ]);
-# Default print speeds.
-$config->set('bridge_speed',                60);
-$config->set('external_perimeter_speed',    '50%');
-$config->set('first_layer_speed',           30);
-$config->set('gap_fill_speed',              20);
-$config->set('infill_speed',                80);
-$config->set('perimeter_speed',             60);
-$config->set('small_perimeter_speed',       15);
-$config->set('solid_infill_speed',          20);
-$config->set('top_solid_infill_speed',      15);
-$config->set('max_print_speed',             80);
-# Override for tests.
-$config->set('disable_fan_first_layers',    [ 0 ]);
-
-{
-    my $gcode_src  = "G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1";
-    # Print time of $gcode.
-    my $print_time = 100 / (3000 / 60);
-    my $buffer = buffer($config, { 'slowdown_below_layer_time' => [ $print_time * 0.999 ] });
-    my $gcode = $buffer->process_layer($gcode_src, 0);
-    like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold';
-}
-
-{
-    my $gcode_src  = 
-        "G1 X50 F2500\n" .
-        "G1 F3000;_EXTRUDE_SET_SPEED\n" .
-        "G1 X100 E1\n" .
-        ";_EXTRUDE_END\n" .
-        "G1 E4 F400",
-    # Print time of $gcode.
-    my $print_time = 50 / (2500 / 60) + 100 / (3000 / 60) + 4 / (400 / 60);
-    my $buffer = buffer($config, { 'slowdown_below_layer_time' => [ $print_time * 1.001 ] });
-    my $gcode  = $buffer->process_layer($gcode_src, 0);
-    unlike $gcode, qr/F3000/, 'speed is altered when elapsed time is lower than slowdown threshold';
-    like $gcode, qr/F2500/, 'speed is not altered for travel moves';
-    like $gcode, qr/F400/, 'speed is not altered for extruder-only moves';
-}
-
-{
-    my $buffer = buffer($config, {
-            'fan_below_layer_time'      => [ $print_time1 * 0.88 ],
-            'slowdown_below_layer_time' => [ $print_time1 * 0.99 ]
-        });
-    my $gcode = $buffer->process_layer($gcode1, 0);
-    unlike $gcode, qr/M106/, 'fan is not activated when elapsed time is greater than fan threshold';
-}
-
-{
-    my $gcode .= buffer($config, { 'slowdown_below_layer_time', [ $print_time2 * 0.99 ] })->process_layer($gcode2, 0);
-    like $gcode, qr/F3000/, 'slowdown is computed on all objects printing at the same Z';
-}
-
-{
-    # use an elapsed time which is < the threshold but greater than it when summed twice
-    my $buffer = buffer($config, {
-            'fan_below_layer_time'      => [ $print_time2 * 0.65], 
-            'slowdown_below_layer_time' => [ $print_time2 * 0.7 ] 
-        });
-    my $gcode = $buffer->process_layer($gcode2, 0) .
-                $buffer->process_layer($gcode2, 1);
-    unlike $gcode, qr/M106/, 'fan is not activated on all objects printing at different Z';
-}
-
-{
-    # use an elapsed time which is < the threshold even when summed twice
-    my $buffer = buffer($config, {
-            'fan_below_layer_time'      => [ $print_time2 + 1 ], 
-            'slowdown_below_layer_time' => [ $print_time2 + 2 ] 
-        });
-    my $gcode = $buffer->process_layer($gcode2, 0) .
-                $buffer->process_layer($gcode2, 1);
-    like $gcode, qr/M106/, 'fan is activated on all objects printing at different Z';
-}
-
-{
-    my $buffer = buffer($config, {
-            'cooling'                   => [ 1               , 0                ],
-            'fan_below_layer_time'      => [ $print_time2 + 1, $print_time2 + 1 ], 
-            'slowdown_below_layer_time' => [ $print_time2 + 2, $print_time2 + 2 ]
-        },
-	[ 0, 1]);
-    my $gcode = $buffer->process_layer($gcode1 . "T1\nG1 X0 E1 F3000\n", 0);
-    like $gcode, qr/^M106/, 'fan is activated for the 1st tool';
-    like $gcode, qr/.*M107/, 'fan is disabled for the 2nd tool';
-}
-
-{
-    my $config = Slic3r::Config::new_from_defaults;
-    $config->set('cooling', [ 1 ]);
-    $config->set('bridge_fan_speed', [ 100 ]);
-    $config->set('fan_below_layer_time', [ 0 ]);
-    $config->set('slowdown_below_layer_time', [ 0 ]);
-    $config->set('bridge_speed', 99);
-    $config->set('top_solid_layers', 1);     # internal bridges use solid_infil speed
-    $config->set('bottom_solid_layers', 1);  # internal bridges use solid_infil speed
-    
-    my $print = Slic3r::Test::init_print('overhang', config => $config);
-    my $fan = 0;
-    my $fan_with_incorrect_speeds = my $fan_with_incorrect_print_speeds = 0;
-    my $bridge_with_no_fan = 0;
-    Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
-        my ($self, $cmd, $args, $info) = @_;
-        
-        if ($cmd eq 'M106') {
-            $fan = $args->{S};
-            $fan_with_incorrect_speeds++ if $fan != 255;
-        } elsif ($cmd eq 'M107') {
-            $fan = 0;
-        } elsif ($info->{extruding} && $info->{dist_XY} > 0) {
-            $fan_with_incorrect_print_speeds++
-                if ($fan > 0) && ($args->{F} // $self->F) != 60*$config->bridge_speed;
-            $bridge_with_no_fan++
-                if !$fan && ($args->{F} // $self->F) == 60*$config->bridge_speed;
-        }
-    });
-    ok !$fan_with_incorrect_speeds, 'bridge fan speed is applied correctly';
-    ok !$fan_with_incorrect_print_speeds, 'bridge fan is only turned on for bridges';
-    ok !$bridge_with_no_fan, 'bridge fan is turned on for all bridges';
-}
-
-{
-    my $config = Slic3r::Config::new_from_defaults;
-    $config->set('cooling', [ 1 ]);
-    $config->set('fan_below_layer_time', [ 0 ]);
-    $config->set('slowdown_below_layer_time', [ 10 ]);
-    $config->set('min_print_speed', [ 0 ]);
-    $config->set('start_gcode', '');
-    $config->set('first_layer_speed', '100%');
-    $config->set('external_perimeter_speed', 99);
-    
-    my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
-    my @layer_times = (0);  # in seconds
-    my %layer_external = ();  # z => 1
-    Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub {
-        my ($self, $cmd, $args, $info) = @_;
-        
-        if ($cmd eq 'G1') {
-            if ($info->{dist_Z}) {
-                push @layer_times, 0;
-                $layer_external{ $args->{Z} } = 0;
-            }
-            $layer_times[-1] += abs($info->{dist_XY} || $info->{dist_E} || $info->{dist_Z} || 0) / ($args->{F} // $self->F) * 60;
-            if ($args->{F} && $args->{F} == $config->external_perimeter_speed*60) {
-                $layer_external{ $self->Z }++;
-            }
-        }
-    });
-    @layer_times = grep $_, @layer_times;
-    my $all_below = none { $_ < $config->slowdown_below_layer_time->[0] } @layer_times;
-    ok $all_below, 'slowdown_below_layer_time is honored';
-    
-    # check that all layers have at least one unaltered external perimeter speed
-#    my $external = all { $_ > 0 } values %layer_external;
-#    ok $external, 'slowdown_below_layer_time does not alter external perimeters';
-}
-
-__END__
diff --git a/t/flow.t b/t/flow.t
deleted file mode 100644
index 50c491604..000000000
--- a/t/flow.t
+++ /dev/null
@@ -1,83 +0,0 @@
-use Test::More tests => 6;
-use strict;
-use warnings;
-
-BEGIN {
-    use FindBin;
-    use lib "$FindBin::Bin/../lib";
-    use local::lib "$FindBin::Bin/../local-lib";
-}
-
-use List::Util qw(first sum);
-use Slic3r;
-use Slic3r::Geometry qw(scale PI);
-use Slic3r::Test;
-
-{
-    my $config = Slic3r::Config::new_from_defaults;
-    $config->set('skirts', 1);
-    $config->set('brim_width', 2);
-    $config->set('perimeters', 3);
-    $config->set('fill_density', 0.4);
-    $config->set('bottom_solid_layers', 1);
-    $config->set('first_layer_extrusion_width', 2);
-    $config->set('first_layer_height', $config->layer_height);
-    $config->set('filament_diameter', [ 3.0 ]);
-    $config->set('nozzle_diameter', [ 0.5 ]);
-    
-    my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
-    my @E_per_mm = ();
-    Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub {
-        my ($self, $cmd, $args, $info) = @_;
-        
-        if ($self->Z == $config->layer_height) {  # only consider first layer
-            if ($info->{extruding} && $info->{dist_XY} > 0) {
-                push @E_per_mm, $info->{dist_E} / $info->{dist_XY};
-            }
-        }
-    });
-    my $E_per_mm_avg = sum(@E_per_mm) / @E_per_mm;
-    # allow some tolerance because solid rectilinear infill might be adjusted/stretched
-    ok !(defined first { abs($_ - $E_per_mm_avg) > 0.015 } @E_per_mm),
-        'first_layer_extrusion_width applies to everything on first layer';
-}
-
-{
-    my $config = Slic3r::Config::new_from_defaults;
-    $config->set('bridge_speed', 99);
-    $config->set('bridge_flow_ratio', 1);
-    $config->set('cooling', [ 0 ]);             # to prevent speeds from being altered
-    $config->set('first_layer_speed', '100%');  # to prevent speeds from being altered
-    
-    my $test = sub {
-        my $print = Slic3r::Test::init_print('overhang', config => $config);
-        my @E_per_mm = ();
-        Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub {
-            my ($self, $cmd, $args, $info) = @_;
-        
-            if ($info->{extruding} && $info->{dist_XY} > 0) {
-                if (($args->{F} // $self->F) == $config->bridge_speed*60) {
-                    push @E_per_mm, $info->{dist_E} / $info->{dist_XY};
-                }
-            }
-        });
-        my $expected_mm3_per_mm = ($config->nozzle_diameter->[0]**2) * PI/4 * $config->bridge_flow_ratio;
-        my $expected_E_per_mm = $expected_mm3_per_mm / ((($config->filament_diameter->[0]/2)**2)*PI);
-        ok !(defined first { abs($_ - $expected_E_per_mm) > 0.01 } @E_per_mm),
-            'expected flow when using bridge_flow_ratio = ' . $config->bridge_flow_ratio;
-    };
-    
-    $config->set('bridge_flow_ratio', 0.5);
-    $test->();
-    $config->set('bridge_flow_ratio', 2);
-    $test->();
-    $config->set('extrusion_width', 0.4);
-    $config->set('bridge_flow_ratio', 1);
-    $test->();
-    $config->set('bridge_flow_ratio', 0.5);
-    $test->();
-    $config->set('bridge_flow_ratio', 2);
-    $test->();
-}
-
-__END__
diff --git a/t/gaps.t b/t/gaps.t
deleted file mode 100644
index 2594ac087..000000000
--- a/t/gaps.t
+++ /dev/null
@@ -1,59 +0,0 @@
-use Test::More tests => 1;
-use strict;
-use warnings;
-
-BEGIN {
-    use FindBin;
-    use lib "$FindBin::Bin/../lib";
-    use local::lib "$FindBin::Bin/../local-lib";
-}
-
-use List::Util qw(first);
-use Slic3r;
-use Slic3r::Geometry qw(PI scale unscale convex_hull);
-use Slic3r::Surface ':types';
-use Slic3r::Test;
-
-{
-    my $config = Slic3r::Config::new_from_defaults;
-    $config->set('skirts', 0);
-    $config->set('perimeter_speed', 66);
-    $config->set('external_perimeter_speed', 66);
-    $config->set('small_perimeter_speed', 66);
-    $config->set('gap_fill_speed', 99);
-    $config->set('perimeters', 1);
-    $config->set('cooling', [ 0 ]);                 # to prevent speeds from being altered
-    $config->set('first_layer_speed', '100%');      # to prevent speeds from being altered
-    $config->set('perimeter_extrusion_width', 0.35);
-    $config->set('first_layer_extrusion_width', 0.35);
-    
-    my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config);
-    my @perimeter_points = ();
-    my $last = '';  # perimeter | gap
-    my $gap_fills_outside_last_perimeters = 0;
-    Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
-        my ($self, $cmd, $args, $info) = @_;
-        
-        if ($info->{extruding} && $info->{dist_XY} > 0) {
-            my $F = $args->{F} // $self->F;
-            my $point = Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y});
-            if ($F == $config->perimeter_speed*60) {
-                if ($last eq 'gap') {
-                    @perimeter_points = ();
-                }
-                push @perimeter_points, $point;
-                $last = 'perimeter';
-            } elsif ($F == $config->gap_fill_speed*60) {
-                my $convex_hull = convex_hull(\@perimeter_points);
-                if (!$convex_hull->contains_point($point)) {
-                    $gap_fills_outside_last_perimeters++;
-                }
-                
-                $last = 'gap';
-            }
-        }
-    });
-    is $gap_fills_outside_last_perimeters, 0, 'gap fills are printed before leaving islands';
-}
-
-__END__
diff --git a/t/gcode.t b/t/gcode.t
index b95505e43..902c40b83 100644
--- a/t/gcode.t
+++ b/t/gcode.t
@@ -1,4 +1,4 @@
-use Test::More tests => 24;
+use Test::More tests => 23;
 use strict;
 use warnings;
 
@@ -13,13 +13,6 @@ use Slic3r;
 use Slic3r::Geometry qw(scale convex_hull);
 use Slic3r::Test;
 
-{
-    my $gcodegen = Slic3r::GCode->new();
-    $gcodegen->set_layer_count(1);
-    $gcodegen->set_origin(Slic3r::Pointf->new(10, 10));
-    is_deeply $gcodegen->last_pos->arrayref, [scale -10, scale -10], 'last_pos is shifted correctly';
-}
-
 {
     my $config = Slic3r::Config::new_from_defaults;
     $config->set('wipe', [1]);
diff --git a/t/loops.t b/t/loops.t
deleted file mode 100644
index e662469ca..000000000
--- a/t/loops.t
+++ /dev/null
@@ -1,57 +0,0 @@
-use Test::More;
-use strict;
-use warnings;
-
-plan skip_all => 'temporarily disabled';
-plan tests => 4;
-
-BEGIN {
-    use FindBin;
-    use lib "$FindBin::Bin/../lib";
-    use local::lib "$FindBin::Bin/../local-lib";
-}
-
-use Slic3r;
-use Slic3r::Test;
-
-{
-    # We only need to slice at one height, so we'll build a non-manifold mesh
-    # that still produces complete loops at that height. Triangular walls are 
-    # enough for this purpose.
-    # Basically we want to check what happens when three concentric loops happen
-    # to be at the same height, the two external ones being ccw and the other being
-    # a hole, thus cw.
-    my (@vertices, @facets) = ();
-    Slic3r::Test::add_facet($_, \@vertices, \@facets) for
-        # external surface below the slicing Z
-        [ [0,0,0],   [20,0,10],   [0,0,10]    ],
-        [ [20,0,0],  [20,20,10],  [20,0,10]   ],
-        [ [20,20,0], [0,20,10],   [20,20,10]  ],
-        [ [0,20,0],  [0,0,10],    [0,20,10]   ],
-        
-        # external insetted surface above the slicing Z
-        [ [2,2,10],   [18,2,10],  [2,2,20]    ],
-        [ [18,2,10],  [18,18,10], [18,2,20]   ],
-        [ [18,18,10], [2,18,10],  [18,18,20]  ],
-        [ [2,18,10],  [2,2,10],   [2,18,20]   ],
-        
-        # insetted hole below the slicing Z
-        [ [15,5,0],   [5,5,10],   [15,5,10]   ],
-        [ [15,15,0],  [15,5,10],  [15,15,10]  ],
-        [ [5,15,0],   [15,15,10], [5,15,10]   ],
-        [ [5,5,0],    [5,15,10],  [5,5,10]    ];
-    
-    my $mesh = Slic3r::TriangleMesh->new;
-    $mesh->ReadFromPerl(\@vertices, \@facets);
-    $mesh->analyze;
-    my @lines = map $mesh->intersect_facet($_, 10), 0..$#facets;
-    my $loops = Slic3r::TriangleMesh::make_loops(\@lines);
-    is scalar(@$loops), 3, 'correct number of loops detected';
-    is scalar(grep $_->is_counter_clockwise, @$loops), 2, 'correct number of ccw loops detected';
-    
-    my @surfaces = Slic3r::Layer::Region::_merge_loops($loops, 0);
-    is scalar(@surfaces), 1, 'one surface detected';
-    is scalar(@{$surfaces[0]->expolygon})-1, 1, 'surface has one hole';
-}
-
-__END__
diff --git a/t/thin.t b/t/thin.t
deleted file mode 100644
index 50e7abc95..000000000
--- a/t/thin.t
+++ /dev/null
@@ -1,185 +0,0 @@
-use Test::More tests => 23;
-use strict;
-use warnings;
-
-BEGIN {
-    use FindBin;
-    use lib "$FindBin::Bin/../lib";
-    use local::lib "$FindBin::Bin/../local-lib";
-}
-
-use Slic3r;
-use List::Util qw(first sum none);
-use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon Y);
-use Slic3r::Test;
-
-# Disable this until a more robust implementation is provided. It currently
-# fails on Linux 32bit because some spurious extrudates are generated.
-if (0) {
-    my $config = Slic3r::Config::new_from_defaults;
-    $config->set('layer_height', 0.2);
-    $config->set('first_layer_height', $config->layer_height);
-    $config->set('extrusion_width', 0.5);
-    $config->set('first_layer_extrusion_width', '200%'); # check this one too
-    $config->set('skirts', 0);
-    $config->set('thin_walls', 1);
-    
-    my $print = Slic3r::Test::init_print('gt2_teeth', config => $config);
-    
-    my %extrusion_paths = ();  # Z => count of continuous extrusions
-    my $extruding = 0;
-    Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
-        my ($self, $cmd, $args, $info) = @_;
-        
-        if ($cmd eq 'G1') {
-            if ($info->{extruding} && $info->{dist_XY}) {
-                if (!$extruding) {
-                    $extrusion_paths{$self->Z} //= 0;
-                    $extrusion_paths{$self->Z}++;
-                }
-                $extruding = 1;
-            } else {
-                $extruding = 0;
-            }
-        }
-    });
-    
-    ok !(first { $_ != 3 } values %extrusion_paths),
-        'no superfluous thin walls are generated for toothed profile';
-}
-
-{
-    my $square = Slic3r::Polygon->new_scale(  # ccw
-        [100, 100],
-        [200, 100],
-        [200, 200],
-        [100, 200],
-    );
-    my $hole_in_square = Slic3r::Polygon->new_scale(  # cw
-        [140, 140],
-        [140, 160],
-        [160, 160],
-        [160, 140],
-    );
-    my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square);
-    my $res = $expolygon->medial_axis(scale 40, scale 0.5);
-    is scalar(@$res), 1, 'medial axis of a square shape is a single path';
-    isa_ok $res->[0], 'Slic3r::Polyline', 'medial axis result is a polyline';
-    ok $res->[0]->first_point->coincides_with($res->[0]->last_point), 'polyline forms a closed loop';
-    ok $res->[0]->length > $hole_in_square->length && $res->[0]->length < $square->length,
-        'medial axis loop has reasonable length';
-}
-
-{
-    my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale(
-        [100, 100],
-        [120, 100],
-        [120, 200],
-        [100, 200],
-    ));
-    my $res = $expolygon->medial_axis(scale 20, scale 0.5);
-    is scalar(@$res), 1, 'medial axis of a narrow rectangle is a single line';
-    ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length';
-    
-    $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale(
-        [100, 100],
-        [120, 100],
-        [120, 200],
-        [105, 200],  # extra point in the short side
-        [100, 200],
-    ));
-    my $res2 = $expolygon->medial_axis(scale 1, scale 0.5);
-    is scalar(@$res), 1, 'medial axis of a narrow rectangle with an extra vertex is still a single line';
-    ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has still a reasonable length';
-    ok !(grep { abs($_ - scale 150) < scaled_epsilon } map $_->[Y], map @$_, @$res2), "extra vertices don't influence medial axis";
-}
-
-{
-    my $expolygon = Slic3r::ExPolygon->new(
-        Slic3r::Polygon->new([1185881,829367],[1421988,1578184],[1722442,2303558],[2084981,2999998],[2506843,3662186],[2984809,4285086],[3515250,4863959],[4094122,5394400],[4717018,5872368],[5379210,6294226],[6075653,6656769],[6801033,6957229],[7549842,7193328],[8316383,7363266],[9094809,7465751],[9879211,7500000],[10663611,7465750],[11442038,7363265],[12208580,7193327],[12957389,6957228],[13682769,6656768],[14379209,6294227],[15041405,5872366],[15664297,5394401],[16243171,4863960],[16758641,4301424],[17251579,3662185],[17673439,3000000],[18035980,2303556],[18336441,1578177],[18572539,829368],[18750748,0],[19758422,0],[19727293,236479],[19538467,1088188],[19276136,1920196],[18942292,2726179],[18539460,3499999],[18070731,4235755],[17539650,4927877],[16950279,5571067],[16307090,6160437],[15614974,6691519],[14879209,7160248],[14105392,7563079],[13299407,7896927],[12467399,8159255],[11615691,8348082],[10750769,8461952],[9879211,8500000],[9007652,8461952],[8142729,8348082],[7291022,8159255],[6459015,7896927],[5653029,7563079],[4879210,7160247],[4143447,6691519],[3451331,6160437],[2808141,5571066],[2218773,4927878],[1687689,4235755],[1218962,3499999],[827499,2748020],[482284,1920196],[219954,1088186],[31126,236479],[0,0],[1005754,0]),
-    );
-    my $res = $expolygon->medial_axis(scale 1.324888, scale 0.25);
-    is scalar(@$res), 1, 'medial axis of a semicircumference is a single line';
-    
-    # check whether turns are all CCW or all CW
-    my @lines = @{$res->[0]->lines};
-    my @angles = map { $lines[$_-1]->ccw($lines[$_]->b) } 1..$#lines;
-    ok !!(none { $_ < 0 } @angles) || (none { $_ > 0 } @angles),
-        'all medial axis segments of a semicircumference have the same orientation';
-}
-
-{
-    my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale(
-        [100, 100],
-        [120, 100],
-        [112, 200],
-        [108, 200],
-    ));
-    my $res = $expolygon->medial_axis(scale 20, scale 0.5);
-    is scalar(@$res), 1, 'medial axis of a narrow trapezoid is a single line';
-    ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length';
-}
-
-{
-    my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale(
-        [100, 100],
-        [120, 100],
-        [120, 180],
-        [200, 180],
-        [200, 200],
-        [100, 200],
-    ));
-    my $res = $expolygon->medial_axis(scale 20, scale 0.5);
-    is scalar(@$res), 1, 'medial axis of a L shape is a single polyline';
-    my $len = unscale($res->[0]->length) + 20;  # 20 is the thickness of the expolygon, which is subtracted from the ends
-    ok $len > 80*2 && $len < 100*2, 'medial axis has reasonable length';
-}
-
-{
-    my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new(
-        [-203064906,-51459966],[-219312231,-51459966],[-219335477,-51459962],[-219376095,-51459962],[-219412047,-51459966],
-        [-219572094,-51459966],[-219624814,-51459962],[-219642183,-51459962],[-219656665,-51459966],[-220815482,-51459966],
-        [-220815482,-37738966],[-221117540,-37738966],[-221117540,-51762024],[-203064906,-51762024],
-    ));
-    my $polylines = $expolygon->medial_axis(819998, 102499.75);
-    
-    my $perimeter = $expolygon->contour->split_at_first_point->length;
-    ok sum(map $_->length, @$polylines) > $perimeter/2/4*3, 'medial axis has a reasonable length';
-}
-
-{
-    my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale(
-        [50, 100],
-        [1000, 102],
-        [50, 104],
-    ));
-    my $res = $expolygon->medial_axis(scale 4, scale 0.5);
-    is scalar(@$res), 1, 'medial axis of a narrow triangle is a single line';
-    ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length';
-}
-
-{
-    # GH #2474
-    my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new(
-        [91294454,31032190],[11294481,31032190],[11294481,29967810],[44969182,29967810],[89909960,29967808],[91294454,29967808]
-    ));
-    my $polylines = $expolygon->medial_axis(1871238, 500000);
-    is scalar(@$polylines), 1, 'medial axis is a single polyline';
-    my $polyline = $polylines->[0];
-    
-    my $expected_y = $expolygon->bounding_box->center->y; #;;
-    ok abs(sum(map $_->y, @$polyline) / @$polyline - $expected_y) < scaled_epsilon, #,,
-        'medial axis is horizontal and is centered';
-    
-    # order polyline from left to right
-    $polyline->reverse if $polyline->first_point->x > $polyline->last_point->x;
-    
-    my $polyline_bb = $polyline->bounding_box;
-    is $polyline->first_point->x, $polyline_bb->x_min, 'expected x_min';
-    is $polyline->last_point->x,  $polyline_bb->x_max, 'expected x_max';
-    
-    is_deeply [ map $_->x, @$polyline ], [ sort map $_->x, @$polyline ],
-        'medial axis is not self-overlapping';
-}
-
-__END__
diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt
index 9e039c913..4e8821287 100644
--- a/tests/fff_print/CMakeLists.txt
+++ b/tests/fff_print/CMakeLists.txt
@@ -3,11 +3,14 @@ add_executable(${_TEST_NAME}_tests
 	${_TEST_NAME}_tests.cpp
 	test_avoid_crossing_perimeters.cpp
 	test_bridges.cpp
+	test_cooling.cpp
+	test_custom_gcode.cpp
 	test_data.cpp
 	test_data.hpp
 	test_extrusion_entity.cpp
 	test_fill.cpp
 	test_flow.cpp
+	test_gaps.cpp
 	test_gcode.cpp
 	test_gcodefindreplace.cpp
 	test_gcodewriter.cpp
@@ -19,6 +22,7 @@ add_executable(${_TEST_NAME}_tests
 	test_printobject.cpp
 	test_skirt_brim.cpp
 	test_support_material.cpp
+	test_thin_walls.cpp
 	test_trianglemesh.cpp
 	)
 target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
diff --git a/tests/fff_print/test_cooling.cpp b/tests/fff_print/test_cooling.cpp
new file mode 100644
index 000000000..f743783b1
--- /dev/null
+++ b/tests/fff_print/test_cooling.cpp
@@ -0,0 +1,274 @@
+#include <catch2/catch.hpp>
+
+#include <numeric>
+#include <sstream>
+
+#include "test_data.hpp" // get access to init_print, etc
+
+#include "libslic3r/Config.hpp"
+#include "libslic3r/GCode.hpp"
+#include "libslic3r/GCodeReader.hpp"
+#include "libslic3r/GCode/CoolingBuffer.hpp"
+#include "libslic3r/libslic3r.h"
+
+using namespace Slic3r;
+
+std::unique_ptr<CoolingBuffer> make_cooling_buffer(
+    GCode                           &gcode,
+    const DynamicPrintConfig        &config         = DynamicPrintConfig{}, 
+    const std::vector<unsigned int> &extruder_ids   = { 0 })
+{
+    PrintConfig print_config;
+    print_config.apply(config, true); // ignore_nonexistent
+    gcode.apply_print_config(print_config);
+    gcode.set_layer_count(10);
+    gcode.writer().set_extruders(extruder_ids);
+    gcode.writer().set_extruder(0);
+    return std::make_unique<CoolingBuffer>(gcode);
+}
+
+SCENARIO("Cooling unit tests", "[Cooling]") {
+    const std::string   gcode1        = "G1 X100 E1 F3000\n";
+    // 2 sec
+    const double        print_time1   = 100. / (3000. / 60.);
+    const std::string   gcode2        = gcode1 + "G1 X0 E1 F3000\n";
+    // 4 sec
+    const double        print_time2   = 2. * print_time1;
+
+    auto config = DynamicPrintConfig::full_print_config_with({
+        // Default cooling settings.
+        { "bridge_fan_speed",            "100" },
+        { "cooling",                     "1" },
+        { "fan_always_on",               "0" },
+        { "fan_below_layer_time",        "60" },
+        { "max_fan_speed",               "100" },
+        { "min_print_speed",             "10" },
+        { "slowdown_below_layer_time",   "5" },
+        // Default print speeds.
+        { "bridge_speed",                60 },
+        { "external_perimeter_speed",    "50%" },
+        { "first_layer_speed",           30 },
+        { "gap_fill_speed",              20 },
+        { "infill_speed",                80 },
+        { "perimeter_speed",             60 },
+        { "small_perimeter_speed",       15 },
+        { "solid_infill_speed",          20 },
+        { "top_solid_infill_speed",      15 },
+        { "max_print_speed",             80 },
+        // Override for tests.
+        { "disable_fan_first_layers",    "0" }
+    });
+
+    WHEN("G-code block 3") {
+        THEN("speed is not altered when elapsed time is greater than slowdown threshold") {
+            // Print time of gcode.
+            const double print_time = 100. / (3000. / 60.);
+            //FIXME slowdown_below_layer_time is rounded down significantly from 1.8s to 1s.
+            config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 0.999) } } });
+            GCode gcodegen;
+            auto buffer = make_cooling_buffer(gcodegen, config);
+            std::string gcode = buffer->process_layer("G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1", 0, true);
+            bool speed_not_altered = gcode.find("F3000") != gcode.npos;
+            REQUIRE(speed_not_altered);
+        }
+    }
+
+    WHEN("G-code block 4") {
+        const std::string gcode_src = 
+            "G1 X50 F2500\n"
+            "G1 F3000;_EXTRUDE_SET_SPEED\n"
+            "G1 X100 E1\n"
+            ";_EXTRUDE_END\n"
+            "G1 E4 F400";
+        // Print time of gcode.
+        const double print_time = 50. / (2500. / 60.) + 100. / (3000. / 60.) + 4. / (400. / 60.);
+        config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 1.001) } } });
+        GCode gcodegen;
+        auto buffer = make_cooling_buffer(gcodegen, config);
+        std::string gcode = buffer->process_layer(gcode_src, 0, true);
+        THEN("speed is altered when elapsed time is lower than slowdown threshold") {
+            bool speed_is_altered = gcode.find("F3000") == gcode.npos;
+            REQUIRE(speed_is_altered);
+        }
+        THEN("speed is not altered for travel moves") {
+            bool speed_not_altered = gcode.find("F2500") != gcode.npos;
+            REQUIRE(speed_not_altered);
+        }
+        THEN("speed is not altered for extruder-only moves") {
+            bool speed_not_altered = gcode.find("F400") != gcode.npos;
+            REQUIRE(speed_not_altered);   
+        }
+    }
+
+    WHEN("G-code block 1") {
+        THEN("fan is not activated when elapsed time is greater than fan threshold") {
+            config.set_deserialize_strict({
+                { "fan_below_layer_time"      , int(print_time1 * 0.88) },
+                { "slowdown_below_layer_time" , int(print_time1 * 0.99) }
+            });
+            GCode gcodegen;
+            auto buffer = make_cooling_buffer(gcodegen, config);
+            std::string gcode = buffer->process_layer(gcode1, 0, true);
+            bool fan_not_activated = gcode.find("M106") == gcode.npos;
+            REQUIRE(fan_not_activated);      
+        }
+    }
+    WHEN("G-code block 1 with two extruders") {
+        config.set_deserialize_strict({
+            { "cooling",                   "1, 0" },
+            { "fan_below_layer_time",      { int(print_time2 + 1.), int(print_time2 + 1.) } },
+            { "slowdown_below_layer_time", { int(print_time2 + 2.), int(print_time2 + 2.) } }
+        });
+        GCode gcodegen;
+        auto buffer = make_cooling_buffer(gcodegen, config, { 0, 1 });
+        std::string gcode = buffer->process_layer(gcode1 + "T1\nG1 X0 E1 F3000\n", 0, true);
+        THEN("fan is activated for the 1st tool") {
+            bool ok = gcode.find("M106") == 0;
+            REQUIRE(ok);      
+        }
+        THEN("fan is disabled for the 2nd tool") {
+            bool ok = gcode.find("\nM107") > 0;
+            REQUIRE(ok);      
+        }
+    }
+    WHEN("G-code block 2") {
+        THEN("slowdown is computed on all objects printing at the same Z") {
+            config.set_deserialize_strict({ { "slowdown_below_layer_time", int(print_time2 * 0.99) } });
+            GCode gcodegen;
+            auto buffer = make_cooling_buffer(gcodegen, config);
+            std::string gcode = buffer->process_layer(gcode2, 0, true);
+            bool ok = gcode.find("F3000") != gcode.npos;
+            REQUIRE(ok);      
+        }
+        THEN("fan is not activated on all objects printing at different Z") {
+            config.set_deserialize_strict({ 
+                { "fan_below_layer_time",      int(print_time2 * 0.65) },
+                { "slowdown_below_layer_time", int(print_time2 * 0.7) }
+            });
+            GCode gcodegen;
+            auto buffer = make_cooling_buffer(gcodegen, config);
+            // use an elapsed time which is < the threshold but greater than it when summed twice
+            std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true);
+            bool fan_not_activated = gcode.find("M106") == gcode.npos;
+            REQUIRE(fan_not_activated);      
+        }
+        THEN("fan is activated on all objects printing at different Z") {
+            // use an elapsed time which is < the threshold even when summed twice
+            config.set_deserialize_strict({ 
+                { "fan_below_layer_time",      int(print_time2 + 1) },
+                { "slowdown_below_layer_time", int(print_time2 + 1) }
+            });
+            GCode gcodegen;
+            auto buffer = make_cooling_buffer(gcodegen, config);
+            // use an elapsed time which is < the threshold but greater than it when summed twice
+            std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true);
+            bool fan_activated = gcode.find("M106") != gcode.npos;
+            REQUIRE(fan_activated);      
+        }
+    }
+}
+
+SCENARIO("Cooling integration tests", "[Cooling]") {
+    GIVEN("overhang") {
+        auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
+            { "cooling",                    { 1 } },
+            { "bridge_fan_speed",           { 100 } },
+            { "fan_below_layer_time",       { 0 } },
+            { "slowdown_below_layer_time",  { 0 } },
+            { "bridge_speed",               99 },
+            // internal bridges use solid_infil speed
+            { "bottom_solid_layers",        1 },
+            // internal bridges use solid_infil speed
+        });
+    
+        GCodeReader parser;
+        int fan = 0;
+        int fan_with_incorrect_speeds = 0;
+        int fan_with_incorrect_print_speeds = 0;
+        int bridge_with_no_fan = 0;
+        const double bridge_speed = config.opt_float("bridge_speed") * 60;
+        parser.parse_buffer(
+            Slic3r::Test::slice({ Slic3r::Test::TestMesh::overhang }, config),
+            [&fan, &fan_with_incorrect_speeds, &fan_with_incorrect_print_speeds, &bridge_with_no_fan, bridge_speed]
+                (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
+        {
+            if (line.cmd_is("M106")) {
+                line.has_value('S', fan);
+                if (fan != 255)
+                    ++ fan_with_incorrect_speeds;
+            } else if (line.cmd_is("M107")) {
+                fan = 0;
+            } else if (line.extruding(self) && line.dist_XY(self) > 0) {
+                if (is_approx<double>(line.new_F(self), bridge_speed)) {
+                    if (fan != 255)
+                        ++ bridge_with_no_fan;
+                } else {
+                    if (fan != 0)
+                        ++ fan_with_incorrect_print_speeds;
+                }
+            }
+        });
+        THEN("bridge fan speed is applied correctly") {
+            REQUIRE(fan_with_incorrect_speeds == 0);
+        }
+        THEN("bridge fan is only turned on for bridges") {
+            REQUIRE(fan_with_incorrect_print_speeds == 0);
+        }
+        THEN("bridge fan is turned on for all bridges") {
+            REQUIRE(bridge_with_no_fan == 0);
+        }
+    }
+    GIVEN("20mm cube") {
+        auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
+            { "cooling",                    { 1 } },
+            { "fan_below_layer_time",       { 0 } },
+            { "slowdown_below_layer_time",  { 10 } },
+            { "min_print_speed",            { 0 } },
+            { "start_gcode",                "" },
+            { "first_layer_speed",          "100%" },
+            { "external_perimeter_speed",   99 }
+        });
+        GCodeReader parser;
+        const double external_perimeter_speed = config.opt<ConfigOptionFloatOrPercent>("external_perimeter_speed")->value * 60;
+        std::vector<double> layer_times;
+        // z => 1
+        std::map<coord_t, int> layer_external;
+        parser.parse_buffer(
+            Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config),
+            [&layer_times, &layer_external, external_perimeter_speed]
+                (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
+        {
+            if (line.cmd_is("G1")) {
+                if (line.dist_Z(self) != 0) {
+                    layer_times.emplace_back(0.);
+                    layer_external[scaled<coord_t>(line.new_Z(self))] = 0;
+                }
+                double l = line.dist_XY(self);
+                if (l == 0)
+                    l = line.dist_E(self);
+                if (l == 0)
+                    l = line.dist_Z(self);
+                if (l > 0.) {
+                    if (layer_times.empty())
+                        layer_times.emplace_back(0.);
+                    layer_times.back() += 60. * std::abs(l) / line.new_F(self);
+                }
+                if (line.has('F') && line.f() == external_perimeter_speed)
+                    ++ layer_external[scaled<coord_t>(self.z())];
+            }
+        });            
+        THEN("slowdown_below_layer_time is honored") {
+            // Account for some inaccuracies.
+            const double slowdown_below_layer_time = config.opt<ConfigOptionInts>("slowdown_below_layer_time")->values.front() - 0.2;
+            size_t minimum_time_honored = std::count_if(layer_times.begin(), layer_times.end(), 
+                [slowdown_below_layer_time](double t){ return t > slowdown_below_layer_time; });
+            REQUIRE(minimum_time_honored == layer_times.size());
+        }
+        THEN("slowdown_below_layer_time does not alter external perimeters") {
+            // Broken by Vojtech
+            // check that all layers have at least one unaltered external perimeter speed
+            // my $external = all { $_ > 0 } values %layer_external;
+            // ok $external, '';
+        }
+    }
+}
diff --git a/tests/fff_print/test_custom_gcode.cpp b/tests/fff_print/test_custom_gcode.cpp
new file mode 100644
index 000000000..0368b9604
--- /dev/null
+++ b/tests/fff_print/test_custom_gcode.cpp
@@ -0,0 +1,220 @@
+#include <catch2/catch.hpp>
+
+#include <numeric>
+#include <sstream>
+
+#include "libslic3r/Config.hpp"
+#include "libslic3r/Print.hpp"
+#include "libslic3r/PrintConfig.hpp"
+#include "libslic3r/libslic3r.h"
+
+#include "test_data.hpp"
+
+using namespace Slic3r;
+
+#if 0
+SCENARIO("Output file format", "[CustomGCode]")
+{
+    WHEN("output_file_format set") {
+        auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
+            { "output_filename_format",         "ts_[travel_speed]_lh_[layer_height].gcode" },
+            { "start_gcode",                    "TRAVEL:[travel_speed] HEIGHT:[layer_height]\n" }
+        });
+
+        Print print;
+        Model model;
+        Test::init_print({ Test::TestMesh::cube_2x20x10 }, print, model, config);
+    
+        std::string output_file = print.output_filepath();
+        THEN("print config options are replaced in output filename") {
+            output_file.find(std::string("ts_") + )
+        }
+        my ($t, $h) = map $config->$_, qw(travel_speed layer_height);
+        ok $output_file =~ /ts_${t}_/, '';
+        ok $output_file =~ /lh_$h\./, 'region config options are replaced in output filename';
+    
+        std::string gcode = print.gcode(print);
+        THEN("print config options are replaced in custom G-code") {
+            ok $gcode =~ /TRAVEL:$t/, '';    
+        }
+        THEN("region config options are replaced in custom G-code") {
+            ok $gcode =~ /HEIGHT:$h/, '';
+        }
+    }
+}
+
+SCENARIO("Custom G-code", "[CustomGCode]")
+{
+    WHEN("start_gcode and layer_gcode set") {
+        auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
+            { "start_gcode", "_MY_CUSTOM_START_GCODE_" },  // to avoid dealing with the nozzle lift in start G-code
+            { "layer_gcode", "_MY_CUSTOM_LAYER_GCODE_" }
+        });
+        GCodeReader parser;
+        bool        last_move_was_z_change = false;
+        int         num_layer_changes_not_applied = 0;
+        parser.parse_buffer(Slic3r::Test::slice({ Test::TestMesh::cube_2x20x10 }, config), 
+            [&last_move_was_z_change, &num_layer_changes_not_applied](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
+        {
+            if (line.extruding(self)) {
+                if (! was_extruding)
+                    seam_points.emplace_back(self.xy_scaled());
+                was_extruding = true;
+            } else if (! line.cmd_is("M73")) {
+                // skips remaining time lines (M73)
+                was_extruding = false;
+            }
+            if (last_move_was_z_change != line.cmd_is("_MY_CUSTOM_LAYER_GCODE_"))
+                ++ num_layer_changes_not_applied;
+            last_move_was_z_change = line.dist_Z(self) > 0;
+        });
+        THEN("custom layer G-code is applied after Z move and before other moves");
+    };
+
+{
+    my $config = Slic3r::Config->new;
+    $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]);
+    $config->set('extruder', 2);
+    $config->set('first_layer_temperature', [200,205]);
+    
+    {
+        my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
+        my $gcode = Slic3r::Test::gcode($print);
+        ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for non-zero yet single extruder';
+        ok $gcode !~ /M104 S\d+ T0/, 'unused extruder correctly ignored';
+    }
+    
+    $config->set('infill_extruder', 1);
+    {
+        my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
+        my $gcode = Slic3r::Test::gcode($print);
+        ok $gcode =~ /M104 S200 T0/, 'temperature set correctly for first extruder';
+        ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for second extruder';
+    }
+    
+    my @start_gcode = (qq!
+;__temp0:[first_layer_temperature_0]__
+;__temp1:[first_layer_temperature_1]__
+;__temp2:[first_layer_temperature_2]__
+    !, qq!
+;__temp0:{first_layer_temperature[0]}__
+;__temp1:{first_layer_temperature[1]}__
+;__temp2:{first_layer_temperature[2]}__
+    !);
+    my @syntax_description = (' (legacy syntax)', ' (new syntax)');
+    for my $i (0, 1) {
+        $config->set('start_gcode', $start_gcode[$i]);
+        {
+            my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
+            my $gcode = Slic3r::Test::gcode($print);
+            # we use the [infill_extruder] placeholder to make sure this test doesn't
+            # catch a false positive caused by the unparsed start G-code option itself
+            # being embedded in the G-code
+            ok $gcode =~ /temp0:200/, 'temperature placeholder for first extruder correctly populated' . $syntax_description[$i];
+            ok $gcode =~ /temp1:205/, 'temperature placeholder for second extruder correctly populated' . $syntax_description[$i];
+            ok $gcode =~ /temp2:200/, 'temperature placeholder for unused extruder populated with first value' . $syntax_description[$i];
+        }
+    }
+
+    $config->set('start_gcode', qq!
+;substitution:{if infill_extruder==1}extruder1
+         {elsif infill_extruder==2}extruder2
+         {else}extruder3{endif}
+    !);
+    {
+        my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
+        my $gcode = Slic3r::Test::gcode($print);
+        ok $gcode =~ /substitution:extruder1/, 'if / else / endif - first block returned';
+    }
+}
+
+{
+    my $config = Slic3r::Config::new_from_defaults;
+    $config->set('before_layer_gcode', ';BEFORE [layer_num]');
+    $config->set('layer_gcode', ';CHANGE [layer_num]');
+    $config->set('support_material', 1);
+    $config->set('layer_height', 0.2);
+    my $print = Slic3r::Test::init_print('overhang', config => $config);
+    my $gcode = Slic3r::Test::gcode($print);
+    
+    my @before = ();
+    my @change = ();
+    foreach my $line (split /\R+/, $gcode) {
+        if ($line =~ /;BEFORE (\d+)/) {
+            push @before, $1;
+        } elsif ($line =~ /;CHANGE (\d+)/) {
+            push @change, $1;
+            fail 'inconsistent layer_num before and after layer change'
+                if $1 != $before[-1];
+        }
+    }
+    is_deeply \@before, \@change, 'layer_num is consistent before and after layer changes';
+    ok !defined(first { $change[$_] != $change[$_-1]+1 } 1..$#change),
+        'layer_num grows continously';  # i.e. no duplicates or regressions
+}
+
+{
+    my $config = Slic3r::Config->new;
+    $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6,0.6]);
+    $config->set('start_gcode', qq!
+;substitution:{if infill_extruder==1}if block
+         {elsif infill_extruder==2}elsif block 1
+         {elsif infill_extruder==3}elsif block 2
+         {elsif infill_extruder==4}elsif block 3
+         {else}endif block{endif}
+    !);
+    my @returned = ('', 'if block', 'elsif block 1', 'elsif block 2', 'elsif block 3', 'endif block');
+    for my $i (1,2,3,4,5) {
+        $config->set('infill_extruder', $i);
+        my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
+        my $gcode = Slic3r::Test::gcode($print);
+        my $found_other = 0;
+        for my $j (1,2,3,4,5) {
+            next if $i == $j;
+            $found_other = 1 if $gcode =~ /substitution:$returned[$j]/;
+        }
+        ok $gcode =~ /substitution:$returned[$i]/, 'if / else / endif - ' . $returned[$i] . ' returned';
+        ok !$found_other, 'if / else / endif - only ' . $returned[$i] . ' returned';
+    }
+}
+
+{
+    my $config = Slic3r::Config->new;
+    $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]);
+    $config->set('start_gcode', 
+        ';substitution:{if infill_extruder==1}{if perimeter_extruder==1}block11{else}block12{endif}' .
+        '{elsif infill_extruder==2}{if perimeter_extruder==1}block21{else}block22{endif}' .
+        '{else}{if perimeter_extruder==1}block31{else}block32{endif}{endif}:end');
+    for my $i (1,2,3) {
+        $config->set('infill_extruder', $i);
+        for my $j (1,2) {
+            $config->set('perimeter_extruder', $j);
+            my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
+            my $gcode = Slic3r::Test::gcode($print);
+            ok $gcode =~ /substitution:block$i$j:end/, "two level if / else / endif - block$i$j returned";
+        }
+    }
+}
+
+{
+    my $config = Slic3r::Config->new;
+    $config->set('start_gcode', 
+        ';substitution:{if notes=="MK2"}MK2{elsif notes=="MK3"}MK3{else}MK1{endif}:end');
+    for my $printer_name ("MK2", "MK3", "MK1") {
+        $config->set('notes', $printer_name);
+        my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
+        my $gcode = Slic3r::Test::gcode($print);
+        ok $gcode =~ /substitution:$printer_name:end/, "printer name $printer_name matched";
+    }
+}
+
+{
+    my $config = Slic3r::Config::new_from_defaults;
+    $config->set('complete_objects', 1);
+    $config->set('between_objects_gcode', '_MY_CUSTOM_GCODE_');
+    my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 3);
+    my $gcode = Slic3r::Test::gcode($print);
+    is scalar(() = $gcode =~ /^_MY_CUSTOM_GCODE_/gm), 2, 'between_objects_gcode is applied correctly';
+}
+
+#endif
\ No newline at end of file
diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp
index 6be45d238..a52583cfc 100644
--- a/tests/fff_print/test_data.cpp
+++ b/tests/fff_print/test_data.cpp
@@ -26,6 +26,7 @@ const std::unordered_map<TestMesh, const char*, TestMeshHash> mesh_names {
     std::pair<TestMesh, const char*>(TestMesh::V,						"V"), 
     std::pair<TestMesh, const char*>(TestMesh::_40x10,					"40x10"), 
     std::pair<TestMesh, const char*>(TestMesh::cube_20x20x20,			"cube_20x20x20"), 
+    std::pair<TestMesh, const char*>(TestMesh::cube_2x20x10,            "cube_2x20x10"), 
     std::pair<TestMesh, const char*>(TestMesh::sphere_50mm,				"sphere_50mm"), 
     std::pair<TestMesh, const char*>(TestMesh::bridge,					"bridge"), 
     std::pair<TestMesh, const char*>(TestMesh::bridge_with_hole,		"bridge_with_hole"), 
@@ -49,6 +50,9 @@ TriangleMesh mesh(TestMesh m)
         case TestMesh::cube_20x20x20:
             mesh = Slic3r::make_cube(20, 20, 20);
             break;
+        case TestMesh::cube_2x20x10:
+            mesh = Slic3r::make_cube(2, 20, 10);
+            break;
         case TestMesh::sphere_50mm:
             mesh = Slic3r::make_sphere(50, PI / 243.0);
             break;
diff --git a/tests/fff_print/test_data.hpp b/tests/fff_print/test_data.hpp
index 573ae58a5..b699e5e4e 100644
--- a/tests/fff_print/test_data.hpp
+++ b/tests/fff_print/test_data.hpp
@@ -21,6 +21,7 @@ enum class TestMesh {
     V,
     _40x10,
     cube_20x20x20,
+    cube_2x20x10,
     sphere_50mm,
     bridge,
     bridge_with_hole,
diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp
index c3af6e8fc..0ccb27d9a 100644
--- a/tests/fff_print/test_fill.cpp
+++ b/tests/fff_print/test_fill.cpp
@@ -197,7 +197,7 @@ TEST_CASE("Fill: Pattern Path Length", "[Fill]") {
 SCENARIO("Infill does not exceed perimeters", "[Fill]") 
 {
     auto test = [](const std::string_view pattern) {
-        DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config_with({
+        auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
             { "nozzle_diameter",        "0.4, 0.4, 0.4, 0.4" },
             { "fill_pattern",           pattern },
             { "top_fill_pattern",       pattern },
diff --git a/tests/fff_print/test_flow.cpp b/tests/fff_print/test_flow.cpp
index 81f748e19..b24b59cc6 100644
--- a/tests/fff_print/test_flow.cpp
+++ b/tests/fff_print/test_flow.cpp
@@ -5,8 +5,6 @@
 
 #include "test_data.hpp" // get access to init_print, etc
 
-#include "libslic3r/Config.hpp"
-#include "libslic3r/Model.hpp"
 #include "libslic3r/Config.hpp"
 #include "libslic3r/GCodeReader.hpp"
 #include "libslic3r/Flow.hpp"
@@ -16,61 +14,118 @@ using namespace Slic3r::Test;
 using namespace Slic3r;
 
 SCENARIO("Extrusion width specifics", "[Flow]") {
-    GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer and a 20mm cube mesh") {
-        // this is a sharedptr
-        DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
-		config.set_deserialize_strict({
-			{ "brim_width",			2 },
-			{ "skirts",				1 },
-			{ "perimeters",			3 },
-			{ "fill_density",		"40%" },
-			{ "first_layer_height", 0.3 }
-			});
 
-        WHEN("first layer width set to 2mm") {
-            Slic3r::Model model;
-            config.set("first_layer_extrusion_width", 2);
-            Slic3r::Print print;
-            Slic3r::Test::init_print({TestMesh::cube_20x20x20}, print, model, config);
-
-            std::vector<double> E_per_mm_bottom;
-            std::string gcode = Test::gcode(print);
-            Slic3r::GCodeReader parser;
-            const double layer_height = config.opt_float("layer_height");
-            parser.parse_buffer(gcode, [&E_per_mm_bottom, layer_height] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
-            { 
-                if (self.z() == Approx(layer_height).margin(0.01)) { // only consider first layer
-                    if (line.extruding(self) && line.dist_XY(self) > 0) {
-                        E_per_mm_bottom.emplace_back(line.dist_E(self) / line.dist_XY(self));
-                    }
-                }
-            });
-            THEN(" First layer width applies to everything on first layer.") {
-                bool pass = false;
-                double avg_E = std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast<double>(E_per_mm_bottom.size());
-
-                pass = (std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [avg_E] (const double& v) { return v == Approx(avg_E); }) == 0);
-                REQUIRE(pass == true);
-                REQUIRE(E_per_mm_bottom.size() > 0); // make sure it actually passed because of extrusion
-            }
-            THEN(" First layer width does not apply to upper layer.") {
+    auto test = [](const DynamicPrintConfig &config) {
+        Slic3r::GCodeReader parser;
+        const double        layer_height = config.opt_float("layer_height");
+        std::vector<double> E_per_mm_bottom;
+        parser.parse_buffer(Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config),
+            [&E_per_mm_bottom, layer_height] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
+        { 
+            if (self.z() == Approx(layer_height).margin(0.01)) { // only consider first layer
+                if (line.extruding(self) && line.dist_XY(self) > 0)
+                    E_per_mm_bottom.emplace_back(line.dist_E(self) / line.dist_XY(self));
             }
+        });
+        THEN("First layer width applies to everything on first layer.") {
+            REQUIRE(E_per_mm_bottom.size() > 0);
+            const double E_per_mm_avg = std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast<double>(E_per_mm_bottom.size());
+            bool pass = (std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [E_per_mm_avg] (const double& v) { return v == Approx(E_per_mm_avg); }) == 0);
+            REQUIRE(pass);
+        }
+        THEN("First layer width does not apply to upper layer.") {
+        }
+    };
+    GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer") {
+        auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
+            { "skirts",                         1 },
+            { "brim_width",                     2 },
+            { "perimeters",                     3 },
+            { "fill_density",                   "40%" },
+            { "first_layer_height",             0.3 },
+            { "first_layer_extrusion_width",    "2" },
+        });
+        WHEN("Slicing a 20mm cube") {
+            test(config);
+        }
+    }
+    GIVEN("A config with more options and a 20mm cube ") {
+        auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
+            { "skirts",                         1 },
+            { "brim_width",                     2 },
+            { "perimeters",                     3 },
+            { "fill_density",                   "40%" },
+            { "layer_height",                   "0.35" },
+            { "first_layer_height",             "0.35" },
+            { "bottom_solid_layers",            1 },
+            { "first_layer_extrusion_width",    "2" },
+            { "filament_diameter",              "3" },
+            { "nozzle_diameter",                "0.5" }
+        });
+        WHEN("Slicing a 20mm cube") {
+            test(config);            
         }
     }
 }
-// needs gcode export
+
 SCENARIO(" Bridge flow specifics.", "[Flow]") {
+    auto config = DynamicPrintConfig::full_print_config_with({
+        { "bridge_speed",           99 },
+        { "bridge_flow_ratio",      1 },
+        // to prevent speeds from being altered
+        { "cooling",                "0" },
+        // to prevent speeds from being altered
+        { "first_layer_speed",      "100%" }
+    });
+
+    auto test = [](const DynamicPrintConfig &config) {
+        GCodeReader         parser;
+        const double        bridge_speed = config.opt_float("bridge_speed") * 60.;
+        std::vector<double> E_per_mm;
+        parser.parse_buffer(Slic3r::Test::slice({ Slic3r::Test::TestMesh::overhang }, config), 
+            [&E_per_mm, bridge_speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
+            if (line.extruding(self) && line.dist_XY(self) > 0) {
+                if (is_approx<double>(line.new_F(self), bridge_speed))
+                    E_per_mm.emplace_back(line.dist_E(self) / line.dist_XY(self));
+            }
+        });
+        const double nozzle_dmr                 = config.opt<ConfigOptionFloats>("nozzle_diameter")->get_at(0);
+        const double filament_dmr               = config.opt<ConfigOptionFloats>("filament_diameter")->get_at(0);
+        const double bridge_mm_per_mm           = sqr(nozzle_dmr / filament_dmr) * config.opt_float("bridge_flow_ratio");
+        size_t num_errors = std::count_if(E_per_mm.begin(), E_per_mm.end(), 
+            [bridge_mm_per_mm](double v){ return std::abs(v - bridge_mm_per_mm) > 0.01; });
+        return num_errors == 0;
+    };
+
     GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio and an overhang mesh.") {
-        WHEN("bridge_flow_ratio is set to 1.0") {
+        WHEN("bridge_flow_ratio is set to 0.5 and extrusion width to default") {
+            config.set_deserialize_strict({ { "bridge_flow_ratio", 0.5}, { "extrusion_width", "0" } });
             THEN("Output flow is as expected.") {
+                REQUIRE(test(config));
             }
         }
-        WHEN("bridge_flow_ratio is set to 0.5") {
+        WHEN("bridge_flow_ratio is set to 2.0 and extrusion width to default") {
+            config.set_deserialize_strict({ { "bridge_flow_ratio", 2.0}, { "extrusion_width", "0" } });
             THEN("Output flow is as expected.") {
+                REQUIRE(test(config));
             }
         }
-        WHEN("bridge_flow_ratio is set to 2.0") {
+        WHEN("bridge_flow_ratio is set to 0.5 and extrusion_width to 0.4") {
+            config.set_deserialize_strict({ { "bridge_flow_ratio", 0.5}, { "extrusion_width", 0.4 } });
             THEN("Output flow is as expected.") {
+                REQUIRE(test(config));
+            }
+        }
+        WHEN("bridge_flow_ratio is set to 1.0 and extrusion_width to 0.4") {
+            config.set_deserialize_strict({ { "bridge_flow_ratio", 1.0}, { "extrusion_width", 0.4 } });
+            THEN("Output flow is as expected.") {
+                REQUIRE(test(config));
+            }
+        }
+        WHEN("bridge_flow_ratio is set to 2 and extrusion_width to 0.4") {
+            config.set_deserialize_strict({ { "bridge_flow_ratio", 2.}, { "extrusion_width", 0.4 } });
+            THEN("Output flow is as expected.") {
+                REQUIRE(test(config));
             }
         }
     }
diff --git a/tests/fff_print/test_gaps.cpp b/tests/fff_print/test_gaps.cpp
new file mode 100644
index 000000000..a096087ce
--- /dev/null
+++ b/tests/fff_print/test_gaps.cpp
@@ -0,0 +1,60 @@
+#include <catch2/catch.hpp>
+
+#include "libslic3r/GCodeReader.hpp"
+#include "libslic3r/Geometry/ConvexHull.hpp"
+#include "libslic3r/Layer.hpp"
+
+#include "test_data.hpp" // get access to init_print, etc
+
+using namespace Slic3r::Test;
+using namespace Slic3r;
+
+SCENARIO("Gaps", "[Gaps]") {
+    GIVEN("Two hollow squares") {
+        auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
+            { "skirts",                         0 },
+            { "perimeter_speed",                66 },
+            { "external_perimeter_speed",       66 },
+            { "small_perimeter_speed",          66 },
+            { "gap_fill_speed",                 99 },
+            { "perimeters",                     1 },
+            // to prevent speeds from being altered
+            { "cooling",                        0 },
+            // to prevent speeds from being altered
+            { "first_layer_speed",              "100%" },
+            { "perimeter_extrusion_width",      0.35 },
+            { "first_layer_extrusion_width",    0.35 }
+        });
+    
+        GCodeReader parser;
+        const double perimeter_speed = config.opt_float("perimeter_speed") * 60;
+        const double gap_fill_speed  = config.opt_float("gap_fill_speed") * 60;
+        std::string  last; // perimeter or gap
+        Points       perimeter_points;
+        int          gap_fills_outside_last_perimeters = 0;
+        parser.parse_buffer(
+            Slic3r::Test::slice({ Slic3r::Test::TestMesh::two_hollow_squares }, config),
+            [&perimeter_points, &gap_fills_outside_last_perimeters, &last, perimeter_speed, gap_fill_speed]
+                (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
+        {
+            if (line.extruding(self) && line.dist_XY(self) > 0) {
+                double f = line.new_F(self);
+                Point point = line.new_XY_scaled(self);
+                if (is_approx(f, perimeter_speed)) {
+                    if (last == "gap")
+                        perimeter_points.clear();
+                    perimeter_points.emplace_back(point);
+                    last = "perimeter";
+                } else if (is_approx(f, gap_fill_speed)) {
+                    Polygon convex_hull = Geometry::convex_hull(perimeter_points);
+                    if (! convex_hull.contains(point))
+                        ++ gap_fills_outside_last_perimeters;
+                    last = "gap";
+                }
+            }
+        });
+        THEN("gap fills are printed before leaving islands") {
+            REQUIRE(gap_fills_outside_last_perimeters == 0);
+        }
+    }
+}
diff --git a/tests/fff_print/test_thin_walls.cpp b/tests/fff_print/test_thin_walls.cpp
new file mode 100644
index 000000000..cd80f3bd6
--- /dev/null
+++ b/tests/fff_print/test_thin_walls.cpp
@@ -0,0 +1,191 @@
+#include <catch2/catch.hpp>
+
+#include <numeric>
+#include <sstream>
+
+#include "test_data.hpp" // get access to init_print, etc
+
+#include "libslic3r/ExPolygon.hpp"
+#include "libslic3r/libslic3r.h"
+
+using namespace Slic3r;
+
+SCENARIO("Medial Axis", "[ThinWalls]") {
+    GIVEN("Square with hole") {
+        auto square = Polygon::new_scale({ {100, 100}, {200, 100}, {200, 200}, {100, 200} });
+        auto hole_in_square = Polygon::new_scale({ {140, 140}, {140, 160}, {160, 160}, {160, 140} });
+        ExPolygon expolygon{ square, hole_in_square };
+        WHEN("Medial axis is extracted") {
+            Polylines res = expolygon.medial_axis(scaled<double>(40.), scaled<double>(0.5));
+            THEN("medial axis of a square shape is a single path") {
+                REQUIRE(res.size() == 1);
+            }
+            THEN("polyline forms a closed loop") {
+                REQUIRE(res.front().first_point() ==  res.front().last_point());
+            }
+            THEN("medial axis loop has reasonable length") {
+                REQUIRE(res.front().length() > hole_in_square.length());
+                REQUIRE(res.front().length() < square.length());
+            }
+        }
+    }
+    GIVEN("narrow rectangle") {
+        ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 200}, {100, 200} }) };
+        WHEN("Medial axis is extracted") {
+            Polylines res = expolygon.medial_axis(scaled<double>(20.), scaled<double>(0.5));
+            THEN("medial axis of a narrow rectangle is a single line") {
+                REQUIRE(res.size() == 1);
+            }
+            THEN("medial axis has reasonable length") {
+                REQUIRE(res.front().length() >= scaled<double>(200.-100. - (120.-100.)) - SCALED_EPSILON);
+            }
+        }
+    }
+#if 0
+    //FIXME this test never worked
+    GIVEN("narrow rectangle with an extra vertex") {
+        ExPolygon expolygon{ Polygon::new_scale({ 
+            {100, 100}, {120, 100}, {120, 200}, 
+            {105, 200} /* extra point in the short side*/, 
+            {100, 200} 
+        })};
+        WHEN("Medial axis is extracted") {
+            Polylines res = expolygon.medial_axis(scaled<double>(1.), scaled<double>(0.5));
+            THEN("medial axis of a narrow rectangle with an extra vertex is still a single line") {
+                REQUIRE(res.size() == 1);
+            }
+            THEN("medial axis has still a reasonable length") {
+                REQUIRE(res.front().length() >= scaled<double>(200.-100. - (120.-100.)) - SCALED_EPSILON);
+            }
+            THEN("extra vertices don't influence medial axis") {
+                size_t invalid = 0;
+                for (const Polyline &pl : res)
+                    for (const Point &p : pl.points)
+                        if (std::abs(p.y() - scaled<coord_t>(150.)) < SCALED_EPSILON)
+                            ++ invalid;
+                REQUIRE(invalid == 0);
+            }
+        }
+    }
+#endif
+    GIVEN("semicircumference") {
+        ExPolygon expolygon{{ 
+            {1185881,829367},{1421988,1578184},{1722442,2303558},{2084981,2999998},{2506843,3662186},{2984809,4285086},{3515250,4863959},{4094122,5394400},
+            {4717018,5872368},{5379210,6294226},{6075653,6656769},{6801033,6957229},{7549842,7193328},{8316383,7363266},{9094809,7465751},{9879211,7500000},
+            {10663611,7465750},{11442038,7363265},{12208580,7193327},{12957389,6957228},{13682769,6656768},{14379209,6294227},{15041405,5872366},
+            {15664297,5394401},{16243171,4863960},{16758641,4301424},{17251579,3662185},{17673439,3000000},{18035980,2303556},{18336441,1578177},
+            {18572539,829368},{18750748,0},{19758422,0},{19727293,236479},{19538467,1088188},{19276136,1920196},{18942292,2726179},{18539460,3499999},
+            {18070731,4235755},{17539650,4927877},{16950279,5571067},{16307090,6160437},{15614974,6691519},{14879209,7160248},{14105392,7563079},
+            {13299407,7896927},{12467399,8159255},{11615691,8348082},{10750769,8461952},{9879211,8500000},{9007652,8461952},{8142729,8348082},
+            {7291022,8159255},{6459015,7896927},{5653029,7563079},{4879210,7160247},{4143447,6691519},{3451331,6160437},{2808141,5571066},{2218773,4927878},
+            {1687689,4235755},{1218962,3499999},{827499,2748020},{482284,1920196},{219954,1088186},{31126,236479},{0,0},{1005754,0}
+        }};
+        WHEN("Medial axis is extracted") {
+            Polylines res = expolygon.medial_axis(scaled<double>(1.324888), scaled<double>(0.25));
+            THEN("medial axis of a semicircumference is a single line") {
+                REQUIRE(res.size() == 1);
+            }
+            THEN("all medial axis segments of a semicircumference have the same orientation") {
+                int nccw = 0;
+                int ncw = 0;
+                for (const Polyline &pl : res)
+                    for (size_t i = 1; i + 1 < pl.size(); ++ i) {
+                        double cross = cross2((pl.points[i] - pl.points[i - 1]).cast<double>(), (pl.points[i + 1] - pl.points[i]).cast<double>());
+                        if (cross > 0.)
+                            ++ nccw;
+                        else if (cross < 0.)
+                            ++ ncw;
+                    }
+                REQUIRE((ncw == 0 || nccw == 0));
+            }
+        }
+    }
+    GIVEN("narrow trapezoid") {
+        ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {112, 200}, {108, 200} }) };
+        WHEN("Medial axis is extracted") {
+            Polylines res = expolygon.medial_axis(scaled<double>(20.), scaled<double>(0.5));
+            THEN("medial axis of a narrow trapezoid is a single line") {
+                REQUIRE(res.size() == 1);
+            }
+            THEN("medial axis has reasonable length") {
+                REQUIRE(res.front().length() >= scaled<double>(200.-100. - (120.-100.)) - SCALED_EPSILON);
+            }
+        }
+    }
+    GIVEN("L shape") {
+        ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 180}, {200, 180}, {200, 200}, {100, 200}, }) };
+        WHEN("Medial axis is extracted") {
+            Polylines res = expolygon.medial_axis(scaled<double>(20.), scaled<double>(0.5));
+            THEN("medial axis of an L shape is a single line") {
+                REQUIRE(res.size() == 1);
+            }
+            THEN("medial axis has reasonable length") {
+                // 20 is the thickness of the expolygon, which is subtracted from the ends
+                auto len = unscale<double>(res.front().length()) + 20;
+                REQUIRE(len > 80. * 2.);
+                REQUIRE(len < 100. * 2.);
+            }
+        }
+    }
+    GIVEN("whatever shape") {
+        ExPolygon expolygon{{ 
+            {-203064906,-51459966},{-219312231,-51459966},{-219335477,-51459962},{-219376095,-51459962},{-219412047,-51459966},
+            {-219572094,-51459966},{-219624814,-51459962},{-219642183,-51459962},{-219656665,-51459966},{-220815482,-51459966},
+            {-220815482,-37738966},{-221117540,-37738966},{-221117540,-51762024},{-203064906,-51762024},
+        }};
+        WHEN("Medial axis is extracted") {
+            Polylines res = expolygon.medial_axis(819998., 102499.75);
+            THEN("medial axis is a single line") {
+                REQUIRE(res.size() == 1);
+            }
+            THEN("medial axis has reasonable length") {
+                double perimeter = expolygon.contour.split_at_first_point().length();
+                REQUIRE(total_length(res) > perimeter / 2. / 4. * 3.);
+            }
+        }
+    }
+    GIVEN("narrow triangle") {
+        ExPolygon expolygon{ Polygon::new_scale({ {50, 100}, {1000, 102}, {50, 104} }) };
+        WHEN("Medial axis is extracted") {
+            Polylines res = expolygon.medial_axis(scaled<double>(4.), scaled<double>(0.5));
+            THEN("medial axis of a narrow triangle is a single line") {
+                REQUIRE(res.size() == 1);
+            }
+            THEN("medial axis has reasonable length") {
+                REQUIRE(res.front().length() >= scaled<double>(200.-100. - (120.-100.)) - SCALED_EPSILON);
+            }
+        }
+    }
+    GIVEN("GH #2474") {
+        ExPolygon expolygon{{ {91294454,31032190},{11294481,31032190},{11294481,29967810},{44969182,29967810},{89909960,29967808},{91294454,29967808} }};
+        WHEN("Medial axis is extracted") {
+            Polylines res = expolygon.medial_axis(1871238, 500000);
+            THEN("medial axis is a single line") {
+                REQUIRE(res.size() == 1);
+            }
+            Polyline &polyline = res.front();
+            THEN("medial axis is horizontal and is centered") {
+                double expected_y = expolygon.contour.bounding_box().center().y();
+                double center_y   = 0.;
+                for (auto &p : polyline.points)
+                    center_y += double(p.y());
+                REQUIRE(std::abs(center_y / polyline.size() - expected_y) < SCALED_EPSILON);
+            }
+            // order polyline from left to right
+            if (polyline.first_point().x() > polyline.last_point().x())
+                polyline.reverse();
+            BoundingBox polyline_bb = polyline.bounding_box();
+            THEN("expected x_min") {
+                REQUIRE(polyline.first_point().x() == polyline_bb.min.x());
+            }
+            THEN("expected x_max") {
+                REQUIRE(polyline.last_point().x() == polyline_bb.max.x());
+            }
+            THEN("medial axis is monotonous in x (not self intersecting)") {
+                Polyline sorted { polyline };
+                std::sort(sorted.begin(), sorted.end());
+                REQUIRE(polyline == sorted);
+            }
+        }
+    }
+}
diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt
index 248245182..a3984b34b 100644
--- a/tests/libslic3r/CMakeLists.txt
+++ b/tests/libslic3r/CMakeLists.txt
@@ -13,6 +13,7 @@ add_executable(${_TEST_NAME}_tests
 	test_geometry.cpp
 	test_placeholder_parser.cpp
 	test_polygon.cpp
+	test_polyline.cpp
 	test_mutable_polygon.cpp
 	test_mutable_priority_queue.cpp
 	test_stl.cpp
diff --git a/tests/libslic3r/test_config.cpp b/tests/libslic3r/test_config.cpp
index 025459d68..50e61b3b0 100644
--- a/tests/libslic3r/test_config.cpp
+++ b/tests/libslic3r/test_config.cpp
@@ -1,5 +1,6 @@
 #include <catch2/catch.hpp>
 
+#include "libslic3r/Config.hpp"
 #include "libslic3r/PrintConfig.hpp"
 #include "libslic3r/LocalesUtils.hpp"
 
@@ -13,20 +14,20 @@ using namespace Slic3r;
 SCENARIO("Generic config validation performs as expected.", "[Config]") {
     GIVEN("A config generated from default options") {
         Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
-        WHEN( "perimeter_extrusion_width is set to 250%, a valid value") {
+        WHEN("perimeter_extrusion_width is set to 250%, a valid value") {
             config.set_deserialize_strict("perimeter_extrusion_width", "250%");
             THEN( "The config is read as valid.") {
                 REQUIRE(config.validate().empty());
             }
         }
-        WHEN( "perimeter_extrusion_width is set to -10, an invalid value") {
+        WHEN("perimeter_extrusion_width is set to -10, an invalid value") {
             config.set("perimeter_extrusion_width", -10);
             THEN( "Validate returns error") {
                 REQUIRE(! config.validate().empty());
             }
         }
 
-        WHEN( "perimeters is set to -10, an invalid value") {
+        WHEN("perimeters is set to -10, an invalid value") {
             config.set("perimeters", -10);
             THEN( "Validate returns error") {
                 REQUIRE(! config.validate().empty());
@@ -36,8 +37,7 @@ SCENARIO("Generic config validation performs as expected.", "[Config]") {
 }
 
 SCENARIO("Config accessor functions perform as expected.", "[Config]") {
-    GIVEN("A config generated from default options") {
-        Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
+    auto test = [](ConfigBase &config) {
         WHEN("A boolean option is set to a boolean value") {
             REQUIRE_NOTHROW(config.set("gcode_comments", true));
             THEN("The underlying value is set correctly.") {
@@ -70,8 +70,8 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
             }
         }
 #if 0
-		//FIXME better design accessors for vector elements.
-		WHEN("An integer-based option is set through the integer interface") {
+        //FIXME better design accessors for vector elements.
+        WHEN("An integer-based option is set through the integer interface") {
             config.set("bed_temperature", 100);
             THEN("The underlying value is set correctly.") {
                 REQUIRE(config.opt<ConfigOptionInts>("bed_temperature")->get_at(0) == 100);
@@ -193,6 +193,14 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
                 REQUIRE(config.opt_float("layer_height") == 0.5);
             }
         }
+    };
+    GIVEN("DynamicPrintConfig generated from default options") {
+        auto config = Slic3r::DynamicPrintConfig::full_print_config();
+        test(config);
+    }
+    GIVEN("FullPrintConfig generated from default options") {
+        Slic3r::FullPrintConfig config;
+        test(config);
     }
 }
 
diff --git a/tests/libslic3r/test_geometry.cpp b/tests/libslic3r/test_geometry.cpp
index 34e625e6d..41ef69aaa 100644
--- a/tests/libslic3r/test_geometry.cpp
+++ b/tests/libslic3r/test_geometry.cpp
@@ -162,14 +162,34 @@ TEST_CASE("Splitting a Polygon generates a polyline correctly", "[Geometry]"){
 }
 
 
-TEST_CASE("Bounding boxes are scaled appropriately", "[Geometry]"){
-    BoundingBox bb(std::vector<Point>({Point(0, 1), Point(10, 2), Point(20, 2)}));
-    bb.scale(2);
-    REQUIRE(bb.min == Point(0,2));
-    REQUIRE(bb.max == Point(40,4));
+SCENARIO("BoundingBox", "[Geometry]") {
+    WHEN("Bounding boxes are scaled") {
+        BoundingBox bb(std::vector<Point>({Point(0, 1), Point(10, 2), Point(20, 2)}));
+        bb.scale(2);
+        REQUIRE(bb.min == Point(0,2));
+        REQUIRE(bb.max == Point(40,4));
+    }
+    WHEN("BoundingBox constructed from points") {
+        BoundingBox bb(Points{ {100,200}, {100, 200}, {500, -600} });
+        THEN("minimum is correct") {
+            REQUIRE(bb.min == Point{100,-600});
+        }
+        THEN("maximum is correct") {
+            REQUIRE(bb.max == Point{500,200});
+        }
+    }
+    WHEN("BoundingBox constructed from a single point") {
+        BoundingBox bb;
+        bb.merge({10, 10});
+        THEN("minimum equals to the only defined point") {
+            REQUIRE(bb.min == Point{10,10});
+        }
+        THEN("maximum equals to the only defined point") {
+            REQUIRE(bb.max == Point{10,10});
+        }
+    }
 }
 
-
 TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){
 	Slic3r::Polyline tmp = { Point(10,10), Point(20,10) };
     Slic3r::Polygon area = offset(tmp,5).at(0);
diff --git a/tests/libslic3r/test_polygon.cpp b/tests/libslic3r/test_polygon.cpp
index f2c78cace..7774b44a6 100644
--- a/tests/libslic3r/test_polygon.cpp
+++ b/tests/libslic3r/test_polygon.cpp
@@ -148,3 +148,65 @@ SCENARIO("Remove collinear points from Polygon", "[Polygon]") {
         }
     }
 }
+
+SCENARIO("Simplify polygon", "[Polygon]")
+{
+    GIVEN("gear") {
+        auto gear = Polygon::new_scale({
+            {144.9694,317.1543}, {145.4181,301.5633}, {146.3466,296.921}, {131.8436,294.1643}, {131.7467,294.1464},
+            {121.7238,291.5082}, {117.1631,290.2776}, {107.9198,308.2068}, {100.1735,304.5101}, {104.9896,290.3672},
+            {106.6511,286.2133}, {93.453,279.2327}, {81.0065,271.4171}, {67.7886,286.5055}, {60.7927,280.1127},
+            {69.3928,268.2566}, {72.7271,264.9224}, {61.8152,253.9959}, {52.2273,242.8494}, {47.5799,245.7224},
+            {34.6577,252.6559}, {30.3369,245.2236}, {42.1712,236.3251}, {46.1122,233.9605}, {43.2099,228.4876},
+            {35.0862,211.5672}, {33.1441,207.0856}, {13.3923,212.1895}, {10.6572,203.3273}, {6.0707,204.8561},
+            {7.2775,204.4259}, {29.6713,196.3631}, {25.9815,172.1277}, {25.4589,167.2745}, {19.8337,167.0129},
+            {5.0625,166.3346}, {5.0625,156.9425}, {5.3701,156.9282}, {21.8636,156.1628}, {25.3713,156.4613},
+            {25.4243,155.9976}, {29.3432,155.8157}, {30.3838,149.3549}, {26.3596,147.8137}, {27.1085,141.2604},
+            {29.8466,126.8337}, {24.5841,124.9201}, {10.6664,119.8989}, {13.4454,110.9264}, {33.1886,116.0691},
+            {38.817,103.1819}, {45.8311,89.8133}, {30.4286,76.81}, {35.7686,70.0812}, {48.0879,77.6873},
+            {51.564,81.1635}, {61.9006,69.1791}, {72.3019,58.7916}, {60.5509,42.5416}, {68.3369,37.1532},
+            {77.9524,48.1338}, {80.405,52.2215}, {92.5632,44.5992}, {93.0123,44.3223}, {106.3561,37.2056},
+            {100.8631,17.4679}, {108.759,14.3778}, {107.3148,11.1283}, {117.0002,32.8627}, {140.9109,27.3974},
+            {145.7004,26.4994}, {145.1346,6.1011}, {154.502,5.4063}, {156.9398,25.6501}, {171.0557,26.2017},
+            {181.3139,27.323}, {186.2377,27.8532}, {191.6031,8.5474}, {200.6724,11.2756}, {197.2362,30.2334},
+            {220.0789,39.1906}, {224.3261,41.031}, {236.3506,24.4291}, {243.6897,28.6723}, {234.2956,46.7747},
+            {245.6562,55.1643}, {257.2523,65.0901}, {261.4374,61.5679}, {273.1709,52.8031}, {278.555,59.5164},
+            {268.4334,69.8001}, {264.1615,72.3633}, {268.2763,77.9442}, {278.8488,93.5305}, {281.4596,97.6332},
+            {286.4487,95.5191}, {300.2821,90.5903}, {303.4456,98.5849}, {286.4523,107.7253}, {293.7063,131.1779},
+            {294.9748,135.8787}, {314.918,133.8172}, {315.6941,143.2589}, {300.9234,146.1746}, {296.6419,147.0309},
+            {297.1839,161.7052}, {296.6136,176.3942}, {302.1147,177.4857}, {316.603,180.3608}, {317.1658,176.7341},
+            {315.215,189.6589}, {315.1749,189.6548}, {294.9411,187.5222}, {291.13,201.7233}, {286.2615,215.5916},
+            {291.1944,218.2545}, {303.9158,225.1271}, {299.2384,233.3694}, {285.7165,227.6001}, {281.7091,225.1956},
+            {273.8981,237.6457}, {268.3486,245.2248}, {267.4538,246.4414}, {264.8496,250.0221}, {268.6392,253.896},
+            {278.5017,265.2131}, {272.721,271.4403}, {257.2776,258.3579}, {234.4345,276.5687}, {242.6222,294.8315},
+            {234.9061,298.5798}, {227.0321,286.2841}, {225.2505,281.8301}, {211.5387,287.8187}, {202.3025,291.0935},
+            {197.307,292.831}, {199.808,313.1906}, {191.5298,315.0787}, {187.3082,299.8172}, {186.4201,295.3766},
+            {180.595,296.0487}, {161.7854,297.4248}, {156.8058,297.6214}, {154.3395,317.8592}
+        });
+     
+        WHEN("simplified") {
+            size_t num_points = gear.size();
+            Polygons simplified = gear.simplify(1000.);
+            THEN("gear simplified to a single polygon") {
+                REQUIRE(simplified.size() == 1);
+            }
+            THEN("gear was reduced using Douglas-Peucker") {
+                //note printf "original points: %d\nnew points: %d", $num_points, scalar(@{$simplified->[0]});
+                REQUIRE(simplified.front().size() < num_points);
+            }
+        }
+    }
+    GIVEN("hole in square") {
+        // CW oriented
+        auto hole_in_square = Polygon{ {140, 140}, {140, 160}, {160, 160}, {160, 140} };
+        WHEN("simplified") {
+            Polygons simplified = hole_in_square.simplify(2.);
+            THEN("hole simplification returns one polygon") {
+                REQUIRE(simplified.size() == 1);
+            }
+            THEN("hole simplification turns cw polygon into ccw polygon") {
+                REQUIRE(simplified.front().is_counter_clockwise());
+            }
+        }
+    }
+}
diff --git a/tests/libslic3r/test_polyline.cpp b/tests/libslic3r/test_polyline.cpp
new file mode 100644
index 000000000..827155404
--- /dev/null
+++ b/tests/libslic3r/test_polyline.cpp
@@ -0,0 +1,28 @@
+#include <catch2/catch.hpp>
+
+#include "libslic3r/Point.hpp"
+#include "libslic3r/Polyline.hpp"
+
+using namespace Slic3r;
+
+SCENARIO("Simplify polyline", "[Polyline]")
+{
+    GIVEN("polyline 1") {
+        auto polyline = Polyline{ {0,0},{1,0},{2,0},{2,1},{2,2},{1,2},{0,2},{0,1},{0,0} };
+        WHEN("simplified with Douglas-Peucker") {
+            polyline.simplify(1.);
+            THEN("simplified correctly") {
+                REQUIRE(polyline == Polyline{ {0,0}, {2,0}, {2,2}, {0,2}, {0,0} });
+            }
+        }
+    }
+    GIVEN("polyline 2") {
+        auto polyline = Polyline{ {0,0}, {50,50}, {100,0}, {125,-25}, {150,50} };
+        WHEN("simplified with Douglas-Peucker") {
+            polyline.simplify(25.);
+            THEN("not simplified") {
+                REQUIRE(polyline == Polyline{ {0,0}, {50,50}, {125,-25}, {150,50} });
+            }
+        }
+    }
+}
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
index 1a58aab12..c2343b032 100644
--- a/xs/CMakeLists.txt
+++ b/xs/CMakeLists.txt
@@ -49,7 +49,6 @@ set(XS_XSP_FILES
     ${XSP_DIR}/ExtrusionEntityCollection.xsp
     ${XSP_DIR}/ExtrusionLoop.xsp
     ${XSP_DIR}/ExtrusionPath.xsp
-    ${XSP_DIR}/GCode.xsp
     ${XSP_DIR}/Geometry.xsp
     ${XSP_DIR}/Layer.xsp
     ${XSP_DIR}/Line.xsp
diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm
index 1675ac193..87fb267c5 100644
--- a/xs/lib/Slic3r/XS.pm
+++ b/xs/lib/Slic3r/XS.pm
@@ -158,7 +158,6 @@ for my $class (qw(
         Slic3r::ExtrusionLoop
         Slic3r::ExtrusionPath
         Slic3r::ExtrusionPath::Collection
-        Slic3r::GCode
         Slic3r::Geometry::BoundingBox
         Slic3r::Layer
         Slic3r::Layer::Region
diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp
index 4e293a2f9..8484b1d64 100644
--- a/xs/src/perlglue.cpp
+++ b/xs/src/perlglue.cpp
@@ -7,7 +7,6 @@ REGISTER_CLASS(ExPolygon, "ExPolygon");
 REGISTER_CLASS(ExtrusionPath, "ExtrusionPath");
 REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop");
 REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection");
-REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer");
 REGISTER_CLASS(GCode, "GCode");
 REGISTER_CLASS(Layer, "Layer");
 REGISTER_CLASS(LayerRegion, "Layer::Region");
diff --git a/xs/t/01_trianglemesh.t b/xs/t/01_trianglemesh.t
deleted file mode 100644
index a071a75a2..000000000
--- a/xs/t/01_trianglemesh.t
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use Slic3r::XS;
-use Test::More tests => 4;
-
-my $cube = {
-    vertices    => [ [20,20,0], [20,0,0], [0,0,0], [0,20,0], [20,20,20], [0,20,20], [0,0,20], [20,0,20] ],
-    facets      => [ [0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5] ],
-};
-
-{
-    my $m = Slic3r::TriangleMesh->new;
-    $m->ReadFromPerl($cube->{vertices}, $cube->{facets});
-    my ($vertices, $facets) = ($m->vertices, $m->facets);
-    
-    is_deeply $vertices, $cube->{vertices}, 'vertices arrayref roundtrip';
-    is_deeply $facets, $cube->{facets}, 'facets arrayref roundtrip';
-    
-    {
-        my $m2 = $m->clone;
-        is_deeply $m2->vertices, $cube->{vertices}, 'cloned vertices arrayref roundtrip';
-        is_deeply $m2->facets, $cube->{facets}, 'cloned facets arrayref roundtrip';
-        $m2->scale(3);  # check that it does not affect $m
-    }
-}
-
-__END__
diff --git a/xs/t/03_point.t b/xs/t/03_point.t
index c950998fb..f888349b3 100644
--- a/xs/t/03_point.t
+++ b/xs/t/03_point.t
@@ -4,10 +4,9 @@ use strict;
 use warnings;
 
 use Slic3r::XS;
-use Test::More tests => 24;
+use Test::More tests => 21;
 
 my $point = Slic3r::Point->new(10, 15);
-is_deeply [ @$point ], [10, 15], 'point roundtrip';
 
 my $point2 = $point->clone;
 $point2->scale(2);
@@ -16,9 +15,6 @@ is_deeply [ @$point2 ], [20, 30], 'scale';
 $point2->translate(10, -15);
 is_deeply [ @$point2 ], [30, 15], 'translate';
 
-ok $point->coincides_with($point->clone), 'coincides_with';
-ok !$point->coincides_with($point2), 'coincides_with';
-
 {
     my $point3 = Slic3r::Point->new(4300000, -9880845);
     is $point->[0], $point->x, 'x accessor';
diff --git a/xs/t/04_expolygon.t b/xs/t/04_expolygon.t
index 48eaed551..9132c44b9 100644
--- a/xs/t/04_expolygon.t
+++ b/xs/t/04_expolygon.t
@@ -5,7 +5,7 @@ use warnings;
 
 use List::Util qw(first sum);
 use Slic3r::XS;
-use Test::More tests => 15;
+use Test::More tests => 7;
 
 use constant PI => 4 * atan2(1, 1);
 
@@ -25,21 +25,6 @@ my $hole_in_square = [  # cw
 my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square);
 
 ok $expolygon->is_valid, 'is_valid';
-is ref($expolygon->pp), 'ARRAY', 'expolygon pp is unblessed';
-is_deeply $expolygon->pp, [$square, $hole_in_square], 'expolygon roundtrip';
-
-is ref($expolygon->arrayref), 'ARRAY', 'expolygon arrayref is unblessed';
-isa_ok $expolygon->[0], 'Slic3r::Polygon::Ref', 'expolygon polygon is blessed';
-isa_ok $expolygon->contour, 'Slic3r::Polygon::Ref', 'expolygon contour is blessed';
-isa_ok $expolygon->holes->[0], 'Slic3r::Polygon::Ref', 'expolygon hole is blessed';
-isa_ok $expolygon->[0][0], 'Slic3r::Point::Ref', 'expolygon point is blessed';
-
-{
-    my $expolygon2 = $expolygon->clone;
-    my $polygon = $expolygon2->[0];
-    $polygon->scale(2);
-    is $expolygon2->[0][0][0], $polygon->[0][0], 'polygons are returned by reference';
-}
 
 is_deeply $expolygon->clone->pp, [$square, $hole_in_square], 'clone';
 
diff --git a/xs/t/05_surface.t b/xs/t/05_surface.t
index 34feb4734..4d9eb5b89 100644
--- a/xs/t/05_surface.t
+++ b/xs/t/05_surface.t
@@ -4,7 +4,7 @@ use strict;
 use warnings;
 
 use Slic3r::XS;
-use Test::More tests => 15;
+use Test::More tests => 11;
 
 my $square = [  # ccw
     [100, 100],
@@ -27,10 +27,6 @@ my $surface = Slic3r::Surface->new(
 
 $surface = $surface->clone;
 
-isa_ok $surface->expolygon, 'Slic3r::ExPolygon::Ref', 'expolygon';
-is_deeply [ @{$surface->expolygon->pp} ], [$square, $hole_in_square], 'expolygon roundtrip';
-is scalar(@{$surface->polygons}), 2, 'polygons roundtrip';
-
 is $surface->surface_type, Slic3r::Surface::S_TYPE_INTERNAL, 'surface_type';
 $surface->surface_type(Slic3r::Surface::S_TYPE_BOTTOM);
 is $surface->surface_type, Slic3r::Surface::S_TYPE_BOTTOM, 'modify surface_type';
@@ -59,7 +55,6 @@ is $surface->extra_perimeters, 2, 'extra_perimeters';
     is scalar(@$collection), 1, 'append to collection';
     
     my $item = $collection->[0];
-    isa_ok $item, 'Slic3r::Surface::Ref';
     $item->surface_type(Slic3r::Surface::S_TYPE_INTERNAL);
     is $item->surface_type, $collection->[0]->surface_type, 'collection returns items by reference';
 }
diff --git a/xs/t/06_polygon.t b/xs/t/06_polygon.t
deleted file mode 100644
index 7bbcd5356..000000000
--- a/xs/t/06_polygon.t
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use Slic3r::XS;
-use Test::More tests => 3;
-
-my $square = [  # ccw
-    [100, 100],
-    [200, 100],
-    [200, 200],
-    [100, 200],
-];
-
-my $polygon = Slic3r::Polygon->new(@$square);
-is ref($polygon->arrayref), 'ARRAY', 'polygon arrayref is unblessed';
-isa_ok $polygon->[0], 'Slic3r::Point::Ref', 'polygon point is blessed';
-ok ref($polygon->first_point) eq 'Slic3r::Point', 'first_point';
-
-__END__
diff --git a/xs/t/07_extrusionpath.t b/xs/t/07_extrusionpath.t
index 008b51b00..084b4f03e 100644
--- a/xs/t/07_extrusionpath.t
+++ b/xs/t/07_extrusionpath.t
@@ -4,7 +4,7 @@ use strict;
 use warnings;
 
 use Slic3r::XS;
-use Test::More tests => 7;
+use Test::More tests => 5;
 
 my $points = [
     [100, 100],
@@ -17,8 +17,6 @@ my $path = Slic3r::ExtrusionPath->new(
     role     => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER,
     mm3_per_mm => 1,
 );
-isa_ok $path->polyline, 'Slic3r::Polyline::Ref', 'path polyline';
-is_deeply $path->polyline->pp, $points, 'path points roundtrip';
 
 $path->reverse;
 is_deeply $path->polyline->pp, [ reverse @$points ], 'reverse path';
diff --git a/xs/t/08_extrusionloop.t b/xs/t/08_extrusionloop.t
index e0660a9fd..3abfbd728 100644
--- a/xs/t/08_extrusionloop.t
+++ b/xs/t/08_extrusionloop.t
@@ -5,7 +5,7 @@ use warnings;
 
 use List::Util qw(sum);
 use Slic3r::XS;
-use Test::More tests => 47;
+use Test::More tests => 46;
 
 {
     my $square = [
@@ -33,7 +33,6 @@ use Test::More tests => 47;
     is scalar(@$loop), 1, 'loop contains one path';
     {
         my $path = $loop->[0];
-        isa_ok $path, 'Slic3r::ExtrusionPath::Ref';
         is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'role';
     }
 
diff --git a/xs/t/09_polyline.t b/xs/t/09_polyline.t
index 5203ec5ef..7da74b93d 100644
--- a/xs/t/09_polyline.t
+++ b/xs/t/09_polyline.t
@@ -4,7 +4,7 @@ use strict;
 use warnings;
 
 use Slic3r::XS;
-use Test::More tests => 18;
+use Test::More tests => 15;
 
 my $points = [
     [100, 100],
@@ -14,11 +14,6 @@ my $points = [
 
 my $polyline = Slic3r::Polyline->new(@$points);
 
-is_deeply $polyline->pp, $points, 'polyline roundtrip';
-
-is ref($polyline->arrayref), 'ARRAY', 'polyline arrayref is unblessed';
-isa_ok $polyline->[0], 'Slic3r::Point::Ref', 'polyline point is blessed';
-
 my $lines = $polyline->lines;
 is_deeply [ map $_->pp, @$lines ], [
     [ [100, 100], [200, 100] ],
@@ -88,41 +83,4 @@ is_deeply $polyline->pp, [ @$points, @$points ], 'append_polyline';
     is scalar(@$p2), 4, 'split_at';
 }
 
-# disabled because we now use a more efficient but incomplete algorithm
-#if (0) {
-#    my $polyline = Slic3r::Polyline->new(
-#        map [$_,10], (0,10,20,30,40,50,60)
-#    );
-#    {
-#        my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new(
-#            [25,0], [55,0], [55,30], [25,30],
-#        ));
-#        my $p = $polyline->clone;
-#        $p->simplify_by_visibility($expolygon);
-#        is_deeply $p->pp, [
-#            map [$_,10], (0,10,20,30,50,60)
-#        ], 'simplify_by_visibility()';
-#    }
-#    {
-#        my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new(
-#            [-15,0], [75,0], [75,30], [-15,30],
-#        ));
-#        my $p = $polyline->clone;
-#        $p->simplify_by_visibility($expolygon);
-#        is_deeply $p->pp, [
-#            map [$_,10], (0,60)
-#        ], 'simplify_by_visibility()';
-#    }
-#    {
-#        my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new(
-#            [-15,0], [25,0], [25,30], [-15,30],
-#        ));
-#        my $p = $polyline->clone;
-#        $p->simplify_by_visibility($expolygon);
-#        is_deeply $p->pp, [
-#            map [$_,10], (0,20,30,40,50,60)
-#        ], 'simplify_by_visibility()';
-#    }
-#}
-
 __END__
diff --git a/xs/t/10_line.t b/xs/t/10_line.t
index 8f82e988c..886573f7b 100644
--- a/xs/t/10_line.t
+++ b/xs/t/10_line.t
@@ -4,7 +4,7 @@ use strict;
 use warnings;
 
 use Slic3r::XS;
-use Test::More tests => 40;
+use Test::More tests => 35;
 
 use constant PI         => 4 * atan2(1, 1);
 use constant EPSILON    => 1E-4;
@@ -15,21 +15,6 @@ my $points = [
 ];
 
 my $line = Slic3r::Line->new(@$points);
-is_deeply $line->pp, $points, 'line roundtrip';
-
-is ref($line->arrayref), 'ARRAY', 'line arrayref is unblessed';
-isa_ok $line->[0], 'Slic3r::Point::Ref', 'line point is blessed';
-
-{
-    my $clone = $line->clone;
-    $clone->reverse;
-    is_deeply $clone->pp, [ reverse @$points ], 'reverse';
-}
-
-{
-    my $line2 = Slic3r::Line->new($line->a->clone, $line->b->clone);
-    is_deeply $line2->pp, $points, 'line roundtrip with cloned points';
-}
 
 {
     my $clone = $line->clone;
diff --git a/xs/t/12_extrusionpathcollection.t b/xs/t/12_extrusionpathcollection.t
index e7e0b1316..e02854245 100644
--- a/xs/t/12_extrusionpathcollection.t
+++ b/xs/t/12_extrusionpathcollection.t
@@ -4,7 +4,7 @@ use strict;
 use warnings;
 
 use Slic3r::XS;
-use Test::More tests => 18;
+use Test::More tests => 13;
 
 my $points = [
     [100, 100],
@@ -41,12 +41,6 @@ is scalar(@$collection), 3, 'append ExtrusionPath';
 $collection->append($loop);
 is scalar(@$collection), 4, 'append ExtrusionLoop';
 
-isa_ok $collection->[1], 'Slic3r::ExtrusionPath::Collection::Ref', 'correct object returned for collection';
-isa_ok $collection->[2], 'Slic3r::ExtrusionPath::Ref', 'correct object returned for path';
-isa_ok $collection->[3], 'Slic3r::ExtrusionLoop::Ref', 'correct object returned for loop';
-is ref($collection->[2]->clone), 'Slic3r::ExtrusionPath', 'correct object returned for cloned path';
-is ref($collection->[3]->clone), 'Slic3r::ExtrusionLoop', 'correct object returned for cloned loop';
-
 is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated';
 
 {
diff --git a/xs/t/17_boundingbox.t b/xs/t/17_boundingbox.t
deleted file mode 100644
index 349e0024d..000000000
--- a/xs/t/17_boundingbox.t
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use Slic3r::XS;
-use Test::More tests => 5;
-
-{
-    my @points = (
-        Slic3r::Point->new(100, 200),
-        Slic3r::Point->new(500, -600),
-    );
-    my $bb = Slic3r::Geometry::BoundingBox->new_from_points(\@points);
-    isa_ok $bb, 'Slic3r::Geometry::BoundingBox', 'new_from_points';
-    is_deeply $bb->min_point->pp, [100,-600], 'min_point';
-    is_deeply $bb->max_point->pp, [500,200], 'max_point';
-}
-
-{
-    my $bb = Slic3r::Geometry::BoundingBox->new;
-    $bb->merge_point(Slic3r::Point->new(10, 10));
-    is_deeply $bb->min_point->pp, [10,10], 'min_point equals to the only defined point';
-    is_deeply $bb->max_point->pp, [10,10], 'max_point equals to the only defined point';
-}
-
-__END__
diff --git a/xs/xsp/ExPolygon.xsp b/xs/xsp/ExPolygon.xsp
index a57bcfbcb..50b32544e 100644
--- a/xs/xsp/ExPolygon.xsp
+++ b/xs/xsp/ExPolygon.xsp
@@ -29,8 +29,6 @@
         %code{% RETVAL = THIS->contains(*point); %};
     ExPolygons simplify(double tolerance);
     Polygons simplify_p(double tolerance);
-    Polylines medial_axis(double max_width, double min_width)
-        %code{% THIS->medial_axis(max_width, min_width, &RETVAL); %};
 %{
 
 ExPolygon*
diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp
deleted file mode 100644
index 4c2583894..000000000
--- a/xs/xsp/GCode.xsp
+++ /dev/null
@@ -1,53 +0,0 @@
-%module{Slic3r::XS};
-
-%{
-#include <xsinit.h>
-#include "libslic3r/GCode.hpp"
-#include "libslic3r/GCode/CoolingBuffer.hpp"
-%}
-
-%name{Slic3r::GCode::CoolingBuffer} class CoolingBuffer {
-    CoolingBuffer(GCode* gcode)
-        %code{% RETVAL = new CoolingBuffer(*gcode); %};
-    ~CoolingBuffer();
-    std::string process_layer(std::string gcode, size_t layer_id)
-        %code{% RETVAL = THIS->process_layer(std::move(gcode), layer_id, true); %};
-
-};
-
-%name{Slic3r::GCode} class GCode {
-    GCode();
-    ~GCode();
-    void do_export(Print *print, const char *path)
-        %code%{
-            try {
-                THIS->do_export(print, path);
-            } catch (std::exception& e) {
-                croak("%s\n", e.what());
-            }
-        %};
-
-    Ref<Vec2d> origin()
-        %code{% RETVAL = &(THIS->origin()); %};
-    void set_origin(Vec2d* pointf)
-        %code{% THIS->set_origin(*pointf); %};
-    Ref<Point> last_pos()
-        %code{% RETVAL = &(THIS->last_pos()); %};
-
-    unsigned int    layer_count() const;
-    void            set_layer_count(unsigned int value);
-    void            set_extruders(std::vector<unsigned int> extruders) 
-        %code{% THIS->writer().set_extruders(extruders); THIS->writer().set_extruder(0); %};
-
-    void apply_print_config(StaticPrintConfig* print_config)
-        %code{%
-            if (const PrintConfig* config = dynamic_cast<PrintConfig*>(print_config)) {
-                THIS->apply_print_config(*config);
-            } else {
-                CONFESS("A PrintConfig object was not supplied to apply_print_config()");
-            }
-        %};
-
-    Ref<StaticPrintConfig> config()
-        %code{% RETVAL = const_cast<StaticPrintConfig*>(static_cast<const StaticPrintConfig*>(static_cast<const PrintObjectConfig*>(&THIS->config()))); %};
-};
diff --git a/xs/xsp/my.map b/xs/xsp/my.map
index 5174bdfa9..42ca74292 100644
--- a/xs/xsp/my.map
+++ b/xs/xsp/my.map
@@ -125,18 +125,9 @@ Ref<LayerRegion>           O_OBJECT_SLIC3R_T
 Layer*                     O_OBJECT_SLIC3R
 Ref<Layer>                 O_OBJECT_SLIC3R_T
 
-CoolingBuffer*             O_OBJECT_SLIC3R
-Ref<CoolingBuffer>         O_OBJECT_SLIC3R_T
-Clone<CoolingBuffer>       O_OBJECT_SLIC3R_T
-
-GCode*                      O_OBJECT_SLIC3R
-Ref<GCode>                  O_OBJECT_SLIC3R_T
-Clone<GCode>                O_OBJECT_SLIC3R_T
-
 Axis                  T_UV
 ExtrusionLoopRole     T_UV
 ExtrusionRole     T_UV
-FlowRole     T_UV
 SurfaceType     T_UV
 
 # we return these types whenever we want the items to be cloned
diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt
index 243e80dbc..bf1e8e2ac 100644
--- a/xs/xsp/typemap.xspt
+++ b/xs/xsp/typemap.xspt
@@ -92,18 +92,6 @@
 %typemap{Layer*};
 %typemap{Ref<Layer>}{simple};
 
-%typemap{CoolingBuffer*};
-%typemap{Ref<CoolingBuffer>}{simple};
-%typemap{Clone<CoolingBuffer>}{simple};
-
-%typemap{GCode*};
-%typemap{Ref<GCode>}{simple};
-%typemap{Clone<GCode>}{simple};
-
-//%typemap{GCodePreviewData*};
-//%typemap{Ref<GCodePreviewData>}{simple};
-//%typemap{Clone<GCodePreviewData>}{simple};
-
 %typemap{Points};
 %typemap{Pointfs};
 %typemap{Lines};
@@ -167,9 +155,3 @@
     $CVar = (ExtrusionRole)SvUV($PerlVar);
   %};
 };
-%typemap{FlowRole}{parsed}{
-  %cpp_type{FlowRole};
-  %precall_code{%
-    $CVar = (FlowRole)SvUV($PerlVar);
-  %};
-};