From 60cf002e948d5a1a56a69b267189b8611957e2c6 Mon Sep 17 00:00:00 2001
From: Lukas Matena <lukasmatena@seznam.cz>
Date: Tue, 1 Sep 2020 18:33:56 +0200
Subject: [PATCH 1/9] Fixed merge conflicts (whitespace only)

---
 src/libslic3r/GCode.cpp | 590 ++++++++++++++++++++--------------------
 src/libslic3r/GCode.hpp |   1 +
 2 files changed, 297 insertions(+), 294 deletions(-)

diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 135389eb3..2049fd76b 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -662,7 +662,7 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
     return layers_to_print;
 }
 
-// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z 
+// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z
 // will be printed for  all objects at once.
 // Return a list of <print_z, per object LayerToPrint> items.
 std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collect_layers_to_print(const Print& print)
@@ -820,7 +820,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
             "Is " + path_tmp + " locked?" + '\n');
 
     BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info();
-	print->set_done(psGCodeExport);
+    print->set_done(psGCodeExport);
 
     // Write the profiler measurements to file
     PROFILE_UPDATE();
@@ -983,7 +983,8 @@ namespace DoExport {
 	    return volumetric_speed;
 	}
 
-	static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention)
+
+    static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention)
 	{
 	    // Calculate wiping points if needed
 	    if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) {
@@ -1123,26 +1124,26 @@ namespace DoExport {
 	        }
 	        filament_stats_string_out += out_filament_used_mm.first;
             filament_stats_string_out += "\n" + out_filament_used_cm3.first;
-			if (out_filament_used_g.second)
+            if (out_filament_used_g.second)
                 filament_stats_string_out += "\n" + out_filament_used_g.first;
-			if (out_filament_cost.second)
+            if (out_filament_cost.second)
                 filament_stats_string_out += "\n" + out_filament_cost.first;
-	    }
-	    return filament_stats_string_out;
-	}
+        }
+        return filament_stats_string_out;
+    }
 }
 
 // Sort the PrintObjects by their increasing Z, likely useful for avoiding colisions on Deltas during sequential prints.
 static inline std::vector<const PrintInstance*> sort_object_instances_by_max_z(const Print &print)
 {
     std::vector<const PrintObject*> objects(print.objects().begin(), print.objects().end());
-	std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->height() < po2->height(); });
-	std::vector<const PrintInstance*> instances;
-	instances.reserve(objects.size());
-	for (const PrintObject *object : objects)
-		for (size_t i = 0; i < object->instances().size(); ++ i)
-			instances.emplace_back(&object->instances()[i]);
-	return instances;
+    std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->height() < po2->height(); });
+    std::vector<const PrintInstance*> instances;
+    instances.reserve(objects.size());
+    for (const PrintObject *object : objects)
+        for (size_t i = 0; i < object->instances().size(); ++ i)
+            instances.emplace_back(&object->instances()[i]);
+    return instances;
 }
 
 // Produce a vector of PrintObjects in the order of their respective ModelObjects in print.model().
@@ -1246,8 +1247,8 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
     // Write information on the generator.
     _write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str());
 
-    DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values, 
-        [this, file](const char* sz) { this->_write(file, sz); }, 
+    DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values,
+        [this, file](const char* sz) { this->_write(file, sz); },
         [&print]() { print.throw_if_canceled(); });
 
     // Write notes (content of the Print Settings tab -> Notes)
@@ -1282,7 +1283,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
         _write_format(file, "\n");
     }
     print.throw_if_canceled();
-    
+
     // adds tags for time estimators
 #if ENABLE_GCODE_VIEWER
     if (print.config().remaining_times.value)
@@ -1321,12 +1322,12 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
         }
         // We don't allow switching of extruders per layer by Model::custom_gcode_per_print_z in sequential mode.
         // Use the extruder IDs collected from Regions.
-    	this->set_extruders(print.extruders());
+        this->set_extruders(print.extruders());
     } else {
-		// Find tool ordering for all the objects at once, and the initial extruder ID.
+        // Find tool ordering for all the objects at once, and the initial extruder ID.
         // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it.
-		tool_ordering = print.tool_ordering();
-		tool_ordering.assign_custom_gcodes(print);
+        tool_ordering = print.tool_ordering();
+        tool_ordering.assign_custom_gcodes(print);
         has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower();
         initial_extruder_id = (has_wipe_tower && ! print.config().single_extruder_multi_material_priming) ?
             // The priming towers will be skipped.
@@ -1335,7 +1336,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
             tool_ordering.first_extruder();
         // In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z.
         // Therefore initialize the printing extruders from there.
-    	this->set_extruders(tool_ordering.all_extruders());
+        this->set_extruders(tool_ordering.all_extruders());
         // Order object instances using a nearest neighbor search.
         print_object_instances_ordering = chain_print_object_instances(print);
     }
@@ -1435,7 +1436,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
     // Calculate wiping points if needed
     DoExport::init_ooze_prevention(print, m_ooze_prevention);
     print.throw_if_canceled();
-    
+
     if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) {
         // Set initial extruder only after custom start G-code.
         // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed.
@@ -1510,7 +1511,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
             m_wipe_tower.reset(new WipeTowerIntegration(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()));
             _write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height"));
             if (print.config().single_extruder_multi_material_priming) {
-    		    _write(file, m_wipe_tower->prime(*this));
+                _write(file, m_wipe_tower->prime(*this));
                 // Verify, whether the print overaps the priming extrusions.
                 BoundingBoxf bbox_print(get_print_extrusions_extents(print));
                 coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON;
@@ -1577,7 +1578,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
             _writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config().end_filament_gcode.get_at(extruder_id), extruder_id, &config));
         } else {
             for (const std::string &end_gcode : print.config().end_filament_gcode.values) {
-				int extruder_id = (unsigned int)(&end_gcode - &print.config().end_filament_gcode.values.front());
+                int extruder_id = (unsigned int)(&end_gcode - &print.config().end_filament_gcode.values.front());
                 config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id));
                 _writeln(file, this->placeholder_parser_process("end_filament_gcode", end_gcode, extruder_id, &config));
             }
@@ -1651,8 +1652,8 @@ std::string GCode::placeholder_parser_process(const std::string &name, const std
         m_placeholder_parser_failed_templates.insert(name);
         // Insert the macro error message into the G-code.
         return
-            std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" + 
-            err.what() + 
+            std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" +
+            err.what() +
             "!!!!! End of an error report for the custom G-code template " + name + "\n\n";
     }
 }
@@ -1678,12 +1679,12 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc
             int mcode = int(strtol(ptr, &endptr, 10));
             if (endptr != nullptr && endptr != ptr && (mcode == mcode_set_temp_dont_wait || mcode == mcode_set_temp_and_wait)) {
                 // M104/M109 or M140/M190 found.
-				ptr = endptr;
+                ptr = endptr;
                 // Let the caller know that the custom G-code sets the temperature.
                 temp_set_by_gcode = true;
                 // Now try to parse the temperature value.
-				// While not at the end of the line:
-				while (strchr(";\r\n\0", *ptr) == nullptr) {
+                // While not at the end of the line:
+                while (strchr(";\r\n\0", *ptr) == nullptr) {
                     // Skip whitespaces.
                     for (; *ptr == ' ' || *ptr == '\t'; ++ ptr);
                     if (*ptr == 'S') {
@@ -1692,22 +1693,22 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc
                         // Parse an int.
                         endptr = nullptr;
                         long temp_parsed = strtol(ptr, &endptr, 10);
-						if (endptr > ptr) {
-							ptr = endptr;
-							temp_out = temp_parsed;
-						}
+                        if (endptr > ptr) {
+                            ptr = endptr;
+                            temp_out = temp_parsed;
+                        }
                     } else {
                         // Skip this word.
-						for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr);
+                        for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr);
                     }
                 }
             }
         }
         // Skip the rest of the line.
         for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr);
-		// Skip the end of line indicators.
+        // Skip the end of line indicators.
         for (; *ptr == '\r' || *ptr == '\n'; ++ ptr);
-	}
+    }
     return temp_set_by_gcode;
 }
 
@@ -1796,9 +1797,9 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c
 }
 
 inline GCode::ObjectByExtruder& object_by_extruder(
-    std::map<unsigned int, std::vector<GCode::ObjectByExtruder>> &by_extruder, 
-    unsigned int                                                  extruder_id, 
-    size_t                                                        object_idx, 
+    std::map<unsigned int, std::vector<GCode::ObjectByExtruder>> &by_extruder,
+    unsigned int                                                  extruder_id,
+    size_t                                                        object_idx,
     size_t                                                        num_objects)
 {
     std::vector<GCode::ObjectByExtruder> &objects_by_extruder = by_extruder[extruder_id];
@@ -1808,9 +1809,9 @@ inline GCode::ObjectByExtruder& object_by_extruder(
 }
 
 inline std::vector<GCode::ObjectByExtruder::Island>& object_islands_by_extruder(
-    std::map<unsigned int, std::vector<GCode::ObjectByExtruder>>  &by_extruder, 
-    unsigned int                                                   extruder_id, 
-    size_t                                                         object_idx, 
+    std::map<unsigned int, std::vector<GCode::ObjectByExtruder>>  &by_extruder,
+    unsigned int                                                   extruder_id,
+    size_t                                                         object_idx,
     size_t                                                         num_objects,
     size_t                                                         num_islands)
 {
@@ -1821,82 +1822,82 @@ inline std::vector<GCode::ObjectByExtruder::Island>& object_islands_by_extruder(
 }
 
 std::vector<GCode::InstanceToPrint> GCode::sort_print_object_instances(
-	std::vector<GCode::ObjectByExtruder> 		&objects_by_extruder,
-	const std::vector<LayerToPrint> 			&layers,
-	// Ordering must be defined for normal (non-sequential print).
-	const std::vector<const PrintInstance*> 	*ordering,
-	// For sequential print, the instance of the object to be printing has to be defined.
-	const size_t                     		 	 single_object_instance_idx)
+    std::vector<GCode::ObjectByExtruder> 		&objects_by_extruder,
+    const std::vector<LayerToPrint> 			&layers,
+    // Ordering must be defined for normal (non-sequential print).
+    const std::vector<const PrintInstance*> 	*ordering,
+    // For sequential print, the instance of the object to be printing has to be defined.
+    const size_t                     		 	 single_object_instance_idx)
 {
     std::vector<InstanceToPrint> out;
 
     if (ordering == nullptr) {
-    	// Sequential print, single object is being printed.
-		for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
-		    const size_t       layer_id     = &object_by_extruder - objects_by_extruder.data();
-		    const PrintObject *print_object = layers[layer_id].object();
-		    if (print_object)
-		    	out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx);
-		}
+        // Sequential print, single object is being printed.
+        for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
+            const size_t       layer_id     = &object_by_extruder - objects_by_extruder.data();
+            const PrintObject *print_object = layers[layer_id].object();
+            if (print_object)
+                out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx);
+        }
     } else {
-		// Create mapping from PrintObject* to ObjectByExtruder*.
-		std::vector<std::pair<const PrintObject*, ObjectByExtruder*>> sorted;
-		sorted.reserve(objects_by_extruder.size());
-		for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
-		    const size_t       layer_id     = &object_by_extruder - objects_by_extruder.data();
-		    const PrintObject *print_object = layers[layer_id].object();
-		    if (print_object)
-		    	sorted.emplace_back(print_object, &object_by_extruder);
-		}
-		std::sort(sorted.begin(), sorted.end());
+        // Create mapping from PrintObject* to ObjectByExtruder*.
+        std::vector<std::pair<const PrintObject*, ObjectByExtruder*>> sorted;
+        sorted.reserve(objects_by_extruder.size());
+        for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
+            const size_t       layer_id     = &object_by_extruder - objects_by_extruder.data();
+            const PrintObject *print_object = layers[layer_id].object();
+            if (print_object)
+                sorted.emplace_back(print_object, &object_by_extruder);
+        }
+        std::sort(sorted.begin(), sorted.end());
 
-		if (! sorted.empty()) {
-		    out.reserve(sorted.size());
-		    for (const PrintInstance *instance : *ordering) {
-		    	const PrintObject &print_object = *instance->print_object;
-		    	std::pair<const PrintObject*, ObjectByExtruder*> key(&print_object, nullptr);
-		    	auto it = std::lower_bound(sorted.begin(), sorted.end(), key);
-		    	if (it != sorted.end() && it->first == &print_object)
-		    		// ObjectByExtruder for this PrintObject was found.
-					out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data());
-		    }
-		}
-	}
-	return out;
+        if (! sorted.empty()) {
+            out.reserve(sorted.size());
+            for (const PrintInstance *instance : *ordering) {
+                const PrintObject &print_object = *instance->print_object;
+                std::pair<const PrintObject*, ObjectByExtruder*> key(&print_object, nullptr);
+                auto it = std::lower_bound(sorted.begin(), sorted.end(), key);
+                if (it != sorted.end() && it->first == &print_object)
+                    // ObjectByExtruder for this PrintObject was found.
+                    out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data());
+            }
+        }
+    }
+    return out;
 }
 
 namespace ProcessLayer
 {
 
     static std::string emit_custom_gcode_per_print_z(
-    	const CustomGCode::Item 								*custom_gcode,
+        const CustomGCode::Item 								*custom_gcode,
         // ID of the first extruder printing this layer.
         unsigned int                                             first_extruder_id,
         const PrintConfig                                       &config)
-	{
+    {
         std::string gcode;
         bool single_extruder_printer = config.nozzle_diameter.size() == 1;
-        
+
         if (custom_gcode != nullptr) {
-			// Extruder switches are processed by LayerTools, they should be filtered out.
-			assert(custom_gcode->type != CustomGCode::ToolChange);
+            // Extruder switches are processed by LayerTools, they should be filtered out.
+            assert(custom_gcode->type != CustomGCode::ToolChange);
 
             CustomGCode::Type   gcode_type   = custom_gcode->type;
             bool  				color_change = gcode_type == CustomGCode::ColorChange;
             bool 				tool_change  = gcode_type == CustomGCode::ToolChange;
-		    // Tool Change is applied as Color Change for a single extruder printer only.
-		    assert(! tool_change || single_extruder_printer);
+            // Tool Change is applied as Color Change for a single extruder printer only.
+            assert(! tool_change || single_extruder_printer);
 
-		    std::string pause_print_msg;
-		    int m600_extruder_before_layer = -1;
-	        if (color_change && custom_gcode->extruder > 0)
-	            m600_extruder_before_layer = custom_gcode->extruder - 1;
-	        else if (gcode_type == CustomGCode::PausePrint)
-	            pause_print_msg = custom_gcode->extra;
+            std::string pause_print_msg;
+            int m600_extruder_before_layer = -1;
+            if (color_change && custom_gcode->extruder > 0)
+                m600_extruder_before_layer = custom_gcode->extruder - 1;
+            else if (gcode_type == CustomGCode::PausePrint)
+                pause_print_msg = custom_gcode->extra;
 
-		    // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
-	        if (color_change || tool_change)
-	        {
+            // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
+            if (color_change || tool_change)
+            {
                 assert(m600_extruder_before_layer >= 0);
 		        // Color Change or Tool Change as Color Change.
 #if ENABLE_GCODE_VIEWER
@@ -1910,13 +1911,13 @@ namespace ProcessLayer
 #endif // ENABLE_GCODE_VIEWER
 
                 if (!single_extruder_printer && m600_extruder_before_layer >= 0 && first_extruder_id != (unsigned)m600_extruder_before_layer
-	                // && !MMU1
-	                ) {
-	                //! FIXME_in_fw show message during print pause
-	                gcode += config.pause_print_gcode;// pause print
+                    // && !MMU1
+                    ) {
+                    //! FIXME_in_fw show message during print pause
+                    gcode += config.pause_print_gcode;// pause print
                     gcode += "\n";
-	                gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n";
-	            }
+                    gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n";
+                }
                 else {
                     gcode += config.color_change_gcode;//ColorChangeCode;
                     gcode += "\n";
@@ -1956,32 +1957,32 @@ namespace ProcessLayer
                     if (gcode_type == CustomGCode::Template)    // Template Cistom Gcode
                         gcode += config.template_custom_gcode;
                     else                                        // custom Gcode
-	                    gcode += custom_gcode->extra;
+                        gcode += custom_gcode->extra;
 
-	            }
-	            gcode += "\n";
-	        }
-		}
+                }
+                gcode += "\n";
+            }
+        }
 
