From e7b9e81e84d5b9ccb31928f38df9c5aaabd04e0c Mon Sep 17 00:00:00 2001 From: "qing.zhang" Date: Mon, 15 Jul 2024 11:02:12 +0800 Subject: [PATCH] ENH: smoothing z direction speed Jira: none get z direction continuous info smoothing speed based on the max speed of the loop rewite the pipeline set perset param to enable z smoothing function Signed-off-by: qing.zhang Change-Id: Ib0e6a6a353c92a441a0c99a8d3d7902f7aeae6df --- .../BBL/process/fdm_process_dual_common.json | 3 +- src/libslic3r/CMakeLists.txt | 8 +- src/libslic3r/ExtrusionEntity.hpp | 24 + src/libslic3r/ExtrusionEntityCollection.hpp | 8 +- src/libslic3r/GCode.cpp | 253 +++++- src/libslic3r/GCode.hpp | 12 +- src/libslic3r/GCode/CoolingBuffer.cpp | 860 ++---------------- src/libslic3r/GCode/CoolingBuffer.hpp | 69 +- src/libslic3r/GCode/GCodeEditer.cpp | 573 ++++++++++++ src/libslic3r/GCode/GCodeEditer.hpp | 294 ++++++ src/libslic3r/GCode/Smoothing.cpp | 224 +++++ src/libslic3r/GCode/Smoothing.hpp | 103 +++ src/libslic3r/Layer.cpp | 147 ++- src/libslic3r/Layer.hpp | 6 +- src/libslic3r/LayerRegion.cpp | 5 +- src/libslic3r/PerimeterGenerator.cpp | 101 +- src/libslic3r/PerimeterGenerator.hpp | 12 +- src/libslic3r/Point.cpp | 43 + src/libslic3r/Point.hpp | 1 + src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 3 +- src/libslic3r/Print.hpp | 2 +- src/libslic3r/PrintConfig.cpp | 7 + src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/PrintObject.cpp | 88 ++ src/slic3r/GUI/Tab.cpp | 1 + 26 files changed, 1947 insertions(+), 903 deletions(-) create mode 100644 src/libslic3r/GCode/GCodeEditer.cpp create mode 100644 src/libslic3r/GCode/GCodeEditer.hpp create mode 100644 src/libslic3r/GCode/Smoothing.cpp create mode 100644 src/libslic3r/GCode/Smoothing.hpp diff --git a/resources/profiles/BBL/process/fdm_process_dual_common.json b/resources/profiles/BBL/process/fdm_process_dual_common.json index 232ff84c4..8c983c0e1 100644 --- a/resources/profiles/BBL/process/fdm_process_dual_common.json +++ b/resources/profiles/BBL/process/fdm_process_dual_common.json @@ -178,5 +178,6 @@ "0", "0", "0" - ] + ], + "z_direction_outwall_speed_continuous": "1" } \ No newline at end of file diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index ad576b0fe..e661c5bc9 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -139,8 +139,8 @@ set(lisbslic3r_sources Format/svg.cpp GCode/ThumbnailData.cpp GCode/ThumbnailData.hpp - GCode/CoolingBuffer.cpp - GCode/CoolingBuffer.hpp + GCode/GCodeEditer.cpp + GCode/GCodeEditer.hpp GCode/PostProcessor.cpp GCode/PostProcessor.hpp # GCode/PressureEqualizer.cpp @@ -163,6 +163,10 @@ set(lisbslic3r_sources GCode/AvoidCrossingPerimeters.hpp GCode/ConflictChecker.cpp GCode/ConflictChecker.hpp + GCode/Smoothing.cpp + GCode/Smoothing.hpp + GCode/CoolingBuffer.cpp + GCode/CoolingBuffer.hpp GCode.cpp GCode.hpp GCodeReader.cpp diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index d64feb701..4d4fcf475 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -16,6 +16,30 @@ using ExPolygons = std::vector; class ExtrusionEntityCollection; class Extruder; + +struct NodeContour +{ + Points pts; //for lines contour + std::vector widths; + bool is_loop; +}; + +struct LoopNode +{ + //store outer wall and mark if it's loop + NodeContour node_contour; + int node_id; + int loop_id = 0; + BoundingBox bbox; + int merged_id = -1; + + //upper loop info + std::vector upper_node_id; + + //lower loop info + std::vector lower_node_id; +}; + // Each ExtrusionRole value identifies a distinct set of { extruder, speed } enum ExtrusionRole : uint8_t { erNone, diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index 413834db2..dec981eb8 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -31,9 +31,12 @@ public: ExtrusionEntitiesPtr entities; // we own these entities bool no_sort; + + std::pair loop_node_range; ExtrusionEntityCollection(): no_sort(false) {} - ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : no_sort(other.no_sort), is_reverse(other.is_reverse) { this->append(other.entities); } - ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), no_sort(other.no_sort), is_reverse(other.is_reverse) {} + ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : no_sort(other.no_sort), is_reverse(other.is_reverse), loop_node_range(other.loop_node_range) { this->append(other.entities); } + ExtrusionEntityCollection(ExtrusionEntityCollection &&other) + : entities(std::move(other.entities)), no_sort(other.no_sort), is_reverse(other.is_reverse), loop_node_range(other.loop_node_range) {} explicit ExtrusionEntityCollection(const ExtrusionPaths &paths); ExtrusionEntityCollection& operator=(const ExtrusionEntityCollection &other); ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other) @@ -41,6 +44,7 @@ public: this->entities = std::move(other.entities); this->no_sort = other.no_sort; is_reverse = other.is_reverse; + loop_node_range = other.loop_node_range; return *this; } ~ExtrusionEntityCollection() { clear(); } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 44be6dc64..f4f263562 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -67,6 +67,8 @@ using namespace std::literals::string_view_literals; #endif #include +#include +#include namespace Slic3r { @@ -1954,8 +1956,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato print.throw_if_canceled(); - m_cooling_buffer = make_unique(*this); - m_cooling_buffer->set_current_extruder(initial_extruder_id); + m_gcode_editer = make_unique(*this); + m_gcode_editer->set_current_extruder(initial_extruder_id); int extruder_id = get_extruder_id(initial_extruder_id); @@ -2257,7 +2259,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato if (finished_objects > 0) { // Move to the origin position for the copy we're going to print. // This happens before Z goes down to layer 0 again, so that no collision happens hopefully. - m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer + m_enable_cooling_markers = false; // we're not filtering these moves through GCodeEditer m_avoid_crossing_perimeters.use_external_mp_once(); // BBS. change tool before moving to origin point. if (m_writer.need_toolchange(initial_extruder_id)) { @@ -2285,8 +2287,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.writeln(printing_by_object_gcode); } // Reset the cooling buffer internal state (the current position, feed rate, accelerations). - m_cooling_buffer->reset(this->writer().get_position()); - m_cooling_buffer->set_current_extruder(initial_extruder_id); + m_gcode_editer->reset(this->writer().get_position()); + m_gcode_editer->set_current_extruder(initial_extruder_id); // 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. @@ -2564,8 +2566,17 @@ void GCode::process_layers( const std::vector>> &layers_to_print, GCodeOutputStream &output_stream) { - // The pipeline is variable: The vase mode filter is optional. + //BBS: get object label id size_t layer_to_print_idx = 0; + std::vector object_label; + + for (const PrintInstance *instance : print_object_instances_ordering) + object_label.push_back(instance->model_instance->get_labeled_id()); + + std::vector layers_results; + layers_results.resize(layers_to_print.size()); + + // The pipeline is variable: The vase mode filter is optional. const auto generator = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &layer_to_print_idx](tbb::flow_control& fc) -> GCode::LayerResult { if (layer_to_print_idx == layers_to_print.size()) { @@ -2594,21 +2605,87 @@ void GCode::process_layers( slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), & layers_to_print](GCode::LayerResult in) -> GCode::LayerResult { spiral_mode.enable(in.spiral_vase_enable); bool last_layer = in.layer_id == layers_to_print.size() - 1; - return { spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush}; + return {spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush, in.gcode_store_pos}; }); - const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string { - return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); - }); - const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&output_stream](std::string s) { output_stream.write(s); } - ); + std::vector> layers_extruder_adjustments(layers_to_print.size()); + + const auto parsing = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments, object_label](GCode::LayerResult in) -> GCode::LayerResult{ + //record gcode + in.gcode = gcode_editer.process_layer(std::move(in.gcode), in.layer_id, layers_extruder_adjustments[in.gcode_store_pos], object_label, in.cooling_buffer_flush, false); + return std::move(in); + }); + + //step2: cooling + std::vector> layers_wall_collection(layers_to_print.size()); + + CoolingBuffer cooling_processor; + + const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&cooling_processor, &layers_extruder_adjustments](GCode::LayerResult in) -> GCode::LayerResult { + in.layer_time = cooling_processor.calculate_layer_slowdown(layers_extruder_adjustments[in.gcode_store_pos]); + return std::move(in); + }); + + // step 4.1: record node date + SmoothCalculator smooth_calculator(object_label.size()); + + const auto build_node = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&smooth_calculator, &layers_wall_collection, &layers_extruder_adjustments, object_label, &layers_results](GCode::LayerResult in){ + smooth_calculator.build_node(layers_wall_collection[in.gcode_store_pos], object_label, layers_extruder_adjustments[in.gcode_store_pos]); + layers_results[in.gcode_store_pos] = std::move(in); + return; + }); + + // step 5: rewite + const auto write_gocde= tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments](GCode::LayerResult in) -> std::string { + return gcode_editer.write_layer_gcode(std::move(in.gcode), in.layer_id, in.layer_time, layers_extruder_adjustments[in.gcode_store_pos]); + }); + + std::vector gcode_res; + + // BBS: apply new feedrate of outwall and recalculate layer time + int layer_idx = 0; + const auto calculate_layer_time= tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [&layer_idx, &smooth_calculator, &layers_extruder_adjustments, &gcode_res](tbb::flow_control& fc) -> GCode::LayerResult { + if(layer_idx == gcode_res.size()){ + fc.stop(); + return{}; + }else{ + if (layer_idx > 0){ + gcode_res[layer_idx].layer_time = smooth_calculator.recaculate_layer_time(layer_idx, layers_extruder_adjustments[gcode_res[layer_idx].gcode_store_pos]); + } + return gcode_res[layer_idx++]; + } + }); + + + const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&output_stream](std::string s) { output_stream.write(s); }); + + // BBS: apply cooling // The pipeline elements are joined using const references, thus no copying is performed. if (m_spiral_vase) - tbb::parallel_pipeline(12, generator & spiral_mode & cooling & output); - else - tbb::parallel_pipeline(12, generator & cooling & output); + tbb::parallel_pipeline(12, generator & spiral_mode & parsing & cooling & write_gocde & output); + else if (!m_config.z_direction_outwall_speed_continuous) + tbb::parallel_pipeline(12, generator & parsing & cooling & write_gocde & output); + else { + tbb::parallel_pipeline(12, generator & parsing & cooling & build_node); + + //append data + for (const LayerResult &res : layers_results) { + //remove empty gcode layer caused by support independent layers + if (res.cooling_buffer_flush) { + smooth_calculator.append_data(layers_wall_collection[res.gcode_store_pos]); + gcode_res.push_back(std::move(res)); + } + } + + smooth_calculator.smooth_layer_speed(); + + tbb::parallel_pipeline(12, calculate_layer_time & write_gocde & output); + } } // Process all layers of a single object instance (sequential mode) with a parallel pipeline: @@ -2623,8 +2700,21 @@ void GCode::process_layers( // BBS const bool prime_extruder) { + // the pipeline should be + // generatotr + (spira) + parse + (cooling) + (smoothing) + rewrite + // rewrite pipeline to get better schu + + // BBS: get object label id + size_t layer_to_print_idx = 0; + std::vector object_label; + for (LayerToPrint layer : layers_to_print) + object_label.push_back(layer.original_object->instances()[single_object_idx].model_instance->get_labeled_id()); + + std::vector layers_results; + layers_results.resize(layers_to_print.size()); + + //step 1: generator // The pipeline is variable: The vase mode filter is optional. - size_t layer_to_print_idx = 0; const auto generator = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx, prime_extruder](tbb::flow_control& fc) -> GCode::LayerResult { if (layer_to_print_idx == layers_to_print.size()) { @@ -2636,7 +2726,9 @@ void GCode::process_layers( //BBS check_placeholder_parser_failed(); 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, prime_extruder); + GCode::LayerResult res = this->process_layer(print, {std::move(layer)}, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx, prime_extruder); + res.gcode_store_pos = layer_to_print_idx - 1; + return std::move(res); } }); if (m_spiral_vase) { @@ -2648,21 +2740,92 @@ void GCode::process_layers( slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](GCode::LayerResult in) -> GCode::LayerResult { spiral_mode.enable(in.spiral_vase_enable); bool last_layer = in.layer_id == layers_to_print.size() - 1; - return { spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; + return {spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush, in.gcode_store_pos}; }); - const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in)->std::string { - return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); - }); - const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&output_stream](std::string s) { output_stream.write(s); } - ); + //BBS: get objects and nodes info, for better arrange + const ConstPrintObjectPtrsAdaptor &objects = print.objects(); + + // step 2: parse + std::vector> layers_extruder_adjustments(layers_to_print.size()); + + const auto parsing = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments, object_label](GCode::LayerResult in) -> GCode::LayerResult{ + //record gcode + in.gcode = gcode_editer.process_layer(std::move(in.gcode), in.layer_id, layers_extruder_adjustments[in.gcode_store_pos], object_label, in.cooling_buffer_flush, false); + return std::move(in); + }); + + // step 3: cooling + std::vector> layers_wall_collection(layers_to_print.size()); + CoolingBuffer cooling_processor; + + const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&cooling_processor, &layers_extruder_adjustments](GCode::LayerResult in) -> GCode::LayerResult { + in.layer_time = cooling_processor.calculate_layer_slowdown(layers_extruder_adjustments[in.gcode_store_pos]); + return std::move(in); + }); + + // step 4.1: record node date + SmoothCalculator smooth_calculator(object_label.size()); + + const auto build_node = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&smooth_calculator, &layers_wall_collection, &layers_extruder_adjustments, object_label, &layers_results](GCode::LayerResult in){ + smooth_calculator.build_node(layers_wall_collection[in.gcode_store_pos], object_label, layers_extruder_adjustments[in.gcode_store_pos]); + layers_results[in.gcode_store_pos] = std::move(in); + return; + }); + + // step 5: rewite + const auto write_gocde= tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&gcode_editer = *this->m_gcode_editer.get(), &layers_extruder_adjustments](GCode::LayerResult in) -> std::string { + return gcode_editer.write_layer_gcode(std::move(in.gcode), in.layer_id, in.layer_time, layers_extruder_adjustments[in.gcode_store_pos]); + }); + + std::vector gcode_res; + + // BBS: apply new feedrate of outwall and recalculate layer time + int layer_idx = 0; + //restart pipeline + const auto calculate_layer_time = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [&layer_idx, &gcode_res, &smooth_calculator, &layers_extruder_adjustments](tbb::flow_control& fc) -> GCode::LayerResult { + if(layer_idx == gcode_res.size()){ + fc.stop(); + return{}; + }else{ + if (layer_idx > 0) { + gcode_res[layer_idx].layer_time = smooth_calculator.recaculate_layer_time(layer_idx, layers_extruder_adjustments[gcode_res[layer_idx].gcode_store_pos]); + } + return gcode_res[layer_idx++]; + } + }); + + + const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&output_stream](std::string s) { output_stream.write(s); }); + + // BBS: apply cooling // The pipeline elements are joined using const references, thus no copying is performed. if (m_spiral_vase) - tbb::parallel_pipeline(12, generator & spiral_mode & cooling & output); - else - tbb::parallel_pipeline(12, generator & cooling & output); + tbb::parallel_pipeline(12, generator & spiral_mode & parsing & cooling & write_gocde & output); + else if (!m_config.z_direction_outwall_speed_continuous) + tbb::parallel_pipeline(12, generator & parsing & cooling & write_gocde & output); + else { + tbb::parallel_pipeline(12, generator & parsing & cooling & build_node); + // step 4.2: smoothing + // break pipeline and do z smoothing + // append data + for (const LayerResult &res : layers_results) { + // remove empty gcode layer caused by support independent layers + if (res.cooling_buffer_flush) { + smooth_calculator.append_data(layers_wall_collection[res.gcode_store_pos]); + gcode_res.push_back(res); + } + } + + smooth_calculator.smooth_layer_speed(); + + tbb::parallel_pipeline(12, calculate_layer_time & write_gocde & output); + } } std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_filament_id, const DynamicConfig *config_override) @@ -3526,7 +3689,8 @@ GCode::LayerResult GCode::process_layer( // 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.entities : layerm->perimeters.entities) { + bool is_infill = entity_type == ObjectByExtruder::Island::Region::INFILL; + for (const ExtrusionEntity *ee : is_infill ? layerm->fills.entities : layerm->perimeters.entities) { // extrusions represents infill or perimeter extrusions of a single island. assert(dynamic_cast(ee) != nullptr); const auto *extrusions = static_cast(ee); @@ -3579,6 +3743,15 @@ GCode::LayerResult GCode::process_layer( 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); + int start = extrusions->loop_node_range.first; + int end = extrusions->loop_node_range.second; + //BBS: add merged node infor + if (!is_infill) { + for (; start < end; ++start) { + const LoopNode *node = &layer.loop_nodes[start]; + islands[island_idx].by_region[region.print_region_id()].merged_node.emplace_back(node); + } + } break; } } @@ -3713,6 +3886,8 @@ GCode::LayerResult GCode::process_layer( if (m_config.reduce_crossing_wall) m_avoid_crossing_perimeters.init_layer(*m_layer); + //BBS: label object id, prepare for cooling + gcode += "; OBJECT_ID: " + std::to_string(instance_to_print.label_object_id) + "\n"; std::string temp_start_str; if (m_enable_label_object) { std::string start_str = std::string("; start printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; @@ -3918,8 +4093,8 @@ GCode::LayerResult GCode::process_layer( 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(), + if (m_gcode_editer) + gcode = m_gcode_editer->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); @@ -4435,8 +4610,18 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vectorloop_id) { + gcode += "; COOLING_NODE: " + std::to_string(region.merged_node[curr_node]->merged_id) + "\n"; + curr_node++; + } + gcode += this->extrude_entity(*ee, "perimeter", -1.); + } } return gcode; } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 7410c46c9..7ce7ed95f 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -9,7 +9,7 @@ #include "PlaceholderParser.hpp" #include "PrintConfig.hpp" #include "GCode/AvoidCrossingPerimeters.hpp" -#include "GCode/CoolingBuffer.hpp" +#include "GCode/GCodeEditer.hpp" #include "GCode/RetractWhenCrossingPerimeters.hpp" #include "GCode/SpiralVase.hpp" #include "GCode/ToolOrdering.hpp" @@ -303,7 +303,9 @@ private: // Should the cooling buffer content be flushed at the end of this layer? bool cooling_buffer_flush { false }; // the layer store pos of gcode - size_t gcode_store_pos; + size_t gcode_store_pos = 0; + //store each layer_time + float layer_time = 0; LayerResult() = default; LayerResult(const std::string& gcode_, const size_t layer_id_, const bool spiral_vase_enable_, const bool cooling_buffer_flush_, const size_t gcode_store_pos_ = static_cast(-1)) : gcode(gcode_), layer_id(layer_id_), spiral_vase_enable(spiral_vase_enable_), cooling_buffer_flush(cooling_buffer_flush_), gcode_store_pos(gcode_store_pos_){} @@ -315,6 +317,7 @@ private: spiral_vase_enable = other.spiral_vase_enable; cooling_buffer_flush = other.cooling_buffer_flush; gcode_store_pos = other.gcode_store_pos; + layer_time = other.layer_time; } LayerResult& operator=(LayerResult&& other) noexcept { @@ -324,6 +327,7 @@ private: spiral_vase_enable = other.spiral_vase_enable; cooling_buffer_flush = other.cooling_buffer_flush; gcode_store_pos = other.gcode_store_pos; + layer_time = other.layer_time; } return *this; } @@ -400,7 +404,7 @@ private: ExtrusionEntitiesPtr perimeters; // Non-owned references to LayerRegion::fills::entities ExtrusionEntitiesPtr infills; - + std::vector merged_node; std::vector infills_overrides; std::vector perimeters_overrides; @@ -514,7 +518,7 @@ private: Point m_last_pos; bool m_last_pos_defined; bool m_last_scarf_seam_flag; - std::unique_ptr m_cooling_buffer; + std::unique_ptr m_gcode_editer; std::unique_ptr m_spiral_vase; #ifdef HAS_PRESSURE_EQUALIZER std::unique_ptr m_pressure_equalizer; diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 51bde8912..87e1937df 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -1,272 +1,34 @@ -#include "../GCode.hpp" #include "CoolingBuffer.hpp" -#include -#include -#include -#include -#include - -#if 0 - #define DEBUG - #define _DEBUG - #undef NDEBUG -#endif - -#include namespace Slic3r { - -CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0) +float new_feedrate_to_reach_time_stretch(std::vector::const_iterator it_begin, + std::vector::const_iterator it_end, + float min_feedrate, + float time_stretch, + size_t max_iter = 20) { - this->reset(gcodegen.writer().get_position()); - - const std::vector &extruders = gcodegen.writer().extruders(); - m_extruder_ids.reserve(extruders.size()); - for (const Extruder &ex : extruders) { - m_num_extruders = std::max(ex.id() + 1, m_num_extruders); - m_extruder_ids.emplace_back(ex.id()); - } -} - -void CoolingBuffer::reset(const Vec3d &position) -{ - // BBS: add I and J axis to store center of arc - m_current_pos.assign(7, 0.f); - m_current_pos[0] = float(position.x()); - m_current_pos[1] = float(position.y()); - m_current_pos[2] = float(position.z()); - m_current_pos[4] = float(m_config.travel_speed.get_at(get_extruder_index(m_config, m_current_extruder))); - m_fan_speed = -1; - m_additional_fan_speed = -1; - m_current_fan_speed = -1; -} - -struct CoolingLine -{ - enum Type { - TYPE_SET_TOOL = 1 << 0, - TYPE_EXTRUDE_END = 1 << 1, - TYPE_OVERHANG_FAN_START = 1 << 2, - TYPE_OVERHANG_FAN_END = 1 << 3, - TYPE_G0 = 1 << 4, - TYPE_G1 = 1 << 5, - TYPE_ADJUSTABLE = 1 << 6, - TYPE_EXTERNAL_PERIMETER = 1 << 7, - // The line sets a feedrate. - TYPE_HAS_F = 1 << 8, - TYPE_WIPE = 1 << 9, - TYPE_G4 = 1 << 10, - TYPE_G92 = 1 << 11, - //BBS: add G2 G3 type - TYPE_G2 = 1 << 12, - TYPE_G3 = 1 << 13, - TYPE_FORCE_RESUME_FAN = 1 << 14, - TYPE_SET_FAN_CHANGING_LAYER = 1 << 15, - }; - - CoolingLine(unsigned int type, size_t line_start, size_t line_end) : - type(type), line_start(line_start), line_end(line_end), - length(0.f), feedrate(0.f), time(0.f), time_max(0.f), slowdown(false) {} - - bool adjustable(bool slowdown_external_perimeters) const { - return (this->type & TYPE_ADJUSTABLE) && - (! (this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) && - this->time < this->time_max; - } - - bool adjustable() const { - return (this->type & TYPE_ADJUSTABLE) && this->time < this->time_max; - } - - size_t type; - // Start of this line at the G-code snippet. - size_t line_start; - // End of this line at the G-code snippet. - size_t line_end; - // XY Euclidian length of this segment. - float length; - // Current feedrate, possibly adjusted. - float feedrate; - // Current duration of this segment. - float time; - // Maximum duration of this segment. - float time_max; - // If marked with the "slowdown" flag, the line has been slowed down. - bool slowdown; -}; - -// Calculate the required per extruder time stretches. -struct PerExtruderAdjustments -{ - // Calculate the total elapsed time per this extruder, adjusted for the slowdown. - float elapsed_time_total() const { - float time_total = 0.f; - for (const CoolingLine &line : lines) - time_total += line.time; - return time_total; - } - // Calculate the total elapsed time when slowing down - // to the minimum extrusion feed rate defined for the current material. - float maximum_time_after_slowdown(bool slowdown_external_perimeters) const { - float time_total = 0.f; - for (const CoolingLine &line : lines) - if (line.adjustable(slowdown_external_perimeters)) { - if (line.time_max == FLT_MAX) - return FLT_MAX; - else - time_total += line.time_max; - } else - time_total += line.time; - return time_total; - } - // Calculate the adjustable part of the total time. - float adjustable_time(bool slowdown_external_perimeters) const { - float time_total = 0.f; - for (const CoolingLine &line : lines) - if (line.adjustable(slowdown_external_perimeters)) - time_total += line.time; - return time_total; - } - // Calculate the non-adjustable part of the total time. - float non_adjustable_time(bool slowdown_external_perimeters) const { - float time_total = 0.f; - for (const CoolingLine &line : lines) - if (! line.adjustable(slowdown_external_perimeters)) - time_total += line.time; - return time_total; - } - // Slow down the adjustable extrusions to the minimum feedrate allowed for the current extruder material. - // Used by both proportional and non-proportional slow down. - float slowdown_to_minimum_feedrate(bool slowdown_external_perimeters) { - float time_total = 0.f; - for (CoolingLine &line : lines) { - if (line.adjustable(slowdown_external_perimeters)) { - assert(line.time_max >= 0.f && line.time_max < FLT_MAX); - line.slowdown = true; - line.time = line.time_max; - line.feedrate = line.length / line.time; - } - time_total += line.time; - } - return time_total; - } - // Slow down each adjustable G-code line proportionally by a factor. - // Used by the proportional slow down. - float slow_down_proportional(float factor, bool slowdown_external_perimeters) { - assert(factor >= 1.f); - float time_total = 0.f; - for (CoolingLine &line : lines) { - if (line.adjustable(slowdown_external_perimeters)) { - line.slowdown = true; - line.time = std::min(line.time_max, line.time * factor); - line.feedrate = line.length / line.time; - } - time_total += line.time; - } - return time_total; - } - - // Sort the lines, adjustable first, higher feedrate first. - // Used by non-proportional slow down. - void sort_lines_by_decreasing_feedrate() { - std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) { - bool adj1 = l1.adjustable(); - bool adj2 = l2.adjustable(); - return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1; - }); - for (n_lines_adjustable = 0; - n_lines_adjustable < lines.size() && this->lines[n_lines_adjustable].adjustable(); - ++ n_lines_adjustable); - time_non_adjustable = 0.f; - for (size_t i = n_lines_adjustable; i < lines.size(); ++ i) - time_non_adjustable += lines[i].time; - } - - // Calculate the maximum time stretch when slowing down to min_feedrate. - // Slowdown to min_feedrate shall be allowed for this extruder's material. - // Used by non-proportional slow down. - float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) const { - float time_stretch = 0.f; - assert(this->slow_down_min_speed < min_feedrate + EPSILON); - for (size_t i = 0; i < n_lines_adjustable; ++ i) { - const CoolingLine &line = lines[i]; - if (line.feedrate > min_feedrate) - time_stretch += line.time * (line.feedrate / min_feedrate - 1.f); - } - return time_stretch; - } - - // Slow down all adjustable lines down to min_feedrate. - // Slowdown to min_feedrate shall be allowed for this extruder's material. - // Used by non-proportional slow down. - void slow_down_to_feedrate(float min_feedrate) { - assert(this->slow_down_min_speed < min_feedrate + EPSILON); - for (size_t i = 0; i < n_lines_adjustable; ++ i) { - CoolingLine &line = lines[i]; - if (line.feedrate > min_feedrate) { - line.time *= std::max(1.f, line.feedrate / min_feedrate); - line.feedrate = min_feedrate; - line.slowdown = true; - } - } - } - - // Extruder, for which the G-code will be adjusted. - unsigned int extruder_id = 0; - // Is the cooling slow down logic enabled for this extruder's material? - bool cooling_slow_down_enabled = false; - // Slow down the print down to slow_down_min_speed if the total layer time is below slow_down_layer_time. - float slow_down_layer_time = 0.f; - // Minimum print speed allowed for this extruder. - float slow_down_min_speed = 0.f; - - // Parsed lines. - std::vector lines; - // The following two values are set by sort_lines_by_decreasing_feedrate(): - // Number of adjustable lines, at the start of lines. - size_t n_lines_adjustable = 0; - // Non-adjustable time of lines starting with n_lines_adjustable. - float time_non_adjustable = 0; - // Current total time for this extruder. - float time_total = 0; - // Maximum time for this extruder, when the maximum slow down is applied. - float time_maximum = 0; - - // Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable. - size_t idx_line_begin = 0; - size_t idx_line_end = 0; -}; - -// Calculate a new feedrate when slowing down by time_stretch for segments faster than min_feedrate. -// Used by non-proportional slow down. -float new_feedrate_to_reach_time_stretch( - std::vector::const_iterator it_begin, std::vector::const_iterator it_end, - float min_feedrate, float time_stretch, size_t max_iter = 20) -{ - float new_feedrate = min_feedrate; - for (size_t iter = 0; iter < max_iter; ++ iter) { + float new_feedrate = min_feedrate; + for (size_t iter = 0; iter < max_iter; ++iter) { double nomin = 0; double denom = time_stretch; - for (auto it = it_begin; it != it_end; ++ it) { - assert((*it)->slow_down_min_speed < min_feedrate + EPSILON); - for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) { - const CoolingLine &line = (*it)->lines[i]; + for (auto it = it_begin; it != it_end; ++it) { + assert((*it)->slow_down_min_speed < min_feedrate + EPSILON); + for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) { + const CoolingLine &line = (*it)->lines[i]; if (line.feedrate > min_feedrate) { - nomin += (double)line.time * (double)line.feedrate; - denom += (double)line.time; + nomin += (double) line.time * (double) line.feedrate; + denom += (double) line.time; } } } assert(denom > 0); - if (denom < 0) - return min_feedrate; - new_feedrate = (float)(nomin / denom); + if (denom < 0) return min_feedrate; + new_feedrate = (float) (nomin / denom); assert(new_feedrate > min_feedrate - EPSILON); - if (new_feedrate < min_feedrate + EPSILON) - goto finished; - for (auto it = it_begin; it != it_end; ++ it) - for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) { - const CoolingLine &line = (*it)->lines[i]; + if (new_feedrate < min_feedrate + EPSILON) goto finished; + for (auto it = it_begin; it != it_end; ++it) + for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) { + const CoolingLine &line = (*it)->lines[i]; if (line.feedrate > min_feedrate && line.feedrate < new_feedrate) // Some of the line segments taken into account in the calculation of nomin / denom are now slower than new_feedrate, // which makes the new_feedrate lower than it should be. @@ -275,283 +37,65 @@ float new_feedrate_to_reach_time_stretch( goto not_finished_yet; } goto finished; -not_finished_yet: + not_finished_yet: min_feedrate = new_feedrate; } // Failed to find the new feedrate for the time_stretch. -finished: +finished : // Test whether the time_stretch was achieved. #ifndef NDEBUG - { - float time_stretch_final = 0.f; - for (auto it = it_begin; it != it_end; ++ it) - time_stretch_final += (*it)->time_stretch_when_slowing_down_to_feedrate(new_feedrate); - assert(std::abs(time_stretch - time_stretch_final) < EPSILON); - } +{ + float time_stretch_final = 0.f; + for (auto it = it_begin; it != it_end; ++it) time_stretch_final += (*it)->time_stretch_when_slowing_down_to_feedrate(new_feedrate); + assert(std::abs(time_stretch - time_stretch_final) < EPSILON); +} #endif /* NDEBUG */ - return new_feedrate; + return new_feedrate; } - -std::string CoolingBuffer::process_layer(std::string &&gcode, size_t layer_id, bool flush) -{ - // Cache the input G-code. - if (m_gcode.empty()) - m_gcode = std::move(gcode); - else - m_gcode += gcode; - - std::string out; - if (flush) { - // This is either an object layer or the very last print layer. Calculate cool down over the collected support layers - // and one object layer. - std::vector per_extruder_adjustments = this->parse_layer_gcode(m_gcode, m_current_pos); - float layer_time_stretched = this->calculate_layer_slowdown(per_extruder_adjustments); - out = this->apply_layer_cooldown(m_gcode, layer_id, layer_time_stretched, per_extruder_adjustments); - m_gcode.clear(); - } - return out; -} - -// Parse the layer G-code for the moves, which could be adjusted. -// Return the list of parsed lines, bucketed by an extruder. -std::vector CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector ¤t_pos) const -{ - std::vector per_extruder_adjustments(m_extruder_ids.size()); - std::vector map_extruder_to_per_extruder_adjustment(m_num_extruders, 0); - for (size_t i = 0; i < m_extruder_ids.size(); ++ i) { - PerExtruderAdjustments &adj = per_extruder_adjustments[i]; - unsigned int extruder_id = m_extruder_ids[i]; - adj.extruder_id = extruder_id; - adj.cooling_slow_down_enabled = m_config.slow_down_for_layer_cooling.get_at(extruder_id); - adj.slow_down_layer_time = float(m_config.slow_down_layer_time.get_at(extruder_id)); - adj.slow_down_min_speed = float(m_config.slow_down_min_speed.get_at(extruder_id)); - map_extruder_to_per_extruder_adjustment[extruder_id] = i; - } - - unsigned int current_extruder = m_current_extruder; - PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; - const char *line_start = gcode.c_str(); - const char *line_end = line_start; - // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command - // for a sequence of extrusion moves. - size_t active_speed_modifier = size_t(-1); - - for (; *line_start != 0; line_start = line_end) - { - while (*line_end != '\n' && *line_end != 0) - ++ line_end; - // sline will not contain the trailing '\n'. - std::string sline(line_start, line_end); - // CoolingLine will contain the trailing '\n'. - if (*line_end == '\n') - ++ line_end; - CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str()); - if (boost::starts_with(sline, "G0 ")) - line.type = CoolingLine::TYPE_G0; - else if (boost::starts_with(sline, "G1 ")) - line.type = CoolingLine::TYPE_G1; - else if (boost::starts_with(sline, "G92 ")) - line.type = CoolingLine::TYPE_G92; - else if (boost::starts_with(sline, "G2 ")) - line.type = CoolingLine::TYPE_G2; - else if (boost::starts_with(sline, "G3 ")) - line.type = CoolingLine::TYPE_G3; - if (line.type) { - // G0, G1 or G92 - // Parse the G-code line. - std::vector new_pos(current_pos); - const char *c = sline.data() + 3; - for (;;) { - // Skip whitespaces. - for (; *c == ' ' || *c == '\t'; ++ c); - if (*c == 0 || *c == ';') - break; - - assert(is_decimal_separator_point()); // for atof - //BBS: Parse the axis. - size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : - (*c == 'E') ? 3 : (*c == 'F') ? 4 : - (*c == 'I') ? 5 : (*c == 'J') ? 6 : size_t(-1); - if (axis != size_t(-1)) { - new_pos[axis] = float(atof(++c)); - if (axis == 4) { - // Convert mm/min to mm/sec. - new_pos[4] /= 60.f; - if ((line.type & CoolingLine::TYPE_G92) == 0) - // This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls. - line.type |= CoolingLine::TYPE_HAS_F; - } else if (axis == 5 || axis == 6) { - // BBS: get position of arc center - new_pos[axis] += current_pos[axis - 5]; - } - } - // Skip this word. - for (; *c != ' ' && *c != '\t' && *c != 0; ++ c); - } - bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER"); - bool wipe = boost::contains(sline, ";_WIPE"); - if (external_perimeter) - line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER; - if (wipe) - line.type |= CoolingLine::TYPE_WIPE; - if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) { - line.type |= CoolingLine::TYPE_ADJUSTABLE; - active_speed_modifier = adjustment->lines.size(); - } - if ((line.type & CoolingLine::TYPE_G92) == 0) { - //BBS: G0, G1, G2, G3. Calculate the duration. - if (m_config.use_relative_e_distances.value) - // Reset extruder accumulator. - current_pos[3] = 0.f; - float dif[4]; - for (size_t i = 0; i < 4; ++ i) - dif[i] = new_pos[i] - current_pos[i]; - float dxy2 = 0; - //BBS: support to calculate length of arc - if (line.type & CoolingLine::TYPE_G2 || line.type & CoolingLine::TYPE_G3) { - Vec3f start(current_pos[0], current_pos[1], 0); - Vec3f end(new_pos[0], new_pos[1], 0); - Vec3f center(new_pos[5], new_pos[6], 0); - bool is_ccw = line.type & CoolingLine::TYPE_G3; - float dxy = ArcSegment::calc_arc_length(start, end, center, is_ccw); - dxy2 = dxy * dxy; - } else { - dxy2 = dif[0] * dif[0] + dif[1] * dif[1]; - } - float dxyz2 = dxy2 + dif[2] * dif[2]; - if (dxyz2 > 0.f) { - // Movement in xyz, calculate time from the xyz Euclidian distance. - line.length = sqrt(dxyz2); - } else if (std::abs(dif[3]) > 0.f) { - // Movement in the extruder axis. - line.length = std::abs(dif[3]); - } - line.feedrate = new_pos[4]; - assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f); - if (line.length > 0) - line.time = line.length / line.feedrate; - line.time_max = line.time; - if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1)) - line.time_max = (adjustment->slow_down_min_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->slow_down_min_speed); - // BBS: add G2 and G3 support - if (active_speed_modifier < adjustment->lines.size() && ((line.type & CoolingLine::TYPE_G1) || - (line.type & CoolingLine::TYPE_G2) || - (line.type & CoolingLine::TYPE_G3))) { - // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry. - assert((line.type & CoolingLine::TYPE_HAS_F) == 0); - CoolingLine &sm = adjustment->lines[active_speed_modifier]; - assert(sm.feedrate > 0.f); - sm.length += line.length; - sm.time += line.time; - if (sm.time_max != FLT_MAX) { - if (line.time_max == FLT_MAX) - sm.time_max = FLT_MAX; - else - sm.time_max += line.time_max; - } - // Don't store this line. - line.type = 0; - } - } - current_pos = std::move(new_pos); - } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { - line.type = CoolingLine::TYPE_EXTRUDE_END; - active_speed_modifier = size_t(-1); - } else if (boost::starts_with(sline, m_toolchange_prefix)) { - unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + m_toolchange_prefix.size()); - // Only change extruder in case the number is meaningful. User could provide an out-of-range index through custom gcodes - those shall be ignored. - if (new_extruder < map_extruder_to_per_extruder_adjustment.size()) { - if (new_extruder != current_extruder) { - // Switch the tool. - line.type = CoolingLine::TYPE_SET_TOOL; - current_extruder = new_extruder; - adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; - } - } - else { - // Only log the error in case of MM printer. Single extruder printers likely ignore any T anyway. - if (map_extruder_to_per_extruder_adjustment.size() > 1) - BOOST_LOG_TRIVIAL(error) << "CoolingBuffer encountered an invalid toolchange, maybe from a custom gcode: " << sline; - } - - } else if (boost::starts_with(sline, ";_OVERHANG_FAN_START")) { - line.type = CoolingLine::TYPE_OVERHANG_FAN_START; - } else if (boost::starts_with(sline, ";_OVERHANG_FAN_END")) { - line.type = CoolingLine::TYPE_OVERHANG_FAN_END; - } else if (boost::starts_with(sline, "G4 ")) { - // Parse the wait time. - line.type = CoolingLine::TYPE_G4; - size_t pos_S = sline.find('S', 3); - size_t pos_P = sline.find('P', 3); - assert(is_decimal_separator_point()); // for atof - line.time = line.time_max = float( - (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) : - (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.); - } else if (boost::starts_with(sline, ";_FORCE_RESUME_FAN_SPEED")) { - line.type = CoolingLine::TYPE_FORCE_RESUME_FAN; - } else if (boost::starts_with(sline, ";_SET_FAN_SPEED_CHANGING_LAYER")) { - line.type = CoolingLine::TYPE_SET_FAN_CHANGING_LAYER; - } - if (line.type != 0) - adjustment->lines.emplace_back(std::move(line)); - } - - return per_extruder_adjustments; -} - // Slow down an extruder range proportionally down to slow_down_layer_time. // Return the total time for the complete layer. -static inline float extruder_range_slow_down_proportional( - std::vector::iterator it_begin, - std::vector::iterator it_end, - // Elapsed time for the extruders already processed. - float elapsed_time_total0, - // Initial total elapsed time before slow down. - float elapsed_time_before_slowdown, - // Target time for the complete layer (all extruders applied). - float slow_down_layer_time) +static inline float extruder_range_slow_down_proportional(std::vector::iterator it_begin, + std::vector::iterator it_end, + // Elapsed time for the extruders already processed. + float elapsed_time_total0, + // Initial total elapsed time before slow down. + float elapsed_time_before_slowdown, + // Target time for the complete layer (all extruders applied). + float slow_down_layer_time) { // Total layer time after the slow down has been applied. float total_after_slowdown = elapsed_time_before_slowdown; // Now decide, whether the external perimeters shall be slowed down as well. float max_time_nep = elapsed_time_total0; - for (auto it = it_begin; it != it_end; ++ it) - max_time_nep += (*it)->maximum_time_after_slowdown(false); + for (auto it = it_begin; it != it_end; ++it) max_time_nep += (*it)->maximum_time_after_slowdown(false); if (max_time_nep > slow_down_layer_time) { // It is sufficient to slow down the non-external perimeter moves to reach the target layer time. // Slow down the non-external perimeters proportionally. float non_adjustable_time = elapsed_time_total0; - for (auto it = it_begin; it != it_end; ++ it) - non_adjustable_time += (*it)->non_adjustable_time(false); + for (auto it = it_begin; it != it_end; ++it) non_adjustable_time += (*it)->non_adjustable_time(false); // The following step is a linear programming task due to the minimum movement speeds of the print moves. // Run maximum 5 iterations until a good enough approximation is reached. - for (size_t iter = 0; iter < 5; ++ iter) { + for (size_t iter = 0; iter < 5; ++iter) { float factor = (slow_down_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time); assert(factor > 1.f); total_after_slowdown = elapsed_time_total0; - for (auto it = it_begin; it != it_end; ++ it) - total_after_slowdown += (*it)->slow_down_proportional(factor, false); - if (total_after_slowdown > 0.95f * slow_down_layer_time) - break; + for (auto it = it_begin; it != it_end; ++it) total_after_slowdown += (*it)->slow_down_proportional(factor, false); + if (total_after_slowdown > 0.95f * slow_down_layer_time) break; } } else { // Slow down everything. First slow down the non-external perimeters to maximum. - for (auto it = it_begin; it != it_end; ++ it) - (*it)->slowdown_to_minimum_feedrate(false); + for (auto it = it_begin; it != it_end; ++it) (*it)->slowdown_to_minimum_feedrate(false); // Slow down the external perimeters proportionally. float non_adjustable_time = elapsed_time_total0; - for (auto it = it_begin; it != it_end; ++ it) - non_adjustable_time += (*it)->non_adjustable_time(true); - for (size_t iter = 0; iter < 5; ++ iter) { + for (auto it = it_begin; it != it_end; ++it) non_adjustable_time += (*it)->non_adjustable_time(true); + for (size_t iter = 0; iter < 5; ++iter) { float factor = (slow_down_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time); assert(factor > 1.f); total_after_slowdown = elapsed_time_total0; - for (auto it = it_begin; it != it_end; ++ it) - total_after_slowdown += (*it)->slow_down_proportional(factor, true); - if (total_after_slowdown > 0.95f * slow_down_layer_time) - break; + for (auto it = it_begin; it != it_end; ++it) total_after_slowdown += (*it)->slow_down_proportional(factor, true); + if (total_after_slowdown > 0.95f * slow_down_layer_time) break; } } return total_after_slowdown; @@ -559,39 +103,36 @@ static inline float extruder_range_slow_down_proportional( // Slow down an extruder range to slow_down_layer_time. // Return the total time for the complete layer. -static inline void extruder_range_slow_down_non_proportional( - std::vector::iterator it_begin, - std::vector::iterator it_end, - float time_stretch) +static inline void extruder_range_slow_down_non_proportional(std::vector::iterator it_begin, + std::vector::iterator it_end, + float time_stretch) { // Slow down. Try to equalize the feedrates. - std::vector by_min_print_speed(it_begin, it_end); + std::vector by_min_print_speed(it_begin, it_end); // Find the next highest adjustable feedrate among the extruders. float feedrate = 0; for (PerExtruderAdjustments *adj : by_min_print_speed) { adj->idx_line_begin = 0; adj->idx_line_end = 0; assert(adj->idx_line_begin < adj->n_lines_adjustable); - if (adj->lines[adj->idx_line_begin].feedrate > feedrate) - feedrate = adj->lines[adj->idx_line_begin].feedrate; + if (adj->lines[adj->idx_line_begin].feedrate > feedrate) feedrate = adj->lines[adj->idx_line_begin].feedrate; } assert(feedrate > 0.f); // Sort by slow_down_min_speed, maximum speed first. std::sort(by_min_print_speed.begin(), by_min_print_speed.end(), - [](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->slow_down_min_speed > p2->slow_down_min_speed; }); + [](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2) { return p1->slow_down_min_speed > p2->slow_down_min_speed; }); // Slow down, fast moves first. for (;;) { // For each extruder, find the span of lines with a feedrate close to feedrate. for (PerExtruderAdjustments *adj : by_min_print_speed) { - for (adj->idx_line_end = adj->idx_line_begin; - adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON; - ++ adj->idx_line_end) ; + for (adj->idx_line_end = adj->idx_line_begin; adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON; + ++adj->idx_line_end) + ; } // Find the next highest adjustable feedrate among the extruders. float feedrate_next = 0.f; for (PerExtruderAdjustments *adj : by_min_print_speed) - if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next) - feedrate_next = adj->lines[adj->idx_line_end].feedrate; + if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next) feedrate_next = adj->lines[adj->idx_line_end].feedrate; // Slow down, limited by max(feedrate_next, slow_down_min_speed). for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) { // Slow down at most by time_stretch. @@ -599,31 +140,27 @@ static inline void extruder_range_slow_down_non_proportional( // All the adjustable speeds are now lowered to the same speed, // and the minimum speed is set to zero. float time_adjustable = 0.f; - for (auto it = adj; it != by_min_print_speed.end(); ++ it) - time_adjustable += (*it)->adjustable_time(true); + for (auto it = adj; it != by_min_print_speed.end(); ++it) time_adjustable += (*it)->adjustable_time(true); float rate = (time_adjustable + time_stretch) / time_adjustable; - for (auto it = adj; it != by_min_print_speed.end(); ++ it) - (*it)->slow_down_proportional(rate, true); + for (auto it = adj; it != by_min_print_speed.end(); ++it) (*it)->slow_down_proportional(rate, true); return; } else { - float feedrate_limit = std::max(feedrate_next, (*adj)->slow_down_min_speed); - bool done = false; + float feedrate_limit = std::max(feedrate_next, (*adj)->slow_down_min_speed); + bool done = false; float time_stretch_max = 0.f; - for (auto it = adj; it != by_min_print_speed.end(); ++ it) - time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit); + for (auto it = adj; it != by_min_print_speed.end(); ++it) time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit); if (time_stretch_max >= time_stretch) { feedrate_limit = new_feedrate_to_reach_time_stretch(adj, by_min_print_speed.end(), feedrate_limit, time_stretch, 20); - done = true; + done = true; } else time_stretch -= time_stretch_max; - for (auto it = adj; it != by_min_print_speed.end(); ++ it) - (*it)->slow_down_to_feedrate(feedrate_limit); - if (done) - return; + for (auto it = adj; it != by_min_print_speed.end(); ++it) (*it)->slow_down_to_feedrate(feedrate_limit); + if (done) return; } // Skip the other extruders with nearly the same slow_down_min_speed, as they have been processed already. auto next = adj; - for (++ next; next != by_min_print_speed.end() && (*next)->slow_down_min_speed > (*adj)->slow_down_min_speed - EPSILON; ++ next); + for (++next; next != by_min_print_speed.end() && (*next)->slow_down_min_speed > (*adj)->slow_down_min_speed - EPSILON; ++next) + ; adj = next; } if (feedrate_next == 0.f) @@ -631,7 +168,7 @@ static inline void extruder_range_slow_down_non_proportional( break; for (PerExtruderAdjustments *adj : by_min_print_speed) { adj->idx_line_begin = adj->idx_line_end; - feedrate = feedrate_next; + feedrate = feedrate_next; } } } @@ -642,34 +179,32 @@ float CoolingBuffer::calculate_layer_slowdown(std::vector by_slowdown_time; + std::vector by_slowdown_time; by_slowdown_time.reserve(per_extruder_adjustments.size()); // Only insert entries, which are adjustable (have cooling enabled and non-zero stretchable time). // Collect total print time of non-adjustable extruders. float elapsed_time_total0 = 0.f; for (PerExtruderAdjustments &adj : per_extruder_adjustments) { // Curren total time for this extruder. - adj.time_total = adj.elapsed_time_total(); + adj.time_total = adj.elapsed_time_total(); // Maximum time for this extruder, when all extrusion moves are slowed down to min_extrusion_speed. adj.time_maximum = adj.maximum_time_after_slowdown(true); if (adj.cooling_slow_down_enabled && adj.lines.size() > 0) { by_slowdown_time.emplace_back(&adj); - if (! m_cooling_logic_proportional) + if (!m_cooling_logic_proportional) // sorts the lines, also sets adj.time_non_adjustable adj.sort_lines_by_decreasing_feedrate(); } else elapsed_time_total0 += adj.elapsed_time_total(); } std::sort(by_slowdown_time.begin(), by_slowdown_time.end(), - [](const PerExtruderAdjustments *adj1, const PerExtruderAdjustments *adj2) - { return adj1->slow_down_layer_time < adj2->slow_down_layer_time; }); + [](const PerExtruderAdjustments *adj1, const PerExtruderAdjustments *adj2) { return adj1->slow_down_layer_time < adj2->slow_down_layer_time; }); - for (auto cur_begin = by_slowdown_time.begin(); cur_begin != by_slowdown_time.end(); ++ cur_begin) { + for (auto cur_begin = by_slowdown_time.begin(); cur_begin != by_slowdown_time.end(); ++cur_begin) { PerExtruderAdjustments &adj = *(*cur_begin); // Calculate the current adjusted elapsed_time_total over the non-finalized extruders. float total = elapsed_time_total0; - for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it) - total += (*it)->time_total; + for (auto it = cur_begin; it != by_slowdown_time.end(); ++it) total += (*it)->time_total; float slow_down_layer_time = adj.slow_down_layer_time * 1.001f; if (total > slow_down_layer_time) { // The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything. @@ -677,8 +212,7 @@ float CoolingBuffer::calculate_layer_slowdown(std::vectortime_maximum; + for (auto it = cur_begin; it != by_slowdown_time.end(); ++it) max_time += (*it)->time_maximum; if (max_time > slow_down_layer_time) { if (m_cooling_logic_proportional) extruder_range_slow_down_proportional(cur_begin, by_slowdown_time.end(), elapsed_time_total0, total, slow_down_layer_time); @@ -686,8 +220,7 @@ float CoolingBuffer::calculate_layer_slowdown(std::vectorslowdown_to_minimum_feedrate(true); + for (auto it = cur_begin; it != by_slowdown_time.end(); ++it) (*it)->slowdown_to_minimum_feedrate(true); } } elapsed_time_total0 += adj.elapsed_time_total(); @@ -695,247 +228,4 @@ float CoolingBuffer::calculate_layer_slowdown(std::vector &per_extruder_adjustments) -{ - // First sort the adjustment lines by of multiple extruders by their position in the source G-code. - std::vector lines; - { - size_t n_lines = 0; - for (const PerExtruderAdjustments &adj : per_extruder_adjustments) - n_lines += adj.lines.size(); - lines.reserve(n_lines); - for (const PerExtruderAdjustments &adj : per_extruder_adjustments) - for (const CoolingLine &line : adj.lines) - lines.emplace_back(&line); - std::sort(lines.begin(), lines.end(), [](const CoolingLine *ln1, const CoolingLine *ln2) { return ln1->line_start < ln2->line_start; } ); - } - // Second generate the adjusted G-code. - std::string new_gcode; - new_gcode.reserve(gcode.size() * 2); - bool overhang_fan_control= false; - int overhang_fan_speed = 0; - - enum class SetFanType { - sfChangingLayer = 0, - sfChangingFilament, - sfImmediatelyApply - }; - - auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &overhang_fan_control, &overhang_fan_speed](SetFanType type) { -#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) - int fan_min_speed = EXTRUDER_CONFIG(fan_min_speed); - int fan_speed_new = EXTRUDER_CONFIG(reduce_fan_stop_start_freq) ? fan_min_speed : 0; - //BBS - int additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed); - int close_fan_the_first_x_layers = EXTRUDER_CONFIG(close_fan_the_first_x_layers); - // Is the fan speed ramp enabled? - int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer); - if (close_fan_the_first_x_layers <= 0 && full_fan_speed_layer > 0) { - // When ramping up fan speed from close_fan_the_first_x_layers to full_fan_speed_layer, force close_fan_the_first_x_layers above zero, - // so there will be a zero fan speed at least at the 1st layer. - close_fan_the_first_x_layers = 1; - } - if (int(layer_id) >= close_fan_the_first_x_layers) { - int fan_max_speed = EXTRUDER_CONFIG(fan_max_speed); - float slow_down_layer_time = float(EXTRUDER_CONFIG(slow_down_layer_time)); - float fan_cooling_layer_time = float(EXTRUDER_CONFIG(fan_cooling_layer_time)); - //BBS: always enable the fan speed interpolation according to layer time - //if (EXTRUDER_CONFIG(cooling)) { - if (layer_time < slow_down_layer_time) { - // Layer time very short. Enable the fan to a full throttle. - fan_speed_new = fan_max_speed; - } else if (layer_time < fan_cooling_layer_time) { - // Layer time quite short. Enable the fan proportionally according to the current layer time. - assert(layer_time >= slow_down_layer_time); - double t = (layer_time - slow_down_layer_time) / (fan_cooling_layer_time - slow_down_layer_time); - fan_speed_new = int(floor(t * fan_min_speed + (1. - t) * fan_max_speed) + 0.5); - } - //} - overhang_fan_speed = EXTRUDER_CONFIG(overhang_fan_speed); - if (int(layer_id) >= close_fan_the_first_x_layers && int(layer_id) + 1 < full_fan_speed_layer) { - // Ramp up the fan speed from close_fan_the_first_x_layers to full_fan_speed_layer. - float factor = float(int(layer_id + 1) - close_fan_the_first_x_layers) / float(full_fan_speed_layer - close_fan_the_first_x_layers); - fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255); - overhang_fan_speed = std::clamp(int(float(overhang_fan_speed) * factor + 0.5f), 0, 255); - } -#undef EXTRUDER_CONFIG - overhang_fan_control= overhang_fan_speed > fan_speed_new; - } else { - overhang_fan_control= false; - overhang_fan_speed = 0; - fan_speed_new = 0; - additional_fan_speed_new = 0; - } - if (fan_speed_new != m_fan_speed) { - m_fan_speed = fan_speed_new; - //BBS - m_current_fan_speed = fan_speed_new; - if (type == SetFanType::sfImmediatelyApply) - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed); - else if (type == SetFanType::sfChangingLayer) - this->m_set_fan_changing_layer = true; - //BBS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished - } - //BBS - if (additional_fan_speed_new != m_additional_fan_speed) { - m_additional_fan_speed = additional_fan_speed_new; - if (type == SetFanType::sfImmediatelyApply) - new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); - else if (type == SetFanType::sfChangingLayer) - this->m_set_addition_fan_changing_layer = true; - //BBS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished - } - }; - - const char *pos = gcode.c_str(); - int current_feedrate = 0; - //BBS - m_set_fan_changing_layer = false; - m_set_addition_fan_changing_layer = false; - change_extruder_set_fan(SetFanType::sfChangingLayer); - for (const CoolingLine *line : lines) { - const char *line_start = gcode.c_str() + line->line_start; - const char *line_end = gcode.c_str() + line->line_end; - if (line_start > pos) - new_gcode.append(pos, line_start - pos); - if (line->type & CoolingLine::TYPE_SET_TOOL) { - unsigned int new_extruder = (unsigned int)atoi(line_start + m_toolchange_prefix.size()); - if (new_extruder != m_current_extruder) { - m_current_extruder = new_extruder; - change_extruder_set_fan(SetFanType::sfChangingFilament); //BBS: will force to resume fan speed when filament change is finished - } - new_gcode.append(line_start, line_end - line_start); - } else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_START) { - if (overhang_fan_control) { - //BBS - m_current_fan_speed = overhang_fan_speed; - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed); - } - } else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_END) { - if (overhang_fan_control) { - //BBS - m_current_fan_speed = m_fan_speed; - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed); - } - } else if (line->type & CoolingLine::TYPE_FORCE_RESUME_FAN) { - //BBS: force to write a fan speed command again - if (m_current_fan_speed != -1) - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed); - if (m_additional_fan_speed != -1) - new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); - } else if (line->type & CoolingLine::TYPE_SET_FAN_CHANGING_LAYER) { - //BBS: check whether fan speed need to changed when change layer - if (m_current_fan_speed != -1 && m_set_fan_changing_layer) { - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed); - m_set_fan_changing_layer = false; - } - if (m_additional_fan_speed != -1 && m_set_addition_fan_changing_layer) { - new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); - m_set_addition_fan_changing_layer = false; - } - } - else if (line->type & CoolingLine::TYPE_EXTRUDE_END) { - // Just remove this comment. - } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) { - // Find the start of a comment, or roll to the end of line. - const char *end = line_start; - for (; end < line_end && *end != ';'; ++ end); - // Find the 'F' word. - const char *fpos = strstr(line_start + 2, " F") + 2; - int new_feedrate = current_feedrate; - // Modify the F word of the current G-code line. - bool modify = false; - // Remove the F word from the current G-code line. - bool remove = false; - assert(fpos != nullptr); - new_feedrate = line->slowdown ? int(floor(60. * line->feedrate + 0.5)) : atoi(fpos); - if (new_feedrate == current_feedrate) { - // No need to change the F value. - if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) - // Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment. - end = line_end; - else - // Remove the feedrate from the G0/G1 line. The G-code line may become empty! - remove = true; - } else if (line->slowdown) { - // The F value will be overwritten. - modify = true; - } else { - // The F value is different from current_feedrate, but not slowed down, thus the G-code line will not be modified. - // Emit the line without the comment. - new_gcode.append(line_start, end - line_start); - current_feedrate = new_feedrate; - } - if (modify || remove) { - if (modify) { - // Replace the feedrate. - new_gcode.append(line_start, fpos - line_start); - current_feedrate = new_feedrate; - char buf[64]; - sprintf(buf, "%d", int(current_feedrate)); - new_gcode += buf; - } else { - // Remove the feedrate word. - const char *f = fpos; - // Roll the pointer before the 'F' word. - for (f -= 2; f > line_start && (*f == ' ' || *f == '\t'); -- f); - - if ((f - line_start == 1) && *line_start == 'G' && (*f == '1' || *f == '0')) { - // BBS: only remain "G1" or "G0" of this line after remove 'F' part, don't save - } else { - // Append up to the F word, without the trailing whitespace. - new_gcode.append(line_start, f - line_start + 1); - } - } - // Skip the non-whitespaces of the F parameter up the comment or end of line. - for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++ fpos); - // Append the rest of the line without the comment. - if (fpos < end) - // The G-code line is not empty yet. Emit the rest of it. - new_gcode.append(fpos, end - fpos); - else if (remove && new_gcode == "G1") { - // The G-code line only contained the F word, now it is empty. Remove it completely including the comments. - new_gcode.resize(new_gcode.size() - 2); - end = line_end; - } - } - // Process the rest of the line. - if (end < line_end) { - if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) { - // Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE" - std::string comment(end, line_end); - boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", ""); - if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER) - boost::replace_all(comment, ";_EXTERNAL_PERIMETER", ""); - if (line->type & CoolingLine::TYPE_WIPE) - boost::replace_all(comment, ";_WIPE", ""); - new_gcode += comment; - } else { - // Just attach the rest of the source line. - new_gcode.append(end, line_end - end); - } - } - } else { - new_gcode.append(line_start, line_end - line_start); - } - pos = line_end; - } - const char *gcode_end = gcode.c_str() + gcode.size(); - if (pos < gcode_end) - new_gcode.append(pos, gcode_end - pos); - - return new_gcode; -} - -} // namespace Slic3r +} \ No newline at end of file diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index 299bcae85..6768df485 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -1,70 +1,21 @@ -#ifndef slic3r_CoolingBuffer_hpp_ -#define slic3r_CoolingBuffer_hpp_ - +#ifndef slic3r_CoolingProcess_hpp_ +#define slic3r_CoolingProcess_hpp_ #include "../libslic3r.h" -#include -#include -#include +#include "GCodeEditer.hpp" namespace Slic3r { -class GCode; -class Layer; -struct PerExtruderAdjustments; - -// A standalone G-code filter, to control cooling of the print. -// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited -// and the print is modified to stretch over a minimum layer time. -// -// The simple it sounds, the actual implementation is significantly more complex. -// Namely, for a multi-extruder print, each material may require a different cooling logic. -// For example, some materials may not like to print too slowly, while with some materials -// we may slow down significantly. -// -class CoolingBuffer { +class CoolingBuffer +{ public: - CoolingBuffer(GCode &gcodegen); - void reset(const Vec3d &position); - void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; } - std::string process_layer(std::string &&gcode, size_t layer_id, bool flush); + CoolingBuffer(){}; + + float calculate_layer_slowdown(std::vector &per_extruder_adjustments); private: - CoolingBuffer& operator=(const CoolingBuffer&) = delete; - std::vector parse_layer_gcode(const std::string &gcode, std::vector ¤t_pos) const; - float calculate_layer_slowdown(std::vector &per_extruder_adjustments); - // Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed. - // Returns the adjusted G-code. - std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector &per_extruder_adjustments); - - // G-code snippet cached for the support layers preceding an object layer. - std::string m_gcode; - // Internal data. - // BBS: X,Y,Z,E,F,I,J - std::vector m_axis; - std::vector m_current_pos; - // Current known fan speed or -1 if not known yet. - int m_fan_speed; - int m_additional_fan_speed; - // Cached from GCodeWriter. - // Printing extruder IDs, zero based. - std::vector m_extruder_ids; - // Highest of m_extruder_ids plus 1. - unsigned int m_num_extruders { 0 }; - const std::string m_toolchange_prefix; - // Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified, - // the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required. - const PrintConfig &m_config; - unsigned int m_current_extruder; - // Old logic: proportional. - bool m_cooling_logic_proportional = false; - //BBS: current fan speed - int m_current_fan_speed; - //BBS: - bool m_set_fan_changing_layer = false; - bool m_set_addition_fan_changing_layer = false; + bool m_cooling_logic_proportional = false; }; } - -#endif +#endif \ No newline at end of file diff --git a/src/libslic3r/GCode/GCodeEditer.cpp b/src/libslic3r/GCode/GCodeEditer.cpp new file mode 100644 index 000000000..50ba2d7ce --- /dev/null +++ b/src/libslic3r/GCode/GCodeEditer.cpp @@ -0,0 +1,573 @@ +#include "../GCode.hpp" +#include "GCodeEditer.hpp" +#include +#include +#include +#include +#include + +#if 0 + #define DEBUG + #define _DEBUG + #undef NDEBUG +#endif + +#include + +namespace Slic3r { + +GCodeEditer::GCodeEditer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0) +{ + this->reset(gcodegen.writer().get_position()); + + const std::vector &extruders = gcodegen.writer().extruders(); + m_extruder_ids.reserve(extruders.size()); + for (const Extruder &ex : extruders) { + m_num_extruders = std::max(ex.id() + 1, m_num_extruders); + m_extruder_ids.emplace_back(ex.id()); + } +} + +void GCodeEditer::reset(const Vec3d &position) +{ + // BBS: add I and J axis to store center of arc + m_current_pos.assign(7, 0.f); + m_current_pos[0] = float(position.x()); + m_current_pos[1] = float(position.y()); + m_current_pos[2] = float(position.z()); + m_current_pos[4] = float(m_config.travel_speed.get_at(get_extruder_index(m_config, m_current_extruder))); + m_fan_speed = -1; + m_additional_fan_speed = -1; + m_current_fan_speed = -1; +} + +static void record_wall_lines(bool &flag, int &line_idx, PerExtruderAdjustments *adjustment, const std::pair &node_pos) +{ + if (flag && line_idx < adjustment->lines.size()) { + CoolingLine &ptr = adjustment->lines[line_idx]; + ptr.outwall_smooth_mark = true; + ptr.object_id = node_pos.first; + ptr.cooling_node_id = node_pos.second; + flag = false; + } +} + +static void mark_node_pos( + bool &flag, int &line_idx, std::pair &node_pos, const std::vector &object_label, int cooling_node_id, int object_id, PerExtruderAdjustments *adjustment) +{ + for (size_t object_idx = 0; object_idx < object_label.size(); ++object_idx) { + if (object_label[object_idx] == object_id) { + if (cooling_node_id == -1) break; + line_idx = adjustment->lines.size(); + flag = true; + node_pos.first = object_idx; + node_pos.second = cooling_node_id; + break; + } + } +} + + +std::string GCodeEditer::process_layer(std::string && gcode, + const size_t layer_id, + std::vector &per_extruder_adjustments, + const std::vector & object_label, + const bool flush, + const bool spiral_vase) +{ + // Cache the input G-code. + if (m_gcode.empty()) + m_gcode = std::move(gcode); + else + m_gcode += gcode; + + std::string out; + if (flush) { + // This is either an object layer or the very last print layer. Calculate cool down over the collected support layers + // and one object layer. + // record parse gcode info to per_extruder_adjustments + per_extruder_adjustments = this->parse_layer_gcode(m_gcode, m_current_pos, object_label, spiral_vase, layer_id > 0); + out = m_gcode; + m_gcode.clear(); + } + return out; +} + +//native-resource://sandbox_fs/webcontent/resource/assets/img/41ecc25c56.png +// Parse the layer G-code for the moves, which could be adjusted. +// Return the list of parsed lines, bucketed by an extruder. +std::vector GCodeEditer::parse_layer_gcode( + const std::string &gcode, + std::vector & current_pos, + const std::vector & object_label, + bool spiral_vase, + bool join_z_smooth) +{ + std::vector per_extruder_adjustments(m_extruder_ids.size()); + std::vector map_extruder_to_per_extruder_adjustment(m_num_extruders, 0); + for (size_t i = 0; i < m_extruder_ids.size(); ++ i) { + PerExtruderAdjustments &adj = per_extruder_adjustments[i]; + unsigned int extruder_id = m_extruder_ids[i]; + adj.extruder_id = extruder_id; + adj.cooling_slow_down_enabled = m_config.slow_down_for_layer_cooling.get_at(extruder_id); + adj.slow_down_layer_time = float(m_config.slow_down_layer_time.get_at(extruder_id)); + adj.slow_down_min_speed = float(m_config.slow_down_min_speed.get_at(extruder_id)); + map_extruder_to_per_extruder_adjustment[extruder_id] = i; + } + + unsigned int current_extruder = m_parse_gcode_extruder; + PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; + const char *line_start = gcode.c_str(); + const char *line_end = line_start; + // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command + // for a sequence of extrusion moves. + size_t active_speed_modifier = size_t(-1); + int object_id = -1; + int cooling_node_id = -1; + std::string object_id_string = "; OBJECT_ID: "; + std::string cooling_node_label = "; COOLING_NODE: "; + bool append_wall_ptr = false; + bool append_inner_wall_ptr = false; + + std::pair node_pos; + int line_idx = -1; + for (; *line_start != 0; line_start = line_end) + { + while (*line_end != '\n' && *line_end != 0) + ++ line_end; + // sline will not contain the trailing '\n'. + std::string sline(line_start, line_end); + // CoolingLine will contain the trailing '\n'. + if (*line_end == '\n') + ++ line_end; + CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str()); + if (boost::starts_with(sline, "G0 ")) + line.type = CoolingLine::TYPE_G0; + else if (boost::starts_with(sline, "G1 ")) + line.type = CoolingLine::TYPE_G1; + else if (boost::starts_with(sline, "G92 ")) + line.type = CoolingLine::TYPE_G92; + else if (boost::starts_with(sline, "G2 ")) + line.type = CoolingLine::TYPE_G2; + else if (boost::starts_with(sline, "G3 ")) + line.type = CoolingLine::TYPE_G3; + //BBS: parse object id & node id + else if (boost::starts_with(sline, object_id_string)) { + std::string sub = sline.substr(object_id_string.size()); + object_id = std::stoi(sub); + } else if (boost::starts_with(sline, cooling_node_label)) { + std::string sub = sline.substr(cooling_node_label.size()); + cooling_node_id = std::stoi(sub); + } + + if (line.type) { + // G0, G1 or G92 + // Parse the G-code line. + std::vector new_pos(current_pos); + const char *c = sline.data() + 3; + for (;;) { + // Skip whitespaces. + for (; *c == ' ' || *c == '\t'; ++ c); + if (*c == 0 || *c == ';') + break; + + assert(is_decimal_separator_point()); // for atof + //BBS: Parse the axis. + size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : + (*c == 'E') ? 3 : (*c == 'F') ? 4 : + (*c == 'I') ? 5 : (*c == 'J') ? 6 : size_t(-1); + if (axis != size_t(-1)) { + new_pos[axis] = float(atof(++c)); + if (axis == 4) { + // Convert mm/min to mm/sec. + new_pos[4] /= 60.f; + if ((line.type & CoolingLine::TYPE_G92) == 0) + // This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls. + line.type |= CoolingLine::TYPE_HAS_F; + } else if (axis == 5 || axis == 6) { + // BBS: get position of arc center + new_pos[axis] += current_pos[axis - 5]; + } + } + // Skip this word. + for (; *c != ' ' && *c != '\t' && *c != 0; ++ c); + } + bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER"); + bool wipe = boost::contains(sline, ";_WIPE"); + + record_wall_lines(append_inner_wall_ptr, line_idx, adjustment, node_pos); + + if (wipe) + line.type |= CoolingLine::TYPE_WIPE; + if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) { + line.type |= CoolingLine::TYPE_ADJUSTABLE; + active_speed_modifier = adjustment->lines.size(); + } + + record_wall_lines(append_wall_ptr, line_idx, adjustment, node_pos); + + if (external_perimeter) { + line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER; + if (line.type & CoolingLine::TYPE_ADJUSTABLE && join_z_smooth && !spiral_vase) { + // BBS: collect outwall info + mark_node_pos(append_wall_ptr, line_idx, node_pos, object_label, cooling_node_id, object_id, adjustment); + } + } + + if ((line.type & CoolingLine::TYPE_G92) == 0) { + //BBS: G0, G1, G2, G3. Calculate the duration. + if (m_config.use_relative_e_distances.value) + // Reset extruder accumulator. + current_pos[3] = 0.f; + float dif[4]; + for (size_t i = 0; i < 4; ++ i) + dif[i] = new_pos[i] - current_pos[i]; + float dxy2 = 0; + //BBS: support to calculate length of arc + if (line.type & CoolingLine::TYPE_G2 || line.type & CoolingLine::TYPE_G3) { + Vec3f start(current_pos[0], current_pos[1], 0); + Vec3f end(new_pos[0], new_pos[1], 0); + Vec3f center(new_pos[5], new_pos[6], 0); + bool is_ccw = line.type & CoolingLine::TYPE_G3; + float dxy = ArcSegment::calc_arc_length(start, end, center, is_ccw); + dxy2 = dxy * dxy; + } else { + dxy2 = dif[0] * dif[0] + dif[1] * dif[1]; + } + float dxyz2 = dxy2 + dif[2] * dif[2]; + if (dxyz2 > 0.f) { + // Movement in xyz, calculate time from the xyz Euclidian distance. + line.length = sqrt(dxyz2); + } else if (std::abs(dif[3]) > 0.f) { + // Movement in the extruder axis. + line.length = std::abs(dif[3]); + } + line.feedrate = new_pos[4]; + line.origin_feedrate = new_pos[4]; + + assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f); + if (line.length > 0) + line.time = line.length / line.feedrate; + + if (line.feedrate == 0) + line.time = 0; + + line.time_max = line.time; + if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1)) + line.time_max = (adjustment->slow_down_min_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->slow_down_min_speed); + line.origin_time_max = line.time_max; + // BBS: add G2 and G3 support + if (active_speed_modifier < adjustment->lines.size() && ((line.type & CoolingLine::TYPE_G1) || + (line.type & CoolingLine::TYPE_G2) || + (line.type & CoolingLine::TYPE_G3))) { + // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry. + assert((line.type & CoolingLine::TYPE_HAS_F) == 0); + CoolingLine &sm = adjustment->lines[active_speed_modifier]; + assert(sm.feedrate > 0.f); + sm.length += line.length; + sm.time += line.time; + if (sm.time_max != FLT_MAX) { + if (line.time_max == FLT_MAX) + sm.time_max = FLT_MAX; + else + sm.time_max += line.time_max; + + sm.origin_time_max = sm.time_max; + } + // Don't store this line. + line.type = 0; + } + } + current_pos = std::move(new_pos); + } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { + line.type = CoolingLine::TYPE_EXTRUDE_END; + active_speed_modifier = size_t(-1); + } else if (boost::starts_with(sline, m_toolchange_prefix)) { + unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + m_toolchange_prefix.size()); + // Only change extruder in case the number is meaningful. User could provide an out-of-range index through custom gcodes - those shall be ignored. + if (new_extruder < map_extruder_to_per_extruder_adjustment.size()) { + if (new_extruder != current_extruder) { + // Switch the tool. + line.type = CoolingLine::TYPE_SET_TOOL; + current_extruder = new_extruder; + adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; + } + } + else { + // Only log the error in case of MM printer. Single extruder printers likely ignore any T anyway. + if (map_extruder_to_per_extruder_adjustment.size() > 1) + BOOST_LOG_TRIVIAL(error) << "CoolingBuffer encountered an invalid toolchange, maybe from a custom gcode: " << sline; + } + + } else if (boost::starts_with(sline, ";_OVERHANG_FAN_START")) { + line.type = CoolingLine::TYPE_OVERHANG_FAN_START; + } else if (boost::starts_with(sline, ";_OVERHANG_FAN_END")) { + line.type = CoolingLine::TYPE_OVERHANG_FAN_END; + } else if (boost::starts_with(sline, "G4 ")) { + // Parse the wait time. + line.type = CoolingLine::TYPE_G4; + size_t pos_S = sline.find('S', 3); + size_t pos_P = sline.find('P', 3); + assert(is_decimal_separator_point()); // for atof + line.time = line.time_max = float( + (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) : + (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.); + line.origin_time_max = line.time_max; + } else if (boost::starts_with(sline, ";_FORCE_RESUME_FAN_SPEED")) { + line.type = CoolingLine::TYPE_FORCE_RESUME_FAN; + } else if (boost::starts_with(sline, ";_SET_FAN_SPEED_CHANGING_LAYER")) { + line.type = CoolingLine::TYPE_SET_FAN_CHANGING_LAYER; + } + if (line.type != 0) + adjustment->lines.emplace_back(std::move(line)); + } + m_parse_gcode_extruder = current_extruder; + return per_extruder_adjustments; +} + +// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed. +// Returns the adjusted G-code. +std::string GCodeEditer::write_layer_gcode( + // Source G-code for the current layer. + const std::string &gcode, + // ID of the current layer, used to disable fan for the first n layers. + size_t layer_id, + // Total time of this layer after slow down, used to control the fan. + float layer_time, + // Per extruder list of G-code lines and their cool down attributes. + std::vector &per_extruder_adjustments) +{ + if (gcode.empty()) + return gcode; + + // First sort the adjustment lines by of multiple extruders by their position in the source G-code. + std::vector lines; + { + size_t n_lines = 0; + for (const PerExtruderAdjustments &adj : per_extruder_adjustments) + n_lines += adj.lines.size(); + lines.reserve(n_lines); + for (const PerExtruderAdjustments &adj : per_extruder_adjustments) + for (const CoolingLine &line : adj.lines) + lines.emplace_back(&line); + std::sort(lines.begin(), lines.end(), [](const CoolingLine *ln1, const CoolingLine *ln2) { return ln1->line_start < ln2->line_start; } ); + } + // Second generate the adjusted G-code. + std::string new_gcode; + new_gcode.reserve(gcode.size() * 2); + bool overhang_fan_control= false; + int overhang_fan_speed = 0; + + enum class SetFanType { + sfChangingLayer = 0, + sfChangingFilament, + sfImmediatelyApply + }; + + auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &overhang_fan_control, &overhang_fan_speed](SetFanType type) { +#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) + int fan_min_speed = EXTRUDER_CONFIG(fan_min_speed); + int fan_speed_new = EXTRUDER_CONFIG(reduce_fan_stop_start_freq) ? fan_min_speed : 0; + //BBS + int additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed); + int close_fan_the_first_x_layers = EXTRUDER_CONFIG(close_fan_the_first_x_layers); + // Is the fan speed ramp enabled? + int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer); + if (close_fan_the_first_x_layers <= 0 && full_fan_speed_layer > 0) { + // When ramping up fan speed from close_fan_the_first_x_layers to full_fan_speed_layer, force close_fan_the_first_x_layers above zero, + // so there will be a zero fan speed at least at the 1st layer. + close_fan_the_first_x_layers = 1; + } + if (int(layer_id) >= close_fan_the_first_x_layers) { + int fan_max_speed = EXTRUDER_CONFIG(fan_max_speed); + float slow_down_layer_time = float(EXTRUDER_CONFIG(slow_down_layer_time)); + float fan_cooling_layer_time = float(EXTRUDER_CONFIG(fan_cooling_layer_time)); + //BBS: always enable the fan speed interpolation according to layer time + //if (EXTRUDER_CONFIG(cooling)) { + if (layer_time < slow_down_layer_time) { + // Layer time very short. Enable the fan to a full throttle. + fan_speed_new = fan_max_speed; + } else if (layer_time < fan_cooling_layer_time) { + // Layer time quite short. Enable the fan proportionally according to the current layer time. + assert(layer_time >= slow_down_layer_time); + double t = (layer_time - slow_down_layer_time) / (fan_cooling_layer_time - slow_down_layer_time); + fan_speed_new = int(floor(t * fan_min_speed + (1. - t) * fan_max_speed) + 0.5); + } + //} + overhang_fan_speed = EXTRUDER_CONFIG(overhang_fan_speed); + if (int(layer_id) >= close_fan_the_first_x_layers && int(layer_id) + 1 < full_fan_speed_layer) { + // Ramp up the fan speed from close_fan_the_first_x_layers to full_fan_speed_layer. + float factor = float(int(layer_id + 1) - close_fan_the_first_x_layers) / float(full_fan_speed_layer - close_fan_the_first_x_layers); + fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255); + overhang_fan_speed = std::clamp(int(float(overhang_fan_speed) * factor + 0.5f), 0, 255); + } +#undef EXTRUDER_CONFIG + overhang_fan_control= overhang_fan_speed > fan_speed_new; + } else { + overhang_fan_control= false; + overhang_fan_speed = 0; + fan_speed_new = 0; + additional_fan_speed_new = 0; + } + if (fan_speed_new != m_fan_speed) { + m_fan_speed = fan_speed_new; + //BBS + m_current_fan_speed = fan_speed_new; + if (type == SetFanType::sfImmediatelyApply) + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed); + else if (type == SetFanType::sfChangingLayer) + this->m_set_fan_changing_layer = true; + //BBS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished + } + //BBS + if (additional_fan_speed_new != m_additional_fan_speed) { + m_additional_fan_speed = additional_fan_speed_new; + if (type == SetFanType::sfImmediatelyApply) + new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); + else if (type == SetFanType::sfChangingLayer) + this->m_set_addition_fan_changing_layer = true; + //BBS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished + } + }; + + const char *pos = gcode.c_str(); + int current_feedrate = 0; + //BBS + m_set_fan_changing_layer = false; + m_set_addition_fan_changing_layer = false; + change_extruder_set_fan(SetFanType::sfChangingLayer); + for (const CoolingLine *line : lines) { + const char *line_start = gcode.c_str() + line->line_start; + const char *line_end = gcode.c_str() + line->line_end; + if (line_start > pos) + new_gcode.append(pos, line_start - pos); + if (line->type & CoolingLine::TYPE_SET_TOOL) { + unsigned int new_extruder = (unsigned int)atoi(line_start + m_toolchange_prefix.size()); + if (new_extruder != m_current_extruder) { + m_current_extruder = new_extruder; + change_extruder_set_fan(SetFanType::sfChangingFilament); //BBS: will force to resume fan speed when filament change is finished + } + new_gcode.append(line_start, line_end - line_start); + } else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_START) { + if (overhang_fan_control) { + //BBS + m_current_fan_speed = overhang_fan_speed; + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed); + } + } else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_END) { + if (overhang_fan_control) { + //BBS + m_current_fan_speed = m_fan_speed; + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed); + } + } else if (line->type & CoolingLine::TYPE_FORCE_RESUME_FAN) { + //BBS: force to write a fan speed command again + if (m_current_fan_speed != -1) + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed); + if (m_additional_fan_speed != -1) + new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); + } else if (line->type & CoolingLine::TYPE_SET_FAN_CHANGING_LAYER) { + //BBS: check whether fan speed need to changed when change layer + if (m_current_fan_speed != -1 && m_set_fan_changing_layer) { + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed); + m_set_fan_changing_layer = false; + } + if (m_additional_fan_speed != -1 && m_set_addition_fan_changing_layer) { + new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); + m_set_addition_fan_changing_layer = false; + } + } + else if (line->type & CoolingLine::TYPE_EXTRUDE_END) { + // Just remove this comment. + } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) { + // Find the start of a comment, or roll to the end of line. + const char *end = line_start; + for (; end < line_end && *end != ';'; ++ end); + // Find the 'F' word. + const char *fpos = strstr(line_start + 2, " F") + 2; + int new_feedrate = current_feedrate; + // Modify the F word of the current G-code line. + bool modify = false; + // Remove the F word from the current G-code line. + bool remove = false; + assert(fpos != nullptr); + new_feedrate = line->slowdown ? int(floor(60. * line->feedrate + 0.5)) : atoi(fpos); + if (new_feedrate == current_feedrate) { + // No need to change the F value. + if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) + // Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment. + end = line_end; + else + // Remove the feedrate from the G0/G1 line. The G-code line may become empty! + remove = true; + } else if (line->slowdown) { + // The F value will be overwritten. + modify = true; + } else { + // The F value is different from current_feedrate, but not slowed down, thus the G-code line will not be modified. + // Emit the line without the comment. + new_gcode.append(line_start, end - line_start); + current_feedrate = new_feedrate; + } + if (modify || remove) { + if (modify) { + // Replace the feedrate. + new_gcode.append(line_start, fpos - line_start); + current_feedrate = new_feedrate; + char buf[64]; + sprintf(buf, "%d", int(current_feedrate)); + new_gcode += buf; + } else { + // Remove the feedrate word. + const char *f = fpos; + // Roll the pointer before the 'F' word. + for (f -= 2; f > line_start && (*f == ' ' || *f == '\t'); -- f); + + if ((f - line_start == 1) && *line_start == 'G' && (*f == '1' || *f == '0')) { + // BBS: only remain "G1" or "G0" of this line after remove 'F' part, don't save + } else { + // Append up to the F word, without the trailing whitespace. + new_gcode.append(line_start, f - line_start + 1); + } + } + // Skip the non-whitespaces of the F parameter up the comment or end of line. + for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++ fpos); + // Append the rest of the line without the comment. + if (fpos < end) + // The G-code line is not empty yet. Emit the rest of it. + new_gcode.append(fpos, end - fpos); + else if (remove && new_gcode == "G1") { + // The G-code line only contained the F word, now it is empty. Remove it completely including the comments. + new_gcode.resize(new_gcode.size() - 2); + end = line_end; + } + } + // Process the rest of the line. + if (end < line_end) { + if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) { + // Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE" + std::string comment(end, line_end); + boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", ""); + if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER) + boost::replace_all(comment, ";_EXTERNAL_PERIMETER", ""); + if (line->type & CoolingLine::TYPE_WIPE) + boost::replace_all(comment, ";_WIPE", ""); + new_gcode += comment; + } else { + // Just attach the rest of the source line. + new_gcode.append(end, line_end - end); + } + } + } else { + new_gcode.append(line_start, line_end - line_start); + } + pos = line_end; + } + const char *gcode_end = gcode.c_str() + gcode.size(); + if (pos < gcode_end) + new_gcode.append(pos, gcode_end - pos); + + return new_gcode; +} + +} // namespace Slic3r diff --git a/src/libslic3r/GCode/GCodeEditer.hpp b/src/libslic3r/GCode/GCodeEditer.hpp new file mode 100644 index 000000000..57d1ee7ef --- /dev/null +++ b/src/libslic3r/GCode/GCodeEditer.hpp @@ -0,0 +1,294 @@ +#ifndef slic3r_GCodeEditer_hpp_ +#define slic3r_GCodeEditer_hpp_ + +#include "../libslic3r.h" +#include +#include +#include +#include +#include + +namespace Slic3r { + +class GCode; +class Layer; + +struct CoolingLine +{ + enum Type { + TYPE_SET_TOOL = 1 << 0, + TYPE_EXTRUDE_END = 1 << 1, + TYPE_OVERHANG_FAN_START = 1 << 2, + TYPE_OVERHANG_FAN_END = 1 << 3, + TYPE_G0 = 1 << 4, + TYPE_G1 = 1 << 5, + TYPE_ADJUSTABLE = 1 << 6, + TYPE_EXTERNAL_PERIMETER = 1 << 7, + // The line sets a feedrate. + TYPE_HAS_F = 1 << 8, + TYPE_WIPE = 1 << 9, + TYPE_G4 = 1 << 10, + TYPE_G92 = 1 << 11, + // BBS: add G2 G3 type + TYPE_G2 = 1 << 12, + TYPE_G3 = 1 << 13, + TYPE_FORCE_RESUME_FAN = 1 << 14, + TYPE_SET_FAN_CHANGING_LAYER = 1 << 15, + }; + + CoolingLine(unsigned int type, size_t line_start, size_t line_end) + : type(type), line_start(line_start), line_end(line_end), length(0.f), feedrate(0.f), origin_feedrate(0.f), time(0.f), time_max(0.f), slowdown(false) + {} + + bool adjustable(bool slowdown_external_perimeters) const + { + return (this->type & TYPE_ADJUSTABLE) && (!(this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) && this->time < this->time_max; + } + + bool adjustable() const { return (this->type & TYPE_ADJUSTABLE) && this->time < this->time_max; } + + size_t type; + // Start of this line at the G-code snippet. + size_t line_start; + // End of this line at the G-code snippet. + size_t line_end; + // XY Euclidian length of this segment. + float length; + // Current feedrate, possibly adjusted. + float feedrate; + // Current duration of this segment. + float time; + // Maximum duration of this segment. + float time_max; + // If marked with the "slowdown" flag, the line has been slowed down. + bool slowdown; + // Current feedrate, possibly adjusted. + float origin_feedrate = 0; + float origin_time_max = 0; + // Current duration of this segment. + //float origin_time; + bool outwall_smooth_mark = false; + int object_id = -1; + int cooling_node_id = -1; +}; + +struct PerExtruderAdjustments +{ + // Calculate the total elapsed time per this extruder, adjusted for the slowdown. + float elapsed_time_total() const + { + float time_total = 0.f; + for (const CoolingLine &line : lines) time_total += line.time; + return time_total; + } + // Calculate the total elapsed time when slowing down + // to the minimum extrusion feed rate defined for the current material. + float maximum_time_after_slowdown(bool slowdown_external_perimeters) const + { + float time_total = 0.f; + for (const CoolingLine &line : lines) + if (line.adjustable(slowdown_external_perimeters)) { + if (line.time_max == FLT_MAX) + return FLT_MAX; + else + time_total += line.time_max; + } else + time_total += line.time; + return time_total; + } + // Calculate the adjustable part of the total time. + float adjustable_time(bool slowdown_external_perimeters) const + { + float time_total = 0.f; + for (const CoolingLine &line : lines) + if (line.adjustable(slowdown_external_perimeters)) time_total += line.time; + return time_total; + } + // Calculate the non-adjustable part of the total time. + float non_adjustable_time(bool slowdown_external_perimeters) const + { + float time_total = 0.f; + for (const CoolingLine &line : lines) + if (!line.adjustable(slowdown_external_perimeters)) time_total += line.time; + return time_total; + } + // Slow down the adjustable extrusions to the minimum feedrate allowed for the current extruder material. + // Used by both proportional and non-proportional slow down. + float slowdown_to_minimum_feedrate(bool slowdown_external_perimeters) + { + float time_total = 0.f; + for (CoolingLine &line : lines) { + if (line.adjustable(slowdown_external_perimeters)) { + assert(line.time_max >= 0.f && line.time_max < FLT_MAX); + line.slowdown = true; + line.time = line.time_max; + line.feedrate = line.length / line.time; + } + time_total += line.time; + } + return time_total; + } + // Slow down each adjustable G-code line proportionally by a factor. + // Used by the proportional slow down. + float slow_down_proportional(float factor, bool slowdown_external_perimeters) + { + assert(factor >= 1.f); + float time_total = 0.f; + for (CoolingLine &line : lines) { + if (line.adjustable(slowdown_external_perimeters)) { + line.slowdown = true; + line.time = std::min(line.time_max, line.time * factor); + line.feedrate = line.length / line.time; + } + time_total += line.time; + } + return time_total; + } + + // Sort the lines, adjustable first, higher feedrate first. + // Used by non-proportional slow down. + void sort_lines_by_decreasing_feedrate() + { + std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) { + bool adj1 = l1.adjustable(); + bool adj2 = l2.adjustable(); + return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1; + }); + for (n_lines_adjustable = 0; n_lines_adjustable < lines.size() && this->lines[n_lines_adjustable].adjustable(); ++n_lines_adjustable) + ; + time_non_adjustable = 0.f; + for (size_t i = n_lines_adjustable; i < lines.size(); ++i) time_non_adjustable += lines[i].time; + } + + // Calculate the maximum time stretch when slowing down to min_feedrate. + // Slowdown to min_feedrate shall be allowed for this extruder's material. + // Used by non-proportional slow down. + float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) const + { + float time_stretch = 0.f; + assert(this->slow_down_min_speed < min_feedrate + EPSILON); + for (size_t i = 0; i < n_lines_adjustable; ++i) { + const CoolingLine &line = lines[i]; + if (line.feedrate > min_feedrate) time_stretch += line.time * (line.feedrate / min_feedrate - 1.f); + } + return time_stretch; + } + + // Slow down all adjustable lines down to min_feedrate. + // Slowdown to min_feedrate shall be allowed for this extruder's material. + // Used by non-proportional slow down. + void slow_down_to_feedrate(float min_feedrate) + { + assert(this->slow_down_min_speed < min_feedrate + EPSILON); + for (size_t i = 0; i < n_lines_adjustable; ++i) { + CoolingLine &line = lines[i]; + if (line.feedrate > min_feedrate) { + line.time *= std::max(1.f, line.feedrate / min_feedrate); + line.feedrate = min_feedrate; + line.slowdown = true; + } + } + } + + //collect lines time + float collection_line_times_of_extruder() { + float times = 0; + for (const CoolingLine &line: lines) { + times += line.time; + } + return times; + } + + // Extruder, for which the G-code will be adjusted. + unsigned int extruder_id = 0; + // Is the cooling slow down logic enabled for this extruder's material? + bool cooling_slow_down_enabled = false; + // Slow down the print down to slow_down_min_speed if the total layer time is below slow_down_layer_time. + float slow_down_layer_time = 0.f; + // Minimum print speed allowed for this extruder. + float slow_down_min_speed = 0.f; + + // Parsed lines. + std::vector lines; + // The following two values are set by sort_lines_by_decreasing_feedrate(): + // Number of adjustable lines, at the start of lines. + size_t n_lines_adjustable = 0; + // Non-adjustable time of lines starting with n_lines_adjustable. + float time_non_adjustable = 0; + // Current total time for this extruder. + float time_total = 0; + // Maximum time for this extruder, when the maximum slow down is applied. + float time_maximum = 0; + + // Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable. + size_t idx_line_begin = 0; + size_t idx_line_end = 0; +}; +// A standalone G-code filter, to control cooling of the print. +// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited +// and the print is modified to stretch over a minimum layer time. +// +// The simple it sounds, the actual implementation is significantly more complex. +// Namely, for a multi-extruder print, each material may require a different cooling logic. +// For example, some materials may not like to print too slowly, while with some materials +// we may slow down significantly. +// +class GCodeEditer { +public: + GCodeEditer(GCode &gcodegen); + void reset(const Vec3d &position); + void set_current_extruder(unsigned int extruder_id) + { + m_current_extruder = extruder_id; + m_parse_gcode_extruder = extruder_id; + } + std::string process_layer(std::string && gcode, + const size_t layer_id, + std::vector &per_extruder_adjustments, + const std::vector & object_label, + const bool flush, + const bool spiral_vase); + + // float calculate_layer_slowdown(std::vector &per_extruder_adjustments); + // Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed. + // Returns the adjusted G-code. + std::string write_layer_gcode(const std::string &gcode, size_t layer_id, float layer_time, std::vector &per_extruder_adjustments); + +private : + GCodeEditer& operator=(const GCodeEditer&) = delete; + std::vector parse_layer_gcode(const std::string & gcode, + std::vector & current_pos, + const std::vector & object_label, + bool spiral_vase, + bool join_z_smooth); + + // G-code snippet cached for the support layers preceding an object layer. + std::string m_gcode; + // Internal data. + // BBS: X,Y,Z,E,F,I,J + std::vector m_axis; + std::vector m_current_pos; + // Current known fan speed or -1 if not known yet. + int m_fan_speed; + int m_additional_fan_speed; + // Cached from GCodeWriter. + // Printing extruder IDs, zero based. + std::vector m_extruder_ids; + // Highest of m_extruder_ids plus 1. + unsigned int m_num_extruders { 0 }; + const std::string m_toolchange_prefix; + // Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified, + // the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required. + const PrintConfig &m_config; + unsigned int m_current_extruder; + unsigned int m_parse_gcode_extruder; + //BBS: current fan speed + int m_current_fan_speed; + //BBS: + bool m_set_fan_changing_layer = false; + bool m_set_addition_fan_changing_layer = false; +}; + +} + +#endif diff --git a/src/libslic3r/GCode/Smoothing.cpp b/src/libslic3r/GCode/Smoothing.cpp new file mode 100644 index 000000000..dd723bd6d --- /dev/null +++ b/src/libslic3r/GCode/Smoothing.cpp @@ -0,0 +1,224 @@ +#include "Smoothing.hpp" + +namespace Slic3r { + +void SmoothCalculator::build_node(std::vector & wall_collection, + const std::vector & object_label, + const std::vector &per_extruder_adjustments) +{ + if (per_extruder_adjustments.empty()) + return; + // BBS: update outwall feedrate + // update feedrate of outwall after initial cooling process + // initial and arrange node collection seq + for (size_t object_idx = 0; object_idx < object_label.size(); ++object_idx) { + OutwallCollection object_level; + object_level.object_id = object_label[object_idx]; + wall_collection.push_back(object_level); + } + + for (size_t extruder_idx = 0; extruder_idx < per_extruder_adjustments.size(); ++extruder_idx) { + const PerExtruderAdjustments &extruder_adjustments = per_extruder_adjustments[extruder_idx]; + for (size_t line_idx = 0; line_idx < extruder_adjustments.lines.size(); ++line_idx) { + const CoolingLine &line = extruder_adjustments.lines[line_idx]; + if (line.outwall_smooth_mark) { + // search node id + if (wall_collection[line.object_id].cooling_nodes.count(line.cooling_node_id) == 0) { + CoolingNode node; + wall_collection[line.object_id].cooling_nodes.emplace(line.cooling_node_id, node); + } + + CoolingNode &node = wall_collection[line.object_id].cooling_nodes[line.cooling_node_id]; + if (line.type & CoolingLine::TYPE_EXTERNAL_PERIMETER) { + node.outwall_line.emplace_back(line_idx, extruder_idx); + if (node.max_feedrate < line.feedrate) { + node.max_feedrate = line.feedrate; + node.filter_feedrate = node.max_feedrate; + } + } + + } + } + } +} + + +static void exclude_participate_in_speed_slowdown(std::vector> & lines, + std::vector &per_extruder_adjustments, + CoolingNode & node) +{ + // BBS: add protect, feedrate will be 0 if the outwall is overhang. just apply not adjust flage + bool apply_speed = node.max_feedrate > 0 && node.filter_feedrate > 0; + if (apply_speed) node.rate = node.filter_feedrate / node.max_feedrate; + + for (std::pair line_pos : lines) { + CoolingLine &line = per_extruder_adjustments[line_pos.second].lines[line_pos.first]; + if (apply_speed && line.feedrate > node.filter_feedrate) { + line.feedrate = node.filter_feedrate; + line.slowdown = true; + } + + // not adjust outwal line speed + line.type = line.type & (~CoolingLine::TYPE_ADJUSTABLE); + // update time cost + if (line.feedrate == 0 || line.length == 0) + line.time = 0; + else + line.time = line.length / line.feedrate; + } +} + +float SmoothCalculator::recaculate_layer_time(int layer_id, std::vector &extruder_adjustments) +{ + // rewrite feedrate + for (size_t obj_id = 0; obj_id < layers_wall_collection[layer_id].size(); ++obj_id) { + for (size_t node_id = 0; node_id < layers_wall_collection[layer_id][obj_id].cooling_nodes.size(); ++node_id) { + CoolingNode &node = layers_wall_collection[layer_id][obj_id].cooling_nodes[node_id]; + // set outwall speed + exclude_participate_in_speed_slowdown(node.outwall_line, extruder_adjustments, node); + } + } + + float layer_time = 0; + for (PerExtruderAdjustments extruder : extruder_adjustments) { + layer_time += extruder.collection_line_times_of_extruder(); + } + + return layer_time; +}; + +void SmoothCalculator::init_object_node_range() +{ + for (size_t object_id = 0; object_id < objects_node_range.size(); ++object_id) { + for (size_t layer_id = 1; layer_id < layers_wall_collection.size(); ++layer_id) { + const OutwallCollection &each_object = layers_wall_collection[layer_id][object_id]; + auto it = each_object.cooling_nodes.begin(); + while (it != each_object.cooling_nodes.end()) { + if (objects_node_range[object_id].count(it->first) == 0) { + objects_node_range[object_id].emplace(it->first, std::pair(layer_id, layer_id)); + } else { + objects_node_range[object_id][it->first].second = layer_id; + } + it++; + } + } + } +} + +void SmoothCalculator::smooth_layer_speed() +{ + init_object_node_range(); + + for (size_t obj_id = 0; obj_id < objects_node_range.size(); ++obj_id) { + auto it = objects_node_range[obj_id].begin(); + while (it != objects_node_range[obj_id].end()) { + int step_count = 0; + while (step_count < max_steps_count && speed_filter_continue(obj_id, it->first)) { + step_count++; + layer_speed_filter(obj_id, it->first); + } + it++; + } + } +} + +void SmoothCalculator::layer_speed_filter(const int object_id, const int node_id) +{ + int start_pos = guassian_filter.size() / 2; + // first layer don't need to be smoothed + int layer_start = objects_node_range[object_id][node_id].first; + int layer_end = objects_node_range[object_id][node_id].second; + + // BBS: some layers may empty as the support has indenpendent layer + for (int layer_id = layer_start; layer_id <= layer_end; ++layer_id) { + if (layers_wall_collection[layer_id].empty()) continue; + + if (layers_wall_collection[layer_id][object_id].cooling_nodes.count(node_id) == 0) break; + + CoolingNode &node = layers_wall_collection[layer_id][object_id].cooling_nodes[node_id]; + + if (node.outwall_line.empty()) continue; + + double conv_sum = 0; + for (int filter_pos_idx = 0; filter_pos_idx < guassian_filter.size(); ++filter_pos_idx) { + int remap_data_pos = layer_id - start_pos + filter_pos_idx; + + if (remap_data_pos < layer_start) + remap_data_pos = layer_start; + else if (remap_data_pos > layer_end) + remap_data_pos = layer_end; + + // some node may not start at layer 1 + double remap_data = node.filter_feedrate; + if (!layers_wall_collection[remap_data_pos][object_id].cooling_nodes[node_id].outwall_line.empty()) + remap_data = layers_wall_collection[remap_data_pos][object_id].cooling_nodes[node_id].filter_feedrate; + + conv_sum += guassian_filter[filter_pos_idx] * remap_data; + } + double filter_res = conv_sum / filter_sum; + if (filter_res < node.filter_feedrate) node.filter_feedrate = filter_res; + } +} + +bool SmoothCalculator::speed_filter_continue(const int object_id, const int node_id) +{ + int layer_id = objects_node_range[object_id][node_id].first; + int layer_end = objects_node_range[object_id][node_id].second; + + // BBS: some layers may empty as the support has indenpendent layer + for (; layer_id < layer_end; ++layer_id) { + if (std::abs(layers_wall_collection[layer_id + 1][object_id].cooling_nodes[node_id].filter_feedrate - + layers_wall_collection[layer_id][object_id].cooling_nodes[node_id].filter_feedrate) > guassian_stop_threshold) + return true; + } + return false; +} + +void SmoothCalculator::filter_layer_time() +{ + int start_pos = guassian_filter.size() / 2; + // first layer don't need to be smoothed + for (int layer_id = 1; layer_id < layers_cooling_time.size(); ++layer_id) { + if (layers_cooling_time[layer_id] > layer_time_smoothing_threshold) continue; + + double conv_sum = 0; + for (int filter_pos_idx = 0; filter_pos_idx < guassian_filter.size(); ++filter_pos_idx) { + int remap_data_pos = layer_id - start_pos + filter_pos_idx; + + if (remap_data_pos < 1) + remap_data_pos = 1; + else if (remap_data_pos > layers_cooling_time.size() - 1) + remap_data_pos = layers_cooling_time.size() - 1; + + // if the layer time big enough, surface defact will disappear + double data_temp = layers_cooling_time[remap_data_pos] > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : layers_cooling_time[remap_data_pos]; + + conv_sum += guassian_filter[filter_pos_idx] * data_temp; + } + double filter_res = conv_sum / filter_sum; + filter_res = filter_res > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : filter_res; + if (filter_res > layers_cooling_time[layer_id]) layers_cooling_time[layer_id] = filter_res; + } +} + +bool SmoothCalculator::layer_time_filter_continue() +{ + for (int layer_id = 1; layer_id < layers_cooling_time.size() - 1; ++layer_id) { + double layer_time = layers_cooling_time[layer_id] > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : layers_cooling_time[layer_id]; + double layer_time_cmp = layers_cooling_time[layer_id + 1] > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : layers_cooling_time[layer_id + 1]; + + if (std::abs(layer_time - layer_time_cmp) > guassian_layer_time_stop_threshold) return true; + } + return false; +} + +void SmoothCalculator::smooth_layer_time() +{ + int step_count = 0; + while (step_count < max_steps_count && layer_time_filter_continue()) { + step_count++; + filter_layer_time(); + } +} + +} // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/GCode/Smoothing.hpp b/src/libslic3r/GCode/Smoothing.hpp new file mode 100644 index 000000000..837ed8557 --- /dev/null +++ b/src/libslic3r/GCode/Smoothing.hpp @@ -0,0 +1,103 @@ +#ifndef slic3r_Smoothing_hpp_ +#define slic3r_Smoothing_hpp_ +#include "../libslic3r.h" +#include +#include + +namespace Slic3r { + +static const int guassian_window_size = 11; +static const int guassian_r = 2; +static const int guassian_stop_threshold = 5; +static const float guassian_layer_time_stop_threshold = 3.0; +static const int max_steps_count = 1000; + +struct CoolingNode +{ + // extruder pos, line pos; + std::vector> outwall_line; + float max_feedrate = 0; + float filter_feedrate = 0; + double rate = 1; +}; + +struct OutwallCollection +{ + int object_id; + std::map cooling_nodes; +}; + +class SmoothCalculator +{ + +public: + std::vector>> objects_node_range; + std::vector> layers_wall_collection; + std::vector layers_cooling_time; + + SmoothCalculator(const int objects_size, const double gap_limit) : layer_time_smoothing_threshold(gap_limit) + { + guassian_filter_generator(); + objects_node_range.resize(objects_size); + } + + SmoothCalculator(const int objects_size) + { + guassian_filter_generator(); + objects_node_range.resize(objects_size); + } + + void append_data(const std::vector &wall_collection, float cooling_time) + { + layers_wall_collection.push_back(wall_collection); + layers_cooling_time.push_back(cooling_time); + } + + void append_data(const std::vector &wall_collection) + { + layers_wall_collection.push_back(wall_collection); + } + + void build_node(std::vector &wall_collection, const std::vector &object_label, const std::vector &per_extruder_adjustments); + + float recaculate_layer_time(int layer_id, std::vector &extruder_adjustments); + + void smooth_layer_speed(); + +private: + // guassian filter + double guassian_function(double x, double r) { + return exp(-x * x / (2 * r * r)) / (r * sqrt(2 * PI)); + } + + void guassian_filter_generator() { + double r = guassian_r; + int half_win_size = guassian_window_size / 2; + for (int start = -half_win_size; start <= half_win_size; ++start) { + double y = guassian_function(start, r); + filter_sum += y; + guassian_filter.push_back(y); + } + } + + void init_object_node_range(); + + // filter the data + void layer_speed_filter(const int object_id, const int node_id); + + bool speed_filter_continue(const int object_id, const int node_id); + + // filter the data + void filter_layer_time(); + + bool layer_time_filter_continue(); + void smooth_layer_time(); + + std::vector guassian_filter; + double filter_sum = .0f; + float layer_time_smoothing_threshold = 30.0f; +}; + +} // namespace Slic3r + +#endif \ No newline at end of file diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 8d7f52e96..a9dbf7371 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -5,8 +5,10 @@ #include "ShortestPath.hpp" #include "SVG.hpp" #include "BoundingBox.hpp" - +#include "libslic3r/AABBTreeLines.hpp" #include +static const int Continuitious_length = scale_(0.01); +static const int dist_scale_threshold = 1.2; namespace Slic3r { @@ -195,7 +197,7 @@ void Layer::make_perimeters() if (layerms.size() == 1) { // optimization (*layerm)->fill_surfaces.surfaces.clear(); - (*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces, &(*layerm)->fill_no_overlap_expolygons); + (*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces, &(*layerm)->fill_no_overlap_expolygons, this->loop_nodes); (*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces); } else { SurfaceCollection new_slices; @@ -219,7 +221,7 @@ void Layer::make_perimeters() SurfaceCollection fill_surfaces; //BBS ExPolygons fill_no_overlap; - layerm_config->make_perimeters(new_slices, &fill_surfaces, &fill_no_overlap); + layerm_config->make_perimeters(new_slices, &fill_surfaces, &fill_no_overlap, this->loop_nodes); // assign fill_surfaces to each layer if (!fill_surfaces.surfaces.empty()) { @@ -234,9 +236,148 @@ void Layer::make_perimeters() } } } + BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done"; } +//BBS: use aabbtree to get distance +class ContinuitiousDistancer +{ + std::vector lines; + AABBTreeIndirect::Tree<2, double> tree; + +public: + ContinuitiousDistancer(const Points &pts) + { + Lines pt_to_lines = to_lines(pts); + for (const auto &line : pt_to_lines) + lines.emplace_back(line.a.cast(), line.b.cast()); + + tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines); + } + + float distance_from_perimeter(const Vec2f &point) const + { + Vec2d p = point.cast(); + size_t hit_idx_out{}; + Vec2d hit_point_out = Vec2d::Zero(); + auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, p, hit_idx_out, hit_point_out); + if (distance < 0) { + return std::numeric_limits::max(); + } + + distance = sqrt(distance); + const Linef &line = lines[hit_idx_out]; + Vec2d v1 = line.b - line.a; + Vec2d v2 = p - line.a; + if ((v1.x() * v2.y()) - (v1.y() * v2.x()) > 0.0) { distance *= -1; } + return distance; + } + + Lines to_lines(const Points &pts) + { + Lines lines; + if (pts.size() >= 2) { + lines.reserve(pts.size() - 1); + for (Points::const_iterator it = pts.begin(); it != pts.end() - 1; ++it) { lines.push_back(Line(*it, *(it + 1))); } + } + return lines; + } +}; + + +static double get_node_continuity_rang_limit(const std::vector &Prev_node_widths, int prev_pt_idx) { + double width = 0; + double prev_width = Prev_node_widths.front(); + if (prev_pt_idx!=0 && prev_pt_idx < Prev_node_widths.size()) { + prev_width = static_cast(Prev_node_widths[prev_pt_idx]); + } + + width = prev_width * dist_scale_threshold; + return width; +} + +void Layer::calculate_perimeter_continuity(std::vector &prev_nodes) { + for (size_t node_pos = 0; node_pos < loop_nodes.size(); ++node_pos) { + LoopNode &node=loop_nodes[node_pos]; + double width = 0; + ContinuitiousDistancer node_distancer(node.node_contour.pts); + for (size_t prev_pos = 0; prev_pos < prev_nodes.size(); ++prev_pos) { + LoopNode &prev_node = prev_nodes[prev_pos]; + + // no overlap or has diff speed + if (!node.bbox.overlap(prev_node.bbox)) + continue; + + //calculate dist, checkout the continuity + Polyline continuitious_pl; + //check start pt + size_t start = 0; + bool conntiouitious_flag = false; + int end = prev_node.node_contour.pts.size() - 1; + + //if the countor is loop + if (prev_node.node_contour.is_loop) { + for (; end >= 0; --end) { + if (continuitious_pl.length() >= Continuitious_length) { + node.lower_node_id.push_back(prev_node.node_id); + prev_node.upper_node_id.push_back(node.node_id); + conntiouitious_flag = true; + break; + } + + Point pt = prev_node.node_contour.pts[end]; + float dist = node_distancer.distance_from_perimeter(pt.cast()); + // get corr width + width = get_node_continuity_rang_limit(prev_node.node_contour.widths, end); + + if (dist < width && dist > -width) + continuitious_pl.append_before(pt); + else + break; + } + + if (conntiouitious_flag || end < 0) + continue; + } + + int last_pt_idx = end; + // line need to check end point + if (!prev_node.node_contour.is_loop) + last_pt_idx ++; + + for (; start < last_pt_idx; ++start) { + Point pt = prev_node.node_contour.pts[start]; + float dist = node_distancer.distance_from_perimeter(pt.cast()); + //get corr width + width = get_node_continuity_rang_limit(prev_node.node_contour.widths, start); + + if (dist < width && dist > -width) { + continuitious_pl.append(pt); + continue; + } + + if (continuitious_pl.empty() || continuitious_pl.length() < Continuitious_length) { + continuitious_pl.clear(); + continue; + } + + node.lower_node_id.push_back(prev_node.node_id); + prev_node.upper_node_id.push_back(node.node_id); + continuitious_pl.clear(); + break; + + } + + if (continuitious_pl.length() >= Continuitious_length) { + node.lower_node_id.push_back(prev_node.node_id); + prev_node.upper_node_id.push_back(node.node_id); + } + } + } + +} + void Layer::export_region_slices_to_svg(const char *path) const { BoundingBox bbox; diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 5ded2c628..c0be3b110 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -80,7 +80,7 @@ public: void slices_to_fill_surfaces_clipped(); void prepare_fill_surfaces(); //BBS - void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap); + void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap, std::vector &loop_nodes); void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered); double infill_area_threshold() const; // Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer. @@ -155,6 +155,7 @@ public: // BBS ExPolygons loverhangs; BoundingBox loverhangs_bbox; + std::vector loop_nodes; size_t region_count() const { return m_regions.size(); } const LayerRegion* get_region(int idx) const { return m_regions[idx]; } LayerRegion* get_region(int idx) { return m_regions[idx]; } @@ -180,6 +181,9 @@ public: return false; } void make_perimeters(); + //BBS + void calculate_perimeter_continuity(std::vector &prev_nodes); + // Phony version of make_fills() without parameters for Perl integration only. void make_fills() { this->make_fills(nullptr, nullptr); } void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator = nullptr); diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 363e1311c..6eb0aa32e 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -64,7 +64,7 @@ void LayerRegion::slices_to_fill_surfaces_clipped() } } -void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap) +void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection *fill_surfaces, ExPolygons *fill_no_overlap, std::vector &loop_nodes) { this->perimeters.clear(); this->thin_fills.clear(); @@ -93,7 +93,8 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec &this->thin_fills, fill_surfaces, //BBS - fill_no_overlap + fill_no_overlap, + &loop_nodes ); if (this->layer()->lower_layer != nullptr) diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index b5c506f04..eb07bf89d 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -7,6 +7,7 @@ #include "Clipper2Utils.hpp" #include "Arachne/WallToolPaths.hpp" #include "Line.hpp" +#include "Layer.hpp" #include #include #include @@ -887,13 +888,32 @@ static void detect_brigde_wall_arachne(const PerimeterGenerator &perimeter_gener static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& perimeter_generator, std::vector& pg_extrusions) { ExtrusionEntityCollection extrusion_coll; - for (PerimeterGeneratorArachneExtrusion& pg_extrusion : pg_extrusions) { + if (perimeter_generator.print_config->z_direction_outwall_speed_continuous) + extrusion_coll.loop_node_range.first = perimeter_generator.loop_nodes->size(); + + for (PerimeterGeneratorArachneExtrusion &pg_extrusion : pg_extrusions) { Arachne::ExtrusionLine* extrusion = pg_extrusion.extrusion; if (extrusion->empty()) continue; - + //get extrusion date const bool is_external = extrusion->inset_idx == 0; - ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter; + ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter; + + if (is_external && perimeter_generator.print_config->z_direction_outwall_speed_continuous) { + LoopNode node; + NodeContour node_contour; + node_contour.is_loop = extrusion->is_closed; + for (size_t i = 0; i < extrusion->junctions.size(); i++) { + node_contour.pts.push_back(extrusion->junctions[i].p); + node_contour.widths.push_back(extrusion->junctions[i].w); + } + node.node_contour = node_contour; + node.node_id = perimeter_generator.loop_nodes->size(); + node.loop_id = extrusion_coll.entities.size(); + node.bbox = get_extents(node.node_contour.pts); + node.bbox.offset(perimeter_generator.config->outer_wall_line_width/2); + perimeter_generator.loop_nodes->push_back(std::move(node)); + } if (pg_extrusion.fuzzify) fuzzy_extrusion_line(*extrusion, scaled(perimeter_generator.config->fuzzy_skin_thickness.value), scaled(perimeter_generator.config->fuzzy_skin_point_distance.value)); @@ -1084,7 +1104,10 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p extrusion_coll.append(ExtrusionMultiPath(std::move(multi_path))); } } + } + if (perimeter_generator.print_config->z_direction_outwall_speed_continuous) + extrusion_coll.loop_node_range.second = perimeter_generator.loop_nodes->size(); return extrusion_coll; } @@ -1166,6 +1189,7 @@ void PerimeterGenerator::process_classic() ExPolygons gaps; ExPolygons top_fills; ExPolygons fill_clip; + std::vector outwall_paths; if (loop_number >= 0) { // In case no perimeters are to be generated, loop_number will equal to -1. std::vector contours(loop_number+1); // depth => loops @@ -1291,6 +1315,35 @@ void PerimeterGenerator::process_classic() //BBS: save perimeter loop which use smaller width if (i == 0) { + //store outer wall + if (print_config->z_direction_outwall_speed_continuous) { + // not loop + for (const ThickPolyline &polyline : thin_walls) { + NodeContour node_contour; + node_contour.is_loop = false; + node_contour.pts = polyline.points; + node_contour.widths.insert(node_contour.widths.end(), polyline.width.begin(), polyline.width.end()); + outwall_paths.push_back(node_contour); + } + + // loop + for (const Polyline &polyline : to_polylines(offsets_with_smaller_width)) { + NodeContour node_contour; + node_contour.is_loop = true; + node_contour.pts = polyline.points; + node_contour.widths.push_back(scale_(this->smaller_ext_perimeter_flow.width())); + outwall_paths.push_back(node_contour); + } + + for (const Polyline &polyline : to_polylines(offsets)) { + NodeContour node_contour; + node_contour.is_loop = true; + node_contour.pts = polyline.points; + node_contour.widths.push_back(scale_(this->ext_perimeter_flow.width())); + outwall_paths.push_back(node_contour); + } + } + for (const ExPolygon& expolygon : offsets_with_smaller_width) { contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, fuzzify_contours, true)); if (!expolygon.holes.empty()) { @@ -1467,6 +1520,48 @@ void PerimeterGenerator::process_classic() } entities.entities = std::move( entities_reorder); } + + //BBS: add node for loops + if (!outwall_paths.empty() && this->layer_id > 0) { + entities.loop_node_range.first = this->loop_nodes->size(); + if (outwall_paths.size() == 1) { + LoopNode node; + node.node_id = this->loop_nodes->size(); + node.loop_id = 0; + node.node_contour = outwall_paths.front(); + node.bbox = get_extents(node.node_contour.pts); + node.bbox.offset(SCALED_EPSILON); + this->loop_nodes->push_back(node); + } else { + std::vector matched; + matched.resize(outwall_paths.size(), false); + for (int entity_idx = 0; entity_idx < entities.entities.size(); ++entity_idx) { + //skip inner wall + if(entities.entities[entity_idx]->role() == erPerimeter) + continue; + + for (size_t lines_idx = 0; lines_idx < outwall_paths.size(); ++lines_idx) { + if(matched[lines_idx]) + continue; + + if (entities.entities[entity_idx]->first_point().is_in_lines(outwall_paths[lines_idx].pts)) { + matched[lines_idx] = true; + LoopNode node; + node.node_id = this->loop_nodes->size(); + node.loop_id = entity_idx; + node.node_contour = outwall_paths[lines_idx]; + node.bbox = get_extents(node.node_contour.pts); + node.bbox.offset(SCALED_EPSILON); + this->loop_nodes->push_back(node); + break; + } + } + } + } + entities.loop_node_range.second = this->loop_nodes->size(); + } + + // append perimeters for this slice as a collection if (! entities.empty()) this->loops->append(entities); diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index 709910a5b..df3477c30 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -40,8 +40,8 @@ public: std::pair m_lower_overhang_dist_boundary; std::pair m_external_overhang_dist_boundary; std::pair m_smaller_external_overhang_dist_boundary; + std::vector *loop_nodes; - PerimeterGenerator( // Input: const SurfaceCollection* slices, @@ -59,14 +59,18 @@ public: // Infills without the gap fills SurfaceCollection* fill_surfaces, //BBS - ExPolygons* fill_no_overlap) + ExPolygons* fill_no_overlap, + std::vector *loop_nodes) : slices(slices), upper_slices(nullptr), lower_slices(nullptr), layer_height(layer_height), layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow), overhang_flow(flow), solid_infill_flow(flow), config(config), object_config(object_config), print_config(print_config), m_spiral_vase(spiral_mode), - m_scaled_resolution(scaled(print_config->resolution.value > EPSILON ? print_config->resolution.value : EPSILON)), - loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces), fill_no_overlap(fill_no_overlap), + m_scaled_resolution(scaled(print_config->resolution.value > EPSILON ? print_config->resolution.value : EPSILON)), loops(loops), + gap_fill(gap_fill), + fill_surfaces(fill_surfaces), + fill_no_overlap(fill_no_overlap), + loop_nodes(loop_nodes), m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1), m_ext_mm3_per_mm_smaller_width(-1) {} diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index b2e1d0bdd..419b5ab69 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -179,6 +179,49 @@ Point Point::projection_onto(const Line &line) const return ((line.a - *this).cast().squaredNorm() < (line.b - *this).cast().squaredNorm()) ? line.a : line.b; } + +bool Point::is_in_lines(const Points &pts) const +{ + const Point check_point = *this; + for (int pt_idx = 1; pt_idx < pts.size(); pt_idx++) { + const Point pt = pts[pt_idx]; + const Point prev_pt = pts[pt_idx - 1]; + + // if on the endpoints + if ((check_point.x() == pt.x() && check_point.y() == pt.y()) || (check_point.x() == prev_pt.x() && check_point.y() == prev_pt.y())) + return true; + + bool in_x_range = !(check_point.x() > pt.x() == check_point.x() > prev_pt.x()); + bool in_y_range = !(check_point.y() > pt.y() == check_point.y() > prev_pt.y()); + + //on vert line + if (pt.x() == prev_pt.x()) { + if (in_y_range && pt.x() == check_point.x()) + return true; + continue; + } + + // on hori line + if (pt.y() == prev_pt.y()) { + if (in_x_range && pt.y() == check_point.y()) + return true; + continue; + } + + //not right range + if (!in_x_range || !in_y_range) + continue; + + // check if in line + Line line(prev_pt, pt); + double distance = line.distance_to(*this); + if (std::abs(distance) < SCALED_EPSILON) + return true; + } + + return false; +} + bool has_duplicate_points(std::vector &&pts) { std::sort(pts.begin(), pts.end()); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 5e158ddef..4e3065b9a 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -236,6 +236,7 @@ public: double ccw_angle(const Point &p1, const Point &p2) const; Point projection_onto(const MultiPoint &poly) const; Point projection_onto(const Line &line) const; + bool is_in_lines(const Points &pts) const; }; inline bool operator<(const Point &l, const Point &r) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 9d8c9e84f..361bbd8c5 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -895,7 +895,7 @@ static std::vector s_Preset_print_options { "seam_gap", "wipe_speed", "top_solid_infill_flow_ratio", "initial_layer_flow_ratio", "default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk", "travel_jerk", "filter_out_gap_fill", "mmu_segmented_region_max_width", "mmu_segmented_region_interlocking_depth", - "small_perimeter_speed", "small_perimeter_threshold", + "small_perimeter_speed", "small_perimeter_threshold", "z_direction_outwall_speed_continuous", // calib "print_flow_ratio", //Orca diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 8248fb373..3c6f86679 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -296,7 +296,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n //|| opt_key == "resolution" //BBS: when enable arc fitting, we must re-generate perimeter || opt_key == "enable_arc_fitting" - || opt_key == "wall_sequence") { + || opt_key == "wall_sequence" + || opt_key == "z_direction_outwall_speed_continuous") { osteps.emplace_back(posPerimeters); osteps.emplace_back(posInfill); osteps.emplace_back(posSupportMaterial); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index c087bf34a..9c3d24042 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -507,7 +507,7 @@ private: // BBS SupportNecessaryType is_support_necessary(); - + void merge_layer_node(const size_t layer_id, int &max_merged_id, std::map>> &node_record); // XYZ in scaled coordinates Vec3crd m_size; double m_max_z; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index b38df622f..f48c0044c 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -678,6 +678,13 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("z_direction_outwall_speed_continuous", coBool); + def->label = L("Z direction outwall speed continuous"); + def->category = L("Quality"); + def->tooltip = L("Smoothing outwall speed in z direction to get better surface quality. Print time will increases."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("max_travel_detour_distance", coFloatOrPercent); def->label = L("Avoid crossing wall - Max detour length"); def->category = L("Quality"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 41cd19a99..d54f35827 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1084,6 +1084,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( //BBS ((ConfigOptionInts, additional_cooling_fan_speed)) ((ConfigOptionBool, reduce_crossing_wall)) + ((ConfigOptionBool, z_direction_outwall_speed_continuous)) ((ConfigOptionFloatOrPercent, max_travel_detour_distance)) ((ConfigOptionPoints, printable_area)) ((ConfigOptionPointsGroups, extruder_printable_area)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d29fb1dfb..85cadf3dc 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -129,6 +129,67 @@ std::vector> PrintObject::all_regions( return out; } +void PrintObject::merge_layer_node(const size_t layer_id, int &max_merged_id, std::map>> &node_record) +{ + Layer *this_layer = m_layers[layer_id]; + std::vector &loop_nodes = this_layer->loop_nodes; + for (size_t idx = 0; idx < loop_nodes.size(); ++idx) { + //new cool node + if (loop_nodes[idx].lower_node_id.empty()) { + max_merged_id++; + loop_nodes[idx].merged_id = max_merged_id; + std::vector> node_pos; + node_pos.emplace_back(layer_id, idx); + node_record.emplace(max_merged_id, node_pos); + continue; + } + + //it should finds key in map + if (loop_nodes[idx].lower_node_id.size() == 1) { + loop_nodes[idx].merged_id = m_layers[layer_id - 1]->loop_nodes[loop_nodes[idx].lower_node_id.front()].merged_id; + node_record[loop_nodes[idx].merged_id].emplace_back(layer_id, idx); + continue; + } + + //min index + int min_merged_id = -1; + std::vector appear_id; + for (size_t lower_idx = 0; lower_idx < loop_nodes[idx].lower_node_id.size(); ++lower_idx) { + int id = m_layers[layer_id - 1]->loop_nodes[loop_nodes[idx].lower_node_id[lower_idx]].merged_id; + if (min_merged_id == -1 || min_merged_id > id) + min_merged_id = id; + appear_id.push_back(id); + } + + loop_nodes[idx].merged_id = min_merged_id; + node_record[min_merged_id].emplace_back(layer_id, idx); + + //update other node merged id + for (size_t appear_node_idx = 0; appear_node_idx < appear_id.size(); ++appear_node_idx) { + if (appear_id[appear_node_idx] == min_merged_id) + continue; + + auto it = node_record.find(appear_id[appear_node_idx]); + //protect + if (it == node_record.end()) + continue; + + std::vector> &appear_node_pos = it->second; + + for (size_t node_idx = 0; node_idx < appear_node_pos.size(); ++node_idx) { + int node_layer = appear_node_pos[node_idx].first; + int node_pos = appear_node_pos[node_idx].second; + + LoopNode &node = m_layers[node_layer]->loop_nodes[node_pos]; + + node.merged_id = min_merged_id; + node_record[min_merged_id].emplace_back(node_layer, node_pos); + } + node_record.erase(it); + } + } +} + // 1) Merges typed region slices into stInternal type. // 2) Increases an "extra perimeters" counter at region slices where needed. // 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal). @@ -236,6 +297,33 @@ void PrintObject::make_perimeters() m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end"; + if (this->m_print->m_config.z_direction_outwall_speed_continuous) { + // BBS: get continuity of nodes + BOOST_LOG_TRIVIAL(debug) << "Calculating perimeters connection in parallel - start"; + tbb::parallel_for(tbb::blocked_range(0, m_layers.size()), [this](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + m_print->throw_if_canceled(); + if (layer_idx > 1) { + Layer &prev_layer = *m_layers[layer_idx - 1]; + m_layers[layer_idx]->calculate_perimeter_continuity(m_layers[layer_idx - 1]->loop_nodes); + } + } + }); + + m_print->throw_if_canceled(); + BOOST_LOG_TRIVIAL(debug) << "Calculating perimeters connection in parallel - end"; + + BOOST_LOG_TRIVIAL(debug) << "Calculating cooling nodes - start"; + + int max_merged_id = -1; + std::map>> node_record; + for (size_t layer_idx = 1; layer_idx < m_layers.size(); ++layer_idx) { + m_print->throw_if_canceled(); + merge_layer_node(layer_idx, max_merged_id, node_record); + } + m_print->throw_if_canceled(); + BOOST_LOG_TRIVIAL(debug) << "Calculating cooling nodes - end"; + } this->set_done(posPerimeters); } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 7c46b2c2b..b14cdf9b0 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2041,6 +2041,7 @@ void TabPrint::build() optgroup->append_single_option_line("smooth_coefficient","parameter/quality-advance-settings"); optgroup->append_single_option_line("reduce_crossing_wall","parameter/quality-advance-settings"); optgroup->append_single_option_line("max_travel_detour_distance","parameter/quality-advance-settings"); + optgroup->append_single_option_line("z_direction_outwall_speed_continuous", "parameter/quality-advance-settings"); page = add_options_page(L("Strength"), "empty"); optgroup = page->new_optgroup(L("Walls"), L"param_wall");