From a3bd1b53024bb5fe2564a08ef8f9a9f99f11c5b8 Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Thu, 22 May 2014 19:34:49 +0200
Subject: [PATCH] New seal_position option that replaces randomize_start,
 start_perimeters_at_concave_points and start_perimeters_at_non_overhang. The
 two latter options are now always on by default. A new "Aligned" seal
 position value has been added, that forces starting points to be aligned when
 not randomized. #1741 #925

---
 README.md                  |  6 +---
 lib/Slic3r/Config.pm       |  7 ++++-
 lib/Slic3r/GCode.pm        | 61 +++++++++++++++++---------------------
 lib/Slic3r/GUI/Tab.pm      |  8 ++---
 lib/Slic3r/Layer/Region.pm | 11 +++++--
 lib/Slic3r/Polygon.pm      | 29 ++++++++++++++----
 lib/Slic3r/Polyline.pm     |  2 +-
 slic3r.pl                  |  6 +---
 t/perimeters.t             | 28 ++---------------
 xs/src/MultiPoint.cpp      | 12 ++++++++
 xs/src/MultiPoint.hpp      | 10 +++++--
 xs/src/Polygon.cpp         | 18 +++++++++++
 xs/src/Polygon.hpp         |  1 +
 xs/src/PrintConfig.hpp     | 52 +++++++++++++++++---------------
 xs/t/06_polygon.t          |  6 +++-
 xs/xsp/ExtrusionLoop.xsp   |  2 +-
 xs/xsp/Polygon.xsp         |  6 ++++
 17 files changed, 153 insertions(+), 112 deletions(-)

diff --git a/README.md b/README.md
index d471f6f16..13bd1300d 100644
--- a/README.md
+++ b/README.md
@@ -219,7 +219,7 @@ The author of the Silk icon set is Mark James.
                             home X axis [G28 X], disable motors [M84]).
         --layer-gcode       Load layer-change G-code from the supplied file (default: nothing).
         --toolchange-gcode  Load tool-change G-code from the supplied file (default: nothing).
-        --randomize-start   Randomize starting point across layers (default: yes)
+        --seal-position     Position of loop starting points (random/nearest/aligned, default: aligned).
         --external-perimeters-first Reverse perimeter order. (default: no)
         --spiral-vase       Experimental option to raise Z gradually when printing single-walled vases
                             (default: no)
@@ -236,10 +236,6 @@ The author of the Silk icon set is Mark James.
        Quality options (slower slicing):
         --extra-perimeters  Add more perimeters when needed (default: yes)
         --avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no)
-        --start-perimeters-at-concave-points
-                            Try to start perimeters at concave points if any (default: no)
-        --start-perimeters-at-non-overhang
-                            Try to start perimeters at non-overhang points if any (default: no)
         --thin-walls        Detect single-width walls (default: yes)
         --overhangs         Experimental option to use bridge flow, speed and fan for overhangs
                             (default: yes)
diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm
index 92a63a61a..625e1f8fd 100644
--- a/lib/Slic3r/Config.pm
+++ b/lib/Slic3r/Config.pm
@@ -8,7 +8,8 @@ use List::Util qw(first max);
 # cemetery of old config settings
 our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool acceleration
     adjust_overhang_flow standby_temperature scale rotate duplicate duplicate_grid
-    rotate scale duplicate_grid);
+    rotate scale duplicate_grid start_perimeters_at_concave_points start_perimeters_at_non_overhang
+    randomize_start);
 
 our $Options = print_config_def();
 
@@ -140,6 +141,10 @@ sub _handle_legacy {
         $value *= 100;
         $value = "$value";  # force update of the PV value, workaround for bug https://rt.cpan.org/Ticket/Display.html?id=94110
     }
+    if ($opt_key eq 'randomize_start' && $value) {
+        $opt_key = 'seal_position';
+        $value = 'random';
+    }
     
     # For historical reasons, the world's full of configs having these very low values;
     # to avoid unexpected behavior we need to ignore them.  Banning these two hard-coded
diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm
index 5ad1cf1ea..73a787d51 100644
--- a/lib/Slic3r/GCode.pm
+++ b/lib/Slic3r/GCode.pm
@@ -19,6 +19,7 @@ has '_layer_index'       => (is => 'rw', default => sub {-1});  # just a counter
 has 'layer'              => (is => 'rw');
 has '_layer_islands'     => (is => 'rw');
 has '_upper_layer_islands'  => (is => 'rw');