-	    return gcode;
-	}
+        return gcode;
+    }
 } // namespace ProcessLayer
 
 namespace Skirt {
-	static void skirt_loops_per_extruder_all_printing(const Print &print, const LayerTools &layer_tools, std::map<unsigned int, std::pair<size_t, size_t>> &skirt_loops_per_extruder_out)
-	{
+    static void skirt_loops_per_extruder_all_printing(const Print &print, const LayerTools &layer_tools, std::map<unsigned int, std::pair<size_t, size_t>> &skirt_loops_per_extruder_out)
+    {
         // Prime all extruders printing over the 1st layer over the skirt lines.
         size_t n_loops = print.skirt().entities.size();
         size_t n_tools = layer_tools.extruders.size();
         size_t lines_per_extruder = (n_loops + n_tools - 1) / n_tools;
         for (size_t i = 0; i < n_loops; i += lines_per_extruder)
             skirt_loops_per_extruder_out[layer_tools.extruders[i / lines_per_extruder]] = std::pair<size_t, size_t>(i, std::min(i + lines_per_extruder, n_loops));
-	}
+    }
 
     static std::map<unsigned int, std::pair<size_t, size_t>> make_skirt_loops_per_extruder_1st_layer(
         const Print             				&print,
-	    const std::vector<GCode::LayerToPrint> 	& /*layers */,
-	    const LayerTools                		&layer_tools,
+        const std::vector<GCode::LayerToPrint> 	& /*layers */,
+        const LayerTools                		&layer_tools,
         // Heights (print_z) at which the skirt has already been extruded.
         std::vector<coordf_t>  			    	&skirt_done)
     {
@@ -1989,7 +1990,7 @@ namespace Skirt {
         // not at the print_z of the interlaced support material layers.
         std::map<unsigned int, std::pair<size_t, size_t>> skirt_loops_per_extruder_out;
         if (skirt_done.empty() && print.has_skirt() && ! print.skirt().entities.empty()) {
-        	skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out);
+            skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out);
             skirt_done.emplace_back(layer_tools.print_z);
         }
         return skirt_loops_per_extruder_out;
@@ -1997,11 +1998,11 @@ namespace Skirt {
 
     static std::map<unsigned int, std::pair<size_t, size_t>> make_skirt_loops_per_extruder_other_layers(
         const Print 							&print,
-	    const std::vector<GCode::LayerToPrint> 	&layers,
-	    const LayerTools                		&layer_tools,
-	    // First non-empty support layer.
-	    const SupportLayer  					*support_layer,
-	    // Heights (print_z) at which the skirt has already been extruded.
+        const std::vector<GCode::LayerToPrint> 	&layers,
+        const LayerTools                		&layer_tools,
+        // First non-empty support layer.
+        const SupportLayer  					*support_layer,
+        // Heights (print_z) at which the skirt has already been extruded.
         std::vector<coordf_t>			    	&skirt_done)
     {
         // Extrude skirt at the print_z of the raft layers and normal object layers
@@ -2019,7 +2020,7 @@ namespace Skirt {
             // Prime just the first printing extruder. This is original Slic3r's implementation.
             skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair<size_t, size_t>(0, print.config().skirts.value);
 #else
-            // Prime all extruders planned for this layer, see 
+            // Prime all extruders planned for this layer, see
             // https://github.com/prusa3d/PrusaSlicer/issues/469#issuecomment-322450619
             skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out);
 #endif
@@ -2031,7 +2032,7 @@ namespace Skirt {
 
 } // namespace Skirt
 
-// In sequential mode, process_layer is called once per each object and its copy, 
+// In sequential mode, process_layer is called once per each object and its copy,
 // therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object.
 // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated.
 // For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths
@@ -2043,8 +2044,8 @@ void GCode::process_layer(
     // Set of object & print layers of the same PrintObject and with the same print_z.
     const std::vector<LayerToPrint> 		&layers,
     const LayerTools        		        &layer_tools,
-	// Pairs of PrintObject index and its instance index.
-	const std::vector<const PrintInstance*> *ordering,
+    // Pairs of PrintObject index and its instance index.
+    const std::vector<const PrintInstance*> *ordering,
     // If set to size_t(-1), then print all copies of all objects.
     // Otherwise print a single copy of a single object.
     const size_t                     		 single_object_instance_idx)
@@ -2091,7 +2092,7 @@ void GCode::process_layer(
     }
     // If we're going to apply spiralvase to this layer, disable loop clipping
     m_enable_loop_clipping = ! m_spiral_vase || ! m_spiral_vase->enable;
-    
+
     std::string gcode;
 
 #if ENABLE_GCODE_VIEWER
@@ -2120,7 +2121,7 @@ void GCode::process_layer(
             + "\n";
     }
     gcode += this->change_layer(print_z);  // this will increase m_layer_index
-	m_layer = &layer;
+    m_layer = &layer;
     if (! print.config().layer_gcode.value.empty()) {
         DynamicConfig config;
         config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
@@ -2212,8 +2213,8 @@ void GCode::process_layer(
         }
         if (layer_to_print.object_layer != nullptr) {
             const Layer &layer = *layer_to_print.object_layer;
-            // We now define a strategy for building perimeters and fills. The separation 
-            // between regions doesn't matter in terms of printing order, as we follow 
+            // We now define a strategy for building perimeters and fills. The separation
+            // between regions doesn't matter in terms of printing order, as we follow
             // another logic instead:
             // - we group all extrusions by extruder so that we minimize toolchanges
             // - we start from the last used extruder
@@ -2228,13 +2229,13 @@ void GCode::process_layer(
             std::vector<size_t> slices_test_order;
             slices_test_order.reserve(n_slices);
             for (size_t i = 0; i < n_slices; ++ i)
-            	slices_test_order.emplace_back(i);
+                slices_test_order.emplace_back(i);
             std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](size_t i, size_t j) {
-            	const Vec2d s1 = layer_surface_bboxes[i].size().cast<double>();
-            	const Vec2d s2 = layer_surface_bboxes[j].size().cast<double>();
-            	return s1.x() * s1.y() < s2.x() * s2.y();
+                const Vec2d s1 = layer_surface_bboxes[i].size().cast<double>();
+                const Vec2d s2 = layer_surface_bboxes[j].size().cast<double>();
+                return s1.x() * s1.y() < s2.x() * s2.y();
             });
-            auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) { 
+            auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) {
                 const BoundingBox &bbox = layer_surface_bboxes[i];
                 return point(0) >= bbox.min(0) && point(0) < bbox.max(0) &&
                        point(1) >= bbox.min(1) && point(1) < bbox.max(1) &&
@@ -2265,27 +2266,27 @@ void GCode::process_layer(
                         // Let's recover vector of extruder overrides:
                         const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr;
                         if (! layer_tools.has_extruder(correct_extruder_id)) {
-							// this entity is not overridden, but its extruder is not in layer_tools - we'll print it
+                            // this entity is not overridden, but its extruder is not in layer_tools - we'll print it
                             // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
                             correct_extruder_id = layer_tools.extruders.back();
                         }
                         printing_extruders.clear();
                         if (is_anything_overridden) {
-                        	entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->instances().size());
-	                        if (entity_overrides == nullptr) {
-		                    	printing_extruders.emplace_back(correct_extruder_id);
-	                        } else {
-	                        	printing_extruders.reserve(entity_overrides->size());
-	                        	for (int extruder : *entity_overrides)
-	                        		printing_extruders.emplace_back(extruder >= 0 ? 
-	                        			// at least one copy is overridden to use this extruder
-	                        			extruder : 
-	                        			// at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
-	                        			static_cast<unsigned int>(- extruder - 1));
-		                        Slic3r::sort_remove_duplicates(printing_extruders);
-	                        }
-	                    } else
-	                    	printing_extruders.emplace_back(correct_extruder_id);
+                            entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->instances().size());
+                            if (entity_overrides == nullptr) {
+                                printing_extruders.emplace_back(correct_extruder_id);
+                            } else {
+                                printing_extruders.reserve(entity_overrides->size());
+                                for (int extruder : *entity_overrides)
+                                    printing_extruders.emplace_back(extruder >= 0 ?
+                                        // at least one copy is overridden to use this extruder
+                                        extruder :
+                                        // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
+                                        static_cast<unsigned int>(- extruder - 1));
+                                Slic3r::sort_remove_duplicates(printing_extruders);
+                            }
+                        } else
+                            printing_extruders.emplace_back(correct_extruder_id);
 
                         // Now we must add this extrusion into the by_extruder map, once for each extruder that will print it:
                         for (unsigned int extruder : printing_extruders)
@@ -2296,10 +2297,10 @@ void GCode::process_layer(
                                 &layer_to_print - layers.data(),
                                 layers.size(), n_slices+1);
                             for (size_t i = 0; i <= n_slices; ++ i) {
-								bool   last = i == n_slices;
-                            	size_t island_idx = last ? n_slices : slices_test_order[i];
+                                bool   last = i == n_slices;
+                                size_t island_idx = last ? n_slices : slices_test_order[i];
                                 if (// extrusions->first_point does not fit inside any slice
-									last ||
+                                    last ||
                                     // extrusions->first_point fits inside ith slice
                                     point_inside_surface(island_idx, extrusions->first_point())) {
                                     if (islands[island_idx].by_region.empty())
@@ -2374,10 +2375,10 @@ void GCode::process_layer(
         if (objects_by_extruder_it == by_extruder.end())
             continue;
 
-		std::vector<InstanceToPrint> instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
+        std::vector<InstanceToPrint> instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
 
         // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
-		std::vector<ObjectByExtruder::Island::Region> by_region_per_copy_cache;
+        std::vector<ObjectByExtruder::Island::Region> by_region_per_copy_cache;
         for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) {
             if (is_anything_overridden && print_wipe_extrusions == 0)
                 gcode+="; PURGING FINISHED\n";
@@ -2406,7 +2407,7 @@ void GCode::process_layer(
                 }
                 for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) {
                     const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast<unsigned int>(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region;
-                	//FIXME the following code prints regions in the order they are defined, the path is not optimized in any way.
+                    //FIXME the following code prints regions in the order they are defined, the path is not optimized in any way.
                     if (print.config().infill_first) {
                         gcode += this->extrude_infill(print, by_region_specific, false);
                         gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]);
@@ -2418,13 +2419,13 @@ void GCode::process_layer(
                     gcode += this->extrude_infill(print,by_region_specific, true);
                 }
                 if (this->config().gcode_label_objects)
-					gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
+                    gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
             }
         }
     }
 
     // Apply spiral vase post-processing if this layer contains suitable geometry
-    // (we must feed all the G-code into the post-processor, including the first 
+    // (we must feed all the G-code into the post-processor, including the first
     // bottom non-spiral layers otherwise it will mess with positions)
     // we apply spiral vase at this stage because it requires a full layer.
     // Just a reminder: A spiral vase mode is allowed for a single object per layer, single material print only.
@@ -2450,7 +2451,7 @@ void GCode::process_layer(
         gcode = m_pressure_equalizer->process(gcode.c_str(), false);
     // printf("G-code after filter:\n%s\n", out.c_str());
 #endif /* HAS_PRESSURE_EQUALIZER */
-    
+
     _write(file, gcode);
 #if !ENABLE_GCODE_VIEWER
     BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
@@ -2470,19 +2471,19 @@ void GCode::apply_print_config(const PrintConfig &print_config)
 
 void GCode::append_full_config(const Print &print, std::string &str)
 {
-	const DynamicPrintConfig &cfg = print.full_print_config();
+    const DynamicPrintConfig &cfg = print.full_print_config();
     // Sorted list of config keys, which shall not be stored into the G-code. Initializer list.
-	static constexpr auto banned_keys = { 
-		"compatible_printers"sv,
-		"compatible_prints"sv,
-		"print_host"sv,
-		"printhost_apikey"sv,
-		"printhost_cafile"sv
-	};
+    static constexpr auto banned_keys = {
+        "compatible_printers"sv,
+        "compatible_prints"sv,
+        "print_host"sv,
+        "printhost_apikey"sv,
+        "printhost_cafile"sv
+    };
     assert(std::is_sorted(banned_keys.begin(), banned_keys.end()));
-	auto is_banned = [](const std::string &key) {
-		return std::binary_search(banned_keys.begin(), banned_keys.end(), key);
-	};
+    auto is_banned = [](const std::string &key) {
+        return std::binary_search(banned_keys.begin(), banned_keys.end(), key);
+    };
     for (const std::string &key : cfg.keys())
         if (! is_banned(key) && ! cfg.option(key)->is_nil())
             str += "; " + key + " = " + cfg.opt_serialize(key) + "\n";
@@ -2491,7 +2492,7 @@ void GCode::append_full_config(const Print &print, std::string &str)
 void GCode::set_extruders(const std::vector<unsigned int> &extruder_ids)
 {
     m_writer.set_extruders(extruder_ids);
-    
+
     // enable wipe path generation if any extruder has wipe enabled
     m_wipe.enable = false;
     for (auto id : extruder_ids)
@@ -2502,7 +2503,7 @@ void GCode::set_extruders(const std::vector<unsigned int> &extruder_ids)
 }
 
 void GCode::set_origin(const Vec2d &pointf)
-{    
+{
     // if origin increases (goes towards right), last_pos decreases because it goes towards left
     const Point translate(
         scale_(m_origin(0) - pointf(0)),
@@ -2516,13 +2517,13 @@ void GCode::set_origin(const Vec2d &pointf)
 std::string GCode::preamble()
 {
     std::string gcode = m_writer.preamble();
-    
+
     /*  Perform a *silent* move to z_offset: we need this to initialize the Z
         position of our writer object so that any initial lift taking place
         before the first layer change will raise the extruder from the correct
         initial Z instead of 0.  */
     m_writer.travel_to_z(m_config.z_offset.value);
-    
+
     return gcode;
 }
 
@@ -2542,10 +2543,10 @@ std::string GCode::change_layer(coordf_t print_z)
         comment << "move to next layer (" << m_layer_index << ")";
         gcode += m_writer.travel_to_z(z, comment.str());
     }
-    
+
     // forget last wiping path as wiping after raising Z is pointless
     m_wipe.reset_path();
-    
+
     return gcode;
 }
 
@@ -2554,16 +2555,16 @@ std::string GCode::change_layer(coordf_t print_z)
 static inline float bspline_kernel(float x)
 {
     x = std::abs(x);
-	if (x < 1.f) {
-		return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x;
-	}
-	else if (x < 2.f) {
-		x -= 1.f;
-		float x2 = x * x;
-		float x3 = x2 * x;
-		return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3;
-	}
-	else
+    if (x < 1.f) {
+        return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x;
+    }
+    else if (x < 2.f) {
+        x -= 1.f;
+        float x2 = x * x;
+        float x3 = x2 * x;
+        return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3;
+    }
+    else
         return 0;
 }
 
@@ -2636,13 +2637,13 @@ static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polyg
                 pt_min = p1;
                 double linv = double(d_seg) / double(l2_seg);
                 pt_min(0) = pt(0) - coord_t(floor(double(v_seg(1)) * linv + 0.5));
-				pt_min(1) = pt(1) + coord_t(floor(double(v_seg(0)) * linv + 0.5));
-				assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5));
+                pt_min(1) = pt(1) + coord_t(floor(double(v_seg(0)) * linv + 0.5));
+                assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5));
             }
         }
     }
 
-	assert(i_min != size_t(-1));
+    assert(i_min != size_t(-1));
     if ((pt_min - polygon.points[i_min]).cast<double>().norm() > eps) {
         // Insert a new point on the segment i_min, i_min+1.
         return polygon.points.insert(polygon.points.begin() + (i_min + 1), pt_min);
@@ -2706,9 +2707,9 @@ std::vector<float> polygon_angles_at_vertices(const Polygon &polygon, const std:
         const Point &p2 = polygon.points[idx_next];
         const Point  v1 = p1 - p0;
         const Point  v2 = p2 - p1;
-		int64_t dot   = int64_t(v1(0))*int64_t(v2(0)) + int64_t(v1(1))*int64_t(v2(1));
-		int64_t cross = int64_t(v1(0))*int64_t(v2(1)) - int64_t(v1(1))*int64_t(v2(0));
-		float angle = float(atan2(double(cross), double(dot)));
+        int64_t dot   = int64_t(v1(0))*int64_t(v2(0)) + int64_t(v1(1))*int64_t(v2(1));
+        int64_t cross = int64_t(v1(0))*int64_t(v2(1)) - int64_t(v1(1))*int64_t(v2(0));
+        float angle = float(atan2(double(cross), double(dot)));
         angles[idx_curr] = angle;
     }
 
@@ -2740,14 +2741,14 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
             #endif
         }
     }
-  
+
     // extrude all loops ccw
     bool was_clockwise = loop.make_counter_clockwise();
-    
+
     SeamPosition seam_position = m_config.seam_position;
-    if (loop.loop_role() == elrSkirt) 
+    if (loop.loop_role() == elrSkirt)
         seam_position = spNearest;
-    
+
     // find the point of the loop that is closest to the current extruder position
     // or randomize if requested
     Point last_pos = this->last_pos();
@@ -2813,8 +2814,8 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
                 penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
             }
             // Give a negative penalty for points close to the last point or the prefered seam location.
-            float dist_to_last_pos_proj = (i < last_pos_proj_idx) ? 
-                std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) : 
+            float dist_to_last_pos_proj = (i < last_pos_proj_idx) ?
+                std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) :
                 std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]);
             float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr
             penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max);
@@ -2861,6 +2862,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
             m_seam_position[m_layer->object()] = polygon.points[idx_min];
         }
 
+
         // Export the contour into a SVG file.
         #if 0
         {
@@ -2904,23 +2906,23 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
         // Find the closest point, avoid overhangs.
         loop.split_at(last_pos, true);
     }
-    
+
     // clip the path to avoid the extruder to get exactly on the first point of the loop;
     // if polyline was shorter than the clipping distance we'd get a null polyline, so
     // we discard it in that case
-    double clip_length = m_enable_loop_clipping ? 
-        scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER : 
+    double clip_length = m_enable_loop_clipping ?
+        scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER :
         0;
 
     // get paths
     ExtrusionPaths paths;
     loop.clip_end(clip_length, &paths);
     if (paths.empty()) return "";
-    
+
     // apply the small perimeter speed
     if (is_perimeter(paths.front().role()) && loop.length() <= SMALL_PERIMETER_LENGTH && speed == -1)
         speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed);
-    
+
     // extrude along the path
     std::string gcode;
     for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) {
@@ -2929,31 +2931,31 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
         path->simplify(SCALED_RESOLUTION);
         gcode += this->_extrude(*path, description, speed);
     }
-    
+
     // reset acceleration
     gcode += m_writer.set_acceleration((unsigned int)(m_config.default_acceleration.value + 0.5));
-    
+
     if (m_wipe.enable)
         m_wipe.path = paths.front().polyline;  // TODO: don't limit wipe to last path
-    
+
     // make a little move inwards before leaving loop
-	if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.perimeters.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) {
+    if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.perimeters.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) {
         // detect angle between last and first segment
         // the side depends on the original winding order of the polygon (left for contours, right for holes)
-		//FIXME improve the algorithm in case the loop is tiny.
-		//FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query).
+        //FIXME improve the algorithm in case the loop is tiny.
+        //FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query).
         Point a = paths.front().polyline.points[1];  // second point
         Point b = *(paths.back().polyline.points.end()-3);       // second to last point
         if (was_clockwise) {
             // swap points
             Point c = a; a = b; b = c;
         }
-        
+
         double angle = paths.front().first_point().ccw_angle(a, b) / 3;
-        
+
         // turn left if contour, turn right if hole
         if (was_clockwise) angle *= -1;
-        
+
         // create the destination point along the first segment and rotate it
         // we make sure we don't exceed the segment length because we don't know
         // the rotation of the second segment so we might cross the object boundary
@@ -2969,7 +2971,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
         // generate the travel move
         gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), "move inwards before travel");
     }
-    
+
     return gcode;
 }
 
@@ -3040,23 +3042,23 @@ std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectBy
     const char*          extrusion_name = ironing ? "ironing" : "infill";
     for (const ObjectByExtruder::Island::Region &region : by_region)
         if (! region.infills.empty()) {
-        	extrusions.clear();
-        	extrusions.reserve(region.infills.size());
-        	for (ExtrusionEntity *ee : region.infills)
-        		if ((ee->role() == erIroning) == ironing)
-        			extrusions.emplace_back(ee);
-        	if (! extrusions.empty()) {
-	            m_config.apply(print.regions()[&region - &by_region.front()]->config());
-			    chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
-	            for (const ExtrusionEntity *fill : extrusions) {
-	                auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill);
-	                if (eec) {
-					    for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
-	                        gcode += this->extrude_entity(*ee, extrusion_name);
-	                } else
-	                    gcode += this->extrude_entity(*fill, extrusion_name);
-	            }
-	        }
+            extrusions.clear();
+            extrusions.reserve(region.infills.size());
+            for (ExtrusionEntity *ee : region.infills)
+                if ((ee->role() == erIroning) == ironing)
+                    extrusions.emplace_back(ee);
+            if (! extrusions.empty()) {
+                m_config.apply(print.regions()[&region - &by_region.front()]->config());
+                chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
+                for (const ExtrusionEntity *fill : extrusions) {
+                    auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill);
+                    if (eec) {
+                        for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
+                            gcode += this->extrude_entity(*ee, extrusion_name);
+                    } else
+                        gcode += this->extrude_entity(*fill, extrusion_name);
+                }
+            }
         }
     return gcode;
 }
@@ -3150,10 +3152,10 @@ void GCode::_write_format(FILE* file, const char* format, ...)
 std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed)
 {
     std::string gcode;
-    
+
     if (is_bridge(path.role()))
         description += " (bridge)";
-    
+
     // go to first point of extrusion path
     if (!m_last_pos_defined || m_last_pos != path.first_point()) {
         gcode += this->travel_to(
@@ -3162,10 +3164,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
             "move to first " + description + " point"
         );
     }
-    
+
     // compensate retraction
     gcode += this->unretract();
-    
+
     // adjust acceleration
     {
         double acceleration;
@@ -3182,11 +3184,11 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
         }
         gcode += m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5));
     }
-    
+
     // calculate extrusion length per distance unit
     double e_per_mm = m_writer.extruder()->e_per_mm3() * path.mm3_per_mm;
     if (m_writer.extrusion_axis().empty()) e_per_mm = 0;
-    
+
     // set speed
     if (speed == -1) {
         if (path.role() == erPerimeter) {
@@ -3228,7 +3230,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
         );
     }
     double F = speed * 60;  // convert mm/sec to mm/min
-    
+
     // extrude arc or line
     if (m_enable_extrusion_role_markers)
     {
@@ -3336,40 +3338,40 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
     }
     if (m_enable_cooling_markers)
         gcode += is_bridge(path.role()) ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n";
-    
+
     this->set_last_pos(path.last_point());
     return gcode;
 }
 
 // This method accepts &point in print coordinates.
 std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment)
