diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm
index 33385daf3..dddc1461f 100644
--- a/lib/Slic3r/Config.pm
+++ b/lib/Slic3r/Config.pm
@@ -174,19 +174,6 @@ sub _handle_legacy {
     return ($opt_key, $value);
 }
 
-sub set_ifndef {
-    my $self = shift;
-    my ($opt_key, $value, $deserialize) = @_;
-    
-    if (!$self->has($opt_key)) {
-        if ($deserialize) {
-            $self->set_deserialize($opt_key, $value);
-        } else {
-            $self->set($opt_key, $value);
-        }
-    }
-}
-
 sub as_ini {
     my ($self) = @_;
     
@@ -213,23 +200,6 @@ sub setenv {
     }
 }
 
-sub equals {
-    my ($self, $other) = @_;
-    return @{ $self->diff($other) } == 0;
-}
-
-# this will *ignore* options not present in both configs
-sub diff {
-    my ($self, $other) = @_;
-    
-    my @diff = ();
-    foreach my $opt_key (sort @{$self->get_keys}) {
-        push @diff, $opt_key
-            if $other->has($opt_key) && $other->serialize($opt_key) ne $self->serialize($opt_key);
-    }
-    return [@diff];
-}
-
 # this method is idempotent by design and only applies to ::DynamicConfig or ::Full
 # objects because it performs cross checks
 sub validate {
diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm
index 443a36715..58fbe0ca8 100644
--- a/lib/Slic3r/Print.pm
+++ b/lib/Slic3r/Print.pm
@@ -34,189 +34,6 @@ sub status_cb {
     return $status_cb // sub {};
 }
 
-sub apply_config {
-    my ($self, $config) = @_;
-    
-    $config = $config->clone;
-    $config->normalize;
-    
-    # apply variables to placeholder parser
-    $self->placeholder_parser->apply_config($config);
-    
-    my $invalidated = 0;
-    
-    # handle changes to print config
-    my $print_diff = $self->config->diff($config);
-    if (@$print_diff) {
-        $self->config->apply_dynamic($config);
-        
-        $invalidated = 1
-            if $self->invalidate_state_by_config_options($print_diff);
-    }
-    
-    # handle changes to object config defaults
-    $self->default_object_config->apply_dynamic($config);
-    foreach my $object (@{$self->objects}) {
-        # we don't assume that $config contains a full ObjectConfig,
-        # so we base it on the current print-wise default
-        my $new = $self->default_object_config->clone;
-        $new->apply_dynamic($config);
-        
-        # we override the new config with object-specific options
-        my $model_object_config = $object->model_object->config->clone;
-        $model_object_config->normalize;
-        $new->apply_dynamic($model_object_config);
-        
-        # check whether the new config is different from the current one
-        my $diff = $object->config->diff($new);
-        if (@$diff) {
-            $object->config->apply($new);
-            
-            $invalidated = 1
-                if $object->invalidate_state_by_config_options($diff);
-        }
-    }
-    
-    # handle changes to regions config defaults
-    $self->default_region_config->apply_dynamic($config);
-    
-    # All regions now have distinct settings.
-    # Check whether applying the new region config defaults we'd get different regions.
-    my $rearrange_regions = 0;
-    my @other_region_configs = ();
-    REGION: foreach my $region_id (0..($self->region_count - 1)) {
-        my $region = $self->regions->[$region_id];
-        my @this_region_configs = ();
-        foreach my $object (@{$self->objects}) {
-            foreach my $volume_id (@{ $object->get_region_volumes($region_id) }) {
-                my $volume = $object->model_object->volumes->[$volume_id];
-                
-                my $new = $self->default_region_config->clone;
-                foreach my $other_config ($object->model_object->config, $volume->config) {
-                    my $other_config = $other_config->clone;
-                    $other_config->normalize;
-                    $new->apply_dynamic($other_config);
-                }
-                if ($volume->material_id ne '') {
-                    my $material_config = $object->model_object->model->get_material($volume->material_id)->config->clone;
-                    $material_config->normalize;
-                    $new->apply_dynamic($material_config);
-                }
-                if (defined first { !$_->equals($new) } @this_region_configs) {
-                    # if the new config for this volume differs from the other
-                    # volume configs currently associated to this region, it means
-                    # the region subdivision does not make sense anymore
-                    $rearrange_regions = 1;
-                    last REGION;
-                }
-                push @this_region_configs, $new;
-                
-                if (defined first { $_->equals($new) } @other_region_configs) {
-                    # if the new config for this volume equals any of the other
-                    # volume configs that are not currently associated to this
-                    # region, it means the region subdivision does not make
-                    # sense anymore
-                    $rearrange_regions = 1;
-                    last REGION;
-                }
-                
-                # if we're here and the new region config is different from the old
-                # one, we need to apply the new config and invalidate all objects
-                # (possible optimization: only invalidate objects using this region)
-                my $region_config_diff = $region->config->diff($new);
-                if (@$region_config_diff) {
-                    $region->config->apply($new);
-                    foreach my $o (@{$self->objects}) {
-                        $invalidated = 1
-                            if $o->invalidate_state_by_config_options($region_config_diff);
-                    }
-                }
-            }
-        }
-        push @other_region_configs, @this_region_configs;
-    }
-    
-    if ($rearrange_regions) {
-        # the current subdivision of regions does not make sense anymore.
-        # we need to remove all objects and re-add them
-        my @model_objects = map $_->model_object, @{$self->objects};
-        $self->clear_objects;
-        $self->add_model_object($_) for @model_objects;
-        $invalidated = 1;
-    }
-    
-    return $invalidated;
-}
-
-# caller is responsible for supplying models whose objects don't collide
-# and have explicit instance positions
-sub add_model_object {
-    my $self = shift;
-    my ($object, $obj_idx) = @_;
-    
-    my $object_config = $object->config->clone;
-    $object_config->normalize;
-
-    # initialize print object and store it at the given position
-    my $o;
-    if (defined $obj_idx) {
-        $o = $self->set_new_object($obj_idx, $object, $object->raw_bounding_box);
-    } else {
-        $o = $self->add_object($object, $object->raw_bounding_box);
-    }
-    
-    $o->set_copies([ map Slic3r::Point->new_scale(@{ $_->offset }), @{ $object->instances } ]);
-    $o->set_layer_height_ranges($object->layer_height_ranges);
-
-    # TODO: translate _trigger_copies to C++, then this can be done by
-        # PrintObject constructor
-    $o->_trigger_copies;
-
-    foreach my $volume_id (0..$#{$object->volumes}) {
-        my $volume = $object->volumes->[$volume_id];
-        
-        # get the config applied to this volume: start from our global defaults
-        my $config = Slic3r::Config::PrintRegion->new;
-        $config->apply($self->default_region_config);
-        
-        # override the defaults with per-object config and then with per-volume and per-material configs
-        foreach my $other_config ($object_config, $volume->config) {
-            my $other_config = $other_config->clone;
-            $other_config->normalize;
-            $config->apply_dynamic($other_config);
-        }
-        if ($volume->material_id ne '') {
-            my $material_config = $volume->material->config->clone;
-            $material_config->normalize;
-            $config->apply_dynamic($material_config);
-        }
-        
-        # find an existing print region with the same config
-        my $region_id;
-        foreach my $i (0..($self->region_count - 1)) {
-            my $region = $self->regions->[$i];
-            if ($config->equals($region->config)) {
-                $region_id = $i;
-                last;
-            }
-        }
-        
-        # if no region exists with the same config, create a new one
-        if (!defined $region_id) {
-            my $r = $self->add_region();
-            $r->config->apply($config);
-            $region_id = $self->region_count - 1;
-        }
-        
-        # assign volume to region
-        $o->add_region_volume($region_id, $volume_id);
-    }
-
-    # apply config to print object
-    $o->config->apply($self->default_object_config);
-    $o->config->apply_dynamic($object_config);
-}
-
 sub reload_object {
     my ($self, $obj_idx) = @_;
     
diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm
index af07f3488..3948f1ca6 100644
--- a/lib/Slic3r/Print/Object.pm
+++ b/lib/Slic3r/Print/Object.pm
@@ -32,34 +32,12 @@ sub support_layers {
     return [ map $self->get_support_layer($_), 0..($self->support_layer_count - 1) ];
 }
 
-# TODO: translate to C++, then call it from constructor (see also
-    # Print->add_model_object)
-sub _trigger_copies {
-    my $self = shift;
-    
-    # TODO: should this mean point is 0,0?
-    return if !defined $self->_copies_shift;
-    
-    # order copies with a nearest neighbor search and translate them by _copies_shift
-    $self->set_shifted_copies([
-        map {
-            my $c = $_->clone;
-            $c->translate(@{ $self->_copies_shift });
-            $c;
-        } @{$self->copies}[@{chained_path($self->copies)}]
-    ]);
-    
-    $self->print->invalidate_step(STEP_SKIRT);
-    $self->print->invalidate_step(STEP_BRIM);
-}
-
 # in unscaled coordinates
 sub add_copy {
     my ($self, $x, $y) = @_;
     my @copies = @{$self->copies};
     push @copies, Slic3r::Point->new_scale($x, $y);
     $self->set_copies(\@copies);
-    $self->_trigger_copies;
 }
 
 sub delete_last_copy {
@@ -67,13 +45,11 @@ sub delete_last_copy {
     my @copies = $self->copies;
     pop @copies;
     $self->set_copies(\@copies);
-    $self->_trigger_copies;
 }
 
 sub delete_all_copies {
     my ($self) = @_;
     $self->set_copies([]);
-    $self->_trigger_copies;
 }
 
 # this is the *total* layer count (including support layers)
diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp
index 417054e18..b3fe6b877 100644
--- a/xs/src/libslic3r/Config.cpp
+++ b/xs/src/libslic3r/Config.cpp
@@ -27,6 +27,27 @@ ConfigBase::apply(const ConfigBase &other, bool ignore_nonexistent) {
     }
 }
 
+bool
+ConfigBase::equals(ConfigBase &other) {
+    return this->diff(other).empty();
+}
+
+// this will *ignore* options not present in both configs
+t_config_option_keys
+ConfigBase::diff(ConfigBase &other) {
+    t_config_option_keys diff;
+    
+    t_config_option_keys my_keys;
+    this->keys(&my_keys);
+    for (t_config_option_keys::const_iterator opt_key = my_keys.begin(); opt_key != my_keys.end(); ++opt_key) {
+        if (other.has(*opt_key) && other.serialize(*opt_key) != this->serialize(*opt_key)) {
+            diff.push_back(*opt_key);
+        }
+    }
+    
+    return diff;
+}
+
 std::string
 ConfigBase::serialize(const t_config_option_key opt_key) {
     ConfigOption* opt = this->option(opt_key);
@@ -248,6 +269,18 @@ ConfigBase::set_deserialize(const t_config_option_key opt_key, SV* str) {
     
     return this->set_deserialize(opt_key, value);
 }
+
+void
+ConfigBase::set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize)
+{
+    if (!this->has(opt_key)) {
+        if (deserialize) {
+            this->set_deserialize(opt_key, value);
+        } else {
+            this->set(opt_key, value);
+        }
+    }
+}
 #endif
 
 DynamicConfig& DynamicConfig::operator= (DynamicConfig other)
diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp
index 437e45ea1..0d86294a3 100644
--- a/xs/src/libslic3r/Config.hpp
+++ b/xs/src/libslic3r/Config.hpp
@@ -464,8 +464,11 @@ class ConfigBase
     virtual const ConfigOption* option(const t_config_option_key opt_key) const = 0;
     virtual void keys(t_config_option_keys *keys) const = 0;
     void apply(const ConfigBase &other, bool ignore_nonexistent = false);
+    bool equals(ConfigBase &other);
+    t_config_option_keys diff(ConfigBase &other);
     std::string serialize(const t_config_option_key opt_key);
     bool set_deserialize(const t_config_option_key opt_key, std::string str);
+    void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false);
     double get_abs_value(const t_config_option_key opt_key);
     double get_abs_value(const t_config_option_key opt_key, double ratio_over);
     
diff --git a/xs/src/libslic3r/Geometry.cpp b/xs/src/libslic3r/Geometry.cpp
index 8e083360b..002f11f46 100644
--- a/xs/src/libslic3r/Geometry.cpp
+++ b/xs/src/libslic3r/Geometry.cpp
@@ -25,7 +25,7 @@ sort_points (Point a, Point b)
 
 /* This implementation is based on Andrew's monotone chain 2D convex hull algorithm */
 void
-convex_hull(Points &points, Polygon* hull)
+convex_hull(Points points, Polygon* hull)
 {
     assert(points.size() >= 3);
     // sort input points
@@ -55,12 +55,12 @@ convex_hull(Points &points, Polygon* hull)
 /* accepts an arrayref of points and returns a list of indices
    according to a nearest-neighbor walk */
 void
-chained_path(Points &points, std::vector<Points::size_type> &retval, Point start_near)
+chained_path(const Points &points, std::vector<Points::size_type> &retval, Point start_near)
 {
-    PointPtrs my_points;
-    std::map<Point*,Points::size_type> indices;
+    PointConstPtrs my_points;
+    std::map<const Point*,Points::size_type> indices;
     my_points.reserve(points.size());
-    for (Points::iterator it = points.begin(); it != points.end(); ++it) {
+    for (Points::const_iterator it = points.begin(); it != points.end(); ++it) {
         my_points.push_back(&*it);
         indices[&*it] = it - points.begin();
     }
@@ -75,7 +75,7 @@ chained_path(Points &points, std::vector<Points::size_type> &retval, Point start
 }
 
 void
-chained_path(Points &points, std::vector<Points::size_type> &retval)
+chained_path(const Points &points, std::vector<Points::size_type> &retval)
 {
     if (points.empty()) return;  // can't call front() on empty vector
     chained_path(points, retval, points.front());
diff --git a/xs/src/libslic3r/Geometry.hpp b/xs/src/libslic3r/Geometry.hpp
index 2dc183f82..31e29816d 100644
--- a/xs/src/libslic3r/Geometry.hpp
+++ b/xs/src/libslic3r/Geometry.hpp
@@ -11,9 +11,9 @@ using boost::polygon::voronoi_diagram;
 
 namespace Slic3r { namespace Geometry {
 
-void convex_hull(Points &points, Polygon* hull);
-void chained_path(Points &points, std::vector<Points::size_type> &retval, Point start_near);
-void chained_path(Points &points, std::vector<Points::size_type> &retval);
+void convex_hull(Points points, Polygon* hull);
+void chained_path(const Points &points, std::vector<Points::size_type> &retval, Point start_near);
+void chained_path(const Points &points, std::vector<Points::size_type> &retval);
 template<class T> void chained_path_items(Points &points, T &items, T &retval);
 bool directions_parallel(double angle1, double angle2, double max_diff = 0);
 
diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp
index ec13eb951..9debe9e8e 100644
--- a/xs/src/libslic3r/Point.cpp
+++ b/xs/src/libslic3r/Point.cpp
@@ -40,6 +40,12 @@ Point::translate(double x, double y)
     this->y += y;
 }
 
+void
+Point::translate(const Point &vector)
+{
+    this->translate(vector.x, vector.y);
+}
+
 void
 Point::rotate(double angle, const Point &center)
 {
diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp
index d70a14093..3af5b36d0 100644
--- a/xs/src/libslic3r/Point.hpp
+++ b/xs/src/libslic3r/Point.hpp
@@ -27,10 +27,14 @@ class Point
     Point(int _x, int _y): x(_x), y(_y) {};
     Point(long long _x, long long _y): x(_x), y(_y) {};  // for Clipper
     Point(double x, double y);
+    static Point new_scale(coordf_t x, coordf_t y) {
+        return Point(scale_(x), scale_(y));
+    };
     bool operator==(const Point& rhs) const;
     std::string wkt() const;
     void scale(double factor);
     void translate(double x, double y);
+    void translate(const Point &vector);
     void rotate(double angle, const Point &center);
     bool coincides_with(const Point &point) const;
     bool coincides_with_epsilon(const Point &point) const;
diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp
index d0ec15cb5..54ad73bcf 100644
--- a/xs/src/libslic3r/Print.cpp
+++ b/xs/src/libslic3r/Print.cpp
@@ -308,6 +308,189 @@ Print::max_allowed_layer_height() const
     return *std::max_element(nozzle_diameter.begin(), nozzle_diameter.end());
 }
 
+/*  Caller is responsible for supplying models whose objects don't collide
+    and have explicit instance positions */
+void
+Print::add_model_object(ModelObject* model_object, int idx)
+{
+    DynamicPrintConfig object_config = model_object->config;  // clone
+    object_config.normalize();
+
+    // initialize print object and store it at the given position
+    PrintObject* o;
+    {
+        BoundingBoxf3 bb;
+        model_object->raw_bounding_box(&bb);
+        o = (idx != -1)
+            ? this->set_new_object(idx, model_object, bb)
+            : this->add_object(model_object, bb);
+    }
+    
+    {
+        Points copies;
+        for (ModelInstancePtrs::const_iterator i = model_object->instances.begin(); i != model_object->instances.end(); ++i) {
+            copies.push_back(Point::new_scale((*i)->offset.x, (*i)->offset.y));
+        }
+        o->set_copies(copies);
+    }
+    o->layer_height_ranges = model_object->layer_height_ranges;
+
+    for (ModelVolumePtrs::const_iterator v_i = model_object->volumes.begin(); v_i != model_object->volumes.end(); ++v_i) {
+        size_t volume_id = v_i - model_object->volumes.begin();
+        ModelVolume* volume = *v_i;
+        
+        // get the config applied to this volume
+        PrintRegionConfig config = this->_region_config_from_model_volume(*volume);
+        
+        // find an existing print region with the same config
+        int region_id = -1;
+        for (PrintRegionPtrs::const_iterator region = this->regions.begin(); region != this->regions.end(); ++region) {
+            if (config.equals((*region)->config)) {
+                region_id = region - this->regions.begin();
+                break;
+            }
+        }
+        
+        // if no region exists with the same config, create a new one
+        if (region_id == -1) {
+            PrintRegion* r = this->add_region();
+            r->config.apply(config);
+            region_id = this->regions.size() - 1;
+        }
+        
+        // assign volume to region
+        o->add_region_volume(region_id, volume_id);
+    }
+
+    // apply config to print object
+    o->config.apply(this->default_object_config);
+    o->config.apply(object_config, true);
+}
+
+bool
+Print::apply_config(DynamicPrintConfig config)
+{
+    // we get a copy of the config object so we can modify it safely
+    config.normalize();
+    
+    // apply variables to placeholder parser
+    this->placeholder_parser.apply_config(config);
+    
+    bool invalidated = false;
+    
+    // handle changes to print config
+    t_config_option_keys print_diff = this->config.diff(config);
+    if (!print_diff.empty()) {
+        this->config.apply(config, true);
+        
+        if (this->invalidate_state_by_config_options(print_diff))
+            invalidated = true;
+    }
+    
+    // handle changes to object config defaults
+    this->default_object_config.apply(config, true);
+    FOREACH_OBJECT(this, obj_ptr) {
+        // we don't assume that config contains a full ObjectConfig,
+        // so we base it on the current print-wise default
+        PrintObjectConfig new_config = this->default_object_config;
+        new_config.apply(config, true);
+        
+        // we override the new config with object-specific options
+        {
+            DynamicPrintConfig model_object_config = (*obj_ptr)->model_object()->config;
+            model_object_config.normalize();
+            new_config.apply(model_object_config, true);
+        }
+        
+        // check whether the new config is different from the current one
+        t_config_option_keys diff = (*obj_ptr)->config.diff(new_config);
+        if (!diff.empty()) {
+            (*obj_ptr)->config.apply(new_config, true);
+            
+            if ((*obj_ptr)->invalidate_state_by_config_options(diff))
+                invalidated = true;
+        }
+    }
+    
+    // handle changes to regions config defaults
+    this->default_region_config.apply(config, true);
+    
+    // All regions now have distinct settings.
+    // Check whether applying the new region config defaults we'd get different regions.
+    bool rearrange_regions = false;
+    std::vector<PrintRegionConfig> other_region_configs;
+    FOREACH_REGION(this, it_r) {
+        size_t region_id = it_r - this->regions.begin();
+        PrintRegion* region = *it_r;
+        
+        std::vector<PrintRegionConfig> this_region_configs;
+        FOREACH_OBJECT(this, it_o) {
+            PrintObject* object = *it_o;
+            
+            std::vector<int> &region_volumes = object->region_volumes[region_id];
+            for (std::vector<int>::const_iterator volume_id = region_volumes.begin(); volume_id != region_volumes.end(); ++volume_id) {
+                ModelVolume* volume = object->model_object()->volumes[*volume_id];
+                
+                PrintRegionConfig new_config = this->_region_config_from_model_volume(*volume);
+                
+                for (std::vector<PrintRegionConfig>::iterator it = this_region_configs.begin(); it != this_region_configs.end(); ++it) {
+                    // if the new config for this volume differs from the other
+                    // volume configs currently associated to this region, it means
+                    // the region subdivision does not make sense anymore
+                    if (!it->equals(new_config)) {
+                        rearrange_regions = true;
+                        goto NEXT_REGION;
+                    }
+                }
+                this_region_configs.push_back(new_config);
+                
+                for (std::vector<PrintRegionConfig>::iterator it = other_region_configs.begin(); it != other_region_configs.end(); ++it) {
+                    // if the new config for this volume equals any of the other
+                    // volume configs that are not currently associated to this
+                    // region, it means the region subdivision does not make
+                    // sense anymore
+                    if (it->equals(new_config)) {
+                        rearrange_regions = true;
+                        goto NEXT_REGION;
+                    }
+                }
+                
+                // if we're here and the new region config is different from the old
+                // one, we need to apply the new config and invalidate all objects
+                // (possible optimization: only invalidate objects using this region)
+                t_config_option_keys region_config_diff = region->config.diff(new_config);
+                if (!region_config_diff.empty()) {
+                    region->config.apply(new_config);
+                    FOREACH_OBJECT(this, o) {
+                        if ((*o)->invalidate_state_by_config_options(region_config_diff))
+                            invalidated = true;
+                    }
+                }
+            }
+        }
+        other_region_configs.insert(other_region_configs.end(), this_region_configs.begin(), this_region_configs.end());
+        
+        NEXT_REGION:
+            continue;
+    }
+    
+    if (rearrange_regions) {
+        // the current subdivision of regions does not make sense anymore.
+        // we need to remove all objects and re-add them
+        ModelObjectPtrs model_objects;
+        FOREACH_OBJECT(this, o) {
+            model_objects.push_back((*o)->model_object());
+        }
+        this->clear_objects();
+        for (ModelObjectPtrs::iterator it = model_objects.begin(); it != model_objects.end(); ++it) {
+            this->add_model_object(*it);
+        }
+        invalidated = true;
+    }
+    
+    return invalidated;
+}
+
 void
 Print::init_extruders()
 {
@@ -324,6 +507,28 @@ Print::init_extruders()
     this->state.set_done(psInitExtruders);
 }
 
+PrintRegionConfig
+Print::_region_config_from_model_volume(const ModelVolume &volume)
+{
+    PrintRegionConfig config = this->default_region_config;
+    {
+        DynamicPrintConfig other_config = volume.get_object()->config;
+        other_config.normalize();
+        config.apply(other_config, true);
+    }
+    {
+        DynamicPrintConfig other_config = volume.config;
+        other_config.normalize();
+        config.apply(other_config, true);
+    }
+    if (!volume.material_id().empty()) {
+        DynamicPrintConfig material_config = volume.material()->config;
+        material_config.normalize();
+        config.apply(material_config, true);
+    }
+    return config;
+}
+
 bool
 Print::has_support_material() const
 {
diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp
index 8b2a45044..9436d590e 100644
--- a/xs/src/libslic3r/Print.hpp
+++ b/xs/src/libslic3r/Print.hpp
@@ -8,6 +8,7 @@
 #include "PrintConfig.hpp"
 #include "Point.hpp"
 #include "Layer.hpp"
+#include "Model.hpp"
 #include "PlaceholderParser.hpp"
 
 
@@ -70,7 +71,6 @@ class PrintObject
     public:
     // vector of (vectors of volume ids), indexed by region_id
     std::vector<std::vector<int> > region_volumes;
-    Points copies;      // Slic3r::Point objects in scaled G-code coordinates
     PrintObjectConfig config;
     t_layer_height_ranges layer_height_ranges;
     
@@ -95,7 +95,10 @@ class PrintObject
     
     Print* print();
     ModelObject* model_object();
-
+    
+    Points copies() const;
+    void set_copies(const Points &points);
+    
     // adds region_id, too, if necessary
     void add_region_volume(int region_id, int volume_id);
 
@@ -119,6 +122,7 @@ class PrintObject
     private:
     Print* _print;
     ModelObject* _model_object;
+    Points _copies;      // Slic3r::Point objects in scaled G-code coordinates
 
     // TODO: call model_object->get_bounding_box() instead of accepting
         // parameter
@@ -164,6 +168,8 @@ class Print
     bool invalidate_step(PrintStep step);
     bool invalidate_all_steps();
     
+    void add_model_object(ModelObject* model_object, int idx = -1);
+    bool apply_config(DynamicPrintConfig config);
     void init_extruders();
     
     std::set<size_t> extruders() const;
@@ -174,6 +180,7 @@ class Print
     private:
     void clear_regions();
     void delete_region(size_t idx);
+    PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume);
 };
 
 #define FOREACH_BASE(type, container, iterator) for (type::const_iterator iterator = (container).begin(); iterator != (container).end(); ++iterator)
diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp
index 15fa0f0ba..5f1fd19ee 100644
--- a/xs/src/libslic3r/PrintObject.cpp
+++ b/xs/src/libslic3r/PrintObject.cpp
@@ -1,5 +1,6 @@
 #include "Print.hpp"
 #include "BoundingBox.hpp"
+#include "Geometry.hpp"
 
 namespace Slic3r {
 
@@ -21,8 +22,6 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Bounding
         this->_copies_shift = Point(
             scale_(modobj_bbox.min.x), scale_(modobj_bbox.min.y));
 
-        // TODO: $self->_trigger_copies;
-
         // Scale the object size and store it
         Pointf3 size = modobj_bbox.size();
         this->size = Point3(scale_(size.x), scale_(size.y), scale_(size.z));
@@ -45,6 +44,35 @@ PrintObject::model_object()
     return this->_model_object;
 }
 
+Points
+PrintObject::copies() const
+{
+    return this->_copies;
+}
+
+void
+PrintObject::set_copies(const Points &points)
+{
+    this->_copies = points;
+    
+    // order copies with a nearest neighbor search and translate them by _copies_shift
+    this->_shifted_copies.clear();
+    this->_shifted_copies.reserve(points.size());
+    
+    // order copies with a nearest-neighbor search
+    std::vector<Points::size_type> ordered_copies;
+    Slic3r::Geometry::chained_path(points, ordered_copies);
+    
+    for (std::vector<Points::size_type>::const_iterator it = ordered_copies.begin(); it != ordered_copies.end(); ++it) {
+        Point copy = points[*it];
+        copy.translate(this->_copies_shift);
+        this->_shifted_copies.push_back(copy);
+    }
+    
+    this->_print->invalidate_step(psSkirt);
+    this->_print->invalidate_step(psBrim);
+}
+
 void
 PrintObject::add_region_volume(int region_id, int volume_id)
 {
diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp
index b11533e36..1d410062b 100644
--- a/xs/xsp/Config.xsp
+++ b/xs/xsp/Config.xsp
@@ -14,12 +14,15 @@
     SV* get_at(t_config_option_key opt_key, int i);
     bool set(t_config_option_key opt_key, SV* value);
     bool set_deserialize(t_config_option_key opt_key, SV* str);
+    void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false);
     std::string serialize(t_config_option_key opt_key);
     double get_abs_value(t_config_option_key opt_key);
     %name{get_abs_value_over}
         double get_abs_value(t_config_option_key opt_key, double ratio_over);
     void apply(DynamicPrintConfig* other)
         %code{% THIS->apply(*other, true); %};
+    std::vector<std::string> diff(DynamicPrintConfig* other)
+        %code{% RETVAL = THIS->diff(*other); %};
     void apply_static(FullPrintConfig* other)
         %code{% THIS->apply(*other, true); %};
     std::vector<std::string> get_keys()
@@ -37,6 +40,7 @@
     SV* get_at(t_config_option_key opt_key, int i);
     bool set(t_config_option_key opt_key, SV* value);
     bool set_deserialize(t_config_option_key opt_key, SV* str);
+    void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false);
     std::string serialize(t_config_option_key opt_key);
     double get_abs_value(t_config_option_key opt_key);
     %name{get_abs_value_over}
@@ -58,6 +62,7 @@
     SV* get_at(t_config_option_key opt_key, int i);
     bool set(t_config_option_key opt_key, SV* value);
     bool set_deserialize(t_config_option_key opt_key, SV* str);
+    void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false);
     std::string serialize(t_config_option_key opt_key);
     double get_abs_value(t_config_option_key opt_key);
     %name{get_abs_value_over}
@@ -77,6 +82,7 @@
     SV* get_at(t_config_option_key opt_key, int i);
     bool set(t_config_option_key opt_key, SV* value);
     bool set_deserialize(t_config_option_key opt_key, SV* str);
+    void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false);
     std::string serialize(t_config_option_key opt_key);
     double get_abs_value(t_config_option_key opt_key);
     %name{get_abs_value_over}
@@ -98,6 +104,7 @@
     SV* get_at(t_config_option_key opt_key, int i);
     bool set(t_config_option_key opt_key, SV* value);
     bool set_deserialize(t_config_option_key opt_key, SV* str);
+    void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false);
     std::string serialize(t_config_option_key opt_key);
     double get_abs_value(t_config_option_key opt_key);
     %name{get_abs_value_over}
@@ -119,6 +126,7 @@
     SV* get_at(t_config_option_key opt_key, int i);
     bool set(t_config_option_key opt_key, SV* value);
     bool set_deserialize(t_config_option_key opt_key, SV* str);
+    void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false);
     std::string serialize(t_config_option_key opt_key);
     double get_abs_value(t_config_option_key opt_key);
     %name{get_abs_value_over}
diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp
index 330bc6b9a..4a32255fc 100644
--- a/xs/xsp/Print.xsp
+++ b/xs/xsp/Print.xsp
@@ -56,8 +56,7 @@ _constant()
     Ref<ModelObject> model_object();
     Ref<PrintObjectConfig> config()
         %code%{ RETVAL = &THIS->config; %};
-    Points copies()
-        %code%{ RETVAL = THIS->copies; %};
+    Points copies();
     t_layer_height_ranges layer_height_ranges()
         %code%{ RETVAL = THIS->layer_height_ranges; %};
     Ref<Point3> size()
@@ -75,8 +74,7 @@ _constant()
     void set_shifted_copies(Points value)
         %code%{ THIS->_shifted_copies = value; %};
 
-    void set_copies(Points copies)
-        %code%{ THIS->copies = copies; %};
+    void set_copies(Points copies);
     void set_layer_height_ranges(t_layer_height_ranges layer_height_ranges)
         %code%{ THIS->layer_height_ranges = layer_height_ranges; %};
 
@@ -169,6 +167,9 @@ _constant()
     double max_allowed_layer_height() const;
     bool has_support_material() const;
     
+    void add_model_object(ModelObject* model_object, int idx = -1);
+    bool apply_config(DynamicPrintConfig* config)
+        %code%{ RETVAL = THIS->apply_config(*config); %};
     void init_extruders();
 %{