From 8858651bf46dce2ac0b3435ab9b46a4053cf7c3b Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Nov 2022 14:47:43 +0100 Subject: [PATCH] WIP Refactoring of Layers: Reworked G-code export to make use of Layer->LayerSlice->LayerIsland hierarchy. This should improve tool path ordering of multiple parts within the same object #5511. Some shells tests rewritten from Perl to C++. FIXME: Gap fill extrusions are currently not handled by the initial G-code preview! --- src/libslic3r/ExtrusionEntityCollection.hpp | 4 +- src/libslic3r/GCode.cpp | 710 ++++++++------------ src/libslic3r/GCode.hpp | 125 ++-- src/libslic3r/GCode/ToolOrdering.cpp | 45 +- src/libslic3r/GCode/ToolOrdering.hpp | 23 +- src/libslic3r/Layer.cpp | 242 ++++--- src/libslic3r/Layer.hpp | 4 +- t/shells.t | 69 +- tests/fff_print/CMakeLists.txt | 1 + tests/fff_print/test_shells.cpp | 112 +++ 10 files changed, 602 insertions(+), 733 deletions(-) create mode 100644 tests/fff_print/test_shells.cpp diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index a344bb4a9..59d4421d6 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -127,12 +127,12 @@ public: }; void collect_polylines(Polylines &dst) const override { - for (ExtrusionEntity* extrusion_entity : this->entities) + for (const ExtrusionEntity *extrusion_entity : this->entities) extrusion_entity->collect_polylines(dst); } void collect_points(Points &dst) const override { - for (ExtrusionEntity* extrusion_entity : this->entities) + for (const ExtrusionEntity *extrusion_entity : this->entities) extrusion_entity->collect_points(dst); } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c45b6daa9..24e350ac4 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -475,9 +475,9 @@ namespace Slic3r { // Collect pairs of object_layer + support_layer sorted by print_z. // object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON. -std::vector GCode::collect_layers_to_print(const PrintObject& object) +GCode::ObjectsLayerToPrint GCode::collect_layers_to_print(const PrintObject& object) { - std::vector layers_to_print; + GCode::ObjectsLayerToPrint layers_to_print; layers_to_print.reserve(object.layers().size() + object.support_layers().size()); /* @@ -501,9 +501,9 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Pair the object layers with the support layers by z. size_t idx_object_layer = 0; size_t idx_support_layer = 0; - const LayerToPrint* last_extrusion_layer = nullptr; + const ObjectLayerToPrint* last_extrusion_layer = nullptr; while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) { - LayerToPrint layer_to_print; + ObjectLayerToPrint layer_to_print; layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer++] : nullptr; layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer++] : nullptr; if (layer_to_print.object_layer && layer_to_print.support_layer) { @@ -575,8 +575,8 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // 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 items. -std::vector>> GCode::collect_layers_to_print(const Print& print) +// Return a list of items. +std::vector> GCode::collect_layers_to_print(const Print& print) { struct OrderingItem { coordf_t print_z; @@ -584,15 +584,15 @@ std::vector>> GCode::collec size_t layer_idx; }; - std::vector> per_object(print.objects().size(), std::vector()); - std::vector ordering; + std::vector per_object(print.objects().size(), ObjectsLayerToPrint()); + std::vector ordering; for (size_t i = 0; i < print.objects().size(); ++i) { per_object[i] = collect_layers_to_print(*print.objects()[i]); OrderingItem ordering_item; ordering_item.object_idx = i; ordering.reserve(ordering.size() + per_object[i].size()); - const LayerToPrint& front = per_object[i].front(); - for (const LayerToPrint& ltp : per_object[i]) { + const ObjectLayerToPrint &front = per_object[i].front(); + for (const ObjectLayerToPrint <p : per_object[i]) { ordering_item.print_z = ltp.print_z(); ordering_item.layer_idx = <p - &front; ordering.emplace_back(ordering_item); @@ -601,7 +601,7 @@ std::vector>> GCode::collec std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; }); - std::vector>> layers_to_print; + std::vector> layers_to_print; // Merge numerically very close Z values. for (size_t i = 0; i < ordering.size();) { @@ -610,10 +610,10 @@ std::vector>> GCode::collec coordf_t zmax = ordering[i].print_z + EPSILON; for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j); // Merge into layers_to_print. - std::pair> merged; + std::pair merged; // Assign an average print_z to the set of layers with nearly equal print_z. merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z); - merged.second.assign(print.objects().size(), LayerToPrint()); + merged.second.assign(print.objects().size(), ObjectLayerToPrint()); for (; i < j; ++i) { const OrderingItem& oi = ordering[i]; assert(merged.second[oi.object_idx].layer() == nullptr); @@ -1347,7 +1347,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato } else { // Sort layers by Z. // All extrusion moves with the same top layer height are extruded uninterrupted. - std::vector>> layers_to_print = collect_layers_to_print(print); + std::vector> layers_to_print = collect_layers_to_print(print); // Prusa Multi-Material wipe tower. if (has_wipe_tower && ! layers_to_print.empty()) { 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())); @@ -1475,7 +1475,7 @@ void GCode::process_layers( const Print &print, const ToolOrdering &tool_ordering, const std::vector &print_object_instances_ordering, - const std::vector>> &layers_to_print, + const std::vector> &layers_to_print, GCodeOutputStream &output_stream) { // The pipeline is variable: The vase mode filter is optional. @@ -1493,7 +1493,7 @@ void GCode::process_layers( return LayerResult::make_nop_layer_result(); } } else { - const std::pair>& layer = layers_to_print[layer_to_print_idx++]; + const std::pair &layer = layers_to_print[layer_to_print_idx++]; const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first); if (m_wipe_tower && layer_tools.has_wipe_tower) m_wipe_tower->next_layer(); @@ -1559,7 +1559,7 @@ void GCode::process_layers( void GCode::process_layers( const Print &print, const ToolOrdering &tool_ordering, - std::vector layers_to_print, + ObjectsLayerToPrint layers_to_print, const size_t single_object_idx, GCodeOutputStream &output_stream) { @@ -1578,7 +1578,7 @@ void GCode::process_layers( return LayerResult::make_nop_layer_result(); } } else { - LayerToPrint &layer = layers_to_print[layer_to_print_idx ++]; + ObjectLayerToPrint &layer = layers_to_print[layer_to_print_idx ++]; print.throw_if_canceled(); return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx); } @@ -1829,70 +1829,39 @@ void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Pr } } -inline GCode::ObjectByExtruder& object_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects) -{ - std::vector &objects_by_extruder = by_extruder[extruder_id]; - if (objects_by_extruder.empty()) - objects_by_extruder.assign(num_objects, GCode::ObjectByExtruder()); - return objects_by_extruder[object_idx]; -} - -inline std::vector& object_islands_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects, - size_t num_islands) -{ - std::vector &islands = object_by_extruder(by_extruder, extruder_id, object_idx, num_objects).islands; - if (islands.empty()) - islands.assign(num_islands, GCode::ObjectByExtruder::Island()); - return islands; -} - std::vector GCode::sort_print_object_instances( - std::vector &objects_by_extruder, - const std::vector &layers, + const std::vector &object_layers, // Ordering must be defined for normal (non-sequential print). - const std::vector *ordering, + const std::vector *ordering, // For sequential print, the instance of the object to be printing has to be defined. - const size_t single_object_instance_idx) + const size_t single_object_instance_idx) { std::vector 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); - } + assert(object_layers.size() == 1); + const Layer *layer = object_layers.front().object_layer; + assert(layer != nullptr); + out.emplace_back(0, *layer->object(), single_object_instance_idx); } else { - // Create mapping from PrintObject* to ObjectByExtruder*. - std::vector> 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); - } + // Create mapping from PrintObject* to ObjectLayerToPrint ID. + std::vector> sorted; + sorted.reserve(object_layers.size()); + for (const ObjectLayerToPrint &object : object_layers) + if (const PrintObject* print_object = object.object(); print_object) + sorted.emplace_back(print_object, &object - object_layers.data()); 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 key(&print_object, nullptr); + std::pair key(&print_object, 0); 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()); + // ObjectLayerToPrint for this PrintObject was found. + out.emplace_back(it->second, print_object, instance - print_object.instances().data()); } } } @@ -2059,7 +2028,7 @@ namespace Skirt { LayerResult GCode::process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. - const std::vector &layers, + const ObjectsLayerToPrint &layers, const LayerTools &layer_tools, const bool last_layer, // Pairs of PrintObject index and its instance index. @@ -2076,7 +2045,7 @@ LayerResult GCode::process_layer( const Layer *object_layer = nullptr; const SupportLayer *support_layer = nullptr; const SupportLayer *raft_layer = nullptr; - for (const LayerToPrint &l : layers) { + for (const ObjectLayerToPrint &l : layers) { if (l.object_layer && ! object_layer) object_layer = l.object_layer; if (l.support_layer) { @@ -2086,7 +2055,7 @@ LayerResult GCode::process_layer( raft_layer = support_layer; } } - const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer; + const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer; LayerResult result { {}, layer.id(), false, last_layer, false}; if (layer_tools.extruders.empty()) // Nothing to extrude. @@ -2189,157 +2158,6 @@ LayerResult GCode::process_layer( Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) : Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done); - // Group extrusions by an extruder, then by an object, an island and a region. - std::map> by_extruder; - bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); - for (const LayerToPrint &layer_to_print : layers) { - if (layer_to_print.support_layer != nullptr) { - const SupportLayer &support_layer = *layer_to_print.support_layer; - const PrintObject &object = *support_layer.object(); - if (! support_layer.support_fills.entities.empty()) { - ExtrusionRole role = support_layer.support_fills.role(); - bool has_support = role == erMixed || role == erSupportMaterial; - bool has_interface = role == erMixed || role == erSupportMaterialInterface; - // Extruder ID of the support base. -1 if "don't care". - unsigned int support_extruder = object.config().support_material_extruder.value - 1; - // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes? - bool support_dontcare = object.config().support_material_extruder.value == 0; - // Extruder ID of the support interface. -1 if "don't care". - unsigned int interface_extruder = object.config().support_material_interface_extruder.value - 1; - // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes? - bool interface_dontcare = object.config().support_material_interface_extruder.value == 0; - if (support_dontcare || interface_dontcare) { - // Some support will be printed with "don't care" material, preferably non-soluble. - // Is the current extruder assigned a soluble filament? - unsigned int dontcare_extruder = first_extruder_id; - if (print.config().filament_soluble.get_at(dontcare_extruder)) { - // The last extruder printed on the previous layer extrudes soluble filament. - // Try to find a non-soluble extruder on the same layer. - for (unsigned int extruder_id : layer_tools.extruders) - if (! print.config().filament_soluble.get_at(extruder_id)) { - dontcare_extruder = extruder_id; - break; - } - } - if (support_dontcare) - support_extruder = dontcare_extruder; - if (interface_dontcare) - interface_extruder = dontcare_extruder; - } - // Both the support and the support interface are printed with the same extruder, therefore - // the interface may be interleaved with the support base. - bool single_extruder = ! has_support || support_extruder == interface_extruder; - // Assign an extruder to the base. - ObjectByExtruder &obj = object_by_extruder(by_extruder, has_support ? support_extruder : interface_extruder, &layer_to_print - layers.data(), layers.size()); - obj.support = &support_layer.support_fills; - obj.support_extrusion_role = single_extruder ? erMixed : erSupportMaterial; - if (! single_extruder && has_interface) { - ObjectByExtruder &obj_interface = object_by_extruder(by_extruder, interface_extruder, &layer_to_print - layers.data(), layers.size()); - obj_interface.support = &support_layer.support_fills; - obj_interface.support_extrusion_role = erSupportMaterialInterface; - } - } - } - 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 - // another logic instead: - // - we group all extrusions by extruder so that we minimize toolchanges - // - we start from the last used extruder - // - for each extruder, we group extrusions by island - // - for each island, we extrude perimeters first, unless user set the infill_first - // option - // (Still, we have to keep track of regions because we need to apply their config) - size_t n_slices = layer.lslices.size(); - const LayerSlices &layer_surfaces = layer.lslices_ex; - // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, - // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. - auto point_inside_surface = [&layer, &layer_surfaces](const size_t i, const Point &point) { - const BoundingBox &bbox = layer_surfaces[i].bbox; - return point(0) >= bbox.min(0) && point(0) < bbox.max(0) && - point(1) >= bbox.min(1) && point(1) < bbox.max(1) && - layer.lslices[i].contour.contains(point); - }; - - for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) { - const LayerRegion *layerm = layer.regions()[region_id]; - if (layerm == nullptr) - continue; - // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not - // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. - const PrintRegion ®ion = print.get_print_region(layerm->region().print_region_id()); - - // Now we must process perimeters and infills and create islands of extrusions in by_region std::map. - // It is also necessary to save which extrusions are part of MM wiping and which are not. - // The process is almost the same for perimeters and infills - we will do it in a cycle that repeats twice: - std::vector printing_extruders; - for (const ObjectByExtruder::Island::Region::Type entity_type : { ObjectByExtruder::Island::Region::INFILL, ObjectByExtruder::Island::Region::PERIMETERS }) { - for (const ExtrusionEntity *ee : (entity_type == ObjectByExtruder::Island::Region::INFILL) ? layerm->fills() : layerm->perimeters()) { - // extrusions represents infill or perimeter extrusions of a single island. - assert(dynamic_cast(ee) != nullptr); - const auto *extrusions = static_cast(ee); - if (extrusions->entities.empty()) // This shouldn't happen but first_point() would fail. - continue; - - // This extrusion is part of certain Region, which tells us which extruder should be used for it: - int correct_extruder_id = layer_tools.extruder(*extrusions, region); - - // 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 - // 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(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(- 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) - { - std::vector &islands = object_islands_by_extruder( - by_extruder, - extruder, - &layer_to_print - layers.data(), - layers.size(), n_slices+1); - for (size_t i = 0; i <= n_slices; ++ i) { - bool last = i == n_slices; - // Traverse lslices back to front: lslices are produced by traversing ClipperLib::PolyTree, emitting parent contour before its children. - // Therefore traversing layer_surfaces back to front will traverse children contours before their parents. - size_t island_idx = last ? n_slices : layer_surfaces.size() - i - 1; - if (// extrusions->first_point does not fit inside any slice - last || - // extrusions->first_point fits inside ith slice - point_inside_surface(island_idx, extrusions->first_point())) { - if (islands[island_idx].by_region.empty()) - islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); - islands[island_idx].by_region[region.print_region_id()].append(entity_type, extrusions, entity_overrides); - break; - } - } - } - } - } - } // for regions - } - } // for objects - // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. for (unsigned int extruder_id : layer_tools.extruders) { @@ -2387,91 +2205,30 @@ LayerResult GCode::process_layer( } - auto objects_by_extruder_it = by_extruder.find(extruder_id); - if (objects_by_extruder_it == by_extruder.end()) - continue; - - std::vector instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx); + std::vector instances_to_print = sort_print_object_instances(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 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) + //FIXME const_cast + bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); + if (is_anything_overridden) { + // Extrude wipes. + size_t gcode_size_old = gcode.size(); + for (const InstanceToPrint &instance : instances_to_print) + this->process_layer_single_object( + gcode, extruder_id, instance, + layers[instance.object_layer_to_print_id], layer_tools, + is_anything_overridden, true /* print_wipe_extrusions */); + if (gcode_size_old < gcode.size()) gcode+="; PURGING FINISHED\n"; - - for (InstanceToPrint &instance_to_print : instances_to_print) { - const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id]; - // To control print speed of the 1st object layer printed over raft interface. - bool object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 && - instance_to_print.print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id(); - m_config.apply(instance_to_print.print_object.config(), true); - m_layer = layer_to_print.layer(); - m_object_layer_over_raft = object_layer_over_raft; - if (m_config.avoid_crossing_perimeters) - m_avoid_crossing_perimeters.init_layer(*m_layer); - if (this->config().gcode_label_objects) - gcode += std::string("; 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"; - // When starting a new object, use the external motion planner for the first travel move. - const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; - std::pair this_object_copy(&instance_to_print.print_object, offset); - if (m_last_obj_copy != this_object_copy) - m_avoid_crossing_perimeters.use_external_mp_once(); - m_last_obj_copy = this_object_copy; - this->set_origin(unscale(offset)); - if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) { - m_layer = layer_to_print.support_layer; - m_object_layer_over_raft = false; - gcode += this->extrude_support( - // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. - instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role)); - m_layer = layer_to_print.layer(); - m_object_layer_over_raft = object_layer_over_raft; - } - //FIXME order islands? - // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) - 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(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. - if (print.config().infill_first) { - gcode += this->extrude_infill(print, by_region_specific, false); - gcode += this->extrude_perimeters(print, by_region_specific); - } else { - gcode += this->extrude_perimeters(print, by_region_specific); - gcode += this->extrude_infill(print,by_region_specific, false); - } - // ironing - 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"; - } } + // Extrude normal extrusions. + for (const InstanceToPrint &instance : instances_to_print) + this->process_layer_single_object( + gcode, extruder_id, instance, + layers[instance.object_layer_to_print_id], layer_tools, + is_anything_overridden, false /* print_wipe_extrusions */); } -#if 0 - // 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 - // 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. - if (m_spiral_vase) - gcode = m_spiral_vase->process_layer(std::move(gcode)); - - // Apply cooling logic; this may alter speeds. - if (m_cooling_buffer) - gcode = m_cooling_buffer->process_layer(std::move(gcode), layer.id(), - // Flush the cooling buffer at each object layer or possibly at the last layer, even if it contains just supports (This should not happen). - object_layer || last_layer); - - // Apply pressure equalization if enabled; - // printf("G-code before filter:\n%s\n", gcode.c_str()); - if (m_pressure_equalizer) - gcode = m_pressure_equalizer->process(gcode.c_str(), false); - // printf("G-code after filter:\n%s\n", out.c_str()); - - file.write(gcode); -#endif - BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); @@ -2480,6 +2237,214 @@ LayerResult GCode::process_layer( return result; } +static const auto comment_perimeter = "perimeter"sv; +// Comparing string_view pointer & length for speed. +static inline bool comment_is_perimeter(const std::string_view comment) { + return comment.data() == comment_perimeter.data() && comment.size() == comment_perimeter.size(); +} + +void GCode::process_layer_single_object( + // output + std::string &gcode, + // Index of the extruder currently active. + const unsigned int extruder_id, + // What object and instance is going to be printed. + const InstanceToPrint &print_instance, + // and the object & support layer of the above. + const ObjectLayerToPrint &layer_to_print, + // Container for extruder overrides (when wiping into object or infill). + const LayerTools &layer_tools, + // Is any extrusion possibly marked as wiping extrusion? + const bool is_anything_overridden, + // Round 1 (wiping into object or infill) or round 2 (normal extrusions). + const bool print_wipe_extrusions) +{ + //FIXME what the heck ID is this? Layer ID or Object ID? More likely an Object ID. + uint32_t layer_id = 0; + bool first = true; + // Delay layer initialization as many layers may not print with all extruders. + auto init_layer_delayed = [this, &print_instance, &layer_to_print, layer_id, &first, &gcode]() { + if (first) { + first = false; + const PrintObject &print_object = print_instance.print_object; + const Print &print = *print_object.print(); + m_config.apply(print_object.config(), true); + m_layer = layer_to_print.layer(); + if (print.config().avoid_crossing_perimeters) + m_avoid_crossing_perimeters.init_layer(*m_layer); + // When starting a new object, use the external motion planner for the first travel move. + const Point &offset = print_object.instances()[print_instance.instance_id].shift; + std::pair this_object_copy(&print_object, offset); + if (m_last_obj_copy != this_object_copy) + m_avoid_crossing_perimeters.use_external_mp_once(); + m_last_obj_copy = this_object_copy; + this->set_origin(unscale(offset)); + if (this->config().gcode_label_objects) + gcode += std::string("; printing object ") + print_object.model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(print_instance.instance_id) + "\n"; + } + }; + + const PrintObject &print_object = print_instance.print_object; + const Print &print = *print_object.print(); + + if (! print_wipe_extrusions && layer_to_print.support_layer != nullptr) + if (const SupportLayer &support_layer = *layer_to_print.support_layer; ! support_layer.support_fills.entities.empty()) { + ExtrusionRole role = support_layer.support_fills.role(); + bool has_support = role == erMixed || role == erSupportMaterial; + bool has_interface = role == erMixed || role == erSupportMaterialInterface; + // Extruder ID of the support base. -1 if "don't care". + unsigned int support_extruder = print_object.config().support_material_extruder.value - 1; + // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes? + bool support_dontcare = print_object.config().support_material_extruder.value == 0; + // Extruder ID of the support interface. -1 if "don't care". + unsigned int interface_extruder = print_object.config().support_material_interface_extruder.value - 1; + // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes? + bool interface_dontcare = print_object.config().support_material_interface_extruder.value == 0; + if (support_dontcare || interface_dontcare) { + // Some support will be printed with "don't care" material, preferably non-soluble. + // Is the current extruder assigned a soluble filament? + unsigned int dontcare_extruder = layer_tools.extruders.front(); + if (print.config().filament_soluble.get_at(dontcare_extruder)) { + // The last extruder printed on the previous layer extrudes soluble filament. + // Try to find a non-soluble extruder on the same layer. + for (unsigned int extruder_id : layer_tools.extruders) + if (! print.config().filament_soluble.get_at(extruder_id)) { + dontcare_extruder = extruder_id; + break; + } + } + if (support_dontcare) + support_extruder = dontcare_extruder; + if (interface_dontcare) + interface_extruder = dontcare_extruder; + } + bool extrude_support = has_support && support_extruder == extruder_id; + bool extrude_interface = interface_extruder && interface_extruder == extruder_id; + if (extrude_support || extrude_interface) { + init_layer_delayed(); + m_layer = layer_to_print.support_layer; + m_object_layer_over_raft = false; + gcode += this->extrude_support( + // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. + support_layer.support_fills.chained_path_from(m_last_pos, has_support ? (has_interface ? erMixed : erSupportMaterial) : erSupportMaterialInterface)); + } + } + + m_layer = layer_to_print.layer(); + // To control print speed of the 1st object layer printed over raft interface. + m_object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 && + print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id(); + + // Check whether this ExtrusionEntityCollection should be printed now with extruder_id, given print_wipe_extrusions + // (wipe extrusions are printed before regular extrusions). + auto shall_print_this_extrusion_collection = [extruder_id, instance_id = print_instance.instance_id, &layer_tools, is_anything_overridden, print_wipe_extrusions](const ExtrusionEntityCollection *eec, const PrintRegion ®ion) -> bool { + assert(eec != nullptr); + if (eec->entities.empty()) + // This shouldn't happen. FIXME why? but first_point() would fail. + return false; + // This extrusion is part of certain Region, which tells us which extruder should be used for it: + int correct_extruder_id = layer_tools.extruder(*eec, region); + 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 + // 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(); + } + //FIXME const_cast! + int extruder_override_id = is_anything_overridden ? const_cast(layer_tools).wiping_extrusions().get_extruder_override(eec, instance_id) : -1; + return print_wipe_extrusions ? + extruder_override_id == int(extruder_id) : + extruder_override_id < 0 && extruder_id == correct_extruder_id; + }; + + ExtrusionEntitiesPtr temp_fill_extrusions; + if (const Layer *layer = layer_to_print.object_layer; layer) + for (const LayerSlice &lslice : layer->lslices_ex) { + auto extrude_infill_range = [&](const LayerRegion &layerm, const ExtrusionEntityCollection &fills, const ExtrusionRange fill_range, bool ironing) { + // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not + // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. + const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); + temp_fill_extrusions.clear(); + for (uint32_t fill_id : fill_range) + if (auto *eec = static_cast(layerm.fills().entities[fill_id]); + (eec->role() == erIroning) == ironing && shall_print_this_extrusion_collection(eec, region)) { + if (eec->can_reverse()) + // Flatten the infill collection for better path planning. + for (auto *ee : eec->entities) + temp_fill_extrusions.emplace_back(ee); + else + temp_fill_extrusions.emplace_back(eec); + } + if (! temp_fill_extrusions.empty()) { + init_layer_delayed(); + m_config.apply(region.config()); + //FIXME The source extrusions may be reversed, thus modifying the extrusions! Is it a problem? How about the initial G-code preview? + // Will parallel access of initial G-code preview to these extrusions while reordering them at backend cause issues? + chain_and_reorder_extrusion_entities(temp_fill_extrusions, &m_last_pos); + const auto extrusion_name = ironing ? "ironing"sv : "infill"sv; + for (const ExtrusionEntity *fill : temp_fill_extrusions) + if (auto *eec = dynamic_cast(fill); eec) { + for (const 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); + } + }; + + //FIXME order islands? + // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) + for (const LayerIsland &island : lslice.islands) { + auto process_perimeters = [&]() { + const LayerRegion &layerm = *layer->get_region(island.perimeters.region()); + // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not + // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. + const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); + bool first = true; + for (uint32_t perimeter_id : island.perimeters) + if (const auto *eec = static_cast(layerm.perimeters().entities[perimeter_id]); + shall_print_this_extrusion_collection(eec, region)) { + assert(! eec->can_reverse()); + if (first) { + first = false; + init_layer_delayed(); + m_config.apply(region.config()); + } + for (const ExtrusionEntity *ee : *eec) + gcode += this->extrude_entity(*ee, comment_perimeter, -1.); + } + }; + auto process_infill = [&]() { + for (LayerExtrusionRange fill_range : island.fills) { + const LayerRegion &layerm = *layer->get_region(fill_range.region()); + extrude_infill_range(layerm, layerm.fills(), fill_range, false /* normal extrusions, not ironing */); + } + { + const LayerRegion &layerm = *layer->get_region(island.perimeters.region()); + extrude_infill_range(layerm, layerm.thin_fills(), island.thin_fills, false); + } + }; + if (print.config().infill_first) { + process_infill(); + process_perimeters(); + } else { + process_perimeters(); + process_infill(); + } + } + // ironing + //FIXME move ironing into the loop above over LayerIslands? + // First Ironing changes extrusion rate quickly, second single ironing may be done over multiple perimeter regions. + // Ironing in a second phase is safer, but it may be less efficient. + for (const LayerIsland &island : lslice.islands) { + for (LayerExtrusionRange fill_range : island.fills) { + const LayerRegion &layerm = *layer->get_region(fill_range.region()); + extrude_infill_range(layerm, layerm.fills(), fill_range, true /* ironing, not normal extrusions */); + } + } + } + if (! first && this->config().gcode_label_objects) + gcode += std::string("; stop printing object ") + print_object.model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(print_instance.instance_id) + "\n"; +} + void GCode::apply_print_config(const PrintConfig &print_config) { m_writer.apply_print_config(print_config); @@ -2569,12 +2534,6 @@ std::string GCode::change_layer(coordf_t print_z) return gcode; } -static const auto comment_perimeter = "perimeter"sv; -// Comparing string_view pointer & length for speed. -static inline bool comment_is_perimeter(const std::string_view comment) { - return comment.data() == comment_perimeter.data() && comment.size() == comment_perimeter.size(); -} - std::string GCode::extrude_loop(ExtrusionLoop loop, const std::string_view description, double speed) { // get a copy; don't modify the orientation of the original loop object otherwise @@ -2741,49 +2700,6 @@ std::string GCode::extrude_path(ExtrusionPath path, std::string_view description return gcode; } -// Extrude perimeters: Decide where to put seams (hide or align seams). -std::string GCode::extrude_perimeters(const Print &print, const std::vector &by_region) -{ - std::string gcode; - for (const ObjectByExtruder::Island::Region ®ion : by_region) - if (! region.perimeters.empty()) { - m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); - - for (const ExtrusionEntity* ee : region.perimeters) - gcode += this->extrude_entity(*ee, comment_perimeter, -1.); - } - return gcode; -} - -// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance. -std::string GCode::extrude_infill(const Print &print, const std::vector &by_region, bool ironing) -{ - std::string gcode; - ExtrusionEntitiesPtr extrusions; - const auto extrusion_name = ironing ? "ironing"sv : "infill"sv; - for (const ObjectByExtruder::Island::Region ®ion : 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.get_print_region(®ion - &by_region.front()).config()); - chain_and_reorder_extrusion_entities(extrusions, &m_last_pos); - for (const ExtrusionEntity *fill : extrusions) { - auto *eec = dynamic_cast(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; -} - std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fills) { static constexpr const auto support_label = "support material"sv; @@ -3302,104 +3218,4 @@ Point GCode::gcode_to_point(const Vec2d &point) const scale_(point(1) - m_origin(1) + extruder_offset(1))); } -// Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed -// during infill/perimeter wiping, or normally (depends on wiping_entities parameter) -// Fills in by_region_per_copy_cache and returns its reference. -const std::vector& GCode::ObjectByExtruder::Island::by_region_per_copy(std::vector &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities) const -{ - bool has_overrides = false; - for (const auto& reg : by_region) - if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) { - has_overrides = true; - break; - } - - // 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; - - // 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. - // Filter out the extrusions based on the infill_overrides / perimeter_overrides: - - for (const auto& reg : by_region) { - by_region_per_copy_cache.emplace_back(); // creates a region in the newly created Island - - // Now we are going to iterate through perimeters and infills and pick ones that are supposed to be printed - // References are used so that we don't have to repeat the same code - for (int iter = 0; iter < 2; ++iter) { - const ExtrusionEntitiesPtr& entities = (iter ? reg.infills : reg.perimeters); - ExtrusionEntitiesPtr& target_eec = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters); - const std::vector& overrides = (iter ? reg.infills_overrides : reg.perimeters_overrides); - - // 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) - target_eec.emplace_back(entities[i]); - } - } - } - return by_region_per_copy_cache; -} - -// This function takes the eec and appends its entities to either perimeters or infills of this Region (depending on the first parameter) -// It also saves pointer to ExtruderPerCopy struct (for each entity), that holds information about which extruders should be used for which copy. -void GCode::ObjectByExtruder::Island::Region::append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copies_extruder) -{ - // We are going to manipulate either perimeters or infills, exactly in the same way. Let's create pointers to the proper structure to not repeat ourselves: - ExtrusionEntitiesPtr* perimeters_or_infills; - std::vector* perimeters_or_infills_overrides; - - switch (type) { - case PERIMETERS: - perimeters_or_infills = &perimeters; - perimeters_or_infills_overrides = &perimeters_overrides; - break; - case INFILL: - perimeters_or_infills = &infills; - perimeters_or_infills_overrides = &infills_overrides; - break; - default: - throw Slic3r::InvalidArgument("Unknown parameter!"); - } - - // First we append the entities, there are eec->entities.size() of them: - size_t old_size = perimeters_or_infills->size(); - 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(eec)); - - if (copies_extruder != nullptr) { - // 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 4592402e3..28290e2e5 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -188,15 +188,16 @@ public: // Object and support extrusions of the same PrintObject at the same print_z. // public, so that it could be accessed by free helper functions from GCode.cpp - struct LayerToPrint + struct ObjectLayerToPrint { - LayerToPrint() : object_layer(nullptr), support_layer(nullptr) {} + ObjectLayerToPrint() : object_layer(nullptr), support_layer(nullptr) {} const Layer* object_layer; const SupportLayer* support_layer; const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; } const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; } coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; } }; + using ObjectsLayerToPrint = std::vector; private: class GCodeOutputStream { @@ -239,13 +240,13 @@ private: }; void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb); - static std::vector collect_layers_to_print(const PrintObject &object); - static std::vector>> collect_layers_to_print(const Print &print); + static ObjectsLayerToPrint collect_layers_to_print(const PrintObject &object); + static std::vector> collect_layers_to_print(const Print &print); LayerResult process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. - const std::vector &layers, + const ObjectsLayerToPrint &layers, const LayerTools &layer_tools, const bool last_layer, // Pairs of PrintObject index and its instance index. @@ -257,18 +258,18 @@ private: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. void process_layers( - const Print &print, - const ToolOrdering &tool_ordering, - const std::vector &print_object_instances_ordering, - const std::vector>> &layers_to_print, - GCodeOutputStream &output_stream); + const Print &print, + const ToolOrdering &tool_ordering, + const std::vector &print_object_instances_ordering, + const std::vector> &layers_to_print, + GCodeOutputStream &output_stream); // Process all layers of a single object instance (sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. void process_layers( const Print &print, const ToolOrdering &tool_ordering, - std::vector layers_to_print, + ObjectsLayerToPrint layers_to_print, const size_t single_object_idx, GCodeOutputStream &output_stream); @@ -282,71 +283,43 @@ private: std::string extrude_multi_path(ExtrusionMultiPath multipath, const std::string_view description, double speed = -1.); std::string extrude_path(ExtrusionPath path, const std::string_view description, double speed = -1.); - // Extruding multiple objects with soluble / non-soluble / combined supports - // on a multi-material printer, trying to minimize tool switches. - // Following structures sort extrusions by the extruder ID, by an order of objects and object islands. - struct ObjectByExtruder + struct InstanceToPrint { - ObjectByExtruder() : support(nullptr), support_extrusion_role(erNone) {} - const ExtrusionEntityCollection *support; - // erSupportMaterial / erSupportMaterialInterface or erMixed. - ExtrusionRole support_extrusion_role; + InstanceToPrint(size_t object_layer_to_print_id, const PrintObject &print_object, size_t instance_id) : + object_layer_to_print_id(object_layer_to_print_id), print_object(print_object), instance_id(instance_id) {} - struct Island - { - struct Region { - // Non-owned references to LayerRegion::perimeters::entities - // std::vector would be better here, but there is no way in C++ to convert from std::vector std::vector without copying. - ExtrusionEntitiesPtr perimeters; - // Non-owned references to LayerRegion::fills::entities - ExtrusionEntitiesPtr infills; - - std::vector infills_overrides; - std::vector perimeters_overrides; - - enum Type { - PERIMETERS, - INFILL, - }; - - // Appends perimeter/infill entities and writes don't indices of those that are not to be extruder as part of perimeter/infill wiping - void append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copy_extruders); - }; - - - std::vector by_region; // all extrusions for this island, grouped by regions - - // Fills in by_region_per_copy_cache and returns its reference. - const std::vector& by_region_per_copy(std::vector &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities = false) const; - }; - std::vector islands; + // Index into std::vector, which contains Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. + const size_t object_layer_to_print_id; + const PrintObject &print_object; + // Instance idx of the copy of a print object. + const size_t instance_id; }; - struct InstanceToPrint - { - InstanceToPrint(ObjectByExtruder &object_by_extruder, size_t layer_id, const PrintObject &print_object, size_t instance_id) : - object_by_extruder(object_by_extruder), layer_id(layer_id), print_object(print_object), instance_id(instance_id) {} + std::vector sort_print_object_instances( + // Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. + const std::vector &layers, + // Ordering must be defined for normal (non-sequential print). + const std::vector *ordering, + // For sequential print, the instance of the object to be printing has to be defined. + const size_t single_object_instance_idx); - // Repository - ObjectByExtruder &object_by_extruder; - // Index into std::vector, which contains Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. - const size_t layer_id; - const PrintObject &print_object; - // Instance idx of the copy of a print object. - const size_t instance_id; - }; + // This function will be called for each printing extruder, possibly twice: First for wiping extrusions, second for normal extrusions. + void process_layer_single_object( + // output + std::string &gcode, + // Index of the extruder currently active. + const unsigned int extruder_id, + // What object and instance is going to be printed. + const InstanceToPrint &print_instance, + // and the object & support layer of the above. + const ObjectLayerToPrint &layer_to_print, + // Container for extruder overrides (when wiping into object or infill). + const LayerTools &layer_tools, + // Is any extrusion possibly marked as wiping extrusion? + const bool is_anything_overridden, + // Round 1 (wiping into object or infill) or round 2 (normal extrusions). + const bool print_wipe_extrusions); - std::vector sort_print_object_instances( - std::vector &objects_by_extruder, - // Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. - const std::vector &layers, - // Ordering must be defined for normal (non-sequential print). - const std::vector *ordering, - // For sequential print, the instance of the object to be printing has to be defined. - const size_t single_object_instance_idx); - - std::string extrude_perimeters(const Print &print, const std::vector &by_region); - std::string extrude_infill(const Print &print, const std::vector &by_region, bool ironing); std::string extrude_support(const ExtrusionEntityCollection &support_fills); std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); @@ -438,18 +411,6 @@ private: // To control print speed of 1st object layer over raft interface. bool object_layer_over_raft() const { return m_object_layer_over_raft; } - friend ObjectByExtruder& object_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects); - friend std::vector& object_islands_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects, - size_t num_islands); - friend class Wipe; friend class WipeTowerIntegration; friend class PressureEqualizer; diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 6c5d0667a..7f14203a9 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -103,7 +103,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude } double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height); - // Collect extruders reuqired to print the layers. + // Collect extruders required to print the layers. this->collect_extruders(object, std::vector>()); // Reorder the extruders to minimize tool switches. @@ -598,14 +598,15 @@ const LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) const // This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual) void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies) { - something_overridden = true; + m_something_overridden = true; - auto entity_map_it = (entity_map.emplace(entity, ExtruderPerCopy())).first; // (add and) return iterator + auto entity_map_it = (m_entity_map.emplace(entity, ExtruderPerCopy())).first; // (add and) return iterator ExtruderPerCopy& copies_vector = entity_map_it->second; copies_vector.resize(num_of_copies, -1); + assert(copies_vector[copy_id] == -1); if (copies_vector[copy_id] != -1) - std::cout << "ERROR: Entity extruder overriden multiple times!!!\n"; // A debugging message - this must never happen. + BOOST_LOG_TRIVIAL(error) << "ERROR: Entity extruder overriden multiple times!!!"; copies_vector[copy_id] = extruder; } @@ -649,13 +650,19 @@ bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, con // Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange // and returns volume that is left to be wiped on the wipe tower. +// Switching from old_extruder to new_extruder, trying to wipe volume_to_wipe into not yet extruded extrusions, that may change material (overridable). float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe) { const LayerTools& lt = *m_layer_tools; const float min_infill_volume = 0.f; // ignore infill with smaller volume than this - if (! this->something_overridable || volume_to_wipe <= 0. || print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder)) - return std::max(0.f, volume_to_wipe); // Soluble filament cannot be wiped in a random infill, neither the filament after it + if (! m_something_overridable || volume_to_wipe <= 0. || + // Don't wipe a soluble filament into another object. + print.config().filament_soluble.get_at(old_extruder) || + // Don't prime a soluble filament into another object. + print.config().filament_soluble.get_at(new_extruder)) + // Soluble filament cannot be wiped in a random infill, neither the filament after it + return std::max(0.f, volume_to_wipe); // we will sort objects so that dedicated for wiping are at the beginning: ConstPrintObjectPtrs object_list(print.objects().begin(), print.objects().end()); @@ -742,7 +749,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int // them again and make sure we override it. void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) { - if (! this->something_overridable) + if (! m_something_overridable) return; const LayerTools& lt = *m_layer_tools; @@ -775,8 +782,8 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) // Either way, we will now force-override it with something suitable: if (print.config().infill_first || object->config().wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely - || lt.is_extruder_order(lt.perimeter_extruder(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints - || ! lt.has_extruder(lt.infill_extruder(region)))) // we have to force override - this could violate infill_first (FIXME) + || lt.is_extruder_order(lt.perimeter_extruder(region), last_nonsoluble_extruder) // !infill_first, but perimeter is already printed when last extruder prints + || ! lt.has_extruder(lt.infill_extruder(region))) // we have to force override - this could violate infill_first (FIXME) set_extruder_override(fill, copy, (print.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies); else { // In this case we can (and should) leave it to be printed normally. @@ -795,24 +802,4 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) } } -// Following function is called from GCode::process_layer and returns pointer to vector with information about which extruders should be used for given copy of this entity. -// If this extrusion does not have any override, nullptr is returned. -// Otherwise it modifies the vector in place and changes all -1 to correct_extruder_id (at the time the overrides were created, correct extruders were not known, -// so -1 was used as "print as usual"). -// The resulting vector therefore keeps track of which extrusions are the ones that were overridden and which were not. If the extruder used is overridden, -// its number is saved as is (zero-based index). Regular extrusions are saved as -number-1 (unfortunately there is no negative zero). -const WipingExtrusions::ExtruderPerCopy* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies) -{ - ExtruderPerCopy *overrides = nullptr; - auto entity_map_it = entity_map.find(entity); - if (entity_map_it != entity_map.end()) { - overrides = &entity_map_it->second; - overrides->resize(num_of_copies, -1); - // Each -1 now means "print as usual" - we will replace it with actual extruder id (shifted it so we don't lose that information): - std::replace(overrides->begin(), overrides->end(), -1, -correct_extruder_id-1); - } - return overrides; -} - - } // namespace Slic3r diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 036730325..d043c48ad 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -24,14 +24,19 @@ class WipingExtrusions { public: bool is_anything_overridden() const { // if there are no overrides, all the agenda can be skipped - this function can tell us if that's the case - return something_overridden; + return m_something_overridden; } // When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place. typedef boost::container::small_vector ExtruderPerCopy; - // This is called from GCode::process_layer - see implementation for further comments: - const ExtruderPerCopy* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies); + // This is called from GCode::process_layer_single_object() + // Returns positive number if the extruder is overridden. + // Returns -1 if not. + int get_extruder_override(const ExtrusionEntity* entity, uint32_t instance_id) const { + auto entity_map_it = m_entity_map.find(entity); + return entity_map_it == m_entity_map.end() ? -1 : entity_map_it->second[instance_id]; + } // This function goes through all infill entities, decides which ones will be used for wiping and // marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower: @@ -42,7 +47,7 @@ public: bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const; bool is_overriddable_and_mark(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) { bool out = this->is_overriddable(ee, print_config, object, region); - this->something_overridable |= out; + m_something_overridable |= out; return out; } @@ -57,13 +62,13 @@ private: // Returns true in case that entity is not printed with its usual extruder for a given copy: bool is_entity_overridden(const ExtrusionEntity* entity, size_t copy_id) const { - auto it = entity_map.find(entity); - return it == entity_map.end() ? false : it->second[copy_id] != -1; + auto it = m_entity_map.find(entity); + return it == m_entity_map.end() ? false : it->second[copy_id] != -1; } - std::map entity_map; // to keep track of who prints what - bool something_overridable = false; - bool something_overridden = false; + std::map m_entity_map; // to keep track of who prints what + bool m_something_overridable = false; + bool m_something_overridden = false; const LayerTools* m_layer_tools = nullptr; // so we know which LayerTools object this belongs to }; diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 1561a2869..95c9cf50e 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -507,16 +507,43 @@ void Layer::sort_perimeters_into_islands( for (const ExPolygon &expolygon : fill_expolygons) fill_expolygons_bboxes.emplace_back(get_extents(expolygon)); + + // Take one sample point for each source slice, to be used to sort source slices into layer slices. + // source slice index + its sample. + std::vector> perimeter_slices_queue; + perimeter_slices_queue.reserve(slices.size()); + for (uint32_t islice = 0; islice < uint32_t(slices.size()); ++ islice) { + const std::pair &extrusions = perimeter_and_gapfill_ranges[islice]; + Point sample; + bool sample_set = false; + if (! extrusions.first.empty()) { + sample = this_layer_region.perimeters().entities[*extrusions.first.begin()]->first_point(); + sample_set = true; + } else if (! extrusions.second.empty()) { + sample = this_layer_region.thin_fills().entities[*extrusions.second.begin()]->first_point(); + sample_set = true; + } else { + for (uint32_t iexpoly : fill_expolygons_ranges[islice]) + if (const ExPolygon &expoly = fill_expolygons[iexpoly]; ! expoly.empty()) { + sample = expoly.contour.points.front(); + sample_set = true; + break; + } + } + // There may be a valid empty island. + // assert(sample_set); + if (sample_set) + perimeter_slices_queue.emplace_back(islice, sample); + } + // Map of source fill_expolygon into region and fill_expolygon of that region. // -1: not set std::vector> map_expolygon_to_region_and_fill; - + const bool has_multiple_regions = layer_region_ids.size() > 1; + assert(has_multiple_regions || layer_region_ids.size() == 1); // assign fill_surfaces to each layer if (! fill_expolygons.empty()) { - if (layer_region_ids.size() == 1) { - this_layer_region.m_fill_expolygons = std::move(fill_expolygons); - this_layer_region.m_fill_expolygons_bboxes = std::move(fill_expolygons_bboxes); - } else { + if (has_multiple_regions) { // Sort the bounding boxes lexicographically. std::vector fill_expolygons_bboxes_sorted(fill_expolygons_bboxes.size()); std::iota(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), 0); @@ -550,106 +577,133 @@ void Layer::sort_perimeters_into_islands( } } } + } else { + this_layer_region.m_fill_expolygons = std::move(fill_expolygons); + this_layer_region.m_fill_expolygons_bboxes = std::move(fill_expolygons_bboxes); } } - // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, - // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. - auto point_inside_surface = [this](const size_t lslice_idx, const Point &point) { - const BoundingBox &bbox = this->lslices_ex[lslice_idx].bbox; - return point.x() >= bbox.min.x() && point.x() < bbox.max.x() && - point.y() >= bbox.min.y() && point.y() < bbox.max.y() && - this->lslices[lslice_idx].contour.contains(point); - }; - - // Take one sample point for each source slice, to be used to sort source slices into layer slices. - // source slice index + its sample. - std::vector> perimeter_slices_queue; - perimeter_slices_queue.reserve(slices.size()); - for (uint32_t islice = 0; islice < uint32_t(slices.size()); ++ islice) { - const std::pair &extrusions = perimeter_and_gapfill_ranges[islice]; - Point sample; - bool sample_set = false; - if (! extrusions.first.empty()) { - sample = this_layer_region.perimeters().entities[*extrusions.first.begin()]->first_point(); - sample_set = true; - } else if (! extrusions.second.empty()) { - sample = this_layer_region.thin_fills().entities[*extrusions.second.begin()]->first_point(); - sample_set = true; - } else if (const ExPolygonRange &fill_expolygon_range = fill_expolygons_ranges[islice]; ! fill_expolygons.empty()) { - for (uint32_t iexpoly : fill_expolygon_range) - if (const ExPolygon &expoly = fill_expolygons[iexpoly]; ! expoly.empty()) { - sample = expoly.contour.points.front(); - sample_set = true; - break; - } - } - if (sample_set) - perimeter_slices_queue.emplace_back(islice, sample); - } - // Sort perimeter extrusions, thin fill extrusions and fill expolygons into islands. std::vector region_fill_sorted_last; - for (int lslice_idx = int(this->lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx) { + auto insert_into_island = [ + // Region where the perimeters, gap fills and fill expolygons are stored. + region_id, + // Whether there are infills with different regions generated for this LayerSlice. + has_multiple_regions, + // Perimeters and gap fills to be sorted into islands. + &perimeter_and_gapfill_ranges, + // Infill regions to be sorted into islands. + &fill_expolygons, &fill_expolygons_bboxes, &fill_expolygons_ranges, + // Mapping of fill_expolygon to region and its infill. + &map_expolygon_to_region_and_fill, + // Output + ®ions = m_regions, &lslices_ex = this->lslices_ex, + // fill_expolygons and fill_expolygons_bboxes need to be sorted into contiguous sequence by island, + // thus region_fill_sorted_last contains last fill_expolygon processed (meaning sorted). + ®ion_fill_sorted_last] + (int lslice_idx, int source_slice_idx) { + lslices_ex[lslice_idx].islands.push_back({}); + LayerIsland &island = lslices_ex[lslice_idx].islands.back(); + island.perimeters = LayerExtrusionRange(region_id, perimeter_and_gapfill_ranges[source_slice_idx].first); + island.thin_fills = perimeter_and_gapfill_ranges[source_slice_idx].second; + if (ExPolygonRange fill_range = fill_expolygons_ranges[source_slice_idx]; ! fill_range.empty()) { + if (has_multiple_regions) { + // Check whether the fill expolygons of this island were split into multiple regions. + island.fill_region_id = LayerIsland::fill_region_composite_id; + for (uint32_t fill_idx : fill_range) { + const std::pair &kvp = map_expolygon_to_region_and_fill[fill_idx]; + if (kvp.first == -1 || (island.fill_region_id != -1 && island.fill_region_id != kvp.second)) { + island.fill_region_id = LayerIsland::fill_region_composite_id; + break; + } else + island.fill_region_id = kvp.second; + } + if (island.fill_expolygons_composite()) { + // They were split, thus store the unsplit "composite" expolygons into the region of perimeters. + LayerRegion &this_layer_region = *regions[region_id]; + auto begin = uint32_t(this_layer_region.fill_expolygons_composite().size()); + this_layer_region.m_fill_expolygons_composite.reserve(this_layer_region.fill_expolygons_composite().size() + fill_range.size()); + std::move(fill_expolygons.begin() + *fill_range.begin(), fill_expolygons.begin() + *fill_range.end(), std::back_inserter(this_layer_region.m_fill_expolygons_composite)); + this_layer_region.m_fill_expolygons_composite_bboxes.insert(this_layer_region.m_fill_expolygons_composite_bboxes.end(), + fill_expolygons_bboxes.begin() + *fill_range.begin(), fill_expolygons_bboxes.begin() + *fill_range.end()); + island.fill_expolygons = ExPolygonRange(begin, uint32_t(this_layer_region.fill_expolygons_composite().size())); + } else { + if (region_fill_sorted_last.empty()) + region_fill_sorted_last.assign(regions.size(), 0); + uint32_t &last = region_fill_sorted_last[island.fill_region_id]; + // They were not split and they belong to the same region. + // Sort the region m_fill_expolygons to a continuous span. + uint32_t begin = last; + LayerRegion &layerm = *regions[island.fill_region_id]; + for (uint32_t fill_id : fill_range) { + uint32_t region_fill_id = map_expolygon_to_region_and_fill[fill_id].second; + assert(region_fill_id >= last); + if (region_fill_id > last) { + std::swap(layerm.m_fill_expolygons[region_fill_id], layerm.m_fill_expolygons[last]); + std::swap(layerm.m_fill_expolygons_bboxes[region_fill_id], layerm.m_fill_expolygons_bboxes[last]); + } + ++ last; + } + island.fill_expolygons = ExPolygonRange(begin, last); + } + } else { + // Layer island is made of one fill region only. + island.fill_expolygons = fill_range; + island.fill_region_id = region_id; + } + } + }; + + // First sort into islands using exact fit. + // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, + // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. + auto point_inside_surface = [&lslices = this->lslices, &lslices_ex = this->lslices_ex](size_t lslice_idx, const Point &point) { + const BoundingBox &bbox = lslices_ex[lslice_idx].bbox; + return point.x() >= bbox.min.x() && point.x() < bbox.max.x() && + point.y() >= bbox.min.y() && point.y() < bbox.max.y() && + // Exact match: Don't just test whether a point is inside the outer contour of an island, + // test also whether the point is not inside some hole of the same expolygon. + // This is unfortunatelly necessary because the point may be inside an expolygon of one of this expolygon's hole + // and missed due to numerical issues. + lslices[lslice_idx].contains(point); + }; + for (int lslice_idx = int(lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx) for (auto it_source_slice = perimeter_slices_queue.begin(); it_source_slice != perimeter_slices_queue.end(); ++ it_source_slice) if (point_inside_surface(lslice_idx, it_source_slice->second)) { - this->lslices_ex[lslice_idx].islands.push_back({}); - LayerIsland &island = this->lslices_ex[lslice_idx].islands.back(); - const uint32_t source_slice_idx = it_source_slice->first; - island.perimeters = LayerExtrusionRange(region_id, perimeter_and_gapfill_ranges[source_slice_idx].first); - island.thin_fills = perimeter_and_gapfill_ranges[source_slice_idx].second; - if (ExPolygonRange fill_range = fill_expolygons_ranges[source_slice_idx]; ! fill_range.empty()) { - if (layer_region_ids.size() == 1) { - // Layer island is made of one fill region only. - island.fill_expolygons = fill_range; - island.fill_region_id = region_id; - } else { - // Check whether the fill expolygons of this island were split into multiple regions. - island.fill_region_id = LayerIsland::fill_region_composite_id; - for (uint32_t fill_idx : fill_range) { - const std::pair &kvp = map_expolygon_to_region_and_fill[fill_idx]; - if (kvp.first == -1 || (island.fill_region_id != -1 && island.fill_region_id != kvp.second)) { - island.fill_region_id = LayerIsland::fill_region_composite_id; - break; - } else - island.fill_region_id = kvp.second; - } - if (island.fill_expolygons_composite()) { - // They were split, thus store the unsplit "composite" expolygons into the region of perimeters. - auto begin = uint32_t(this_layer_region.fill_expolygons_composite().size()); - this_layer_region.m_fill_expolygons_composite.reserve(this_layer_region.fill_expolygons_composite().size() + fill_range.size()); - std::move(fill_expolygons.begin() + *fill_range.begin(), fill_expolygons.begin() + *fill_range.end(), std::back_inserter(this_layer_region.m_fill_expolygons_composite)); - this_layer_region.m_fill_expolygons_composite_bboxes.insert(this_layer_region.m_fill_expolygons_composite_bboxes.end(), - fill_expolygons_bboxes.begin() + *fill_range.begin(), fill_expolygons_bboxes.begin() + *fill_range.end()); - island.fill_expolygons = ExPolygonRange(begin, uint32_t(this_layer_region.fill_expolygons_composite().size())); - } else { - if (region_fill_sorted_last.empty()) - region_fill_sorted_last.assign(m_regions.size(), 0); - uint32_t &last = region_fill_sorted_last[island.fill_region_id]; - // They were not split and they belong to the same region. - // Sort the region m_fill_expolygons to a continuous span. - uint32_t begin = last; - LayerRegion &layerm = *m_regions[island.fill_region_id]; - for (uint32_t fill_id : fill_range) { - uint32_t region_fill_id = map_expolygon_to_region_and_fill[fill_id].second; - assert(region_fill_id >= last); - if (region_fill_id > last) { - std::swap(layerm.m_fill_expolygons[region_fill_id], layerm.m_fill_expolygons[last]); - std::swap(layerm.m_fill_expolygons_bboxes[region_fill_id], layerm.m_fill_expolygons_bboxes[last]); - } - ++ last; - } - island.fill_expolygons = ExPolygonRange(begin, last); - } - } - } - if (std::next(it_source_slice) != perimeter_slices_queue.end()) { + insert_into_island(lslice_idx, it_source_slice->first); + if (std::next(it_source_slice) != perimeter_slices_queue.end()) // Remove the current slice & point pair from the queue. *it_source_slice = perimeter_slices_queue.back(); - perimeter_slices_queue.pop_back(); - } + perimeter_slices_queue.pop_back(); break; } + // If anything fails to be sorted in using exact fit, try to find a closest island. + auto point_inside_surface_dist2 = + [&lslices = this->lslices, &lslices_ex = this->lslices_ex, bbox_eps = scaled(this->object()->print()->config().gcode_resolution.value) + SCALED_EPSILON] + (const size_t lslice_idx, const Point &point) { + const BoundingBox &bbox = lslices_ex[lslice_idx].bbox; + return + point.x() < bbox.min.x() - bbox_eps || point.x() > bbox.max.x() + bbox_eps || + point.y() < bbox.min.y() - bbox_eps || point.y() > bbox.max.y() + bbox_eps ? + std::numeric_limits::max() : + (lslices[lslice_idx].point_projection(point) - point).cast().squaredNorm(); + }; + for (int lslice_idx = int(lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx) { + double d2min = std::numeric_limits::max(); + auto it_source_slice = perimeter_slices_queue.end(); + for (auto it = perimeter_slices_queue.begin(); it != perimeter_slices_queue.end(); ++ it) + if (double d2 = point_inside_surface_dist2(lslice_idx, it->second); d2 < d2min) { + d2min = d2; + it_source_slice = it; + } + assert(it_source_slice != perimeter_slices_queue.end()); + if (it_source_slice != perimeter_slices_queue.end()) { + insert_into_island(lslice_idx, it_source_slice->first); + if (std::next(it_source_slice) != perimeter_slices_queue.end()) + // Remove the current slice & point pair from the queue. + *it_source_slice = perimeter_slices_queue.back(); + perimeter_slices_queue.pop_back(); + } } } diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index d7d0fbffb..45665410d 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -79,8 +79,8 @@ private: uint32_t m_region { 0 }; }; -// Most likely one LayerIsland will be filled with maximum one fill type. -static constexpr const size_t LayerExtrusionRangesStaticSize = 1; +// One LayerIsland may be filled with solid fill, sparse fill, top / bottom fill. +static constexpr const size_t LayerExtrusionRangesStaticSize = 3; using LayerExtrusionRanges = #ifdef NDEBUG // To reduce memory allocation in release mode. diff --git a/t/shells.t b/t/shells.t index 29bc0b5f0..cd96f9cde 100644 --- a/t/shells.t +++ b/t/shells.t @@ -1,4 +1,4 @@ -use Test::More tests => 20; +use Test::More tests => 17; use strict; use warnings; @@ -13,73 +13,6 @@ use Slic3r; use Slic3r::Geometry qw(epsilon); use Slic3r::Test; -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 0); - $config->set('solid_infill_speed', 99); - $config->set('top_solid_infill_speed', 99); - $config->set('bridge_speed', 72); - $config->set('first_layer_speed', '100%'); - $config->set('cooling', [ 0 ]); - - my $test = sub { - my ($conf) = @_; - $conf ||= $config; - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - - my %z = (); # Z => 1 - my %layers_with_solid_infill = (); # Z => $count - my %layers_with_bridge_infill = (); # Z => $count - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($self->Z > 0) { - $z{ $self->Z } = 1; - if ($info->{extruding} && $info->{dist_XY} > 0) { - my $F = $args->{F} // $self->F; - $layers_with_solid_infill{$self->Z} = 1 - if $F == $config->solid_infill_speed*60; - $layers_with_bridge_infill{$self->Z} = 1 - if $F == $config->bridge_speed*60; - } - } - }); - my @z = sort { $a <=> $b } keys %z; - my @shells = map $layers_with_solid_infill{$_} || $layers_with_bridge_infill{$_}, @z; - fail "insufficient number of bottom solid layers" - unless !defined(first { !$_ } @shells[0..$config->bottom_solid_layers-1]); - fail "excessive number of bottom solid layers" - unless scalar(grep $_, @shells[0 .. $#shells/2]) == $config->bottom_solid_layers; - fail "insufficient number of top solid layers" - unless !defined(first { !$_ } @shells[-$config->top_solid_layers..-1]); - fail "excessive number of top solid layers" - unless scalar(grep $_, @shells[($#shells/2)..$#shells]) == $config->top_solid_layers; - if ($config->top_solid_layers > 0) { - fail "unexpected solid infill speed in first solid layer over sparse infill" - if $layers_with_solid_infill{ $z[-$config->top_solid_layers] }; - die "bridge speed not used in first solid layer over sparse infill" - if !$layers_with_bridge_infill{ $z[-$config->top_solid_layers] }; - } - 1; - }; - - $config->set('top_solid_layers', 3); - $config->set('bottom_solid_layers', 3); - ok $test->(), "proper number of shells is applied"; - - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - ok $test->(), "no shells are applied when both top and bottom are set to zero"; - - $config->set('perimeters', 1); - $config->set('top_solid_layers', 3); - $config->set('bottom_solid_layers', 3); - $config->set('fill_density', 0); - ok $test->(), "proper number of shells is applied even when fill density is none"; -} - # issue #1161 { my $config = Slic3r::Config::new_from_defaults; diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 0b3b920b9..6e9a80ef8 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -21,6 +21,7 @@ add_executable(${_TEST_NAME}_tests test_print.cpp test_printgcode.cpp test_printobject.cpp + test_shells.cpp test_skirt_brim.cpp test_support_material.cpp test_thin_walls.cpp diff --git a/tests/fff_print/test_shells.cpp b/tests/fff_print/test_shells.cpp new file mode 100644 index 000000000..df0e8f6bb --- /dev/null +++ b/tests/fff_print/test_shells.cpp @@ -0,0 +1,112 @@ +#include + +#include "libslic3r/GCodeReader.hpp" + +#include "test_data.hpp" // get access to init_print, etc + +using namespace Slic3r::Test; +using namespace Slic3r; + +SCENARIO("Shells", "[Shells]") { + GIVEN("20mm box") { + auto test = [](const DynamicPrintConfig &config){ + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + + std::vector zs; + std::set layers_with_solid_infill; + std::set layers_with_bridge_infill; + const double solid_infill_speed = config.opt_float("solid_infill_speed") * 60; + const double bridge_speed = config.opt_float("bridge_speed") * 60; + GCodeReader parser; + parser.parse_buffer(gcode, + [&zs, &layers_with_solid_infill, &layers_with_bridge_infill, solid_infill_speed, bridge_speed] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + double z = line.new_Z(self); + REQUIRE(z >= 0); + if (z > 0) { + coord_t scaled_z = scaled(z); + zs.emplace_back(scaled_z); + if (line.extruding(self) && line.dist_XY(self) > 0) { + double f = line.new_F(self); + if (std::abs(f - solid_infill_speed) < EPSILON) + layers_with_solid_infill.insert(scaled_z); + if (std::abs(f - bridge_speed) < EPSILON) + layers_with_bridge_infill.insert(scaled_z); + } + } + }); + sort_remove_duplicates(zs); + + auto has_solid_infill = [&layers_with_solid_infill](coord_t z) { return layers_with_solid_infill.find(z) != layers_with_solid_infill.end(); }; + auto has_bridge_infill = [&layers_with_bridge_infill](coord_t z) { return layers_with_bridge_infill.find(z) != layers_with_bridge_infill.end(); }; + auto has_shells = [&has_solid_infill, &has_bridge_infill, &zs](int layer_idx) { coord_t z = zs[layer_idx]; return has_solid_infill(z) || has_bridge_infill(z); }; + const int bottom_solid_layers = config.opt_int("bottom_solid_layers"); + const int top_solid_layers = config.opt_int("top_solid_layers"); + THEN("correct number of bottom solid layers") { + for (int i = 0; i < bottom_solid_layers; ++ i) + REQUIRE(has_shells(i)); + for (int i = bottom_solid_layers; i < int(zs.size() / 2); ++ i) + REQUIRE(! has_shells(i)); + } + THEN("correct number of top solid layers") { + for (int i = 0; i < top_solid_layers; ++ i) + REQUIRE(has_shells(int(zs.size()) - i - 1)); + for (int i = top_solid_layers; i < int(zs.size() / 2); ++ i) + REQUIRE(! has_shells(int(zs.size()) - i - 1)); + } + if (top_solid_layers > 0) { + THEN("solid infill speed is used on solid infill") { + for (int i = 0; i < top_solid_layers - 1; ++ i) { + auto z = zs[int(zs.size()) - i - 1]; + REQUIRE(has_solid_infill(z)); + REQUIRE(! has_bridge_infill(z)); + } + } + THEN("bridge used in first solid layer over sparse infill") { + auto z = zs[int(zs.size()) - top_solid_layers]; + REQUIRE(! has_solid_infill(z)); + REQUIRE(has_bridge_infill(z)); + } + } + }; + + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "skirts", 0 }, + { "perimeters", 0 }, + { "solid_infill_speed", 99 }, + { "top_solid_infill_speed", 99 }, + { "bridge_speed", 72 }, + { "first_layer_speed", "100%" }, + { "cooling", "0" } + }); + + WHEN("three top and bottom layers") { + // proper number of shells is applied + config.set_deserialize_strict({ + { "top_solid_layers", 3 }, + { "bottom_solid_layers", 3 } + }); + test(config); + } + + WHEN("zero top and bottom layers") { + // no shells are applied when both top and bottom are set to zero + config.set_deserialize_strict({ + { "top_solid_layers", 0 }, + { "bottom_solid_layers", 0 } + }); + test(config); + } + + WHEN("three top and bottom layers, zero infill") { + // proper number of shells is applied even when fill density is none + config.set_deserialize_strict({ + { "perimeters", 1 }, + { "top_solid_layers", 3 }, + { "bottom_solid_layers", 3 } + }); + test(config); + } + } +}