-{    
+{
     /*  Define the travel move as a line between current position and the taget point.
         This is expressed in print coordinates, so it will need to be translated by
         this->origin in order to get G-code coordinates.  */
     Polyline travel;
     travel.append(this->last_pos());
     travel.append(point);
-    
+
     // check whether a straight travel move would need retraction
     bool needs_retraction = this->needs_retraction(travel, role);
-    
+
     // if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
     // multi-hop travel path inside the configuration space
     if (needs_retraction
         && m_config.avoid_crossing_perimeters
         && ! m_avoid_crossing_perimeters.disable_once) {
         travel = m_avoid_crossing_perimeters.travel_to(*this, point);
-        
+
         // check again whether the new travel path still needs a retraction
         needs_retraction = this->needs_retraction(travel, role);
         //if (needs_retraction && m_layer_index > 1) exit(0);
     }
-    
+
     // Re-allow avoid_crossing_perimeters for the next travel moves
     m_avoid_crossing_perimeters.disable_once = false;
     m_avoid_crossing_perimeters.use_external_mp_once = false;
-    
+
     // generate G-code for the travel move
     std::string gcode;
     if (needs_retraction)
@@ -3377,12 +3379,12 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
     else
         // Reset the wipe path when traveling, so one would not wipe along an old path.
         m_wipe.reset_path();
-    
+
     // use G1 because we rely on paths being straight (G0 may make round paths)
     Lines lines = travel.lines();
     if (! lines.empty()) {
         for (const Line &line : lines)
-    	    gcode += m_writer.travel_to_xy(this->point_to_gcode(line.b), comment);    
+            gcode += m_writer.travel_to_xy(this->point_to_gcode(line.b), comment);
         this->set_last_pos(lines.back().b);
     }
     return gcode;
@@ -3394,7 +3396,7 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
         // skip retraction if the move is shorter than the configured threshold
         return false;
     }
-    
+
     if (role == erSupportMaterial) {
         const SupportLayer* support_layer = dynamic_cast<const SupportLayer*>(m_layer);
         //FIXME support_layer->support_islands.contains should use some search structure!
@@ -3411,7 +3413,7 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
         // internal infill is enabled (so that stringing is entirely not visible).
         //FIXME any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first.
         return false;
-    
+
     // retract if only_retract_when_crossing_perimeters is disabled or doesn't apply
     return true;
 }
@@ -3419,26 +3421,26 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
 std::string GCode::retract(bool toolchange)
 {
     std::string gcode;
-    
+
     if (m_writer.extruder() == nullptr)
         return gcode;
-    
+
     // wipe (if it's enabled for this extruder and we have a stored wipe path)
     if (EXTRUDER_CONFIG(wipe) && m_wipe.has_path()) {
         gcode += toolchange ? m_writer.retract_for_toolchange(true) : m_writer.retract(true);
         gcode += m_wipe.wipe(*this, toolchange);
     }
-    
+
     /*  The parent class will decide whether we need to perform an actual retraction
-        (the extruder might be already retracted fully or partially). We call these 
+        (the extruder might be already retracted fully or partially). We call these
         methods even if we performed wipe, since this will ensure the entire retraction
         length is honored in case wipe path was too short.  */
     gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract();
-    
+
     gcode += m_writer.reset_e();
     if (m_writer.extruder()->retract_length() > 0 || m_config.use_firmware_retraction)
         gcode += m_writer.lift();
-    
+
     return gcode;
 }
 
@@ -3446,11 +3448,11 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
 {
     if (!m_writer.need_toolchange(extruder_id))
         return "";
-    
+
     // if we are running a single-extruder setup, just set the extruder and return nothing
     if (!m_writer.multiple_extruders) {
         m_placeholder_parser.set("current_extruder", extruder_id);
-        
+
         std::string gcode;
         // Append the filament start G-code.
         const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id);
@@ -3462,13 +3464,13 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
         gcode += m_writer.toolchange(extruder_id);
         return gcode;
     }
-    
+
     // prepend retraction on the current extruder
     std::string gcode = this->retract(true);
 
     // Always reset the extrusion path, even if the tool change retract is set to zero.
     m_wipe.reset_path();
-    
+
     if (m_writer.extruder() != nullptr) {
         // Process the custom end_filament_gcode. set_extruder() is only called if there is no wipe tower
         // so it should not be injected twice.
@@ -3480,7 +3482,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
         }
     }
 
-    
+
     // If ooze prevention is enabled, park current extruder in the nearest
     // standby point and set it to the standby temperature.
     if (m_ooze_prevention.enable && m_writer.extruder() != nullptr)
@@ -3529,7 +3531,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
     // Set the new extruder to the operating temperature.
     if (m_ooze_prevention.enable)
         gcode += m_ooze_prevention.post_toolchange(*this);
-    
+
     return gcode;
 }
 
@@ -3556,17 +3558,17 @@ const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtru
 {
     bool has_overrides = false;
     for (const auto& reg : by_region)
-    	if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) {
-    		has_overrides = true;
-    		break;
-    	}
+        if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) {
+            has_overrides = true;
+            break;
+        }
 
-	// Data is cleared, but the memory is not.
+    // Data is cleared, but the memory is not.
     by_region_per_copy_cache.clear();
 
     if (! has_overrides)
-    	// Simple case. No need to copy the regions.
-    	return wiping_entities ? by_region_per_copy_cache : this->by_region;
+        // Simple case. No need to copy the regions.
+        return wiping_entities ? by_region_per_copy_cache : this->by_region;
 
     // Complex case. Some of the extrusions of some object instances are to be printed first - those are the wiping extrusions.
     // Some of the extrusions of some object instances are printed later - those are the clean print extrusions.
@@ -3585,25 +3587,25 @@ const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtru
             // Now the most important thing - which extrusion should we print.
             // See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack.
             if (wiping_entities) {
-            	// Apply overrides for this region.
-	            for (unsigned int i = 0; i < overrides.size(); ++ i) {
-            		const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
-            		// This copy (aka object instance) should be printed with this extruder, which overrides the default one.
-	                if (this_override != nullptr && (*this_override)[copy] == int(extruder))
-	                    target_eec.emplace_back(entities[i]);
-            	}
-	        } else {
-	        	// Apply normal extrusions (non-overrides) for this region.
-				unsigned int i = 0;
-	            for (; i < overrides.size(); ++ i) {
-            		const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
-            		// This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one.
-            		if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1)
-	                    target_eec.emplace_back(entities[i]);
-	            }
-	            for (; i < entities.size(); ++ i)
+                // Apply overrides for this region.
+                for (unsigned int i = 0; i < overrides.size(); ++ i) {
+                    const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
+                    // This copy (aka object instance) should be printed with this extruder, which overrides the default one.
+                    if (this_override != nullptr && (*this_override)[copy] == int(extruder))
+                        target_eec.emplace_back(entities[i]);
+                }
+            } else {
+                // Apply normal extrusions (non-overrides) for this region.
+                unsigned int i = 0;
+                for (; i < overrides.size(); ++ i) {
+                    const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
+                    // This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one.
+                    if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1)
+                        target_eec.emplace_back(entities[i]);
+                }
+                for (; i < entities.size(); ++ i)
                     target_eec.emplace_back(entities[i]);
-		    }
+            }
         }
     }
     return by_region_per_copy_cache;
@@ -3623,11 +3625,11 @@ void GCode::ObjectByExtruder::Island::Region::append(const Type type, const Extr
         perimeters_or_infills_overrides = &perimeters_overrides;
         break;
     case INFILL:
-    	perimeters_or_infills 			= &infills;
-    	perimeters_or_infills_overrides = &infills_overrides;
+        perimeters_or_infills 			= &infills;
+        perimeters_or_infills_overrides = &infills_overrides;
         break;
     default:
-    	throw std::invalid_argument("Unknown parameter!");
+        throw std::invalid_argument("Unknown parameter!");
     }
 
     // First we append the entities, there are eec->entities.size() of them:
@@ -3635,18 +3637,18 @@ void GCode::ObjectByExtruder::Island::Region::append(const Type type, const Extr
     size_t new_size = old_size + (eec->can_reverse() ? eec->entities.size() : 1);
     perimeters_or_infills->reserve(new_size);
     if (eec->can_reverse()) {
-	    for (auto* ee : eec->entities)
-	        perimeters_or_infills->emplace_back(ee);
-	} else
-		perimeters_or_infills->emplace_back(const_cast<ExtrusionEntityCollection*>(eec));
+        for (auto* ee : eec->entities)
+            perimeters_or_infills->emplace_back(ee);
+    } else
+        perimeters_or_infills->emplace_back(const_cast<ExtrusionEntityCollection*>(eec));
 
     if (copies_extruder != nullptr) {
-    	// Don't reallocate overrides if not needed.
-    	// Missing overrides are implicitely considered non-overridden.
+        // Don't reallocate overrides if not needed.
+        // Missing overrides are implicitely considered non-overridden.
         perimeters_or_infills_overrides->reserve(new_size);
         perimeters_or_infills_overrides->resize(old_size, nullptr);
         perimeters_or_infills_overrides->resize(new_size, copies_extruder);
-	}
+    }
 }
 
 }   // namespace Slic3r
diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp
index 8bae2ef43..5923f63e9 100644
--- a/src/libslic3r/GCode.hpp
+++ b/src/libslic3r/GCode.hpp
@@ -69,6 +69,7 @@ private:
     std::unique_ptr<MotionPlanner> m_layer_mp;
 };
 
+
 class OozePrevention {
 public:
     bool enable;

From 7844ca12fa497863064a20dcda3135e343db62a8 Mon Sep 17 00:00:00 2001
From: Lukas Matena <lukasmatena@seznam.cz>
Date: Wed, 2 Sep 2020 00:26:13 +0200
Subject: [PATCH 2/9] First naive prototype of seam painter

---
 src/libslic3r/GCode.cpp | 65 +++++++++++++++++++++++++++++++++++++++++
 src/libslic3r/GCode.hpp | 12 ++++++++
 2 files changed, 77 insertions(+)

diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 2049fd76b..b4196dc5f 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -175,6 +175,32 @@ namespace Slic3r {
         return islands;
     }
 
+
+    int CustomSeam::get_point_status(const Point& pt, size_t layer_id) const
+    {
+        // TEMPORARY - WILL BE IMPROVED
+        // - quadratic algorithm
+        // - does not support variable layer height
+
+        if (! enforcers.empty()) {
+            assert(layer_id < enforcers.size());
+            for (const ExPolygon& explg : enforcers[layer_id]) {
+                if (explg.contains(pt))
+                    return 1;
+            }
+        }
+        if (! blockers.empty()) {
+            assert(layer_id < blockers.size());
+            for (const ExPolygon& explg : blockers[layer_id]) {
+                if (explg.contains(pt))
+                    return -1;
+            }
+        }
+        return 0;
+    }
+
+
+
     std::string OozePrevention::pre_toolchange(GCode& gcodegen)
     {
         std::string gcode;
@@ -984,6 +1010,22 @@ namespace DoExport {
 	}
 
 
+    static void collect_custom_seam(const Print& print, CustomSeam& custom_seam)
+   {
+       custom_seam = CustomSeam();
+       for (const PrintObject* po : print.objects()) {
+           po->project_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, custom_seam.enforcers);
+           po->project_and_append_custom_facets(true, EnforcerBlockerType::BLOCKER, custom_seam.blockers);
+       }
+       for (ExPolygons& explgs : custom_seam.enforcers) {
+           explgs = Slic3r::offset_ex(explgs, scale_(0.5));
+       }
+       for (ExPolygons& explgs : custom_seam.blockers) {
+           explgs = Slic3r::offset_ex(explgs, scale_(0.5));
+       }
+   }
+
+
     static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention)
 	{
 	    // Calculate wiping points if needed
@@ -1437,6 +1479,9 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
     DoExport::init_ooze_prevention(print, m_ooze_prevention);
     print.throw_if_canceled();
 
+    // Collect custom seam data from all objects.
+    DoExport::collect_custom_seam(print, m_custom_seam);
+
     if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) {
         // Set initial extruder only after custom start G-code.
         // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed.
@@ -2841,6 +2886,13 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
             }
         }
 
+        // Penalty according to custom seam selection. This one is huge compared to
+        // the others so that points outside enforcers/inside blockers never win.
+        for (size_t i = 0; i < polygon.points.size(); ++ i) {
+            const Point &p = polygon.points[i];
+            penalties[i] -= float(100000 * m_custom_seam.get_point_status(p, m_layer->id()));
+        }
+
         // Find a point with a minimum penalty.
         size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
 
@@ -2862,6 +2914,19 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
             m_seam_position[m_layer->object()] = polygon.points[idx_min];
         }
 
+//////////////////////
+//        int layer_id = m_layer->id();
+//        std::ostringstream os;
+//        os << std::setw(3) << std::setfill('0') << layer_id;
+//        int a = scale_(15.);
+//        SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a)));
+//        if (! m_custom_seam.enforcers.empty())
+//            svg.draw(m_custom_seam.enforcers[layer_id], "blue");
+//        if (! m_custom_seam.blockers.empty())
+//            svg.draw(m_custom_seam.blockers[layer_id], "red");
+//        svg.draw(polygon.points, "black");
+////////////////////
+
 
         // Export the contour into a SVG file.
         #if 0
diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp
index 5923f63e9..1004a8efc 100644
--- a/src/libslic3r/GCode.hpp
+++ b/src/libslic3r/GCode.hpp
@@ -70,6 +70,15 @@ private:
 };
 
 
+struct CustomSeam {
+    std::vector<ExPolygons> enforcers;
+    std::vector<ExPolygons> blockers;
+
+    // Finds whether the point is inside an enforcer/blockers.
+    // Returns +1, 0 or -1.
+    int get_point_status(const Point& pt, size_t layer_id) const;
+};
+
 class OozePrevention {
 public:
     bool enable;
@@ -339,6 +348,9 @@ private:
     std::string     unretract() { return m_writer.unlift() + m_writer.unretract(); }
     std::string     set_extruder(unsigned int extruder_id, double print_z);
 
+    // Cache for custom seam enforcers/blockers for each layer.
+    CustomSeam                          m_custom_seam;
+
     /* Origin of print coordinates expressed in unscaled G-code coordinates.
        This affects the input arguments supplied to the extrude*() and travel_to()
        methods. */

From a1fadaf955be836fe3a446cddb9fae9e9c3ff051 Mon Sep 17 00:00:00 2001
From: Lukas Matena <lukasmatena@seznam.cz>
Date: Mon, 31 Aug 2020 07:25:24 +0200
Subject: [PATCH 3/9] Partially working implementation of custom seam backend

---
 src/libslic3r/GCode.cpp   | 225 +++++++++++++++++++++++++++++---------
 src/libslic3r/GCode.hpp   |  18 ++-
 src/libslic3r/Polygon.cpp |  38 +++++++
 src/libslic3r/Polygon.hpp |   2 +
 4 files changed, 227 insertions(+), 56 deletions(-)

diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index b4196dc5f..63dde6606 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -176,27 +176,34 @@ namespace Slic3r {
     }
 
 
-    int CustomSeam::get_point_status(const Point& pt, size_t layer_id) const
+    void CustomSeam::get_indices(size_t layer_id,
+                                 const Polygon& polygon,
+                                 std::vector<size_t>& enforcers_idxs,
+                                 std::vector<size_t>& blockers_idxs) const
     {
-        // TEMPORARY - WILL BE IMPROVED
-        // - quadratic algorithm
-        // - does not support variable layer height
+        enforcers_idxs.clear();
+        blockers_idxs.clear();
 
-        if (! enforcers.empty()) {
-            assert(layer_id < enforcers.size());
-            for (const ExPolygon& explg : enforcers[layer_id]) {
-                if (explg.contains(pt))
-                    return 1;
+        // FIXME: This is quadratic and it should be improved, maybe by building
+        // an AABB tree (or at least utilize bounding boxes).
+        for (size_t i=0; i<polygon.points.size(); ++i) {
+
+            if (! enforcers.empty()) {
+                assert(layer_id < enforcers.size());
+                for (const ExPolygon& explg : enforcers[layer_id]) {
+                    if (explg.contains(polygon.points[i]))
+                        enforcers_idxs.push_back(i);
+                }
+            }
+
+            if (! blockers.empty()) {
+                assert(layer_id < blockers.size());
+                for (const ExPolygon& explg : blockers[layer_id]) {
+                    if (explg.contains(polygon.points[i]))
+                        blockers_idxs.push_back(i);
+                }
             }
         }
-        if (! blockers.empty()) {
-            assert(layer_id < blockers.size());
-            for (const ExPolygon& explg : blockers[layer_id]) {
-                if (explg.contains(pt))
-                    return -1;
-            }
-        }
-        return 0;
     }
 
 
@@ -1017,12 +1024,12 @@ namespace DoExport {
            po->project_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, custom_seam.enforcers);
            po->project_and_append_custom_facets(true, EnforcerBlockerType::BLOCKER, custom_seam.blockers);
        }
-       for (ExPolygons& explgs : custom_seam.enforcers) {
-           explgs = Slic3r::offset_ex(explgs, scale_(0.5));
-       }
-       for (ExPolygons& explgs : custom_seam.blockers) {
-           explgs = Slic3r::offset_ex(explgs, scale_(0.5));
-       }
+       const std::vector<double>& nozzle_dmrs = print.config().nozzle_diameter.values;
+       float max_nozzle_dmr = *std::max_element(nozzle_dmrs.begin(), nozzle_dmrs.end());
+       for (ExPolygons& explgs : custom_seam.enforcers)
+           explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr));
+       for (ExPolygons& explgs : custom_seam.blockers)
+           explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr));
    }
 
 
@@ -2696,15 +2703,7 @@ static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polyg
     return polygon.points.begin() + i_min;
 }
 
