diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index ce52ae152..dfc180689 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -5,6 +5,8 @@ #include "Polygon.hpp" #include "Polyline.hpp" +#include + namespace Slic3r { class ExPolygonCollection; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 0ccc3ddf5..8bd42e59a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6,6 +6,7 @@ #include "Geometry.hpp" #include "GCode/PrintExtents.hpp" #include "GCode/WipeTower.hpp" +#include "ShortestPath.hpp" #include "Utils.hpp" #include @@ -1160,7 +1161,7 @@ void GCode::_do_export(Print &print, FILE *file) for (const LayerToPrint <p : layers_to_print) { std::vector lrs; lrs.emplace_back(std::move(ltp)); - this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), © - object.copies().data()); + this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), nullptr, © - object.copies().data()); print.throw_if_canceled(); } #ifdef HAS_PRESSURE_EQUALIZER @@ -1174,12 +1175,8 @@ void GCode::_do_export(Print &print, FILE *file) } } } else { - // Order objects using a nearest neighbor search. - std::vector object_indices; - Points object_reference_points; - for (PrintObject *object : print.objects()) - object_reference_points.push_back(object->copies().front()); - Slic3r::Geometry::chained_path(object_reference_points, object_indices); + // Order object instances using a nearest neighbor search. + std::vector> print_object_instances_ordering = chain_print_object_instances(print); // 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); @@ -1218,7 +1215,7 @@ void GCode::_do_export(Print &print, FILE *file) 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(); - this->process_layer(file, print, layer.second, layer_tools, size_t(-1)); + this->process_layer(file, print, layer.second, layer_tools, &print_object_instances_ordering, size_t(-1)); print.throw_if_canceled(); } #ifdef HAS_PRESSURE_EQUALIZER @@ -1529,8 +1526,54 @@ inline std::vector& object_islands_by_extruder( return islands; } +std::vector GCode::sort_print_object_instances( + std::vector &objects_by_extruder, + 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::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); + } + } 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); + } + std::sort(sorted.begin(), sorted.end()); + + if (! sorted.empty()) { + const Print &print = *sorted.front().first->print(); + out.reserve(sorted.size()); + for (const std::pair &instance_id : *ordering) { + const PrintObject &print_object = *print.objects()[instance_id.first]; + std::pair 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_id.second); + } + } + } + return out; +} + // In sequential mode, process_layer is called once per each object and its copy, -// therefore layers will contain a single entry and single_object_idx will point to the copy of the object. +// 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 // and performing the extruder specific extrusions together. @@ -1541,14 +1584,16 @@ void GCode::process_layer( // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, const LayerTools &layer_tools, + // Pairs of PrintObject index and its instance index. + const std::vector> *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_idx) + const size_t single_object_instance_idx) { assert(! layers.empty()); // assert(! layer_tools.extruders.empty()); // Either printing all copies of all objects, or just a single copy of a single object. - assert(single_object_idx == size_t(-1) || layers.size() == 1); + assert(single_object_instance_idx == size_t(-1) || layers.size() == 1); if (layer_tools.extruders.empty()) // Nothing to extrude. @@ -1883,62 +1928,49 @@ void GCode::process_layer( 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); + // 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): bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); 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"; - for (ObjectByExtruder &object_by_extruder : objects_by_extruder_it->second) { - const size_t layer_id = &object_by_extruder - objects_by_extruder_it->second.data(); - const PrintObject *print_object = layers[layer_id].object(); - if (print_object == nullptr) - // This layer is empty for this particular object, it has neither object extrusions nor support extrusions at this print_z. - continue; - - m_config.apply(print_object->config(), true); - m_layer = layers[layer_id].layer(); + for (InstanceToPrint &instance_to_print : instances_to_print) { + m_config.apply(instance_to_print.print_object.config(), true); + m_layer = layers[instance_to_print.layer_id].layer(); if (m_config.avoid_crossing_perimeters) m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->slices, true)); - Points copies; - if (single_object_idx == size_t(-1)) - copies = print_object->copies(); - else - copies.push_back(print_object->copies()[single_object_idx]); - // Sort the copies by the closest point starting with the current print position. - unsigned int copy_id = 0; - for (const Point © : copies) { - 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(copy_id) + "\n"; - // When starting a new object, use the external motion planner for the first travel move. - std::pair this_object_copy(print_object, copy); - if (m_last_obj_copy != this_object_copy) - m_avoid_crossing_perimeters.use_external_mp_once = true; - m_last_obj_copy = this_object_copy; - this->set_origin(unscale(copy)); - if (object_by_extruder.support != nullptr && !print_wipe_extrusions) { - m_layer = layers[layer_id].support_layer; - gcode += this->extrude_support( - // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. - object_by_extruder.support->chained_path_from(m_last_pos, false, object_by_extruder.support_extrusion_role)); - m_layer = layers[layer_id].layer(); - } - for (ObjectByExtruder::Island &island : object_by_extruder.islands) { - const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(copy_id, extruder_id, print_wipe_extrusions) : island.by_region; - - if (print.config().infill_first) { - gcode += this->extrude_infill(print, by_region_specific); - gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[layer_id]); - } else { - gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[layer_id]); - gcode += this->extrude_infill(print,by_region_specific); - } - } - if (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(copy_id) + "\n"; - ++ copy_id; + 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.copies()[instance_to_print.instance_id]; + 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 = true; + 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 = layers[instance_to_print.layer_id].support_layer; + 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, false, instance_to_print.object_by_extruder.support_extrusion_role)); + m_layer = layers[instance_to_print.layer_id].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(instance_to_print.instance_id, extruder_id, print_wipe_extrusions) : island.by_region; + + if (print.config().infill_first) { + gcode += this->extrude_infill(print, by_region_specific); + gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]); + } else { + gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]); + gcode += this->extrude_infill(print,by_region_specific); + } + } + 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"; } } } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 72813810b..45ff7eda6 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -202,7 +202,7 @@ protected: 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; } }; - static std::vector collect_layers_to_print(const PrintObject &object); + static std::vector collect_layers_to_print(const PrintObject &object); static std::vector>> collect_layers_to_print(const Print &print); void process_layer( // Write into the output file. @@ -210,7 +210,9 @@ protected: const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, - const LayerTools &layer_tools, + const LayerTools &layer_tools, + // Pairs of PrintObject index and its instance index. + const std::vector> *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_idx = size_t(-1)); @@ -258,6 +260,25 @@ protected: std::vector islands; }; + 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) {} + + ObjectByExtruder &object_by_extruder; + const size_t layer_id; + const PrintObject &print_object; + // Instance idx of the copy of a print object. + const size_t instance_id; + }; + + std::vector sort_print_object_instances( + std::vector &objects_by_extruder, + 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::unique_ptr &lower_layer_edge_grid); std::string extrude_infill(const Print &print, const std::vector &by_region); diff --git a/src/libslic3r/MutablePriorityQueue.hpp b/src/libslic3r/MutablePriorityQueue.hpp index 31946c707..da469b7ba 100644 --- a/src/libslic3r/MutablePriorityQueue.hpp +++ b/src/libslic3r/MutablePriorityQueue.hpp @@ -13,7 +13,7 @@ public: {} ~MutablePriorityQueue() { clear(); } - void clear() { m_heap.clear(); } + void clear(); void reserve(size_t cnt) { m_heap.reserve(cnt); } void push(const T &item); void push(T &&item); @@ -49,6 +49,17 @@ MutablePriorityQueue make_mutable_priority_queue( std::forward(index_setter), std::forward(less_predicate)); } +template +inline void MutablePriorityQueue::clear() +{ +#ifndef NDEBUG + for (size_t idx = 0; idx < m_heap.size(); ++ idx) + // Mark as removed from the queue. + m_index_setter(m_heap[idx], std::numeric_limits::max()); +#endif /* NDEBUG */ + m_heap.clear(); +} + template inline void MutablePriorityQueue::push(const T &item) { @@ -71,6 +82,10 @@ template inline void MutablePriorityQueue::pop() { assert(! m_heap.empty()); +#ifndef NDEBUG + // Mark as removed from the queue. + m_index_setter(m_heap.front(), std::numeric_limits::max()); +#endif /* NDEBUG */ if (m_heap.size() > 1) { m_heap.front() = m_heap.back(); m_heap.pop_back(); @@ -84,6 +99,10 @@ template inline void MutablePriorityQueue::remove(size_t idx) { assert(idx < m_heap.size()); +#ifndef NDEBUG + // Mark as removed from the queue. + m_index_setter(m_heap[idx], std::numeric_limits::max()); +#endif /* NDEBUG */ if (idx + 1 == m_heap.size()) { m_heap.pop_back(); return; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 4d8482743..347cfa2ce 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -7,6 +7,7 @@ #include "Flow.hpp" #include "Geometry.hpp" #include "I18N.hpp" +#include "ShortestPath.hpp" #include "SupportMaterial.hpp" #include "GCode.hpp" #include "GCode/WipeTower.hpp" @@ -1824,8 +1825,8 @@ void Print::_make_brim() [](const std::pair &l, const std::pair &r) { return l.second < r.second; }); - Vec3f last_pt(0.f, 0.f, 0.f); + Point last_pt(0, 0); for (size_t i = 0; i < loops_trimmed_order.size();) { // Find all pieces that the initial loop was split into. size_t j = i + 1; @@ -1841,16 +1842,23 @@ void Print::_make_brim() points.emplace_back(coord_t(pt.X), coord_t(pt.Y)); i = j; } else { - //FIXME this is not optimal as the G-code generator will follow the sequence of paths verbatim without respect to minimum travel distance. + //FIXME The path chaining here may not be optimal. + ExtrusionEntityCollection this_loop_trimmed; + this_loop_trimmed.entities.reserve(j - i); for (; i < j; ++ i) { - m_brim.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()))); + this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()))); const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first; - Points &points = static_cast(m_brim.entities.back())->polyline.points; + Points &points = static_cast(this_loop_trimmed.entities.back())->polyline.points; points.reserve(path.size()); for (const ClipperLib_Z::IntPoint &pt : path) points.emplace_back(coord_t(pt.X), coord_t(pt.Y)); } + chain_and_reorder_extrusion_entities(this_loop_trimmed.entities, &last_pt); + m_brim.entities.reserve(m_brim.entities.size() + this_loop_trimmed.entities.size()); + append(m_brim.entities, std::move(this_loop_trimmed.entities)); + this_loop_trimmed.entities.clear(); } + last_pt = m_brim.last_point(); } } } else { diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 89a5f3e74..5cb13039c 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -96,6 +96,7 @@ public: const SupportLayerPtrs& support_layers() const { return m_support_layers; } const Transform3d& trafo() const { return m_trafo; } const Points& copies() const { return m_copies; } + const Point copy_center(size_t idx) const { return m_copies[idx] + m_copies_shift + Point(this->size.x() / 2, this->size.y() / 2); } // since the object is aligned to origin, bounding box coincides with size BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); } diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp index e98b0a58c..79e0fdf11 100644 --- a/src/libslic3r/ShortestPath.cpp +++ b/src/libslic3r/ShortestPath.cpp @@ -1,12 +1,14 @@ -#include "ShortestPath.hpp" -#include "KDTreeIndirect.hpp" -#include "MutablePriorityQueue.hpp" - #if 0 + #pragma optimize("", off) #undef NDEBUG #undef assert #endif +#include "ShortestPath.hpp" +#include "KDTreeIndirect.hpp" +#include "MutablePriorityQueue.hpp" +#include "Print.hpp" + #include #include @@ -20,135 +22,44 @@ namespace Slic3r { // The algorithm builds a tour for the traveling salesman one edge at a time and thus maintains multiple tour fragments, each of which // is a simple path in the complete graph of cities. At each stage, the algorithm selects the edge of minimal cost that either creates // a new fragment, extends one of the existing paths or creates a cycle of length equal to the number of cities. -std::vector> chain_extrusion_entities(std::vector &entities, const Point *start_near) +template +std::vector> chain_segments(SegmentEndPointFunc end_point_func, size_t num_segments, const PointType *start_near) { std::vector> out; - if (entities.empty()) { + if (num_segments == 0) { // Nothing to do. } - else if (entities.size() == 1) + else if (num_segments == 1) { // Just sort the end points so that the first point visited is closest to start_near. - ExtrusionEntity *extrusion_entity = entities.front(); - out.emplace_back(0, extrusion_entity->can_reverse() && start_near != nullptr && - (extrusion_entity->last_point() - *start_near).cast().squaredNorm() < (extrusion_entity->first_point() - *start_near).cast().squaredNorm()); + out.emplace_back(0, start_near != nullptr && + (end_point_func(0, true) - *start_near).cast().squaredNorm() < (end_point_func(0, false) - *start_near).cast().squaredNorm()); } else { - // End points of entities for the KD tree closest point search. + // End points of segments for the KD tree closest point search. // A single end point is inserted into the search structure for loops, two end points are entered for open paths. struct EndPoint { EndPoint(const Vec2d &pos) : pos(pos) {} - Vec2d pos; - // Identifier of the chain, to which this end point belongs. Zero means unassigned. size_t chain_id = 0; // Link to the closest currently valid end point. EndPoint *edge_out = nullptr; - // Reverse of edge_out. As there may be multiple end points with the same edge_out, - // these other edge_in points are chained using the on_circle_prev / on_circle_next cyclic loop. - EndPoint *edge_in = nullptr; - EndPoint* on_circle_prev = nullptr; - EndPoint* on_circle_next = nullptr; - void on_circle_merge(EndPoint *other) - { - EndPoint *a = this; - EndPoint *b = other; - assert(a->validate()); - assert(b->validate()); - if (a->on_circle_next == nullptr) - std::swap(a, b); - if (a->on_circle_next == nullptr) { - a->on_circle_next = a->on_circle_prev = b; - b->on_circle_next = b->on_circle_prev = a; - } else if (b->on_circle_next == nullptr) { - b->on_circle_next = a; - b->on_circle_prev = a->on_circle_prev; - a->on_circle_prev = b; - b->on_circle_prev->on_circle_next = b; - } else { - EndPoint *next = a->on_circle_next; - EndPoint *prev = b->on_circle_prev; - a->on_circle_next = b; - b->on_circle_prev = a; - prev->on_circle_next = next; - next->on_circle_prev = prev; - } - assert(this->validate()); - } - void on_circle_detach() - { - if (this->on_circle_next) { - EndPoint *next = this->on_circle_next; - EndPoint *prev = this->on_circle_prev; - if (prev == next) { - next->on_circle_next = nullptr; - next->on_circle_prev = nullptr; - } else { - prev->on_circle_next = next; - next->on_circle_prev = prev; - } - assert(prev->validate()); - assert(next->validate()); - this->on_circle_next = this->on_circle_prev = nullptr; - } - assert(this->validate()); - } - bool on_circle_empty() const - { - assert((this->on_circle_prev == nullptr) == (this->on_circle_next == nullptr)); - assert(this->on_circle_prev == nullptr || (this->on_circle_prev != this && this->on_circle_next != this)); - return this->on_circle_next == nullptr; - } - -#ifndef NDEBUG - bool validate() - { - assert((this->on_circle_prev == nullptr) == (this->on_circle_next == nullptr)); - assert(this->on_circle_prev == nullptr || (this->on_circle_prev != this && this->on_circle_next != this)); - assert(this->edge_out == nullptr || edge_out->edge_in != nullptr); - assert(this->distance_out >= 0.); - assert(this->edge_in == nullptr || this->edge_in->edge_out == this); - // Point which is a member of path (chain_id > 0) must not be in circle of some edge_in. - assert(this->chain_id == 0 || this->on_circle_empty()); - if (! this->on_circle_empty()) { - // Iterate over the cycle and validate the loop. - std::set visited; - const EndPoint *ep = this; - bool edge_in_found = false; - do { - // This end point is visited for the first time. - assert(visited.insert(ep).second); - assert(ep->on_circle_next != ep); - assert(ep->on_circle_prev != ep); - assert(ep->on_circle_next->on_circle_prev == ep); - assert(ep->on_circle_prev->on_circle_next == ep); - assert(ep->edge_out != nullptr && ep->edge_out == this->edge_out); - if (ep->edge_out->edge_in == ep) - edge_in_found = true; - ep = ep->on_circle_next; - } while (ep != this); - assert(edge_in_found); - } - return true; - } -#endif /* NDEBUG */ - // Distance to the next end point following the link. // Zero value -> start of the final path. double distance_out = std::numeric_limits::max(); size_t heap_idx = std::numeric_limits::max(); }; std::vector end_points; - end_points.reserve(entities.size() * 2); - for (const ExtrusionEntity* const &entity : entities) { - end_points.emplace_back(entity->first_point().cast()); - end_points.emplace_back(entity->last_point().cast()); + end_points.reserve(num_segments * 2); + for (size_t i = 0; i < num_segments; ++ i) { + end_points.emplace_back(end_point_func(i, true ).cast()); + end_points.emplace_back(end_point_func(i, false).cast()); } - // Construct the closest point KD tree over end points of extrusion entities. + // Construct the closest point KD tree over end points of segments. auto coordinate_fn = [&end_points](size_t idx, size_t dimension) -> double { return end_points[idx].pos[dimension]; }; KDTreeIndirect<2, double, decltype(coordinate_fn)> kdtree(coordinate_fn, end_points.size()); @@ -189,7 +100,7 @@ std::vector> chain_extrusion_entities(std::vector 0); + assert(m_last_chain_id >= 0); assert(m_last_chain_id + 1 == m_equivalent_with.size()); for (size_t i = 0; i < m_equivalent_with.size(); ++ i) { for (size_t last = i;;) { @@ -205,17 +116,16 @@ std::vector> chain_extrusion_entities(std::vector m_equivalent_with; - } equivalent_chain(entities.size()); + } equivalent_chain(num_segments); // Find the first end point closest to start_near. EndPoint *first_point = nullptr; size_t first_point_idx = std::numeric_limits::max(); if (start_near != nullptr) { size_t idx = find_closest_point(kdtree, start_near->cast()); - assert(idx != kdtree.npos); assert(idx < end_points.size()); first_point = &end_points[idx]; first_point->distance_out = 0.; @@ -223,17 +133,7 @@ std::vector> chain_extrusion_entities(std::vector bool { - for (EndPoint& ep : end_points) - ep.validate(); - assert(equivalent_chain.validate()); - return true; - }; -#endif /* NDEBUG */ - // Assign the closest point and distance to the end points. - assert(validate_graph()); for (EndPoint &end_point : end_points) { assert(end_point.edge_out == nullptr); if (&end_point != first_point) { @@ -242,20 +142,11 @@ std::vector> chain_extrusion_entities(std::vector 1; }); - assert(next_idx != kdtree.npos); assert(next_idx < end_points.size()); EndPoint &end_point2 = end_points[next_idx]; end_point.edge_out = &end_point2; - if (end_point2.edge_in == nullptr) - end_point2.edge_in = &end_point; - else { - assert(end_point.on_circle_empty()); - assert(end_point2.edge_in->edge_out == &end_point2); - end_point.on_circle_merge(end_point2.edge_in); - } end_point.distance_out = (end_point2.pos - end_point.pos).squaredNorm(); } - assert(validate_graph()); } // Initialize a heap of end points sorted by the lowest distance to the next valid point of a path. @@ -268,32 +159,21 @@ std::vector> chain_extrusion_entities(std::vector bool { - assert(validate_graph()); + auto validate_graph_and_queue = [&equivalent_chain, &end_points, &queue, first_point]() -> bool { + assert(equivalent_chain.validate()); for (EndPoint &ep : end_points) { if (ep.heap_idx < queue.size()) { // End point is on the heap. assert(*(queue.cbegin() + ep.heap_idx) == &ep); assert(ep.chain_id == 0); - // Point on the heap may only points to other points on the heap. - assert(ep.edge_in == nullptr || ep.edge_in ->heap_idx < queue.size()); - assert(ep.edge_out == nullptr || ep.edge_out->heap_idx < queue.size()); } else { // End point is NOT on the heap, therefore it is part of the output path. assert(ep.heap_idx == std::numeric_limits::max()); assert(ep.chain_id != 0); - assert(ep.on_circle_empty()); if (&ep == first_point) { - assert(ep.edge_in == nullptr); assert(ep.edge_out == nullptr); } else { - assert(ep.edge_in != nullptr); assert(ep.edge_out != nullptr); - assert(ep.edge_in != &ep); - assert(ep.edge_in == ep.edge_out); - assert(ep.edge_in->edge_out == &ep); - assert(ep.edge_out->edge_in == &ep); - assert(ep.edge_in->heap_idx == std::numeric_limits::max()); // Detect loops. for (EndPoint *pt = &ep; pt != nullptr;) { // Out of queue. It is a final point. @@ -314,11 +194,9 @@ std::vector> chain_extrusion_entities(std::vector end_points_update; - end_points_update.reserve(16); - assert(entities.size() >= 2); - for (int iter = int(entities.size()) - 2;; -- iter) { + // Chain the end points: find (num_segments - 1) shortest links not forming bifurcations or loops. + assert(num_segments >= 2); + for (int iter = int(num_segments) - 2;; -- iter) { assert(validate_graph_and_queue()); // Take the first end point, for which the link points to the currently closest valid neighbor. EndPoint &end_point1 = *queue.top(); @@ -327,65 +205,26 @@ std::vector> chain_extrusion_entities(std::vectoredge_in != nullptr); - assert(! end_point1.on_circle_empty() || end_point1.edge_out->edge_in == &end_point1); - end_point1.edge_out->edge_in = end_point1.on_circle_empty() ? nullptr : end_point1.on_circle_next; - end_point1.edge_out = nullptr; - if (! end_point1.on_circle_empty()) - end_point1.on_circle_detach(); - assert(validate_graph_and_queue()); - end_points_update.emplace_back(&end_point1); - } else { + bool valid = true; + size_t end_point1_other_chain_id = 0; + size_t end_point2_other_chain_id = 0; + if (end_point2.chain_id > 0) { + // The other side is part of the output path. Don't connect to end_point2, update end_point1 and try another one. + valid = false; + } else { + // End points of the opposite ends of the segments. + end_point1_other_chain_id = equivalent_chain(end_points[(&end_point1 - &end_points.front()) ^ 1].chain_id); + end_point2_other_chain_id = equivalent_chain(end_points[(&end_point2 - &end_points.front()) ^ 1].chain_id); + if (end_point1_other_chain_id == end_point2_other_chain_id && end_point1_other_chain_id != 0) + // This edge forms a loop. Update end_point1 and try another one. + valid = false; + } + if (valid) { // Remove the first and second point from the queue. queue.pop(); queue.remove(end_point2.heap_idx); - #ifndef NDEBUG - // Mark them as removed from the queue. - end_point1.heap_idx = std::numeric_limits::max(); - end_point2.heap_idx = std::numeric_limits::max(); - #endif /* NDEBUG */ - // Collect the other end points pointing to this one, detach them from the on_circle linked list. - for (EndPoint *pt_first : { end_point1.edge_in, end_point2.edge_in }) - if (pt_first != nullptr) { - EndPoint *pt = pt_first; - do { - if (pt != &end_point1 && pt != &end_point2) { - // Point is in the queue. - assert(pt->heap_idx < queue.size()); - // Point is not connected yet. - assert(pt->chain_id == 0); - end_points_update.emplace_back(pt); - pt->edge_out = nullptr; - } - EndPoint *next = pt->on_circle_next; - pt->on_circle_prev = nullptr; - pt->on_circle_next = nullptr; - pt = next; - } while (pt != nullptr && pt != pt_first); - } - // If end_point1 was on a circle, the circle belonged to end_point2.edge_in, which was broken in the loop above. - assert(end_point1.on_circle_empty()); - // If end_point2 pointed to end_point1, then end_point2 was on a circle that belonged to end_point1.edge_in, which was broken in the loop above. - //assert(end_point2.on_circle_empty() == (end_point2.edge_out == &end_point1)); - assert(end_point2.on_circle_empty() || end_point2.edge_out != nullptr); - end_point2.edge_out->edge_in = end_point2.on_circle_empty() ? nullptr : end_point2.on_circle_next; - // The end_point2.link may not necessarily point back to end_point1 due to numeric issues and points on circles. - // Update the link back. - end_point1.edge_out = &end_point2; - end_point1.edge_in = &end_point2; + assert(end_point1.edge_out = &end_point2); end_point2.edge_out = &end_point1; - end_point2.edge_in = &end_point1; end_point2.distance_out = end_point1.distance_out; // Assign chain IDs to the newly connected end points, set equivalent_chain if two chains were merged. size_t chain_id = @@ -397,37 +236,26 @@ std::vector> chain_extrusion_entities(std::vectoredge_out == nullptr); - // Point is in the queue. - assert(end_point->heap_idx < queue.size()); - // Point is not connected yet. - assert(end_point->chain_id == 0); - } -#endif /* NDEBUG */ - if (iter == 0) { - // Last iteration. There shall be exactly one or two end points waiting to be connected. - if (first_point == nullptr) { - // Two unconnected points are the end points of the constructed path. - assert(end_points_update.size() == 2); - first_point = end_points_update.front(); - } else - assert(end_points_update.size() == 1); - // Mark both points as ends of the path. - for (EndPoint *end_point : end_points_update) - end_point->edge_in = end_point->edge_out = nullptr; - break; - } - // Update links, distances and queue positions of all points that used to point to end_point1 or end_point2. - for (EndPoint *end_point : end_points_update) { - size_t this_idx = end_point - &end_points.front(); + if (iter == 0) { + // Last iteration. There shall be exactly one or two end points waiting to be connected. + assert(queue.size() == ((first_point == nullptr) ? 2 : 1)); + if (first_point == nullptr) + first_point = queue.top(); + while (! queue.empty()) { + queue.top()->edge_out = nullptr; + queue.pop(); + } + break; + } + } else { + // This edge forms a loop. Update end_point1 and try another one. + ++ iter; + end_point1.edge_out = nullptr; + // Update edge_out and distance. + size_t this_idx = &end_point1 - &end_points.front(); // Find the closest point to this end_point, which lies on a different extrusion path (filtered by the filter lambda). - size_t next_idx = find_closest_point(kdtree, end_point->pos, [&end_points, &equivalent_chain, this_idx](size_t idx) { + size_t next_idx = find_closest_point(kdtree, end_point1.pos, [&end_points, &equivalent_chain, this_idx](size_t idx) { assert(end_points[this_idx].edge_out == nullptr); assert(end_points[this_idx].chain_id == 0); if ((idx ^ this_idx) <= 1 || end_points[idx].chain_id != 0) @@ -438,41 +266,97 @@ std::vector> chain_extrusion_entities(std::vectoredge_out = &end_point2; - if (end_point2.edge_in == nullptr) - end_point2.edge_in = end_point; - else { - assert(end_point->on_circle_empty()); - assert(end_point2.edge_in->edge_out == &end_point2); - end_point->on_circle_merge(end_point2.edge_in); - } - end_point->distance_out = (end_points[next_idx].pos - end_point->pos).squaredNorm(); + end_point1.edge_out = &end_points[next_idx]; + end_point1.distance_out = (end_points[next_idx].pos - end_point1.pos).squaredNorm(); // Update position of this end point in the queue based on the distance calculated at the line above. - queue.update(end_point->heap_idx); + queue.update(end_point1.heap_idx); //FIXME Remove the other end point from the KD tree. // As the KD tree update is expensive, do it only after some larger number of points is removed from the queue. assert(validate_graph_and_queue()); } - end_points_update.clear(); } - assert(queue.size() == (first_point == nullptr) ? 1 : 2); + assert(queue.empty()); // Now interconnect pairs of segments into a chain. assert(first_point != nullptr); do { - size_t first_point_id = first_point - &end_points.front(); - size_t extrusion_entity_id = first_point_id >> 1; - EndPoint *second_point = &end_points[first_point_id ^ 1]; - ExtrusionEntity *extrusion_entity = entities[extrusion_entity_id]; - out.emplace_back(extrusion_entity_id, extrusion_entity->can_reverse() && (first_point_id & 1)); + assert(out.size() < num_segments); + size_t first_point_id = first_point - &end_points.front(); + size_t segment_id = first_point_id >> 1; + EndPoint *second_point = &end_points[first_point_id ^ 1]; + out.emplace_back(segment_id, (first_point_id & 1) != 0); first_point = second_point->edge_out; } while (first_point != nullptr); } - assert(out.size() == entities.size()); + assert(out.size() == num_segments); + return out; +} + +std::vector> chain_extrusion_entities(std::vector &entities, const Point *start_near) +{ + auto segment_end_point = [&entities](size_t idx, bool first_point) -> const Point& { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); }; + std::vector> out = chain_segments(segment_end_point, entities.size(), start_near); + for (size_t i = 0; i < entities.size(); ++ i) { + ExtrusionEntity *ee = entities[i]; + if (ee->is_loop()) + // Ignore reversals for loops, as the start point equals the end point. + out[i].second = false; + // Is can_reverse() respected by the reversals? + assert(entities[i]->can_reverse() || ! out[i].second); + } + return out; +} + +void reorder_extrusion_entities(std::vector &entities, std::vector> &chain) +{ + assert(entities.size() == chain.size()); + std::vector out; + out.reserve(entities.size()); + for (const std::pair &idx : chain) { + assert(entities[idx.first] != nullptr); + out.emplace_back(entities[idx.first]); + if (idx.second) + out.back()->reverse(); + } + entities.swap(out); +} + +void chain_and_reorder_extrusion_entities(std::vector &entities, const Point *start_near) +{ + reorder_extrusion_entities(entities, chain_extrusion_entities(entities, start_near)); +} + +std::vector chain_points(const Points &points, Point *start_near) +{ + auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; }; + std::vector> ordered = chain_segments(segment_end_point, points.size(), start_near); + std::vector out; + out.reserve(ordered.size()); + for (auto &segment_and_reversal : ordered) + out.emplace_back(segment_and_reversal.first); + return out; +} + +std::vector> chain_print_object_instances(const Print &print) +{ + // Order objects using a nearest neighbor search. + Points object_reference_points; + std::vector> instances; + for (size_t i = 0; i < print.objects().size(); ++ i) { + const PrintObject &object = *print.objects()[i]; + for (size_t j = 0; j < object.copies().size(); ++ j) { + object_reference_points.emplace_back(object.copy_center(j)); + instances.emplace_back(i, j); + } + } + auto segment_end_point = [&object_reference_points](size_t idx, bool /* first_point */) -> const Point& { return object_reference_points[idx]; }; + std::vector> ordered = chain_segments(segment_end_point, instances.size(), nullptr); + std::vector> out; + out.reserve(instances.size()); + for (auto &segment_and_reversal : ordered) + out.emplace_back(instances[segment_and_reversal.first]); return out; } diff --git a/src/libslic3r/ShortestPath.hpp b/src/libslic3r/ShortestPath.hpp index 2a7e67688..c02e24aee 100644 --- a/src/libslic3r/ShortestPath.hpp +++ b/src/libslic3r/ShortestPath.hpp @@ -10,7 +10,17 @@ namespace Slic3r { +std::vector chain_points(const Points &points, Point *start_near = nullptr); + std::vector> chain_extrusion_entities(std::vector &entities, const Point *start_near = nullptr); +void reorder_extrusion_entities(std::vector &entities, std::vector> &chain); +void chain_and_reorder_extrusion_entities(std::vector &entities, const Point *start_near = nullptr); + +// Chain instances of print objects by an approximate shortest path. +// Returns pairs of PrintObject idx and instance of that PrintObject. +class Print; +std::vector> chain_print_object_instances(const Print &print); + } // namespace Slic3r