+has '_seal_position'     => (is => 'ro', default => sub { {} });  # $object => pos
 has 'shift_x'            => (is => 'rw', default => sub {0} );
 has 'shift_y'            => (is => 'rw', default => sub {0} );
 has 'z'                  => (is => 'rw');
@@ -150,45 +151,37 @@ sub extrude_loop {
     # extrude all loops ccw
     my $was_clockwise = $loop->make_counter_clockwise;
     
-    # find candidate starting points
-    # start looking for concave vertices not being overhangs
-    my $polygon = $loop->polygon;
-    my @concave = ();
-    if ($self->config->start_perimeters_at_concave_points) {
-        @concave = $polygon->concave_points;
-    }
-    my @candidates = ();
-    if ($self->config->start_perimeters_at_non_overhang) {
-        @candidates = grep !$loop->has_overhang_point($_), @concave;
-    }
-    if (!@candidates) {
-        # if none, look for any concave vertex
-        @candidates = @concave;
-        if (!@candidates) {
-            # if none, look for any non-overhang vertex
-            if ($self->config->start_perimeters_at_non_overhang) {
-                @candidates = grep !$loop->has_overhang_point($_), @$polygon;
-            }
-            if (!@candidates) {
-                # if none, all points are valid candidates
-                @candidates = @$polygon;
-            }
-        }
-    }
-    
     # find the point of the loop that is closest to the current extruder position
     # or randomize if requested
     my $last_pos = $self->last_pos;
-    if ($self->config->randomize_start && $loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) {
-        $last_pos = Slic3r::Point->new(scale $self->config->print_center->[X], scale $self->config->bed_size->[Y]);
-        $last_pos->rotate(rand(2*PI), $self->config->print_center);
-    }
-    
-    # split the loop at the starting point
     if ($self->config->spiral_vase) {
         $loop->split_at($last_pos);
-    } else {
-        $loop->split_at_vertex($last_pos->nearest_point(\@candidates));
+    } elsif ($self->config->seal_position eq 'nearest' || $self->config->seal_position eq 'aligned') {
+        my $polygon = $loop->polygon;
+        my @candidates = @{$polygon->concave_points(PI*4/3)};
+        @candidates = @{$polygon->convex_points(PI*2/3)} if !@candidates;
+        @candidates = @{$polygon} if !@candidates;
+        
+        my @non_overhang = grep !$loop->has_overhang_point($_), @candidates;
+        @candidates = @non_overhang if @non_overhang;
+        
+        if ($self->config->seal_position eq 'nearest') {
+            $loop->split_at_vertex($last_pos->nearest_point(\@candidates));
+        } elsif ($self->config->seal_position eq 'aligned') {
+            if (defined $self->layer && defined $self->_seal_position->{$self->layer->object}) {
+                $last_pos = $self->_seal_position->{$self->layer->object};
+            }
+            my $point = $self->_seal_position->{$self->layer->object} = $last_pos->nearest_point(\@candidates);
+            $loop->split_at_vertex($point);
+        }
+    } elsif ($self->config->seal_position eq 'random') {
+        if ($loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) {
+            my $polygon = $loop->polygon;
+            my $centroid = $polygon->centroid;
+            $last_pos = Slic3r::Point->new($polygon->bounding_box->x_max, $centroid->y);  #))
+            $last_pos->rotate(rand(2*PI), $centroid);
+        }
+        $loop->split_at($last_pos);
     }
     
     # clip the path to avoid the extruder to get exactly on the first point of the loop;
diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm
index 9f68b2cd1..1d4945f01 100644
--- a/lib/Slic3r/GUI/Tab.pm
+++ b/lib/Slic3r/GUI/Tab.pm
@@ -417,21 +417,17 @@ sub build {
         },
         {
             title => 'Quality (slower slicing)',
-            options => [qw(extra_perimeters avoid_crossing_perimeters start_perimeters_at_concave_points start_perimeters_at_non_overhang thin_walls overhangs)],
+            options => [qw(extra_perimeters avoid_crossing_perimeters thin_walls overhangs)],
             lines => [
                 Slic3r::GUI::OptionsGroup->single_option_line('extra_perimeters'),
                 Slic3r::GUI::OptionsGroup->single_option_line('avoid_crossing_perimeters'),
-                {
-                    label   => 'Start perimeters at',
-                    options => [qw(start_perimeters_at_concave_points start_perimeters_at_non_overhang)],
-                },
                 Slic3r::GUI::OptionsGroup->single_option_line('thin_walls'),
                 Slic3r::GUI::OptionsGroup->single_option_line('overhangs'),
             ],
         },
         {
             title => 'Advanced',
-            options => [qw(randomize_start external_perimeters_first)],
+            options => [qw(seal_position external_perimeters_first)],
         },
     ]);
     
diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm
index 9a3f17c2a..39cec3d61 100644
--- a/lib/Slic3r/Layer/Region.pm
+++ b/lib/Slic3r/Layer/Region.pm
@@ -260,12 +260,19 @@ sub make_perimeters {
             
             my $role        = EXTR_ROLE_PERIMETER;
             my $loop_role   = EXTRL_ROLE_DEFAULT;
-            if ($is_contour ? $depth == 0 : !@{ $polynode->{children} }) {
+            
+            my $root_level  = $depth == 0;
+            my $no_children = !@{ $polynode->{children} };
+            my $is_external = $is_contour ? $root_level : $no_children;
+            my $is_internal = $is_contour ? $no_children : $root_level;
+            if ($is_external) {
                 # external perimeters are root level in case of contours
                 # and items with no children in case of holes
                 $role       = EXTR_ROLE_EXTERNAL_PERIMETER;
                 $loop_role  = EXTRL_ROLE_EXTERNAL_PERIMETER;
-            } elsif ($depth == 1 && $is_contour) {
+            } elsif ($is_contour && $is_internal) {
+                # internal perimeters are root level in case of holes
+                # and items with no children in case of contours
                 $loop_role  = EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER;
             }
             
diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm
index ee0ec96b9..c12d3dcaf 100644
--- a/lib/Slic3r/Polygon.pm
+++ b/lib/Slic3r/Polygon.pm
@@ -40,15 +40,34 @@ sub subdivide {
     return Slic3r::Polygon->new(@new_points);
 }
 
-# for cw polygons this will return convex points!
+# angle is checked on the internal side of the polygon
 sub concave_points {
-    my $self = shift;
+    my ($self, $angle) = @_;
+    
+    $angle //= PI;
     
     my @points = @$self;
     my @points_pp = @{$self->pp};
-    return map $points[$_],
-        grep Slic3r::Geometry::angle3points(@points_pp[$_, $_-1, $_+1]) < PI - epsilon,
-        -1 .. ($#points-1);
+    return [
+        map $points[$_],
+        grep Slic3r::Geometry::angle3points(@points_pp[$_, $_-1, $_+1]) < $angle,
+        -1 .. ($#points-1)
+    ];
+}
+
+# angle is checked on the internal side of the polygon
+sub convex_points {
+    my ($self, $angle) = @_;
+    
+    $angle //= PI;
+    
+    my @points = @$self;
+    my @points_pp = @{$self->pp};
+    return [
+        map $points[$_],
+        grep Slic3r::Geometry::angle3points(@points_pp[$_, $_-1, $_+1]) > $angle,
+        -1 .. ($#points-1)
+    ];
 }
 
 1;
\ No newline at end of file
diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm
index eeda93047..4e3045cdf 100644
--- a/lib/Slic3r/Polyline.pm
+++ b/lib/Slic3r/Polyline.pm
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 use List::Util qw(first);
-use Slic3r::Geometry qw(X Y epsilon);
+use Slic3r::Geometry qw(X Y PI epsilon);
 use Slic3r::Geometry::Clipper qw(JT_SQUARE);
 
 sub new_scale {
diff --git a/slic3r.pl b/slic3r.pl
index 938f4ed93..aa3a43700 100755
--- a/slic3r.pl
+++ b/slic3r.pl
@@ -342,7 +342,7 @@ $j
                         home X axis [G28 X], disable motors [M84]).
     --layer-gcode       Load layer-change G-code from the supplied file (default: nothing).
     --toolchange-gcode  Load tool-change G-code from the supplied file (default: nothing).
-    --randomize-start   Randomize starting point across layers (default: yes)
+    --seal-position     Position of loop starting points (random/nearest/aligned, default: $config->{seal_position}).
     --external-perimeters-first Reverse perimeter order. (default: no)
     --spiral-vase       Experimental option to raise Z gradually when printing single-walled vases
                         (default: no)
@@ -359,10 +359,6 @@ $j
    Quality options (slower slicing):
     --extra-perimeters  Add more perimeters when needed (default: yes)
     --avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no)
-    --start-perimeters-at-concave-points
-                        Try to start perimeters at concave points if any (default: no)
-    --start-perimeters-at-non-overhang
-                        Try to start perimeters at non-overhang points if any (default: no)
     --thin-walls        Detect single-width walls (default: yes)
     --overhangs         Experimental option to use bridge flow, speed and fan for overhangs
                         (default: yes)
diff --git a/t/perimeters.t b/t/perimeters.t
index 58ebc3ed1..239f80f2f 100644
--- a/t/perimeters.t
+++ b/t/perimeters.t
@@ -1,4 +1,4 @@
-use Test::More tests => 10;
+use Test::More tests => 9;
 use strict;
 use warnings;
 
@@ -85,28 +85,6 @@ use Slic3r::Test;
         ok !$has_outwards_move, 'move inwards after completing external loop';
     }
     
-    {
-        $config->set('start_perimeters_at_concave_points', 1);
-        my $print = Slic3r::Test::init_print('L', config => $config);
-        my $loop_starts_from_convex_point = 0;
-        my $cur_loop;
-        Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
-            my ($self, $cmd, $args, $info) = @_;
-            
-            if ($info->{extruding} && $info->{dist_XY} > 0) {
-                $cur_loop ||= [ [$self->X, $self->Y] ];
-                push @$cur_loop, [ @$info{qw(new_X new_Y)} ];
-            } else {
-                if ($cur_loop) {
-                    $loop_starts_from_convex_point = 1
-                        if Slic3r::Geometry::angle3points(@$cur_loop[0,-1,1]) >= PI;
-                    $cur_loop = undef;
-                }
-            }
-        });
-        ok !$loop_starts_from_convex_point, 'avoid starting from convex points';
-    }
-    
     {
         $config->set('perimeters', 1);
         $config->set('perimeter_speed', 77);
@@ -267,9 +245,9 @@ use Slic3r::Test;
 
 {
     my $config = Slic3r::Config->new_from_defaults;
-    $config->set('randomize_start', 1);
+    $config->set('seal_position', 'random');
     my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
-    ok Slic3r::Test::gcode($print), 'successful generation of G-code with randomize_start option';
+    ok Slic3r::Test::gcode($print), 'successful generation of G-code with seal_position = random';
 }
 
 __END__
diff --git a/xs/src/MultiPoint.cpp b/xs/src/MultiPoint.cpp
index 51567c5e9..47830ce2f 100644
--- a/xs/src/MultiPoint.cpp
+++ b/xs/src/MultiPoint.cpp
@@ -1,7 +1,13 @@
 #include "MultiPoint.hpp"
+#include "BoundingBox.hpp"
 
 namespace Slic3r {
 
+MultiPoint::operator Points() const
+{
+    return this->points;
+}
+
 void
 MultiPoint::scale(double factor)
 {
@@ -64,6 +70,12 @@ MultiPoint::find_point(const Point &point) const
     return -1;  // not found
 }
 
+void
+MultiPoint::bounding_box(BoundingBox* bb) const
+{
+    *bb = BoundingBox(this->points);
+}
+
 Points
 MultiPoint::_douglas_peucker(const Points &points, const double tolerance)
 {
diff --git a/xs/src/MultiPoint.hpp b/xs/src/MultiPoint.hpp
index c1278aa75..075b0dbdb 100644
--- a/xs/src/MultiPoint.hpp
+++ b/xs/src/MultiPoint.hpp
@@ -2,17 +2,21 @@
 #define slic3r_MultiPoint_hpp_
 
 #include <myinit.h>
-#include "Line.hpp"
-#include "Point.hpp"
 #include <algorithm>
 #include <vector>
+#include "Line.hpp"
+#include "Point.hpp"
 
 namespace Slic3r {
 
+class BoundingBox;
+
 class MultiPoint
 {
     public:
     Points points;
+    
+    operator Points() const;
     void scale(double factor);
     void translate(double x, double y);
     void rotate(double angle, const Point &center);
@@ -23,6 +27,8 @@ class MultiPoint
     double length() const;
     bool is_valid() const;
     int find_point(const Point &point) const;
+    void bounding_box(BoundingBox* bb) const;
+    
     static Points _douglas_peucker(const Points &points, const double tolerance);
     
     #ifdef SLIC3RXS
diff --git a/xs/src/Polygon.cpp b/xs/src/Polygon.cpp
index 948ea2301..3fe16bc70 100644
--- a/xs/src/Polygon.cpp
+++ b/xs/src/Polygon.cpp
@@ -191,6 +191,24 @@ Polygon::triangulate_convex(Polygons* polygons) const
     }
 }
 
+// center of mass
+Point
+Polygon::centroid() const
+{
+    double area_temp = this->area();
+    double x_temp = 0;
+    double y_temp = 0;
+    
+    Polyline polyline;
+    this->split_at_first_point(&polyline);
+    for (Points::const_iterator point = polyline.points.begin(); point != polyline.points.end() - 1; ++point) {
+        x_temp += (double)( point->x + (point+1)->x ) * ( (double)point->x*(point+1)->y - (double)(point+1)->x*point->y );
+        y_temp += (double)( point->y + (point+1)->y ) * ( (double)point->x*(point+1)->y - (double)(point+1)->x*point->y );
+    }
+    
+    return Point(x_temp/(6*area_temp), y_temp/(6*area_temp));
+}
+
 #ifdef SLIC3RXS
 REGISTER_CLASS(Polygon, "Polygon");
 
diff --git a/xs/src/Polygon.hpp b/xs/src/Polygon.hpp
index b5859e821..816b6be18 100644
--- a/xs/src/Polygon.hpp
+++ b/xs/src/Polygon.hpp
@@ -35,6 +35,7 @@ class Polygon : public MultiPoint {
     Polygons simplify(double tolerance) const;
     void simplify(double tolerance, Polygons &polygons) const;
     void triangulate_convex(Polygons* polygons) const;
+    Point centroid() const;
     
     #ifdef SLIC3RXS
     void from_SV_check(SV* poly_sv);
diff --git a/xs/src/PrintConfig.hpp b/xs/src/PrintConfig.hpp
index d3ced798a..913d81de2 100644
--- a/xs/src/PrintConfig.hpp
+++ b/xs/src/PrintConfig.hpp
@@ -18,6 +18,10 @@ enum SupportMaterialPattern {
     smpRectilinear, smpRectilinearGrid, smpHoneycomb, smpPillars,
 };
 
+enum SealPosition {
+    spRandom, spNearest, spAligned
+};
+
 template<> inline t_config_enum_values ConfigOptionEnum<GCodeFlavor>::get_enum_values() {
     t_config_enum_values keys_map;
     keys_map["reprap"]          = gcfRepRap;
@@ -50,6 +54,14 @@ template<> inline t_config_enum_values ConfigOptionEnum<SupportMaterialPattern>:
     return keys_map;
 }
 
+template<> inline t_config_enum_values ConfigOptionEnum<SealPosition>::get_enum_values() {
+    t_config_enum_values keys_map;
+    keys_map["random"]              = spRandom;
+    keys_map["nearest"]             = spNearest;
+    keys_map["aligned"]             = spAligned;
+    return keys_map;
+}
+
 class PrintConfigDef
 {
     public:
@@ -584,11 +596,6 @@ class PrintConfigDef
         Options["raft_layers"].sidetext = "layers";
         Options["raft_layers"].cli = "raft-layers=i";
 
-        Options["randomize_start"].type = coBool;
-        Options["randomize_start"].label = "Randomize starting points";
-        Options["randomize_start"].tooltip = "Start each layer from a different vertex to prevent plastic build-up on the same corner.";
-        Options["randomize_start"].cli = "randomize-start!";
-
         Options["resolution"].type = coFloat;
         Options["resolution"].label = "Resolution";
         Options["resolution"].tooltip = "Minimum detail resolution, used to simplify the input file for speeding up the slicing job and reducing memory usage. High-resolution models often carry more detail than printers can render. Set to zero to disable any simplification and use full resolution from input.";
@@ -644,6 +651,19 @@ class PrintConfigDef
         Options["retract_speed"].cli = "retract-speed=f@";
         Options["retract_speed"].max = 1000;
 
+        Options["seal_position"].type = coEnum;
+        Options["seal_position"].label = "Seal position";
+        Options["seal_position"].category = "Layers and perimeters";
+        Options["seal_position"].tooltip = "Position of perimeters starting points.";
+        Options["seal_position"].cli = "seal-position=s";
+        Options["seal_position"].enum_keys_map = ConfigOptionEnum<SealPosition>::get_enum_values();
+        Options["seal_position"].enum_values.push_back("random");
+        Options["seal_position"].enum_values.push_back("nearest");
+        Options["seal_position"].enum_values.push_back("aligned");
+        Options["seal_position"].enum_labels.push_back("Random");
+        Options["seal_position"].enum_labels.push_back("Nearest");
+        Options["seal_position"].enum_labels.push_back("Aligned");
+
         Options["skirt_distance"].type = coFloat;
         Options["skirt_distance"].label = "Distance from object";
         Options["skirt_distance"].tooltip = "Distance between skirt and object(s). Set this to zero to attach the skirt to the object(s) and get a brim for better adhesion.";
@@ -753,16 +773,6 @@ class PrintConfigDef
         Options["start_gcode"].full_width = true;
         Options["start_gcode"].height = 120;
 
-        Options["start_perimeters_at_concave_points"].type = coBool;
-        Options["start_perimeters_at_concave_points"].label = "Concave points";
-        Options["start_perimeters_at_concave_points"].tooltip = "Prefer to start perimeters at a concave point.";
-        Options["start_perimeters_at_concave_points"].cli = "start-perimeters-at-concave-points!";
-
-        Options["start_perimeters_at_non_overhang"].type = coBool;
-        Options["start_perimeters_at_non_overhang"].label = "Non-overhang points";
-        Options["start_perimeters_at_non_overhang"].tooltip = "Prefer to start perimeters at non-overhanging points.";
-        Options["start_perimeters_at_non_overhang"].cli = "start-perimeters-at-non-overhang!";
-
         Options["support_material"].type = coBool;
         Options["support_material"].label = "Generate support material";
         Options["support_material"].category = "Support material";
@@ -996,6 +1006,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig
     ConfigOptionBool                interface_shells;
     ConfigOptionFloat               layer_height;
     ConfigOptionInt                 raft_layers;
+    ConfigOptionEnum<SealPosition>  seal_position;
     ConfigOptionBool                support_material;
     ConfigOptionInt                 support_material_angle;
     ConfigOptionInt                 support_material_enforce_layers;
@@ -1020,6 +1031,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig
         this->interface_shells.value                             = false;
         this->layer_height.value                                 = 0.4;
         this->raft_layers.value                                  = 0;
+        this->seal_position.value                                = spAligned;
         this->support_material.value                             = false;
         this->support_material_angle.value                       = 0;
         this->support_material_enforce_layers.value              = 0;
@@ -1045,6 +1057,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig
         if (opt_key == "interface_shells")                           return &this->interface_shells;
         if (opt_key == "layer_height")                               return &this->layer_height;
         if (opt_key == "raft_layers")                                return &this->raft_layers;
+        if (opt_key == "seal_position")                              return &this->seal_position;
         if (opt_key == "support_material")                           return &this->support_material;
         if (opt_key == "support_material_angle")                     return &this->support_material_angle;
         if (opt_key == "support_material_enforce_layers")            return &this->support_material_enforce_layers;
@@ -1214,7 +1227,6 @@ class PrintConfig : public virtual StaticPrintConfig
     ConfigOptionFloat               perimeter_acceleration;
     ConfigOptionStrings             post_process;
     ConfigOptionPoint               print_center;
-    ConfigOptionBool                randomize_start;
     ConfigOptionFloat               resolution;
     ConfigOptionFloats              retract_before_travel;
     ConfigOptionBools               retract_layer_change;
@@ -1231,8 +1243,6 @@ class PrintConfig : public virtual StaticPrintConfig
     ConfigOptionBool                spiral_vase;
     ConfigOptionInt                 standby_temperature_delta;
     ConfigOptionString              start_gcode;
-    ConfigOptionBool                start_perimeters_at_concave_points;
-    ConfigOptionBool                start_perimeters_at_non_overhang;
     ConfigOptionInts                temperature;
     ConfigOptionInt                 threads;
     ConfigOptionString              toolchange_gcode;
@@ -1296,7 +1306,6 @@ class PrintConfig : public virtual StaticPrintConfig
         this->output_filename_format.value                       = "[input_filename_base].gcode";
         this->perimeter_acceleration.value                       = 0;
         this->print_center.point                                 = Pointf(100,100);
-        this->randomize_start.value                              = false;
         this->resolution.value                                   = 0;
         this->retract_before_travel.values.resize(1);
         this->retract_before_travel.values[0]                    = 2;
@@ -1321,8 +1330,6 @@ class PrintConfig : public virtual StaticPrintConfig
         this->spiral_vase.value                                  = false;
         this->standby_temperature_delta.value                    = -5;
         this->start_gcode.value                                  = "G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n";
-        this->start_perimeters_at_concave_points.value           = false;
-        this->start_perimeters_at_non_overhang.value             = false;
         this->temperature.values.resize(1);
         this->temperature.values[0]                              = 200;
         this->threads.value                                      = 2;
@@ -1383,7 +1390,6 @@ class PrintConfig : public virtual StaticPrintConfig
         if (opt_key == "perimeter_acceleration")                     return &this->perimeter_acceleration;
         if (opt_key == "post_process")                               return &this->post_process;
         if (opt_key == "print_center")                               return &this->print_center;
-        if (opt_key == "randomize_start")                            return &this->randomize_start;
         if (opt_key == "resolution")                                 return &this->resolution;
         if (opt_key == "retract_before_travel")                      return &this->retract_before_travel;
         if (opt_key == "retract_layer_change")                       return &this->retract_layer_change;
@@ -1400,8 +1406,6 @@ class PrintConfig : public virtual StaticPrintConfig
         if (opt_key == "spiral_vase")                                return &this->spiral_vase;
         if (opt_key == "standby_temperature_delta")                  return &this->standby_temperature_delta;
         if (opt_key == "start_gcode")                                return &this->start_gcode;
-        if (opt_key == "start_perimeters_at_concave_points")         return &this->start_perimeters_at_concave_points;
-        if (opt_key == "start_perimeters_at_non_overhang")           return &this->start_perimeters_at_non_overhang;
         if (opt_key == "temperature")                                return &this->temperature;
         if (opt_key == "threads")                                    return &this->threads;
         if (opt_key == "toolchange_gcode")                           return &this->toolchange_gcode;
diff --git a/xs/t/06_polygon.t b/xs/t/06_polygon.t
index 858638889..116919a74 100644
--- a/xs/t/06_polygon.t
+++ b/xs/t/06_polygon.t
@@ -5,7 +5,7 @@ use warnings;
 
 use List::Util qw(first);
 use Slic3r::XS;
-use Test::More tests => 19;
+use Test::More tests => 20;
 
 use constant PI => 4 * atan2(1, 1);
 
@@ -69,6 +69,10 @@ ok $cw_polygon->contains_point(Slic3r::Point->new(150,150)), 'cw contains_point'
     ok !(defined first { $_->is_clockwise } @$triangles), 'all triangles are ccw';
 }
 
+{
+    is_deeply $polygon->centroid->pp, [150,150], 'centroid';
+}
+
 # this is not a test: this just demonstrates bad usage, where $polygon->clone gets
 # DESTROY'ed before the derived object ($point), causing bad memory access
 if (0) {
diff --git a/xs/xsp/ExtrusionLoop.xsp b/xs/xsp/ExtrusionLoop.xsp
index c6f0db1dc..489bca03a 100644
--- a/xs/xsp/ExtrusionLoop.xsp
+++ b/xs/xsp/ExtrusionLoop.xsp
@@ -64,7 +64,7 @@ _constant()
   ALIAS:
     EXTRL_ROLE_DEFAULT                      = elrDefault
     EXTRL_ROLE_EXTERNAL_PERIMETER           = elrExternalPerimeter
-    EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER   = erInternalInfill
+    EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER   = elrContourInternalPerimeter
   PROTOTYPE:
   CODE:
     RETVAL = ix;
diff --git a/xs/xsp/Polygon.xsp b/xs/xsp/Polygon.xsp
index c7b10846f..11fd04360 100644
--- a/xs/xsp/Polygon.xsp
+++ b/xs/xsp/Polygon.xsp
@@ -38,6 +38,12 @@
     Polygons simplify(double tolerance);
     Polygons triangulate_convex()
         %code{% THIS->triangulate_convex(&RETVAL); %};
+    Clone<Point> centroid();
+    BoundingBox* bounding_box()
+        %code{%
+            RETVAL = new BoundingBox();
+            THIS->bounding_box(RETVAL);
+        %};
 %{
 
 Polygon*