-std::vector<float> polygon_parameter_by_length(const Polygon &polygon)
-{
-    // Parametrize the polygon by its length.
-    std::vector<float> lengths(polygon.points.size()+1, 0.);
-    for (size_t i = 1; i < polygon.points.size(); ++ i)
-        lengths[i] = lengths[i-1] + (polygon.points[i] - polygon.points[i-1]).cast<float>().norm();
-    lengths.back() = lengths[lengths.size()-2] + (polygon.points.front() - polygon.points.back()).cast<float>().norm();
-    return lengths;
-}
+
 
 std::vector<float> polygon_angles_at_vertices(const Polygon &polygon, const std::vector<float> &lengths, float min_arm_length)
 {
@@ -2761,6 +2760,136 @@ std::vector<float> polygon_angles_at_vertices(const Polygon &polygon, const std:
     return angles;
 }
 
+
+
+
+// Go through the polygon, identify points inside support enforcers and return
+// indices of points in the middle of each enforcer (measured along the contour).
+static std::vector<size_t> find_enforcer_centers(const Polygon& polygon,
+                                                 const std::vector<float>& lengths,
+                                                 const std::vector<size_t>& enforcers_idxs)
+{
+    std::vector<size_t> out;
+    assert(polygon.points.size()+1 == lengths.size());
+    assert(std::is_sorted(enforcers_idxs.begin(), enforcers_idxs.end()));
+    if (polygon.size() < 2 || enforcers_idxs.empty())
+        return out;
+
+    auto get_center_idx = [&polygon, &lengths](size_t start_idx, size_t end_idx) -> size_t {
+        assert(end_idx >= start_idx);
+        if (start_idx == end_idx)
+            return start_idx;
+        float t_c = lengths[start_idx] + 0.5f * (lengths[end_idx] - lengths[start_idx]);
+        auto it = std::lower_bound(lengths.begin() + start_idx, lengths.begin() + end_idx, t_c);
+        int ret = it - lengths.begin();
+        return ret;
+    };
+
+    int last_enforcer_start_idx = enforcers_idxs.front();
+    bool last_pt_in_list = enforcers_idxs.back() == polygon.points.size() - 1;
+
+    for (size_t i=0; i<enforcers_idxs.size()-1; ++i) {
+        if ((i == enforcers_idxs.size() - 1)
+         || enforcers_idxs[i+1] != enforcers_idxs[i] + 1) {
+            // i is last point of current enforcer
+            out.push_back(get_center_idx(last_enforcer_start_idx, enforcers_idxs[i]));
+            last_enforcer_start_idx = enforcers_idxs[i+1];
+        }
+    }
+
+    if (last_pt_in_list) {
+        // last point is an enforcer - not yet accounted for.
+        if (enforcers_idxs.front() != 0) {
+            size_t center_idx = get_center_idx(last_enforcer_start_idx, enforcers_idxs.back());
+            out.push_back(center_idx);
+        } else {
+            // Wrap-around. Update first center already found.
+            if (out.empty()) {
+                // Probably an enforcer around the whole contour. Return nothing.
+                return out;
+            }
+
+            // find last point of the enforcer at the beginning:
+            size_t idx = 0;
+            while (enforcers_idxs[idx]+1 == enforcers_idxs[idx+1])
+                ++idx;
+
+            float t_s = lengths[last_enforcer_start_idx];
+            float t_e = lengths[idx];
+            float half_dist = 0.5f * (t_e + lengths.back() - t_s);
+            float t_c = (half_dist > t_e) ? t_s + half_dist : t_e - half_dist;
+
+            auto it = std::lower_bound(lengths.begin(), lengths.end(), t_c);
+            out[0] = it - lengths.begin();
+            if (out[0] == lengths.size() - 1)
+                --out[0];
+            assert(out[0] < lengths.size() - 1);
+        }
+    }
+    return out;
+}
+
+
+void CustomSeam::penalize_polygon(const Polygon& polygon,
+                                  std::vector<float>& penalties,
+                                  const std::vector<float>& lengths,
+                                  int layer_id) const
+{
+    std::vector<size_t> enforcers_idxs;
+    std::vector<size_t> blockers_idxs;
+    this->get_indices(layer_id, polygon, enforcers_idxs, blockers_idxs);
+
+    for (size_t i : enforcers_idxs) {
+        assert(i < penalties.size());
+        penalties[i] -= float(ENFORCER_BLOCKER_PENALTY);
+    }
+    for (size_t i : blockers_idxs) {
+        assert(i < penalties.size());
+        penalties[i] += float(ENFORCER_BLOCKER_PENALTY);
+    }
+    std::vector<size_t> enf_centers = find_enforcer_centers(polygon, lengths, enforcers_idxs);
+    for (size_t idx : enf_centers) {
+        assert(idx < penalties.size());
+        penalties[idx] -= 1000.f;
+    }
+
+//    //////////////////////
+//            std::ostringstream os;
+//            os << std::setw(3) << std::setfill('0') << layer_id;
+//            int a = scale_(15.);
+//            SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a)));
+//            /*if (! m_custom_seam.enforcers.empty())
+//                svg.draw(m_custom_seam.enforcers[layer_id], "blue");
+//            if (! m_custom_seam.blockers.empty())
+//                svg.draw(m_custom_seam.blockers[layer_id], "red");*/
+
+//            size_t min_idx = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
+
+//            //svg.draw(polygon.points[idx_min], "red", 6e5);
+//            for (size_t i=0; i<polygon.points.size(); ++i) {
+//                std::string fill;
+//                coord_t size = 0;
+//                if (min_idx == i) {
+//                    fill = "yellow";
+//                    size = 5e5;
+//                } else {
+//                    fill = (std::find(enforcers_idxs.begin(), enforcers_idxs.end(), i) != enforcers_idxs.end() ? "green" : "black");
+//                    if (std::find(enf_centers.begin(), enf_centers.end(), i) != enf_centers.end()) {
+//                        size = 5e5;
+//                        fill = "blue";
+//                    }
+//                }
+//                if (i != 0)
+//                    svg.draw(polygon.points[i], fill, size);
+//                else
+//                    svg.draw(polygon.points[i], "red", 5e5);
+//            }
+//    ////////////////////
+
+
+
+}
+
 std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid)
 {
     // get a copy; don't modify the orientation of the original loop object otherwise
@@ -2804,6 +2933,12 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
         const coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter);
         const coord_t  nozzle_r   = coord_t(scale_(0.5 * nozzle_dmr) + 0.5);
 
+        if (m_custom_seam.is_on_layer(m_layer->id())) {
+            // Seam enf/blockers can begin and end in between the original vertices.
+            // Let add extra points in between and update the leghths.
+            polygon.densify(scale_(0.2f));
+        }
+
         // Retrieve the last start position for this object.
         float last_pos_weight = 1.f;
 
@@ -2829,7 +2964,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
         }
 
         // Parametrize the polygon by its length.
-        std::vector<float> lengths = polygon_parameter_by_length(polygon);
+        std::vector<float> lengths = polygon.parameter_by_length();
 
         // For each polygon point, store a penalty.
         // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r.
@@ -2870,8 +3005,8 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
         // Penalty for overhangs.
         if (lower_layer_edge_grid && (*lower_layer_edge_grid)) {
             // Use the edge grid distance field structure over the lower layer to calculate overhangs.
-            coord_t nozzle_r = coord_t(floor(scale_(0.5 * nozzle_dmr) + 0.5));
-            coord_t search_r = coord_t(floor(scale_(0.8 * nozzle_dmr) + 0.5));
+            coord_t nozzle_r = coord_t(std::floor(scale_(0.5 * nozzle_dmr) + 0.5));
+            coord_t search_r = coord_t(std::floor(scale_(0.8 * nozzle_dmr) + 0.5));
             for (size_t i = 0; i < polygon.points.size(); ++ i) {
                 const Point &p = polygon.points[i];
                 coordf_t dist;
@@ -2888,10 +3023,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
 
         // Penalty according to custom seam selection. This one is huge compared to
         // the others so that points outside enforcers/inside blockers never win.
-        for (size_t i = 0; i < polygon.points.size(); ++ i) {
-            const Point &p = polygon.points[i];
-            penalties[i] -= float(100000 * m_custom_seam.get_point_status(p, m_layer->id()));
-        }
+        m_custom_seam.penalize_polygon(polygon, penalties, lengths, m_layer->id());
 
         // Find a point with a minimum penalty.
         size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
@@ -2906,7 +3038,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
             float penalty_max      = std::max(penalty_min, penalty_aligned);
             float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max;
             // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel);
-            if (penalty_diff_rel < 0.05) {
+            if (std::abs(penalty_diff_rel) < 0.05) {
                 // Penalty of the aligned point is very close to the minimum penalty.
                 // Align the seams as accurately as possible.
                 idx_min = last_pos_proj_idx;
@@ -2914,19 +3046,6 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
             m_seam_position[m_layer->object()] = polygon.points[idx_min];
         }
 
-//////////////////////
-//        int layer_id = m_layer->id();
-//        std::ostringstream os;
-//        os << std::setw(3) << std::setfill('0') << layer_id;
-//        int a = scale_(15.);
-//        SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a)));
-//        if (! m_custom_seam.enforcers.empty())
-//            svg.draw(m_custom_seam.enforcers[layer_id], "blue");
-//        if (! m_custom_seam.blockers.empty())
-//            svg.draw(m_custom_seam.blockers[layer_id], "red");
-//        svg.draw(polygon.points, "black");
-////////////////////
-
 
         // Export the contour into a SVG file.
         #if 0
diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp
index 1004a8efc..43f338235 100644
--- a/src/libslic3r/GCode.hpp
+++ b/src/libslic3r/GCode.hpp
@@ -74,9 +74,21 @@ struct CustomSeam {
     std::vector<ExPolygons> enforcers;
     std::vector<ExPolygons> blockers;
 
-    // Finds whether the point is inside an enforcer/blockers.
-    // Returns +1, 0 or -1.
-    int get_point_status(const Point& pt, size_t layer_id) const;
+    // Get indices of points inside enforcers and blockers.
+    void get_indices(size_t layer_id,
+                    const Polygon& polygon,
+                    std::vector<size_t>& enforcers_idxs,
+                    std::vector<size_t>& blockers_idxs) const;
+    bool is_on_layer(size_t layer_id) const {
+        return ! ((enforcers.empty() || enforcers[layer_id].empty())
+                && (blockers.empty() || blockers[layer_id].empty()));
+    }
+    void penalize_polygon(const Polygon& polygon,
+                          std::vector<float>& penalties,
+                          const std::vector<float>& lengths,
+                          int layer_id) const;
+
+    static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6;
 };
 
 class OozePrevention {
diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp
index 48e63dab3..5a6ce107b 100644
--- a/src/libslic3r/Polygon.cpp
+++ b/src/libslic3r/Polygon.cpp
@@ -259,6 +259,44 @@ Point Polygon::point_projection(const Point &point) const
     return proj;
 }
 
+std::vector<float> Polygon::parameter_by_length() const
+{
+    // Parametrize the polygon by its length.
+    std::vector<float> lengths(points.size()+1, 0.);
+    for (size_t i = 1; i < points.size(); ++ i)
+        lengths[i] = lengths[i-1] + (points[i] - points[i-1]).cast<float>().norm();
+    lengths.back() = lengths[lengths.size()-2] + (points.front() - points.back()).cast<float>().norm();
+    return lengths;
+}
+
+void Polygon::densify(float min_length, std::vector<float>* lengths_ptr)
+{
+    std::vector<float> lengths_local;
+    std::vector<float>& lengths = lengths_ptr ? *lengths_ptr : lengths_local;
+
+    if (! lengths_ptr) {
+        // Length parametrization has not been provided. Calculate our own.
+        lengths = this->parameter_by_length();
+    }
+
+    assert(points.size() == lengths.size() - 1);
+
+    for (size_t j=1; j<=points.size(); ++j) {
+        bool last = j == points.size();
+        int i = last ? 0 : j;
+
+        if (lengths[j] - lengths[j-1] > min_length) {
+            Point diff = points[i] - points[j-1];
+            float diff_len = lengths[j] - lengths[j-1];
+            float r = (min_length/diff_len);
+            Point new_pt = points[j-1] + Point(r*diff[0], r*diff[1]);
+            points.insert(points.begin() + j, new_pt);
+            lengths.insert(lengths.begin() + j, lengths[j-1] + min_length);
+        }
+    }
+    assert(points.size() == lengths.size() - 1);
+}
+
 BoundingBox get_extents(const Points &points)
 { 
 	return BoundingBox(points);
diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp
index ab7c171e3..ae3a71d41 100644
--- a/src/libslic3r/Polygon.hpp
+++ b/src/libslic3r/Polygon.hpp
@@ -61,12 +61,14 @@ public:
     bool contains(const Point &point) const;
     Polygons simplify(double tolerance) const;
     void simplify(double tolerance, Polygons &polygons) const;
+    void densify(float min_length, std::vector<float>* lengths = nullptr);
     void triangulate_convex(Polygons* polygons) const;
     Point centroid() const;
     Points concave_points(double angle = PI) const;
     Points convex_points(double angle = PI) const;
     // Projection of a point onto the polygon.
     Point point_projection(const Point &point) const;
+    std::vector<float> parameter_by_length() const;
 };
 
 inline bool operator==(const Polygon &lhs, const Polygon &rhs) { return lhs.points == rhs.points; }

From e78221409a1127ef30fe24fae80ff84150449671 Mon Sep 17 00:00:00 2001
From: Lukas Matena <lukasmatena@seznam.cz>
Date: Wed, 9 Sep 2020 13:21:39 +0200
Subject: [PATCH 4/9] Renamed CustomSeam to SeamPlacer, move to a separate file

---
 src/libslic3r/CMakeLists.txt       |   2 +
 src/libslic3r/GCode.cpp            | 505 +---------------------------
 src/libslic3r/GCode.hpp            |  25 +-
 src/libslic3r/GCode/SeamPlacer.cpp | 520 +++++++++++++++++++++++++++++
 src/libslic3r/GCode/SeamPlacer.hpp |  51 +++
 5 files changed, 586 insertions(+), 517 deletions(-)
 create mode 100644 src/libslic3r/GCode/SeamPlacer.cpp
 create mode 100644 src/libslic3r/GCode/SeamPlacer.hpp

diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index 3d241dd37..cde2e9eab 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -95,6 +95,8 @@ add_library(libslic3r STATIC
     GCode/PrintExtents.hpp
     GCode/SpiralVase.cpp
     GCode/SpiralVase.hpp
+    GCode/SeamPlacer.cpp
+    GCode/SeamPlacer.hpp
     GCode/ToolOrdering.cpp
     GCode/ToolOrdering.hpp
     GCode/WipeTower.cpp
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 63dde6606..4da1edd7f 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -176,38 +176,6 @@ namespace Slic3r {
     }
 
 
-    void CustomSeam::get_indices(size_t layer_id,
-                                 const Polygon& polygon,
-                                 std::vector<size_t>& enforcers_idxs,
-                                 std::vector<size_t>& blockers_idxs) const
-    {
-        enforcers_idxs.clear();
-        blockers_idxs.clear();
-
-        // FIXME: This is quadratic and it should be improved, maybe by building
-        // an AABB tree (or at least utilize bounding boxes).
-        for (size_t i=0; i<polygon.points.size(); ++i) {
-
-            if (! enforcers.empty()) {
-                assert(layer_id < enforcers.size());
-                for (const ExPolygon& explg : enforcers[layer_id]) {
-                    if (explg.contains(polygon.points[i]))
-                        enforcers_idxs.push_back(i);
-                }
-            }
-
-            if (! blockers.empty()) {
-                assert(layer_id < blockers.size());
-                for (const ExPolygon& explg : blockers[layer_id]) {
-                    if (explg.contains(polygon.points[i]))
-                        blockers_idxs.push_back(i);
-                }
-            }
-        }
-    }
-
-
-
     std::string OozePrevention::pre_toolchange(GCode& gcodegen)
     {
         std::string gcode;
@@ -1017,22 +985,6 @@ namespace DoExport {
 	}
 
 
-    static void collect_custom_seam(const Print& print, CustomSeam& custom_seam)
-   {
-       custom_seam = CustomSeam();
-       for (const PrintObject* po : print.objects()) {
-           po->project_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, custom_seam.enforcers);
-           po->project_and_append_custom_facets(true, EnforcerBlockerType::BLOCKER, custom_seam.blockers);
-       }
-       const std::vector<double>& nozzle_dmrs = print.config().nozzle_diameter.values;
-       float max_nozzle_dmr = *std::max_element(nozzle_dmrs.begin(), nozzle_dmrs.end());
-       for (ExPolygons& explgs : custom_seam.enforcers)
-           explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr));
-       for (ExPolygons& explgs : custom_seam.blockers)
-           explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr));
-   }
-
-
     static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention)
 	{
 	    // Calculate wiping points if needed
@@ -1487,7 +1439,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
     print.throw_if_canceled();
 
     // Collect custom seam data from all objects.
-    DoExport::collect_custom_seam(print, m_custom_seam);
+    m_seam_placer.init(print);
 
     if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) {
         // Set initial extruder only after custom start G-code.
@@ -2602,293 +2554,7 @@ std::string GCode::change_layer(coordf_t print_z)
     return gcode;
 }
 
-// Return a value in <0, 1> of a cubic B-spline kernel centered around zero.
-// The B-spline is re-scaled so it has value 1 at zero.
-static inline float bspline_kernel(float x)
-{
-    x = std::abs(x);
-    if (x < 1.f) {
-        return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x;
-    }
-    else if (x < 2.f) {
-        x -= 1.f;
-        float x2 = x * x;
-        float x3 = x2 * x;
-        return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3;
-    }
-    else
-        return 0;
-}
 
-static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance)
-{
-    // The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve.
-    // Solved by sympy package:
-/*
-from sympy import *
-(x,a,b,c,d,r,z)=symbols('x a b c d r z')
-p = a + b*x + c*x*x + d*x*x*x
-p2 = p.subs(solve([p.subs(x, -r), p.diff(x).subs(x, -r), p.diff(x,x).subs(x, -r), p.subs(x, 0)-z], [a, b, c, d]))
-from sympy.plotting import plot
-plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400)
-*/
-    if (overlap_distance < - nozzle_r) {
-        // The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty.
-        return 0.f;
-    } else {
-        float x  = overlap_distance / nozzle_r;
-        float x2 = x * x;
-        float x3 = x2 * x;
-        return weight_zero * (1.f + 3.f * x + 3.f * x2 + x3);
-    }
-}
-
-static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps)
-{
-    assert(polygon.points.size() >= 2);
-    if (polygon.points.size() <= 1)
-    if (polygon.points.size() == 1)
-        return polygon.points.begin();
-
-    Point  pt_min;
-    double d_min = std::numeric_limits<double>::max();
-    size_t i_min = size_t(-1);
-
-    for (size_t i = 0; i < polygon.points.size(); ++ i) {
-        size_t j = i + 1;
-        if (j == polygon.points.size())
-            j = 0;
-        const Point &p1 = polygon.points[i];
-        const Point &p2 = polygon.points[j];
-        const Slic3r::Point v_seg = p2 - p1;
-        const Slic3r::Point v_pt  = pt - p1;
-        const int64_t l2_seg = int64_t(v_seg(0)) * int64_t(v_seg(0)) + int64_t(v_seg(1)) * int64_t(v_seg(1));
-        int64_t t_pt = int64_t(v_seg(0)) * int64_t(v_pt(0)) + int64_t(v_seg(1)) * int64_t(v_pt(1));
-        if (t_pt < 0) {
-            // Closest to p1.
-            double dabs = sqrt(int64_t(v_pt(0)) * int64_t(v_pt(0)) + int64_t(v_pt(1)) * int64_t(v_pt(1)));
-            if (dabs < d_min) {
-                d_min  = dabs;
-                i_min  = i;
-                pt_min = p1;
-            }
-        }
-        else if (t_pt > l2_seg) {
-            // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the next step.
-            continue;
-        } else {
-            // Closest to the segment.
-            assert(t_pt >= 0 && t_pt <= l2_seg);
-            int64_t d_seg = int64_t(v_seg(1)) * int64_t(v_pt(0)) - int64_t(v_seg(0)) * int64_t(v_pt(1));
-            double d = double(d_seg) / sqrt(double(l2_seg));
-            double dabs = std::abs(d);
-            if (dabs < d_min) {
-                d_min  = dabs;
-                i_min  = i;
-                // Evaluate the foot point.
-                pt_min = p1;
-                double linv = double(d_seg) / double(l2_seg);
-                pt_min(0) = pt(0) - coord_t(floor(double(v_seg(1)) * linv + 0.5));
-                pt_min(1) = pt(1) + coord_t(floor(double(v_seg(0)) * linv + 0.5));
-                assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5));
-            }
-        }
-    }
-
-    assert(i_min != size_t(-1));
-    if ((pt_min - polygon.points[i_min]).cast<double>().norm() > eps) {
-        // Insert a new point on the segment i_min, i_min+1.
-        return polygon.points.insert(polygon.points.begin() + (i_min + 1), pt_min);
-    }
-    return polygon.points.begin() + i_min;
-}
-
-
-
-std::vector<float> polygon_angles_at_vertices(const Polygon &polygon, const std::vector<float> &lengths, float min_arm_length)
-{
-    assert(polygon.points.size() + 1 == lengths.size());
-    if (min_arm_length > 0.25f * lengths.back())
-        min_arm_length = 0.25f * lengths.back();
-
-    // Find the initial prev / next point span.
-    size_t idx_prev = polygon.points.size();
-    size_t idx_curr = 0;
-    size_t idx_next = 1;
-    while (idx_prev > idx_curr && lengths.back() - lengths[idx_prev] < min_arm_length)
-        -- idx_prev;
-    while (idx_next < idx_prev && lengths[idx_next] < min_arm_length)
-        ++ idx_next;
-
-    std::vector<float> angles(polygon.points.size(), 0.f);
-    for (; idx_curr < polygon.points.size(); ++ idx_curr) {
-        // Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length.
-        if (idx_prev >= idx_curr) {
-            while (idx_prev < polygon.points.size() && lengths.back() - lengths[idx_prev] + lengths[idx_curr] > min_arm_length)
-                ++ idx_prev;
-            if (idx_prev == polygon.points.size())
-                idx_prev = 0;
-        }
-        while (idx_prev < idx_curr && lengths[idx_curr] - lengths[idx_prev] > min_arm_length)
-            ++ idx_prev;
-        // Move idx_prev one step back.
-        if (idx_prev == 0)
-            idx_prev = polygon.points.size() - 1;
-        else
-            -- idx_prev;
-        // Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length.
-        if (idx_curr <= idx_next) {
-            while (idx_next < polygon.points.size() && lengths[idx_next] - lengths[idx_curr] < min_arm_length)
-                ++ idx_next;
-            if (idx_next == polygon.points.size())
-                idx_next = 0;
-        }
-        while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length)
-            ++ idx_next;
-        // Calculate angle between idx_prev, idx_curr, idx_next.
-        const Point &p0 = polygon.points[idx_prev];
-        const Point &p1 = polygon.points[idx_curr];
-        const Point &p2 = polygon.points[idx_next];
-        const Point  v1 = p1 - p0;
-        const Point  v2 = p2 - p1;
-        int64_t dot   = int64_t(v1(0))*int64_t(v2(0)) + int64_t(v1(1))*int64_t(v2(1));
-        int64_t cross = int64_t(v1(0))*int64_t(v2(1)) - int64_t(v1(1))*int64_t(v2(0));
-        float angle = float(atan2(double(cross), double(dot)));
-        angles[idx_curr] = angle;
-    }
-
-    return angles;
-}
-
-
-
-
-// Go through the polygon, identify points inside support enforcers and return
-// indices of points in the middle of each enforcer (measured along the contour).
-static std::vector<size_t> find_enforcer_centers(const Polygon& polygon,
-                                                 const std::vector<float>& lengths,
-                                                 const std::vector<size_t>& enforcers_idxs)
-{
-    std::vector<size_t> out;
-    assert(polygon.points.size()+1 == lengths.size());
-    assert(std::is_sorted(enforcers_idxs.begin(), enforcers_idxs.end()));
-    if (polygon.size() < 2 || enforcers_idxs.empty())
-        return out;
-
-    auto get_center_idx = [&polygon, &lengths](size_t start_idx, size_t end_idx) -> size_t {
-        assert(end_idx >= start_idx);
-        if (start_idx == end_idx)
-            return start_idx;
-        float t_c = lengths[start_idx] + 0.5f * (lengths[end_idx] - lengths[start_idx]);
-        auto it = std::lower_bound(lengths.begin() + start_idx, lengths.begin() + end_idx, t_c);
-        int ret = it - lengths.begin();
-        return ret;
-    };
-
-    int last_enforcer_start_idx = enforcers_idxs.front();
-    bool last_pt_in_list = enforcers_idxs.back() == polygon.points.size() - 1;
-
-    for (size_t i=0; i<enforcers_idxs.size()-1; ++i) {
-        if ((i == enforcers_idxs.size() - 1)
-         || enforcers_idxs[i+1] != enforcers_idxs[i] + 1) {
-            // i is last point of current enforcer
-            out.push_back(get_center_idx(last_enforcer_start_idx, enforcers_idxs[i]));
-            last_enforcer_start_idx = enforcers_idxs[i+1];
-        }
-    }
-
-    if (last_pt_in_list) {
-        // last point is an enforcer - not yet accounted for.
-        if (enforcers_idxs.front() != 0) {
-            size_t center_idx = get_center_idx(last_enforcer_start_idx, enforcers_idxs.back());
-            out.push_back(center_idx);
-        } else {
-            // Wrap-around. Update first center already found.
-            if (out.empty()) {
-                // Probably an enforcer around the whole contour. Return nothing.
-                return out;
-            }
-
-            // find last point of the enforcer at the beginning:
-            size_t idx = 0;
-            while (enforcers_idxs[idx]+1 == enforcers_idxs[idx+1])
-                ++idx;
-
-            float t_s = lengths[last_enforcer_start_idx];
-            float t_e = lengths[idx];
-            float half_dist = 0.5f * (t_e + lengths.back() - t_s);
-            float t_c = (half_dist > t_e) ? t_s + half_dist : t_e - half_dist;
-
-            auto it = std::lower_bound(lengths.begin(), lengths.end(), t_c);
-            out[0] = it - lengths.begin();
-            if (out[0] == lengths.size() - 1)
-                --out[0];
-            assert(out[0] < lengths.size() - 1);
-        }
-    }
-    return out;
-}
-
-
-void CustomSeam::penalize_polygon(const Polygon& polygon,
-                                  std::vector<float>& penalties,
-                                  const std::vector<float>& lengths,
-                                  int layer_id) const
-{
-    std::vector<size_t> enforcers_idxs;
-    std::vector<size_t> blockers_idxs;
-    this->get_indices(layer_id, polygon, enforcers_idxs, blockers_idxs);
-
-    for (size_t i : enforcers_idxs) {
-        assert(i < penalties.size());
-        penalties[i] -= float(ENFORCER_BLOCKER_PENALTY);
-    }
-    for (size_t i : blockers_idxs) {
-        assert(i < penalties.size());
-        penalties[i] += float(ENFORCER_BLOCKER_PENALTY);
-    }
-    std::vector<size_t> enf_centers = find_enforcer_centers(polygon, lengths, enforcers_idxs);
-    for (size_t idx : enf_centers) {
-        assert(idx < penalties.size());
-        penalties[idx] -= 1000.f;
-    }
-
-//    //////////////////////
-//            std::ostringstream os;
-//            os << std::setw(3) << std::setfill('0') << layer_id;
-//            int a = scale_(15.);
-//            SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a)));
-//            /*if (! m_custom_seam.enforcers.empty())
-//                svg.draw(m_custom_seam.enforcers[layer_id], "blue");
-//            if (! m_custom_seam.blockers.empty())
-//                svg.draw(m_custom_seam.blockers[layer_id], "red");*/
-
-//            size_t min_idx = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
-
-//            //svg.draw(polygon.points[idx_min], "red", 6e5);
-//            for (size_t i=0; i<polygon.points.size(); ++i) {
-//                std::string fill;
-//                coord_t size = 0;
-//                if (min_idx == i) {
-//                    fill = "yellow";
-//                    size = 5e5;
-//                } else {
-//                    fill = (std::find(enforcers_idxs.begin(), enforcers_idxs.end(), i) != enforcers_idxs.end() ? "green" : "black");
-//                    if (std::find(enf_centers.begin(), enf_centers.end(), i) != enf_centers.end()) {
-//                        size = 5e5;
-//                        fill = "blue";
-//                    }
-//                }
-//                if (i != 0)
-//                    svg.draw(polygon.points[i], fill, size);
-//                else
-//                    svg.draw(polygon.points[i], "red", 5e5);
-//            }
-//    ////////////////////
-
-
-
-}
 
 std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid)
 {
@@ -2928,167 +2594,18 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
     Point last_pos = this->last_pos();
     if (m_config.spiral_vase) {
         loop.split_at(last_pos, false);
-    } else if (seam_position == spNearest || seam_position == spAligned || seam_position == spRear) {
-        Polygon        polygon    = loop.polygon();
-        const coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter);
-        const coord_t  nozzle_r   = coord_t(scale_(0.5 * nozzle_dmr) + 0.5);
-
-        if (m_custom_seam.is_on_layer(m_layer->id())) {
-            // Seam enf/blockers can begin and end in between the original vertices.
-            // Let add extra points in between and update the leghths.
-            polygon.densify(scale_(0.2f));
-        }
-
-        // Retrieve the last start position for this object.
-        float last_pos_weight = 1.f;
-
-        if (seam_position == spAligned) {
-            // Seam is aligned to the seam at the preceding layer.
-            if (m_layer != NULL && m_seam_position.count(m_layer->object()) > 0) {
-                last_pos = m_seam_position[m_layer->object()];
-                last_pos_weight = 1.f;
-            }
-        }
-        else if (seam_position == spRear) {
-            // Object is centered around (0,0) in its current coordinate system.
-            last_pos.x() = 0;
-            last_pos.y() += coord_t(3. * m_layer->object()->bounding_box().radius());
-            last_pos_weight = 5.f;
-        }
-
-        // Insert a projection of last_pos into the polygon.
-        size_t last_pos_proj_idx;
-        {
-            auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r);
-            last_pos_proj_idx = it - polygon.points.begin();
-        }
-
-        // Parametrize the polygon by its length.
-        std::vector<float> lengths = polygon.parameter_by_length();
-
-        // For each polygon point, store a penalty.
-        // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r.
-        std::vector<float> penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r));
-        // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces.
-        const float penaltyConvexVertex = 1.f;
-        const float penaltyFlatSurface  = 5.f;
-        const float penaltyOverhangHalf = 10.f;
-        // Penalty for visible seams.
-        for (size_t i = 0; i < polygon.points.size(); ++ i) {
-            float ccwAngle = penalties[i];
-            if (was_clockwise)
-                ccwAngle = - ccwAngle;
-            float penalty = 0;
-            if (ccwAngle <- float(0.6 * PI))
-                // Sharp reflex vertex. We love that, it hides the seam perfectly.
-                penalty = 0.f;
-            else if (ccwAngle > float(0.6 * PI))
-                // Seams on sharp convex vertices are more visible than on reflex vertices.
-                penalty = penaltyConvexVertex;
-            else if (ccwAngle < 0.f) {
-                // Interpolate penalty between maximum and zero.
-                penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
-            } else {
-                assert(ccwAngle >= 0.f);
-                // Interpolate penalty between maximum and the penalty for a convex vertex.
-                penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
-            }
-            // Give a negative penalty for points close to the last point or the prefered seam location.
-            float dist_to_last_pos_proj = (i < last_pos_proj_idx) ?
-                std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) :
-                std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]);
-            float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr
-            penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max);
-            penalties[i] = std::max(0.f, penalty);
-        }
-
-        // Penalty for overhangs.
-        if (lower_layer_edge_grid && (*lower_layer_edge_grid)) {
-            // Use the edge grid distance field structure over the lower layer to calculate overhangs.
-            coord_t nozzle_r = coord_t(std::floor(scale_(0.5 * nozzle_dmr) + 0.5));
-            coord_t search_r = coord_t(std::floor(scale_(0.8 * nozzle_dmr) + 0.5));
-            for (size_t i = 0; i < polygon.points.size(); ++ i) {
-                const Point &p = polygon.points[i];
-                coordf_t dist;
-                // Signed distance is positive outside the object, negative inside the object.
-                // The point is considered at an overhang, if it is more than nozzle radius
-                // outside of the lower layer contour.
-                [[maybe_unused]] bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist);
-                // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid,
-                // then the signed distnace shall always be known.
-                assert(found);
-                penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist));
-            }
-        }
-
-        // Penalty according to custom seam selection. This one is huge compared to
-        // the others so that points outside enforcers/inside blockers never win.
-        m_custom_seam.penalize_polygon(polygon, penalties, lengths, m_layer->id());
-
-        // Find a point with a minimum penalty.
-        size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
-
-        // For all (aligned, nearest, rear) seams:
-        {
-            // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx.
-            // In that case use last_pos_proj_idx instead.
-            float penalty_aligned  = penalties[last_pos_proj_idx];
-            float penalty_min      = penalties[idx_min];
-            float penalty_diff_abs = std::abs(penalty_min - penalty_aligned);
-            float penalty_max      = std::max(penalty_min, penalty_aligned);
-            float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max;
-            // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel);
-            if (std::abs(penalty_diff_rel) < 0.05) {
-                // Penalty of the aligned point is very close to the minimum penalty.
-                // Align the seams as accurately as possible.
-                idx_min = last_pos_proj_idx;
-            }
-            m_seam_position[m_layer->object()] = polygon.points[idx_min];
-        }
-
-
-        // Export the contour into a SVG file.
-        #if 0
-        {
-            static int iRun = 0;
-            SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++));
-            if (m_layer->lower_layer != NULL)
-                svg.draw(m_layer->lower_layer->slices);
-            for (size_t i = 0; i < loop.paths.size(); ++ i)
-                svg.draw(loop.paths[i].as_polyline(), "red");
-            Polylines polylines;
-            for (size_t i = 0; i < loop.paths.size(); ++ i)
-                polylines.push_back(loop.paths[i].as_polyline());
-            Slic3r::Polygons polygons;
-            coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter);
-            coord_t delta = scale_(0.5*nozzle_dmr);
-            Slic3r::offset(polylines, &polygons, delta);
-//            for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue");
-            svg.draw(last_pos, "green", 3);
-            svg.draw(polygon.points[idx_min], "yellow", 3);
-            svg.Close();
-        }
-        #endif
-
+    } else {
+        const EdgeGrid::Grid* edge_grid_ptr = (lower_layer_edge_grid && *lower_layer_edge_grid)
+                                                ? lower_layer_edge_grid->get()
+                                                : nullptr;
+        Point seam = m_seam_placer.get_seam(m_layer->id(), seam_position, loop,
+                         last_pos, EXTRUDER_CONFIG(nozzle_diameter),
+                         (m_layer == NULL ? nullptr : m_layer->object()),
+                         was_clockwise, edge_grid_ptr);
         // Split the loop at the point with a minium penalty.
-        if (!loop.split_at_vertex(polygon.points[idx_min]))
+        if (!loop.split_at_vertex(seam))
             // The point is not in the original loop. Insert it.
-            loop.split_at(polygon.points[idx_min], true);
-
-    } else if (seam_position == spRandom) {
-        if (loop.loop_role() == elrContourInternalPerimeter) {
-            // This loop does not contain any other loop. Set a random position.
-            // The other loops will get a seam close to the random point chosen
-            // on the inner most contour.
-            //FIXME This works correctly for inner contours first only.
-            //FIXME Better parametrize the loop by its length.
-            Polygon polygon = loop.polygon();
-            Point centroid = polygon.centroid();
-            last_pos = Point(polygon.bounding_box().max(0), centroid(1));
-            last_pos.rotate(fmod((float)rand()/16.0, 2.0*PI), centroid);
-        }
-        // Find the closest point, avoid overhangs.
-        loop.split_at(last_pos, true);
+            loop.split_at(seam, true);
     }
 
     // clip the path to avoid the extruder to get exactly on the first point of the loop;
diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp
index 43f338235..01650b6ee 100644
--- a/src/libslic3r/GCode.hpp
+++ b/src/libslic3r/GCode.hpp
@@ -13,6 +13,7 @@
 #include "GCode/SpiralVase.hpp"
 #include "GCode/ToolOrdering.hpp"
 #include "GCode/WipeTower.hpp"
+#include "GCode/SeamPlacer.hpp"
 #if ENABLE_GCODE_VIEWER
 #include "GCode/GCodeProcessor.hpp"
 #else
@@ -70,27 +71,6 @@ private:
 };
 
 
-struct CustomSeam {
-    std::vector<ExPolygons> enforcers;
-    std::vector<ExPolygons> blockers;
-
-    // Get indices of points inside enforcers and blockers.
-    void get_indices(size_t layer_id,
-                    const Polygon& polygon,
-                    std::vector<size_t>& enforcers_idxs,
-                    std::vector<size_t>& blockers_idxs) const;
-    bool is_on_layer(size_t layer_id) const {
-        return ! ((enforcers.empty() || enforcers[layer_id].empty())
-                && (blockers.empty() || blockers[layer_id].empty()));
-    }
-    void penalize_polygon(const Polygon& polygon,
-                          std::vector<float>& penalties,
-                          const std::vector<float>& lengths,
-                          int layer_id) const;
-
-    static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6;
-};
-
 class OozePrevention {
 public:
     bool enable;
@@ -361,7 +341,7 @@ private:
     std::string     set_extruder(unsigned int extruder_id, double print_z);
 
     // Cache for custom seam enforcers/blockers for each layer.
-    CustomSeam                          m_custom_seam;
+    SeamPlacer                          m_seam_placer;
 
     /* Origin of print coordinates expressed in unscaled G-code coordinates.
        This affects the input arguments supplied to the extrude*() and travel_to()
@@ -401,7 +381,6 @@ private:
     // Current layer processed. Insequential printing mode, only a single copy will be printed.
     // In non-sequential mode, all its copies will be printed.
     const Layer*                        m_layer;
-    std::map<const PrintObject*,Point>  m_seam_position;
     double                              m_volumetric_speed;
     // Support for the extrusion role markers. Which marker is active?
     ExtrusionRole                       m_last_extrusion_role;
diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp
new file mode 100644
index 000000000..da5289fe2
--- /dev/null
+++ b/src/libslic3r/GCode/SeamPlacer.cpp
@@ -0,0 +1,520 @@
+#include "SeamPlacer.hpp"
+
+#include "libslic3r/ExtrusionEntity.hpp"
+#include "libslic3r/Print.hpp"
+#include "libslic3r/BoundingBox.hpp"
+#include "libslic3r/EdgeGrid.hpp"
+#include "libslic3r/ClipperUtils.hpp"
+
+namespace Slic3r {
+
+
+
+static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance)
+{
+    // The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve.
+    // Solved by sympy package:
+/*
+from sympy import *
+(x,a,b,c,d,r,z)=symbols('x a b c d r z')
+p = a + b*x + c*x*x + d*x*x*x
+p2 = p.subs(solve([p.subs(x, -r), p.diff(x).subs(x, -r), p.diff(x,x).subs(x, -r), p.subs(x, 0)-z], [a, b, c, d]))
+from sympy.plotting import plot
+plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400)
+*/
+    if (overlap_distance < - nozzle_r) {
+        // The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty.
+        return 0.f;
+    } else {
+        float x  = overlap_distance / nozzle_r;
+        float x2 = x * x;
+        float x3 = x2 * x;
+        return weight_zero * (1.f + 3.f * x + 3.f * x2 + x3);
+    }
+}
+
+
+
+// Return a value in <0, 1> of a cubic B-spline kernel centered around zero.
+// The B-spline is re-scaled so it has value 1 at zero.
+static inline float bspline_kernel(float x)
+{
+    x = std::abs(x);
+    if (x < 1.f) {
+        return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x;
+    }
+    else if (x < 2.f) {
+        x -= 1.f;
+        float x2 = x * x;
+        float x3 = x2 * x;
+        return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3;
+    }
+    else
+        return 0;
+}
+
+
+
+static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps)
+{
+    assert(polygon.points.size() >= 2);
+    if (polygon.points.size() <= 1)
+    if (polygon.points.size() == 1)
+        return polygon.points.begin();
+
+    Point  pt_min;
+    double d_min = std::numeric_limits<double>::max();
+    size_t i_min = size_t(-1);
+
+    for (size_t i = 0; i < polygon.points.size(); ++ i) {
+        size_t j = i + 1;
+        if (j == polygon.points.size())
+            j = 0;
+        const Point &p1 = polygon.points[i];
+        const Point &p2 = polygon.points[j];
+        const Slic3r::Point v_seg = p2 - p1;
+        const Slic3r::Point v_pt  = pt - p1;
+        const int64_t l2_seg = int64_t(v_seg(0)) * int64_t(v_seg(0)) + int64_t(v_seg(1)) * int64_t(v_seg(1));
+        int64_t t_pt = int64_t(v_seg(0)) * int64_t(v_pt(0)) + int64_t(v_seg(1)) * int64_t(v_pt(1));
+        if (t_pt < 0) {
+            // Closest to p1.
+            double dabs = sqrt(int64_t(v_pt(0)) * int64_t(v_pt(0)) + int64_t(v_pt(1)) * int64_t(v_pt(1)));
+            if (dabs < d_min) {
+                d_min  = dabs;
+                i_min  = i;
+                pt_min = p1;
+            }
+        }
+        else if (t_pt > l2_seg) {
+            // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the next step.
+            continue;
+        } else {
+            // Closest to the segment.
+            assert(t_pt >= 0 && t_pt <= l2_seg);
+            int64_t d_seg = int64_t(v_seg(1)) * int64_t(v_pt(0)) - int64_t(v_seg(0)) * int64_t(v_pt(1));
+            double d = double(d_seg) / sqrt(double(l2_seg));
+            double dabs = std::abs(d);
+            if (dabs < d_min) {
+                d_min  = dabs;
+                i_min  = i;
+                // Evaluate the foot point.
+                pt_min = p1;
+                double linv = double(d_seg) / double(l2_seg);
+                pt_min(0) = pt(0) - coord_t(floor(double(v_seg(1)) * linv + 0.5));
+                pt_min(1) = pt(1) + coord_t(floor(double(v_seg(0)) * linv + 0.5));
+                assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5));
+            }
+        }
+    }
+
+    assert(i_min != size_t(-1));
+    if ((pt_min - polygon.points[i_min]).cast<double>().norm() > eps) {
+        // Insert a new point on the segment i_min, i_min+1.
+        return polygon.points.insert(polygon.points.begin() + (i_min + 1), pt_min);
+    }
+    return polygon.points.begin() + i_min;
+}
+
+
+
+static std::vector<float> polygon_angles_at_vertices(const Polygon &polygon, const std::vector<float> &lengths, float min_arm_length)
+{
+    assert(polygon.points.size() + 1 == lengths.size());
+    if (min_arm_length > 0.25f * lengths.back())
+        min_arm_length = 0.25f * lengths.back();
+
+    // Find the initial prev / next point span.
+    size_t idx_prev = polygon.points.size();
+    size_t idx_curr = 0;
+    size_t idx_next = 1;
+    while (idx_prev > idx_curr && lengths.back() - lengths[idx_prev] < min_arm_length)
+        -- idx_prev;
+    while (idx_next < idx_prev && lengths[idx_next] < min_arm_length)
+        ++ idx_next;
+
+    std::vector<float> angles(polygon.points.size(), 0.f);
+    for (; idx_curr < polygon.points.size(); ++ idx_curr) {
+        // Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length.
+        if (idx_prev >= idx_curr) {
+            while (idx_prev < polygon.points.size() && lengths.back() - lengths[idx_prev] + lengths[idx_curr] > min_arm_length)
+                ++ idx_prev;
+            if (idx_prev == polygon.points.size())
+                idx_prev = 0;
+        }
+        while (idx_prev < idx_curr && lengths[idx_curr] - lengths[idx_prev] > min_arm_length)
+            ++ idx_prev;
+        // Move idx_prev one step back.
+        if (idx_prev == 0)
+            idx_prev = polygon.points.size() - 1;
+        else
+            -- idx_prev;
+        // Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length.
+        if (idx_curr <= idx_next) {
+            while (idx_next < polygon.points.size() && lengths[idx_next] - lengths[idx_curr] < min_arm_length)
+                ++ idx_next;
+            if (idx_next == polygon.points.size())
+                idx_next = 0;
+        }
+        while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length)
+            ++ idx_next;
+        // Calculate angle between idx_prev, idx_curr, idx_next.
+        const Point &p0 = polygon.points[idx_prev];
+        const Point &p1 = polygon.points[idx_curr];
+        const Point &p2 = polygon.points[idx_next];
+        const Point  v1 = p1 - p0;
+        const Point  v2 = p2 - p1;
+        int64_t dot   = int64_t(v1(0))*int64_t(v2(0)) + int64_t(v1(1))*int64_t(v2(1));
+        int64_t cross = int64_t(v1(0))*int64_t(v2(1)) - int64_t(v1(1))*int64_t(v2(0));
+        float angle = float(atan2(double(cross), double(dot)));
+        angles[idx_curr] = angle;
+    }
+
+    return angles;
+}
+
+
+
+void SeamPlacer::init(const Print& print)
+{
+    m_enforcers.clear();
+    m_blockers.clear();
+    m_last_seam_position.clear();
+
+   for (const PrintObject* po : print.objects()) {
+       po->project_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, m_enforcers);
+       po->project_and_append_custom_facets(true, EnforcerBlockerType::BLOCKER, m_blockers);
+   }
+   const std::vector<double>& nozzle_dmrs = print.config().nozzle_diameter.values;
+   float max_nozzle_dmr = *std::max_element(nozzle_dmrs.begin(), nozzle_dmrs.end());
+   for (ExPolygons& explgs : m_enforcers)
+       explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr));
+   for (ExPolygons& explgs : m_blockers)
+       explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr));
+}
+
+
+
+Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_position,
+               const ExtrusionLoop& loop, Point last_pos, coordf_t nozzle_dmr,
+               const PrintObject* po, bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid)
+{
+    if (seam_position == spNearest || seam_position == spAligned || seam_position == spRear) {
+            Polygon        polygon    = loop.polygon();
+            const coord_t  nozzle_r   = coord_t(scale_(0.5 * nozzle_dmr) + 0.5);
+
+            if (this->is_custom(layer_idx)) {
+                // Seam enf/blockers can begin and end in between the original vertices.
+                // Let add extra points in between and update the leghths.
+                polygon.densify(scale_(0.2f));
+            }
+
+            // Retrieve the last start position for this object.
+            float last_pos_weight = 1.f;
+
+            if (seam_position == spAligned) {
+                // Seam is aligned to the seam at the preceding layer.
+                if (po != nullptr && m_last_seam_position.count(po) > 0) {
+                    last_pos = m_last_seam_position[po];
+                    last_pos_weight = 1.f;
+                }
+            }
+            else if (seam_position == spRear) {
+                // Object is centered around (0,0) in its current coordinate system.
+                last_pos.x() = 0;
+                last_pos.y() += coord_t(3. * po->bounding_box().radius());
+                last_pos_weight = 5.f;
+            }
+
+            // Insert a projection of last_pos into the polygon.
+            size_t last_pos_proj_idx;
+            {
+                auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r);
+                last_pos_proj_idx = it - polygon.points.begin();
+            }
+
+            // Parametrize the polygon by its length.
+            std::vector<float> lengths = polygon.parameter_by_length();
+
+            // For each polygon point, store a penalty.
+            // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r.
+            std::vector<float> penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r));
+            // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces.
+            const float penaltyConvexVertex = 1.f;
+            const float penaltyFlatSurface  = 5.f;
+            const float penaltyOverhangHalf = 10.f;
+            // Penalty for visible seams.
+           for (size_t i = 0; i < polygon.points.size(); ++ i) {
+                float ccwAngle = penalties[i];
+                if (was_clockwise)
+                    ccwAngle = - ccwAngle;
+                float penalty = 0;
+                if (ccwAngle <- float(0.6 * PI))
+                    // Sharp reflex vertex. We love that, it hides the seam perfectly.
+                    penalty = 0.f;
+                else if (ccwAngle > float(0.6 * PI))
+                    // Seams on sharp convex vertices are more visible than on reflex vertices.
+                    penalty = penaltyConvexVertex;
+                else if (ccwAngle < 0.f) {
+                    // Interpolate penalty between maximum and zero.
+                    penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
+                } else {
+                    assert(ccwAngle >= 0.f);
+                    // Interpolate penalty between maximum and the penalty for a convex vertex.
+                    penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
+                }
+                // Give a negative penalty for points close to the last point or the prefered seam location.
+                float dist_to_last_pos_proj = (i < last_pos_proj_idx) ?
+                    std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) :
+                    std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]);
+                float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr
+                penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max);
+                penalties[i] = std::max(0.f, penalty);
+            }
+
+            // Penalty for overhangs.
+            if (lower_layer_edge_grid) {
+                // Use the edge grid distance field structure over the lower layer to calculate overhangs.
+                coord_t nozzle_r = coord_t(std::floor(scale_(0.5 * nozzle_dmr) + 0.5));
+                coord_t search_r = coord_t(std::floor(scale_(0.8 * nozzle_dmr) + 0.5));
+                for (size_t i = 0; i < polygon.points.size(); ++ i) {
+                    const Point &p = polygon.points[i];
+                    coordf_t dist;
+                    // Signed distance is positive outside the object, negative inside the object.
+                    // The point is considered at an overhang, if it is more than nozzle radius
+                    // outside of the lower layer contour.
+                    [[maybe_unused]] bool found = lower_layer_edge_grid->signed_distance(p, search_r, dist);
+                    // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid,
+                    // then the signed distnace shall always be known.
+                    assert(found);
+                    penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist));
+                }
+            }
+
+            // Penalty according to custom seam selection. This one is huge compared to
+            // the others so that points outside enforcers/inside blockers never win.
+            this->penalize_polygon(polygon, penalties, lengths, layer_idx);
+
+            // Find a point with a minimum penalty.
+            size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
+
+            // For all (aligned, nearest, rear) seams:
+            {
+                // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx.
+                // In that case use last_pos_proj_idx instead.
+                float penalty_aligned  = penalties[last_pos_proj_idx];
+                float penalty_min      = penalties[idx_min];
+                float penalty_diff_abs = std::abs(penalty_min - penalty_aligned);
+                float penalty_max      = std::max(penalty_min, penalty_aligned);
+                float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max;
+                // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel);
+                if (std::abs(penalty_diff_rel) < 0.05) {
+                    // Penalty of the aligned point is very close to the minimum penalty.
+                    // Align the seams as accurately as possible.
+                    idx_min = last_pos_proj_idx;
+                }
+                m_last_seam_position[po] = polygon.points[idx_min];
+            }
+
+
+            // Export the contour into a SVG file.
+            #if 0
+            {
+                static int iRun = 0;
+                SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++));
+                if (m_layer->lower_layer != NULL)
+                    svg.draw(m_layer->lower_layer->slices);
+                for (size_t i = 0; i < loop.paths.size(); ++ i)
+                    svg.draw(loop.paths[i].as_polyline(), "red");
+                Polylines polylines;
+                for (size_t i = 0; i < loop.paths.size(); ++ i)
+                    polylines.push_back(loop.paths[i].as_polyline());
+                Slic3r::Polygons polygons;
+                coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter);
+                coord_t delta = scale_(0.5*nozzle_dmr);
+                Slic3r::offset(polylines, &polygons, delta);
+    //            for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue");
+                svg.draw(last_pos, "green", 3);
+                svg.draw(polygon.points[idx_min], "yellow", 3);
+                svg.Close();
+            }
+            #endif
+            return polygon.points[idx_min];
+
+        } else { // spRandom
+            if (loop.loop_role() == elrContourInternalPerimeter) {
+                // This loop does not contain any other loop. Set a random position.
+                // The other loops will get a seam close to the random point chosen
+                // on the inner most contour.
+                //FIXME This works correctly for inner contours first only.
+                //FIXME Better parametrize the loop by its length.
+                Polygon polygon = loop.polygon();
+                Point centroid = polygon.centroid();
+                last_pos = Point(polygon.bounding_box().max(0), centroid(1));
+                last_pos.rotate(fmod((float)rand()/16.0, 2.0*PI), centroid);
+            }
+            return last_pos;
+        }
+}
+
+
+
+
+void SeamPlacer::get_indices(size_t layer_id,
+                             const Polygon& polygon,
+                             std::vector<size_t>& enforcers_idxs,
+                             std::vector<size_t>& blockers_idxs) const
+{
+    enforcers_idxs.clear();
+    blockers_idxs.clear();
+
+    // FIXME: This is quadratic and it should be improved, maybe by building
+    // an AABB tree (or at least utilize bounding boxes).
+    for (size_t i=0; i<polygon.points.size(); ++i) {
+
+        if (! m_enforcers.empty()) {
+            assert(layer_id < m_enforcers.size());
+            for (const ExPolygon& explg : m_enforcers[layer_id]) {
+                if (explg.contains(polygon.points[i]))
+                    enforcers_idxs.push_back(i);
+            }
+        }
+
+        if (! m_blockers.empty()) {
+            assert(layer_id < m_blockers.size());
+            for (const ExPolygon& explg : m_blockers[layer_id]) {
+                if (explg.contains(polygon.points[i]))
+                    blockers_idxs.push_back(i);
+            }
+        }
+    }
+}
+
+
+// Go through the polygon, identify points inside support enforcers and return
+// indices of points in the middle of each enforcer (measured along the contour).
+static std::vector<size_t> find_enforcer_centers(const Polygon& polygon,
+                                                 const std::vector<float>& lengths,
+                                                 const std::vector<size_t>& enforcers_idxs)
+{
+    std::vector<size_t> out;
+    assert(polygon.points.size()+1 == lengths.size());
+    assert(std::is_sorted(enforcers_idxs.begin(), enforcers_idxs.end()));
+    if (polygon.size() < 2 || enforcers_idxs.empty())
+        return out;
+
+    auto get_center_idx = [&polygon, &lengths](size_t start_idx, size_t end_idx) -> size_t {
+        assert(end_idx >= start_idx);
+        if (start_idx == end_idx)
+            return start_idx;
+        float t_c = lengths[start_idx] + 0.5f * (lengths[end_idx] - lengths[start_idx]);
+        auto it = std::lower_bound(lengths.begin() + start_idx, lengths.begin() + end_idx, t_c);
+        int ret = it - lengths.begin();
+        return ret;
+    };
+
+    int last_enforcer_start_idx = enforcers_idxs.front();
+    bool last_pt_in_list = enforcers_idxs.back() == polygon.points.size() - 1;
+
+    for (size_t i=0; i<enforcers_idxs.size()-1; ++i) {
+        if ((i == enforcers_idxs.size() - 1)
+         || enforcers_idxs[i+1] != enforcers_idxs[i] + 1) {
+            // i is last point of current enforcer
+            out.push_back(get_center_idx(last_enforcer_start_idx, enforcers_idxs[i]));
+            last_enforcer_start_idx = enforcers_idxs[i+1];
+        }
+    }
+
+    if (last_pt_in_list) {
+        // last point is an enforcer - not yet accounted for.
+        if (enforcers_idxs.front() != 0) {
+            size_t center_idx = get_center_idx(last_enforcer_start_idx, enforcers_idxs.back());
+            out.push_back(center_idx);
+        } else {
+            // Wrap-around. Update first center already found.
+            if (out.empty()) {
+                // Probably an enforcer around the whole contour. Return nothing.
+                return out;
+            }
+
+            // find last point of the enforcer at the beginning:
+            size_t idx = 0;
+            while (enforcers_idxs[idx]+1 == enforcers_idxs[idx+1])
+                ++idx;
+
+            float t_s = lengths[last_enforcer_start_idx];
+            float t_e = lengths[idx];
+            float half_dist = 0.5f * (t_e + lengths.back() - t_s);
+            float t_c = (half_dist > t_e) ? t_s + half_dist : t_e - half_dist;
+
+            auto it = std::lower_bound(lengths.begin(), lengths.end(), t_c);
+            out[0] = it - lengths.begin();
+            if (out[0] == lengths.size() - 1)
+                --out[0];
+            assert(out[0] < lengths.size() - 1);
+        }
+    }
+    return out;
+}
+
+
+
+void SeamPlacer::penalize_polygon(const Polygon& polygon,
+                                  std::vector<float>& penalties,
+                                  const std::vector<float>& lengths,
+                                  int layer_id) const
+{
+    std::vector<size_t> enforcers_idxs;
+    std::vector<size_t> blockers_idxs;
+    this->get_indices(layer_id, polygon, enforcers_idxs, blockers_idxs);
+
+    for (size_t i : enforcers_idxs) {
+        assert(i < penalties.size());
+        penalties[i] -= float(ENFORCER_BLOCKER_PENALTY);
+    }
+    for (size_t i : blockers_idxs) {
+        assert(i < penalties.size());
+        penalties[i] += float(ENFORCER_BLOCKER_PENALTY);
+    }
+    std::vector<size_t> enf_centers = find_enforcer_centers(polygon, lengths, enforcers_idxs);
+    for (size_t idx : enf_centers) {
+        assert(idx < penalties.size());
+        penalties[idx] -= 1000.f;
+    }
+
+//    //////////////////////
+//            std::ostringstream os;
+//            os << std::setw(3) << std::setfill('0') << layer_id;
+//            int a = scale_(20.);
+//            SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a)));
+//            /*if (! m_enforcers.empty())
+//                svg.draw(m_enforcers[layer_id], "blue");
+//            if (! m_blockers.empty())
+//                svg.draw(m_blockers[layer_id], "red");*/
+
+//            size_t min_idx = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
+
+//            //svg.draw(polygon.points[idx_min], "red", 6e5);
+//            for (size_t i=0; i<polygon.points.size(); ++i) {
+//                std::string fill;
+//                coord_t size = 0;
+//                if (min_idx == i) {
+//                    fill = "yellow";
+//                    size = 5e5;
+//                } else {
+//                    fill = (std::find(enforcers_idxs.begin(), enforcers_idxs.end(), i) != enforcers_idxs.end() ? "green" : "black");
+//                    if (std::find(enf_centers.begin(), enf_centers.end(), i) != enf_centers.end()) {
+//                        size = 5e5;
+//                        fill = "blue";
+//                    }
+//                }
+//                if (i != 0)
+//                    svg.draw(polygon.points[i], fill, size);
+//                else
+//                    svg.draw(polygon.points[i], "red", 5e5);
+//            }
+//    ////////////////////
+
+}
+
+
+}
diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp
new file mode 100644
index 000000000..af12dc9fb
--- /dev/null
+++ b/src/libslic3r/GCode/SeamPlacer.hpp
@@ -0,0 +1,51 @@
+#ifndef libslic3r_SeamPlacer_hpp_
+#define libslic3r_SeamPlacer_hpp_
+
+#include "libslic3r/ExPolygon.hpp"
+#include "libslic3r/PrintConfig.hpp"
+
+namespace Slic3r {
+
+class PrintObject;
+class ExtrusionLoop;
+class Print;
+namespace EdgeGrid { class Grid; }
+
+class SeamPlacer {
+public:
+    void init(const Print& print);
+
+    bool is_custom(size_t layer_id) const {
+        return ! ((m_enforcers.empty() || m_enforcers[layer_id].empty())
+                && (m_blockers.empty() || m_blockers[layer_id].empty()));
+    }
+
+    Point get_seam(const size_t layer_idx, const SeamPosition seam_position,
+                   const ExtrusionLoop& loop, Point last_pos,
+                   coordf_t nozzle_diameter, const PrintObject* po,
+                   bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid);
+
+private:
+    std::vector<ExPolygons> m_enforcers;
+    std::vector<ExPolygons> m_blockers;
+
+    std::map<const PrintObject*, Point>  m_last_seam_position;
+
+    // Get indices of points inside enforcers and blockers.
+    void get_indices(size_t layer_id,
+                    const Polygon& polygon,
+                    std::vector<size_t>& enforcers_idxs,
+                    std::vector<size_t>& blockers_idxs) const;
+
+    void penalize_polygon(const Polygon& polygon,
+                          std::vector<float>& penalties,
+                          const std::vector<float>& lengths,
+                          int layer_id) const;
+
+    static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6;
+};
+
+
+}
+
+#endif // libslic3r_SeamPlacer_hpp_

From 5d6bf3261e85642611a1820240a62127965cf395 Mon Sep 17 00:00:00 2001
From: Lukas Matena <lukasmatena@seznam.cz>
Date: Fri, 11 Sep 2020 08:58:59 +0200
Subject: [PATCH 5/9] fixed center-finding algorithm

---
 src/libslic3r/GCode/SeamPlacer.cpp | 127 +++++++++++++++--------------
 1 file changed, 65 insertions(+), 62 deletions(-)

diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp
index da5289fe2..0e3a99dc7 100644
--- a/src/libslic3r/GCode/SeamPlacer.cpp
+++ b/src/libslic3r/GCode/SeamPlacer.cpp
@@ -5,6 +5,7 @@
 #include "libslic3r/BoundingBox.hpp"
 #include "libslic3r/EdgeGrid.hpp"
 #include "libslic3r/ClipperUtils.hpp"
+#include "libslic3r/SVG.hpp"
 
 namespace Slic3r {
 
@@ -413,45 +414,47 @@ static std::vector<size_t> find_enforcer_centers(const Polygon& polygon,
     };
 
     int last_enforcer_start_idx = enforcers_idxs.front();
+    bool first_pt_in_list = enforcers_idxs.front() != 0;
     bool last_pt_in_list = enforcers_idxs.back() == polygon.points.size() - 1;
+    bool wrap_around = last_pt_in_list && first_pt_in_list;
 
-    for (size_t i=0; i<enforcers_idxs.size()-1; ++i) {
-        if ((i == enforcers_idxs.size() - 1)
-         || enforcers_idxs[i+1] != enforcers_idxs[i] + 1) {
-            // i is last point of current enforcer
-            out.push_back(get_center_idx(last_enforcer_start_idx, enforcers_idxs[i]));
-            last_enforcer_start_idx = enforcers_idxs[i+1];
+    for (size_t i=0; i<enforcers_idxs.size(); ++i) {
+        if (i != enforcers_idxs.size() - 1) {
+            if (enforcers_idxs[i+1] != enforcers_idxs[i] + 1) {
+                // i is last point of current enforcer
+                out.push_back(get_center_idx(last_enforcer_start_idx, enforcers_idxs[i]));
+                last_enforcer_start_idx = enforcers_idxs[i+1];
+            }
+        } else {
+            if (! wrap_around) {
+                // we can safely use the last enforcer point.
+                out.push_back(get_center_idx(last_enforcer_start_idx, enforcers_idxs[i]));
+            }
         }
     }
 
-    if (last_pt_in_list) {
-        // last point is an enforcer - not yet accounted for.
-        if (enforcers_idxs.front() != 0) {
-            size_t center_idx = get_center_idx(last_enforcer_start_idx, enforcers_idxs.back());
-            out.push_back(center_idx);
-        } else {
-            // Wrap-around. Update first center already found.
-            if (out.empty()) {
-                // Probably an enforcer around the whole contour. Return nothing.
-                return out;
-            }
-
-            // find last point of the enforcer at the beginning:
-            size_t idx = 0;
-            while (enforcers_idxs[idx]+1 == enforcers_idxs[idx+1])
-                ++idx;
-
-            float t_s = lengths[last_enforcer_start_idx];
-            float t_e = lengths[idx];
-            float half_dist = 0.5f * (t_e + lengths.back() - t_s);
-            float t_c = (half_dist > t_e) ? t_s + half_dist : t_e - half_dist;
-
-            auto it = std::lower_bound(lengths.begin(), lengths.end(), t_c);
-            out[0] = it - lengths.begin();
-            if (out[0] == lengths.size() - 1)
-                --out[0];
-            assert(out[0] < lengths.size() - 1);
+    if (wrap_around) {
+        // Update first center already found.
+        if (out.empty()) {
+            // Probably an enforcer around the whole contour. Return nothing.
+            return out;
         }
+
+        // find last point of the enforcer at the beginning:
+        size_t idx = 0;
+        while (enforcers_idxs[idx]+1 == enforcers_idxs[idx+1])
+            ++idx;
+
+        float t_s = lengths[last_enforcer_start_idx];
+        float t_e = lengths[idx];
+        float half_dist = 0.5f * (t_e + lengths.back() - t_s);
+        float t_c = (half_dist > t_e) ? t_s + half_dist : t_e - half_dist;
+
+        auto it = std::lower_bound(lengths.begin(), lengths.end(), t_c);
+        out[0] = it - lengths.begin();
+        if (out[0] == lengths.size() - 1)
+            --out[0];
+        assert(out[0] < lengths.size() - 1);
     }
     return out;
 }
@@ -481,38 +484,38 @@ void SeamPlacer::penalize_polygon(const Polygon& polygon,
         penalties[idx] -= 1000.f;
     }
 
-//    //////////////////////
-//            std::ostringstream os;
-//            os << std::setw(3) << std::setfill('0') << layer_id;
-//            int a = scale_(20.);
-//            SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a)));
-//            /*if (! m_enforcers.empty())
-//                svg.draw(m_enforcers[layer_id], "blue");
-//            if (! m_blockers.empty())
-//                svg.draw(m_blockers[layer_id], "red");*/
+////////////////////////
+//    std::ostringstream os;
+//    os << std::setw(3) << std::setfill('0') << layer_id;
+//    int a = scale_(20.);
+//    SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a)));
+//    /*if (! m_enforcers.empty())
+//        svg.draw(m_enforcers[layer_id], "blue");
+//    if (! m_blockers.empty())
+//        svg.draw(m_blockers[layer_id], "red");*/
 
-//            size_t min_idx = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
+//    size_t min_idx = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
 
-//            //svg.draw(polygon.points[idx_min], "red", 6e5);
-//            for (size_t i=0; i<polygon.points.size(); ++i) {
-//                std::string fill;
-//                coord_t size = 0;
-//                if (min_idx == i) {
-//                    fill = "yellow";
-//                    size = 5e5;
-//                } else {
-//                    fill = (std::find(enforcers_idxs.begin(), enforcers_idxs.end(), i) != enforcers_idxs.end() ? "green" : "black");
-//                    if (std::find(enf_centers.begin(), enf_centers.end(), i) != enf_centers.end()) {
-//                        size = 5e5;
-//                        fill = "blue";
-//                    }
-//                }
-//                if (i != 0)
-//                    svg.draw(polygon.points[i], fill, size);
-//                else
-//                    svg.draw(polygon.points[i], "red", 5e5);
+//    //svg.draw(polygon.points[idx_min], "red", 6e5);
+//    for (size_t i=0; i<polygon.points.size(); ++i) {
+//        std::string fill;
+//        coord_t size = 0;
+//        if (min_idx == i) {
+//            fill = "yellow";
+//            size = 5e5;
+//        } else {
+//            fill = (std::find(enforcers_idxs.begin(), enforcers_idxs.end(), i) != enforcers_idxs.end() ? "green" : "black");
+//            if (std::find(enf_centers.begin(), enf_centers.end(), i) != enf_centers.end()) {
+//                size = 5e5;
+//                fill = "blue";
 //            }
-//    ////////////////////
+//        }
+//        if (i != 0)
+//            svg.draw(polygon.points[i], fill, size);
+//        else
+//            svg.draw(polygon.points[i], "red", 5e5);
+//    }
+//////////////////////
 
 }
 

From fffb79a085feafaec4ac643c470a0d3a0fb4ab6b Mon Sep 17 00:00:00 2001
From: Lukas Matena <lukasmatena@seznam.cz>
Date: Fri, 11 Sep 2020 14:24:15 +0200
Subject: [PATCH 6/9] Simple implementation of spRandom

---
 src/libslic3r/GCode/SeamPlacer.cpp | 388 +++++++++++++++++------------
 src/libslic3r/GCode/SeamPlacer.hpp |  33 +--
 2 files changed, 248 insertions(+), 173 deletions(-)

diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp
index 0e3a99dc7..73d5a89b3 100644
--- a/src/libslic3r/GCode/SeamPlacer.cpp
+++ b/src/libslic3r/GCode/SeamPlacer.cpp
@@ -199,168 +199,231 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit
                const ExtrusionLoop& loop, Point last_pos, coordf_t nozzle_dmr,
                const PrintObject* po, bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid)
 {
-    if (seam_position == spNearest || seam_position == spAligned || seam_position == spRear) {
-            Polygon        polygon    = loop.polygon();
-            const coord_t  nozzle_r   = coord_t(scale_(0.5 * nozzle_dmr) + 0.5);
+    Polygon polygon = loop.polygon();
+    const coord_t  nozzle_r   = coord_t(scale_(0.5 * nozzle_dmr) + 0.5);
 
-            if (this->is_custom(layer_idx)) {
-                // Seam enf/blockers can begin and end in between the original vertices.
-                // Let add extra points in between and update the leghths.
-                polygon.densify(scale_(0.2f));
+    if (this->is_custom_seam_on_layer(layer_idx)) {
+        // Seam enf/blockers can begin and end in between the original vertices.
+        // Let add extra points in between and update the leghths.
+        polygon.densify(scale_(0.2f));
+    }
+
+    if (seam_position != spRandom) {
+        // Retrieve the last start position for this object.
+        float last_pos_weight = 1.f;
+
+        if (seam_position == spAligned) {
+            // Seam is aligned to the seam at the preceding layer.
+            if (po != nullptr && m_last_seam_position.count(po) > 0) {
+                last_pos = m_last_seam_position[po];
+                last_pos_weight = 1.f;
             }
-
-            // Retrieve the last start position for this object.
-            float last_pos_weight = 1.f;
-
-            if (seam_position == spAligned) {
-                // Seam is aligned to the seam at the preceding layer.
-                if (po != nullptr && m_last_seam_position.count(po) > 0) {
-                    last_pos = m_last_seam_position[po];
-                    last_pos_weight = 1.f;
-                }
-            }
-            else if (seam_position == spRear) {
-                // Object is centered around (0,0) in its current coordinate system.
-                last_pos.x() = 0;
-                last_pos.y() += coord_t(3. * po->bounding_box().radius());
-                last_pos_weight = 5.f;
-            }
-
-            // Insert a projection of last_pos into the polygon.
-            size_t last_pos_proj_idx;
-            {
-                auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r);
-                last_pos_proj_idx = it - polygon.points.begin();
-            }
-
-            // Parametrize the polygon by its length.
-            std::vector<float> lengths = polygon.parameter_by_length();
-
-            // For each polygon point, store a penalty.
-            // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r.
-            std::vector<float> penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r));
-            // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces.
-            const float penaltyConvexVertex = 1.f;
-            const float penaltyFlatSurface  = 5.f;
-            const float penaltyOverhangHalf = 10.f;
-            // Penalty for visible seams.
-           for (size_t i = 0; i < polygon.points.size(); ++ i) {
-                float ccwAngle = penalties[i];
-                if (was_clockwise)
-                    ccwAngle = - ccwAngle;
-                float penalty = 0;
-                if (ccwAngle <- float(0.6 * PI))
-                    // Sharp reflex vertex. We love that, it hides the seam perfectly.
-                    penalty = 0.f;
-                else if (ccwAngle > float(0.6 * PI))
-                    // Seams on sharp convex vertices are more visible than on reflex vertices.
-                    penalty = penaltyConvexVertex;
-                else if (ccwAngle < 0.f) {
-                    // Interpolate penalty between maximum and zero.
-                    penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
-                } else {
-                    assert(ccwAngle >= 0.f);
-                    // Interpolate penalty between maximum and the penalty for a convex vertex.
-                    penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
-                }
-                // Give a negative penalty for points close to the last point or the prefered seam location.
-                float dist_to_last_pos_proj = (i < last_pos_proj_idx) ?
-                    std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) :
-                    std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]);
-                float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr
-                penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max);
-                penalties[i] = std::max(0.f, penalty);
-            }
-
-            // Penalty for overhangs.
-            if (lower_layer_edge_grid) {
-                // Use the edge grid distance field structure over the lower layer to calculate overhangs.
-                coord_t nozzle_r = coord_t(std::floor(scale_(0.5 * nozzle_dmr) + 0.5));
-                coord_t search_r = coord_t(std::floor(scale_(0.8 * nozzle_dmr) + 0.5));
-                for (size_t i = 0; i < polygon.points.size(); ++ i) {
-                    const Point &p = polygon.points[i];
-                    coordf_t dist;
-                    // Signed distance is positive outside the object, negative inside the object.
-                    // The point is considered at an overhang, if it is more than nozzle radius
-                    // outside of the lower layer contour.
-                    [[maybe_unused]] bool found = lower_layer_edge_grid->signed_distance(p, search_r, dist);
-                    // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid,
-                    // then the signed distnace shall always be known.
-                    assert(found);
-                    penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist));
-                }
-            }
-
-            // Penalty according to custom seam selection. This one is huge compared to
-            // the others so that points outside enforcers/inside blockers never win.
-            this->penalize_polygon(polygon, penalties, lengths, layer_idx);
-
-            // Find a point with a minimum penalty.
-            size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
-
-            // For all (aligned, nearest, rear) seams:
-            {
-                // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx.
-                // In that case use last_pos_proj_idx instead.
-                float penalty_aligned  = penalties[last_pos_proj_idx];
-                float penalty_min      = penalties[idx_min];
-                float penalty_diff_abs = std::abs(penalty_min - penalty_aligned);
-                float penalty_max      = std::max(penalty_min, penalty_aligned);
-                float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max;
-                // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel);
-                if (std::abs(penalty_diff_rel) < 0.05) {
-                    // Penalty of the aligned point is very close to the minimum penalty.
-                    // Align the seams as accurately as possible.
-                    idx_min = last_pos_proj_idx;
-                }
-                m_last_seam_position[po] = polygon.points[idx_min];
-            }
-
-
-            // Export the contour into a SVG file.
-            #if 0
-            {
-                static int iRun = 0;
-                SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++));
-                if (m_layer->lower_layer != NULL)
-                    svg.draw(m_layer->lower_layer->slices);
-                for (size_t i = 0; i < loop.paths.size(); ++ i)
-                    svg.draw(loop.paths[i].as_polyline(), "red");
-                Polylines polylines;
-                for (size_t i = 0; i < loop.paths.size(); ++ i)
-                    polylines.push_back(loop.paths[i].as_polyline());
-                Slic3r::Polygons polygons;
-                coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter);
-                coord_t delta = scale_(0.5*nozzle_dmr);
-                Slic3r::offset(polylines, &polygons, delta);
-    //            for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue");
-                svg.draw(last_pos, "green", 3);
-                svg.draw(polygon.points[idx_min], "yellow", 3);
-                svg.Close();
-            }
-            #endif
-            return polygon.points[idx_min];
-
-        } else { // spRandom
-            if (loop.loop_role() == elrContourInternalPerimeter) {
-                // This loop does not contain any other loop. Set a random position.
-                // The other loops will get a seam close to the random point chosen
-                // on the inner most contour.
-                //FIXME This works correctly for inner contours first only.
-                //FIXME Better parametrize the loop by its length.
-                Polygon polygon = loop.polygon();
-                Point centroid = polygon.centroid();
-                last_pos = Point(polygon.bounding_box().max(0), centroid(1));
-                last_pos.rotate(fmod((float)rand()/16.0, 2.0*PI), centroid);
-            }
-            return last_pos;
         }
+        else if (seam_position == spRear) {
+            // Object is centered around (0,0) in its current coordinate system.
+            last_pos.x() = 0;
+            last_pos.y() += coord_t(3. * po->bounding_box().radius());
+            last_pos_weight = 5.f;
+        } if (seam_position == spNearest) {
+            // last_pos already contains current nozzle position
+        }
+
+        // Insert a projection of last_pos into the polygon.
+        size_t last_pos_proj_idx;
+        {
+            auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r);
+            last_pos_proj_idx = it - polygon.points.begin();
+        }
+
+        // Parametrize the polygon by its length.
+        std::vector<float> lengths = polygon.parameter_by_length();
+
+        // For each polygon point, store a penalty.
+        // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r.
+        std::vector<float> penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r));
+        // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces.
+        const float penaltyConvexVertex = 1.f;
+        const float penaltyFlatSurface  = 5.f;
+        const float penaltyOverhangHalf = 10.f;
+        // Penalty for visible seams.
+       for (size_t i = 0; i < polygon.points.size(); ++ i) {
+            float ccwAngle = penalties[i];
+            if (was_clockwise)
+                ccwAngle = - ccwAngle;
+            float penalty = 0;
+            if (ccwAngle <- float(0.6 * PI))
+                // Sharp reflex vertex. We love that, it hides the seam perfectly.
+                penalty = 0.f;
+            else if (ccwAngle > float(0.6 * PI))
+                // Seams on sharp convex vertices are more visible than on reflex vertices.
+                penalty = penaltyConvexVertex;
+            else if (ccwAngle < 0.f) {
+                // Interpolate penalty between maximum and zero.
+                penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
+            } else {
+                assert(ccwAngle >= 0.f);
+                // Interpolate penalty between maximum and the penalty for a convex vertex.
+                penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
+            }
+            // Give a negative penalty for points close to the last point or the prefered seam location.
+            float dist_to_last_pos_proj = (i < last_pos_proj_idx) ?
+                std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) :
+                std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]);
+            float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr
+            penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max);
+            penalties[i] = std::max(0.f, penalty);
+        }
+
+        // Penalty for overhangs.
+        if (lower_layer_edge_grid) {
+            // Use the edge grid distance field structure over the lower layer to calculate overhangs.
+            coord_t nozzle_r = coord_t(std::floor(scale_(0.5 * nozzle_dmr) + 0.5));
+            coord_t search_r = coord_t(std::floor(scale_(0.8 * nozzle_dmr) + 0.5));
+            for (size_t i = 0; i < polygon.points.size(); ++ i) {
+                const Point &p = polygon.points[i];
+                coordf_t dist;
+                // Signed distance is positive outside the object, negative inside the object.
+                // The point is considered at an overhang, if it is more than nozzle radius
+                // outside of the lower layer contour.
+                [[maybe_unused]] bool found = lower_layer_edge_grid->signed_distance(p, search_r, dist);
+                // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid,
+                // then the signed distnace shall always be known.
+                assert(found);
+                penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist));
+            }
+        }
+
+        // Custom seam. Huge (negative) constant penalty is applied inside
+        // blockers (enforcers) to rule out points that should not win.
+        this->apply_custom_seam(polygon, penalties, lengths, layer_idx);
+
+        // Find a point with a minimum penalty.
+        size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
+
+        // For all (aligned, nearest, rear) seams:
+        {
+            // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx.
+            // In that case use last_pos_proj_idx instead.
+            float penalty_aligned  = penalties[last_pos_proj_idx];
+            float penalty_min      = penalties[idx_min];
+            float penalty_diff_abs = std::abs(penalty_min - penalty_aligned);
+            float penalty_max      = std::max(penalty_min, penalty_aligned);
+            float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max;
+            // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel);
+            if (std::abs(penalty_diff_rel) < 0.05) {
+                // Penalty of the aligned point is very close to the minimum penalty.
+                // Align the seams as accurately as possible.
+                idx_min = last_pos_proj_idx;
+            }
+            m_last_seam_position[po] = polygon.points[idx_min];
+        }
+
+
+        // Export the contour into a SVG file.
+        #if 0
+        {
+            static int iRun = 0;
+            SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++));
+            if (m_layer->lower_layer != NULL)
+                svg.draw(m_layer->lower_layer->slices);
+            for (size_t i = 0; i < loop.paths.size(); ++ i)
+                svg.draw(loop.paths[i].as_polyline(), "red");
+            Polylines polylines;
+            for (size_t i = 0; i < loop.paths.size(); ++ i)
+                polylines.push_back(loop.paths[i].as_polyline());
+            Slic3r::Polygons polygons;
+            coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter);
+            coord_t delta = scale_(0.5*nozzle_dmr);
+            Slic3r::offset(polylines, &polygons, delta);
+//            for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue");
+            svg.draw(last_pos, "green", 3);
+            svg.draw(polygon.points[idx_min], "yellow", 3);
+            svg.Close();
+        }
+        #endif
+        return polygon.points[idx_min];
+
+    } else { // spRandom
+        if (loop.loop_role() == elrContourInternalPerimeter && loop.role() != erExternalPerimeter) {
+            // This loop does not contain any other loop. Set a random position.
+            // The other loops will get a seam close to the random point chosen
+            // on the innermost contour.
+            //FIXME This works correctly for inner contours first only.
+            last_pos = this->get_random_seam(layer_idx, polygon);
+        }
+        if (loop.role() == erExternalPerimeter && is_custom_seam_on_layer(layer_idx)) {
+            // There is a possibility that the loop will be influenced by custom
+            // seam enforcer/blocker. In this case do not inherit the seam
+            // from internal loops (which may conflict with the custom selection
+            // and generate another random one.
+            bool saw_custom = false;
+            Point candidate = this->get_random_seam(layer_idx, polygon, &saw_custom);
+            if (saw_custom)
+                last_pos = candidate;
+        }
+        return last_pos;
+    }
+}
+
+
+Point SeamPlacer::get_random_seam(size_t layer_idx, const Polygon& polygon,
+                                  bool* saw_custom) const
+{
+    // Parametrize the polygon by its length.
+    std::vector<float> lengths = polygon.parameter_by_length();
+
+    // Which of the points are inside enforcers/blockers?
+    std::vector<size_t> enforcers_idxs;
+    std::vector<size_t> blockers_idxs;
+    this->get_enforcers_and_blockers(layer_idx, polygon, enforcers_idxs, blockers_idxs);
+
+    bool has_enforcers = ! enforcers_idxs.empty();
+    bool has_blockers = ! blockers_idxs.empty();
+    if (saw_custom)
+        *saw_custom = has_enforcers || has_blockers;
+
+    // FIXME FIXME FIXME: This is just to test the outcome and whether it is
+    // reasonable. The algorithm should really sum the length of all available
+    // pieces, get a random length and find the respective point.
+    float rand_len = 0.f;
+    size_t pt_idx = 0;
+    do {
+        rand_len = lengths.back() * (rand()/float(RAND_MAX));
+        auto it = std::lower_bound(lengths.begin(), lengths.end(), rand_len);
+        pt_idx = it == lengths.end() ? 0 : (it-lengths.begin()-1);
+
+        // If there are blockers and the point is inside, repeat.
+        // If there are enforcers and the point is NOT inside, repeat.
+    } while ((has_blockers && std::binary_search(blockers_idxs.begin(), blockers_idxs.end(), pt_idx))
+         || (has_enforcers && ! std::binary_search(enforcers_idxs.begin(), enforcers_idxs.end(), pt_idx)));
+
+    if (! has_enforcers && ! has_blockers) {
+        // The polygon may be too coarse, calculate the point exactly.
+        bool last_seg = pt_idx == polygon.points.size()-1;
+        size_t next_idx = last_seg ? 0 : pt_idx+1;
+        const Point& prev = polygon.points[pt_idx];
+        const Point& next = polygon.points[next_idx];
+        assert(next_idx == 0 || pt_idx+1 == next_idx);
+        coordf_t diff_x = next.x() - prev.x();
+        coordf_t diff_y = next.y() - prev.y();
+        coordf_t dist = lengths[last_seg ? pt_idx+1 : next_idx] - lengths[pt_idx];
+        return Point(prev.x() + (rand_len - lengths[pt_idx]) * (diff_x/dist),
+                     prev.y() + (rand_len - lengths[pt_idx]) * (diff_y/dist));
+
+    } else {
+        // The polygon should be dense enough.
+        return polygon.points[pt_idx];
+    }
 }
 
 
 
 
-void SeamPlacer::get_indices(size_t layer_id,
+
+
+
+
+void SeamPlacer::get_enforcers_and_blockers(size_t layer_id,
                              const Polygon& polygon,
                              std::vector<size_t>& enforcers_idxs,
                              std::vector<size_t>& blockers_idxs) const
@@ -388,6 +451,8 @@ void SeamPlacer::get_indices(size_t layer_id,
             }
         }
     }
+
+    std::cout << layer_id << ": enforcers.size() = " << enforcers_idxs.size() << std::endl;
 }
 
 
@@ -461,14 +526,19 @@ static std::vector<size_t> find_enforcer_centers(const Polygon& polygon,
 
 
 
-void SeamPlacer::penalize_polygon(const Polygon& polygon,
-                                  std::vector<float>& penalties,
-                                  const std::vector<float>& lengths,
-                                  int layer_id) const
+void SeamPlacer::apply_custom_seam(const Polygon& polygon,
+                                   std::vector<float>& penalties,
+                                   const std::vector<float>& lengths,
+                                   int layer_id) const
 {
+    if (! is_custom_seam_on_layer(layer_id))
+        return;
+
+    static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6;
+
     std::vector<size_t> enforcers_idxs;
     std::vector<size_t> blockers_idxs;
-    this->get_indices(layer_id, polygon, enforcers_idxs, blockers_idxs);
+    this->get_enforcers_and_blockers(layer_id, polygon, enforcers_idxs, blockers_idxs);
 
     for (size_t i : enforcers_idxs) {
         assert(i < penalties.size());
diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp
index af12dc9fb..b55eef1b7 100644
--- a/src/libslic3r/GCode/SeamPlacer.hpp
+++ b/src/libslic3r/GCode/SeamPlacer.hpp
@@ -15,11 +15,6 @@ class SeamPlacer {
 public:
     void init(const Print& print);
 
-    bool is_custom(size_t layer_id) const {
-        return ! ((m_enforcers.empty() || m_enforcers[layer_id].empty())
-                && (m_blockers.empty() || m_blockers[layer_id].empty()));
-    }
-
     Point get_seam(const size_t layer_idx, const SeamPosition seam_position,
                    const ExtrusionLoop& loop, Point last_pos,
                    coordf_t nozzle_diameter, const PrintObject* po,
@@ -32,17 +27,27 @@ private:
     std::map<const PrintObject*, Point>  m_last_seam_position;
 
     // Get indices of points inside enforcers and blockers.
-    void get_indices(size_t layer_id,
-                    const Polygon& polygon,
-                    std::vector<size_t>& enforcers_idxs,
-                    std::vector<size_t>& blockers_idxs) const;
+    void get_enforcers_and_blockers(size_t layer_id,
+                                    const Polygon& polygon,
+                                    std::vector<size_t>& enforcers_idxs,
+                                    std::vector<size_t>& blockers_idxs) const;
 
-    void penalize_polygon(const Polygon& polygon,
-                          std::vector<float>& penalties,
-                          const std::vector<float>& lengths,
-                          int layer_id) const;
+    // Apply penalties to points inside enforcers/blockers.
+    void apply_custom_seam(const Polygon& polygon,
+                           std::vector<float>& penalties,
+                           const std::vector<float>& lengths,
+                           int layer_id) const;
 
-    static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6;
+    // Return random point of a polygon. The distribution will be uniform
+    // along the contour and account for enforcers and blockers.
+    Point get_random_seam(size_t layer_idx, const Polygon& polygon,
+                          bool* saw_custom = nullptr) const;
+
+    // Is there any enforcer/blocker on this layer?
+    bool is_custom_seam_on_layer(size_t layer_id) const {
+        return ! ((m_enforcers.empty() || m_enforcers[layer_id].empty())
+                && (m_blockers.empty() || m_blockers[layer_id].empty()));
+    }
 };
 
 

From 8dd345ed4c776609e2c12d2e0fe08c65416fbaa1 Mon Sep 17 00:00:00 2001
From: Lukas Matena <lukasmatena@seznam.cz>
Date: Fri, 11 Sep 2020 16:27:06 +0200
Subject: [PATCH 7/9] use center of enforcer only with spAligned

---
 src/libslic3r/GCode/SeamPlacer.cpp | 30 +++++++++++++++++++++---------
 src/libslic3r/GCode/SeamPlacer.hpp |  2 +-
 2 files changed, 22 insertions(+), 10 deletions(-)

diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp
index 73d5a89b3..b00b8ac43 100644
--- a/src/libslic3r/GCode/SeamPlacer.cpp
+++ b/src/libslic3r/GCode/SeamPlacer.cpp
@@ -9,6 +9,18 @@
 
 namespace Slic3r {
 
+// This penalty is added to all points inside custom blockers (subtracted from pts inside enforcers).
+static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6;
+
+// In case there are custom enforcers/blockers, the loop polygon shall always have
+// sides smaller than this (so it isn't limited to original resolution).
+static constexpr float MINIMAL_POLYGON_SIDE = scale_(0.2f);
+
+// When spAligned is active and there is a support enforcer,
+// add this penalty to its center.
+static constexpr float ENFORCER_CENTER_PENALTY = -1e3;
+
+
 
 
 static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance)
@@ -205,7 +217,7 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit
     if (this->is_custom_seam_on_layer(layer_idx)) {
         // Seam enf/blockers can begin and end in between the original vertices.
         // Let add extra points in between and update the leghths.
-        polygon.densify(scale_(0.2f));
+        polygon.densify(MINIMAL_POLYGON_SIDE);
     }
 
     if (seam_position != spRandom) {
@@ -295,7 +307,7 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit
 
         // Custom seam. Huge (negative) constant penalty is applied inside
         // blockers (enforcers) to rule out points that should not win.
-        this->apply_custom_seam(polygon, penalties, lengths, layer_idx);
+        this->apply_custom_seam(polygon, penalties, lengths, layer_idx, seam_position);
 
         // Find a point with a minimum penalty.
         size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
@@ -529,13 +541,11 @@ static std::vector<size_t> find_enforcer_centers(const Polygon& polygon,
 void SeamPlacer::apply_custom_seam(const Polygon& polygon,
                                    std::vector<float>& penalties,
                                    const std::vector<float>& lengths,
-                                   int layer_id) const
+                                   int layer_id, SeamPosition seam_position) const
 {
     if (! is_custom_seam_on_layer(layer_id))
         return;
 
-    static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6;
-
     std::vector<size_t> enforcers_idxs;
     std::vector<size_t> blockers_idxs;
     this->get_enforcers_and_blockers(layer_id, polygon, enforcers_idxs, blockers_idxs);
@@ -548,10 +558,12 @@ void SeamPlacer::apply_custom_seam(const Polygon& polygon,
         assert(i < penalties.size());
         penalties[i] += float(ENFORCER_BLOCKER_PENALTY);
     }
-    std::vector<size_t> enf_centers = find_enforcer_centers(polygon, lengths, enforcers_idxs);
-    for (size_t idx : enf_centers) {
-        assert(idx < penalties.size());
-        penalties[idx] -= 1000.f;
+    if (seam_position == spAligned) {
+        std::vector<size_t> enf_centers = find_enforcer_centers(polygon, lengths, enforcers_idxs);
+        for (size_t idx : enf_centers) {
+            assert(idx < penalties.size());
+            penalties[idx] += ENFORCER_CENTER_PENALTY;
+        }
     }
 
 ////////////////////////
diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp
index b55eef1b7..e605e0540 100644
--- a/src/libslic3r/GCode/SeamPlacer.hpp
+++ b/src/libslic3r/GCode/SeamPlacer.hpp
@@ -36,7 +36,7 @@ private:
     void apply_custom_seam(const Polygon& polygon,
                            std::vector<float>& penalties,
                            const std::vector<float>& lengths,
-                           int layer_id) const;
+                           int layer_id, SeamPosition seam_position) const;
 
     // Return random point of a polygon. The distribution will be uniform
     // along the contour and account for enforcers and blockers.

From 8123930ee58bd7f7447d67aff2dcb236906b3e21 Mon Sep 17 00:00:00 2001
From: Lukas Matena <lukasmatena@seznam.cz>
Date: Thu, 17 Sep 2020 15:15:53 +0200
Subject: [PATCH 8/9] Store seam history for more islands

---
 src/libslic3r/GCode/SeamPlacer.cpp | 89 ++++++++++++++++++++++++++----
 src/libslic3r/GCode/SeamPlacer.hpp | 39 ++++++++++++-
 2 files changed, 114 insertions(+), 14 deletions(-)

diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp
index b00b8ac43..a085f08ce 100644
--- a/src/libslic3r/GCode/SeamPlacer.cpp
+++ b/src/libslic3r/GCode/SeamPlacer.cpp
@@ -10,7 +10,7 @@
 namespace Slic3r {
 
 // This penalty is added to all points inside custom blockers (subtracted from pts inside enforcers).
-static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6;
+static constexpr float ENFORCER_BLOCKER_PENALTY = 100;
 
 // In case there are custom enforcers/blockers, the loop polygon shall always have
 // sides smaller than this (so it isn't limited to original resolution).
@@ -18,7 +18,7 @@ static constexpr float MINIMAL_POLYGON_SIDE = scale_(0.2f);
 
 // When spAligned is active and there is a support enforcer,
 // add this penalty to its center.
-static constexpr float ENFORCER_CENTER_PENALTY = -1e3;
+static constexpr float ENFORCER_CENTER_PENALTY = -10.f;
 
 
 
@@ -191,7 +191,8 @@ void SeamPlacer::init(const Print& print)
 {
     m_enforcers.clear();
     m_blockers.clear();
-    m_last_seam_position.clear();
+    //m_last_seam_position.clear();
+    m_seam_history.clear();
 
    for (const PrintObject* po : print.objects()) {
        po->project_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, m_enforcers);
@@ -212,6 +213,7 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit
                const PrintObject* po, bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid)
 {
     Polygon polygon = loop.polygon();
+    BoundingBox polygon_bb = polygon.bounding_box();
     const coord_t  nozzle_r   = coord_t(scale_(0.5 * nozzle_dmr) + 0.5);
 
     if (this->is_custom_seam_on_layer(layer_idx)) {
@@ -226,9 +228,13 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit
 
         if (seam_position == spAligned) {
             // Seam is aligned to the seam at the preceding layer.
-            if (po != nullptr && m_last_seam_position.count(po) > 0) {
-                last_pos = m_last_seam_position[po];
-                last_pos_weight = 1.f;
+            if (po != nullptr) {
+                std::optional<Point> pos = m_seam_history.get_last_seam(po, layer_idx, polygon_bb);
+                if (pos.has_value()) {
+                    //last_pos = m_last_seam_position[po];
+                    last_pos = pos.value();
+                    last_pos_weight = is_custom_enforcer_on_layer(layer_idx) ? 0.f : 1.f;
+                }
             }
         }
         else if (seam_position == spRear) {
@@ -312,8 +318,7 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit
         // Find a point with a minimum penalty.
         size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
 
-        // For all (aligned, nearest, rear) seams:
-        {
+        if (! spAligned || ! is_custom_enforcer_on_layer(layer_idx)) {
             // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx.
             // In that case use last_pos_proj_idx instead.
             float penalty_aligned  = penalties[last_pos_proj_idx];
@@ -327,9 +332,11 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit
                 // Align the seams as accurately as possible.
                 idx_min = last_pos_proj_idx;
             }
-            m_last_seam_position[po] = polygon.points[idx_min];
         }
 
+        if (seam_position == spAligned && loop.role() == erExternalPerimeter)
+            m_seam_history.add_seam(po, polygon.points[idx_min], polygon_bb);
+
 
         // Export the contour into a SVG file.
         #if 0
@@ -463,8 +470,6 @@ void SeamPlacer::get_enforcers_and_blockers(size_t layer_id,
             }
         }
     }
-
-    std::cout << layer_id << ": enforcers.size() = " << enforcers_idxs.size() << std::endl;
 }
 
 
@@ -602,4 +607,66 @@ void SeamPlacer::apply_custom_seam(const Polygon& polygon,
 }
 
 
+
+std::optional<Point> SeamHistory::get_last_seam(const PrintObject* po, size_t layer_id, const BoundingBox& island_bb)
+{
+    assert(layer_id >= m_layer_id);
+    if (layer_id > m_layer_id) {
+        // Get seam was called for different layer than last time.
+        m_data_last_layer = m_data_this_layer;
+        m_data_this_layer.clear();
+        m_layer_id = layer_id;
+    }
+
+
+
+    std::optional<Point> out;
+
+    auto seams_it = m_data_last_layer.find(po);
+    if (seams_it == m_data_last_layer.end())
+        return out;
+
+    const std::vector<SeamPoint>& seam_data_po = seams_it->second;
+
+    // Find a bounding-box on the last layer that is close to one we see now.
+    double min_score = std::numeric_limits<double>::max();
+    for (const SeamPoint& sp : seam_data_po) {
+        const BoundingBox& bb = sp.m_island_bb;
+
+        if (! bb.overlap(island_bb)) {
+            // This bb does not even overlap. It is likely unrelated.
+            continue;
+        }
+
+        double score = std::pow(bb.min(0) - island_bb.min(0), 2.)
+                     + std::pow(bb.min(1) - island_bb.min(1), 2.)
+                     + std::pow(bb.max(0) - island_bb.max(0), 2.)
+                     + std::pow(bb.max(1) - island_bb.max(1), 2.);
+
+        if (score < min_score) {
+            min_score = score;
+            out = sp.m_pos;
+        }
+    }
+
+    return out;
+}
+
+
+
+void SeamHistory::add_seam(const PrintObject* po, const Point& pos, const BoundingBox& island_bb)
+{
+    m_data_this_layer[po].push_back({pos, island_bb});;
+}
+
+
+
+void SeamHistory::clear()
+{
+    m_layer_id = 0;
+    m_data_last_layer.clear();
+    m_data_this_layer.clear();
+}
+
+
 }
diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp
index e605e0540..e603b7d57 100644
--- a/src/libslic3r/GCode/SeamPlacer.hpp
+++ b/src/libslic3r/GCode/SeamPlacer.hpp
@@ -1,8 +1,11 @@
 #ifndef libslic3r_SeamPlacer_hpp_
 #define libslic3r_SeamPlacer_hpp_
 
+#include <optional>
+
 #include "libslic3r/ExPolygon.hpp"
 #include "libslic3r/PrintConfig.hpp"
+#include "libslic3r/BoundingBox.hpp"
 
 namespace Slic3r {
 
@@ -11,6 +14,27 @@ class ExtrusionLoop;
 class Print;
 namespace EdgeGrid { class Grid; }
 
+
+class SeamHistory {
+public:
+    SeamHistory() { clear(); }
+    std::optional<Point> get_last_seam(const PrintObject* po, size_t layer_id, const BoundingBox& island_bb);
+    void add_seam(const PrintObject* po, const Point& pos, const BoundingBox& island_bb);
+    void clear();
+
+private:
+    struct SeamPoint {
+        Point m_pos;
+        BoundingBox m_island_bb;
+    };
+
+    std::map<const PrintObject*, std::vector<SeamPoint>> m_data_last_layer;
+    std::map<const PrintObject*, std::vector<SeamPoint>> m_data_this_layer;
+    size_t m_layer_id;
+};
+
+
+
 class SeamPlacer {
 public:
     void init(const Print& print);
@@ -24,7 +48,8 @@ private:
     std::vector<ExPolygons> m_enforcers;
     std::vector<ExPolygons> m_blockers;
 
-    std::map<const PrintObject*, Point>  m_last_seam_position;
+    //std::map<const PrintObject*, Point>  m_last_seam_position;
+    SeamHistory  m_seam_history;
 
     // Get indices of points inside enforcers and blockers.
     void get_enforcers_and_blockers(size_t layer_id,
@@ -45,8 +70,16 @@ private:
 
     // Is there any enforcer/blocker on this layer?
     bool is_custom_seam_on_layer(size_t layer_id) const {
-        return ! ((m_enforcers.empty() || m_enforcers[layer_id].empty())
-                && (m_blockers.empty() || m_blockers[layer_id].empty()));
+        return is_custom_enforcer_on_layer(layer_id)
+            || is_custom_blocker_on_layer(layer_id);
+    }
+
+    bool is_custom_enforcer_on_layer(size_t layer_id) const {
+        return (! m_enforcers.empty() && ! m_enforcers[layer_id].empty());
+    }
+
+    bool is_custom_blocker_on_layer(size_t layer_id) const {
+        return (! m_blockers.empty() && ! m_blockers[layer_id].empty());
     }
 };
 

From cf50224248a50e48e84f8a763320160c951802e1 Mon Sep 17 00:00:00 2001
From: Lukas Matena <lukasmatena@seznam.cz>
Date: Fri, 18 Sep 2020 14:24:29 +0200
Subject: [PATCH 9/9] Fix build on macOS and one logic error

---
 src/libslic3r/GCode/SeamPlacer.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp
index a085f08ce..db31f8f67 100644
--- a/src/libslic3r/GCode/SeamPlacer.cpp
+++ b/src/libslic3r/GCode/SeamPlacer.cpp
@@ -232,7 +232,7 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit
                 std::optional<Point> pos = m_seam_history.get_last_seam(po, layer_idx, polygon_bb);
                 if (pos.has_value()) {
                     //last_pos = m_last_seam_position[po];
-                    last_pos = pos.value();
+                    last_pos = *pos;
                     last_pos_weight = is_custom_enforcer_on_layer(layer_idx) ? 0.f : 1.f;
                 }
             }
@@ -318,7 +318,7 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit
         // Find a point with a minimum penalty.
         size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
 
-        if (! spAligned || ! is_custom_enforcer_on_layer(layer_idx)) {
+        if (seam_position != spAligned || ! is_custom_enforcer_on_layer(layer_idx)) {
             // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx.
             // In that case use last_pos_proj_idx instead.
             float penalty_aligned  = penalties[last_pos_proj_idx];