diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 76b6c3061c..efa0f2eb33 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -62,6 +62,8 @@ set(lisbslic3r_sources ExtrusionEntity.hpp ExtrusionEntityCollection.cpp ExtrusionEntityCollection.hpp + ExtrusionRole.cpp + ExtrusionRole.hpp ExtrusionSimulator.cpp ExtrusionSimulator.hpp FileParserError.hpp @@ -128,8 +130,8 @@ set(lisbslic3r_sources GCode/FanMover.hpp GCode/PostProcessor.cpp GCode/PostProcessor.hpp -# GCode/PressureEqualizer.cpp -# GCode/PressureEqualizer.hpp + GCode/PressureEqualizer.cpp + GCode/PressureEqualizer.hpp GCode/PrintExtents.cpp GCode/PrintExtents.hpp GCode/RetractWhenCrossingPerimeters.cpp diff --git a/src/libslic3r/ExtrusionRole.cpp b/src/libslic3r/ExtrusionRole.cpp new file mode 100644 index 0000000000..81dcee695b --- /dev/null +++ b/src/libslic3r/ExtrusionRole.cpp @@ -0,0 +1,94 @@ +///|/ Copyright (c) Prusa Research 2023 Pavel Mikuš @Godrak, Oleksandra Iushchenko @YuSanka, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#include "ExtrusionRole.hpp" +#include "I18N.hpp" + +#include +#include +#include + + +namespace Slic3r { + +// Convert a rich bitmask based ExtrusionRole to a less expressive ordinal GCodeExtrusionRole. +// GCodeExtrusionRole is to be serialized into G-code and deserialized by G-code viewer, +GCodeExtrusionRole extrusion_role_to_gcode_extrusion_role(ExtrusionRole role) +{ + if (role == erNone) return GCodeExtrusionRole::None; + if (role == erOverhangPerimeter) return GCodeExtrusionRole::OverhangPerimeter; + if (role == erExternalPerimeter) return GCodeExtrusionRole::ExternalPerimeter; + if (role == erPerimeter) return GCodeExtrusionRole::Perimeter; + if (role == erInternalInfill) return GCodeExtrusionRole::InternalInfill; + if (role == erSolidInfill) return GCodeExtrusionRole::SolidInfill; + if (role == erTopSolidInfill) return GCodeExtrusionRole::TopSolidInfill; + if (role == erIroning) return GCodeExtrusionRole::Ironing; + if (role == erBridgeInfill) return GCodeExtrusionRole::BridgeInfill; + if (role == erGapFill) return GCodeExtrusionRole::GapFill; + if (role == erSkirt) return GCodeExtrusionRole::Skirt; + if (role == erSupportMaterial) return GCodeExtrusionRole::SupportMaterial; + if (role == erSupportMaterialInterface) return GCodeExtrusionRole::SupportMaterialInterface; + if (role == erWipeTower) return GCodeExtrusionRole::WipeTower; + assert(false); + return GCodeExtrusionRole::None; +} + +std::string gcode_extrusion_role_to_string(GCodeExtrusionRole role) +{ + switch (role) { + case GCodeExtrusionRole::None : return L("Unknown"); + case GCodeExtrusionRole::Perimeter : return L("Perimeter"); + case GCodeExtrusionRole::ExternalPerimeter : return L("External perimeter"); + case GCodeExtrusionRole::OverhangPerimeter : return L("Overhang perimeter"); + case GCodeExtrusionRole::InternalInfill : return L("Internal infill"); + case GCodeExtrusionRole::SolidInfill : return L("Solid infill"); + case GCodeExtrusionRole::TopSolidInfill : return L("Top solid infill"); + case GCodeExtrusionRole::Ironing : return L("Ironing"); + case GCodeExtrusionRole::BridgeInfill : return L("Bridge infill"); + case GCodeExtrusionRole::GapFill : return L("Gap fill"); + case GCodeExtrusionRole::Skirt : return L("Skirt/Brim"); + case GCodeExtrusionRole::SupportMaterial : return L("Support material"); + case GCodeExtrusionRole::SupportMaterialInterface : return L("Support material interface"); + case GCodeExtrusionRole::WipeTower : return L("Wipe tower"); + case GCodeExtrusionRole::Custom : return L("Custom"); + default : assert(false); + } + return {}; +} + +GCodeExtrusionRole string_to_gcode_extrusion_role(const std::string_view role) +{ + if (role == L("Perimeter")) + return GCodeExtrusionRole::Perimeter; + else if (role == L("External perimeter")) + return GCodeExtrusionRole::ExternalPerimeter; + else if (role == L("Overhang perimeter")) + return GCodeExtrusionRole::OverhangPerimeter; + else if (role == L("Internal infill")) + return GCodeExtrusionRole::InternalInfill; + else if (role == L("Solid infill")) + return GCodeExtrusionRole::SolidInfill; + else if (role == L("Top solid infill")) + return GCodeExtrusionRole::TopSolidInfill; + else if (role == L("Ironing")) + return GCodeExtrusionRole::Ironing; + else if (role == L("Bridge infill")) + return GCodeExtrusionRole::BridgeInfill; + else if (role == L("Gap fill")) + return GCodeExtrusionRole::GapFill; + else if (role == L("Skirt") || role == L("Skirt/Brim")) // "Skirt" is for backward compatibility with 2.3.1 and earlier + return GCodeExtrusionRole::Skirt; + else if (role == L("Support material")) + return GCodeExtrusionRole::SupportMaterial; + else if (role == L("Support material interface")) + return GCodeExtrusionRole::SupportMaterialInterface; + else if (role == L("Wipe tower")) + return GCodeExtrusionRole::WipeTower; + else if (role == L("Custom")) + return GCodeExtrusionRole::Custom; + else + return GCodeExtrusionRole::None; +} + +} diff --git a/src/libslic3r/ExtrusionRole.hpp b/src/libslic3r/ExtrusionRole.hpp new file mode 100644 index 0000000000..e6e4300fc3 --- /dev/null +++ b/src/libslic3r/ExtrusionRole.hpp @@ -0,0 +1,83 @@ +///|/ Copyright (c) 2023 Robert Schiele @schiele +///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#ifndef slic3r_ExtrusionRole_hpp_ +#define slic3r_ExtrusionRole_hpp_ + +#include "enum_bitmask.hpp" +#include "ExtrusionEntity.hpp" + +#include +#include +#include + +namespace Slic3r { + +enum class ExtrusionRoleModifier : uint16_t { +// 1) Extrusion types + // Perimeter (external, inner, ...) + Perimeter, + // Infill (top / bottom / solid inner / sparse inner / bridging inner ...) + Infill, + // Variable width extrusion + Thin, + // Support material extrusion + Support, + Skirt, + Wipe, +// 2) Extrusion modifiers + External, + Solid, + Ironing, + Bridge, +// 3) Special types + // Indicator that the extrusion role was mixed from multiple differing extrusion roles, + // for example from Support and SupportInterface. + Mixed, + // Stopper, there should be maximum 16 modifiers defined for uint16_t bit mask. + Count +}; +// There should be maximum 16 modifiers defined for uint16_t bit mask. +static_assert(int(ExtrusionRoleModifier::Count) <= 16, "ExtrusionRoleModifier: there must be maximum 16 modifiers defined to fit a 16 bit bitmask"); + +using ExtrusionRoleModifiers = enum_bitmask; +ENABLE_ENUM_BITMASK_OPERATORS(ExtrusionRoleModifier); + + + +// Be careful when editing this list as many parts of the code depend +// on the values of these ordinars, for example +// GCodeViewer::Extrusion_Role_Colors +enum class GCodeExtrusionRole : uint8_t { + None, + Perimeter, + ExternalPerimeter, + OverhangPerimeter, + InternalInfill, + SolidInfill, + TopSolidInfill, + Ironing, + BridgeInfill, + GapFill, + Skirt, + SupportMaterial, + SupportMaterialInterface, + WipeTower, + // Custom (user defined) G-code block, for example start / end G-code. + Custom, + // Stopper to count number of enums. + Count +}; + +// Convert a rich bitmask based ExtrusionRole to a less expressive ordinal GCodeExtrusionRole. +// GCodeExtrusionRole is to be serialized into G-code and deserialized by G-code viewer, +GCodeExtrusionRole extrusion_role_to_gcode_extrusion_role(ExtrusionRole role); + +std::string gcode_extrusion_role_to_string(GCodeExtrusionRole role); +GCodeExtrusionRole string_to_gcode_extrusion_role(const std::string_view role); + +} + +#endif // slic3r_ExtrusionRole_hpp_ diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index db46eb534f..c97fda2403 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1873,14 +1873,13 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato if (print.config().spiral_mode.value) m_spiral_vase = make_unique(print.config()); -#ifdef HAS_PRESSURE_EQUALIZER - if (print.config().max_volumetric_extrusion_rate_slope_positive.value > 0 || - print.config().max_volumetric_extrusion_rate_slope_negative.value > 0) - m_pressure_equalizer = make_unique(&print.config()); - m_enable_extrusion_role_markers = (bool)m_pressure_equalizer; -#else /* HAS_PRESSURE_EQUALIZER */ - m_enable_extrusion_role_markers = false; -#endif /* HAS_PRESSURE_EQUALIZER */ + + if (print.config().max_volumetric_extrusion_rate_slope.value > 0){ + m_pressure_equalizer = make_unique(print.config()); + m_enable_extrusion_role_markers = (bool)m_pressure_equalizer; + } else + m_enable_extrusion_role_markers = false; + file.write_format("; HEADER_BLOCK_START\n"); // Write information on the generator. @@ -2388,10 +2387,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.write("M1003 S0\n"); } } - #ifdef HAS_PRESSURE_EQUALIZER - if (m_pressure_equalizer) - file.write(m_pressure_equalizer->process("", true)); - #endif /* HAS_PRESSURE_EQUALIZER */ ++ finished_objects; // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed. // Reset it when starting another object from 1st layer. @@ -2459,10 +2454,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.write("M1003 S0\n"); } } - #ifdef HAS_PRESSURE_EQUALIZER - if (m_pressure_equalizer) - file.write(m_pressure_equalizer->process("", true)); - #endif /* HAS_PRESSURE_EQUALIZER */ if (m_wipe_tower) // Purge the extruder, pull out the active filament. file.write(m_wipe_tower->finalize(*this)); @@ -2595,11 +2586,18 @@ void GCode::process_layers( { // 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, &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()) { - fc.stop(); - return {}; + 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) -> LayerResult { + if (layer_to_print_idx >= layers_to_print.size()) { + if ((!m_pressure_equalizer && layer_to_print_idx == layers_to_print.size()) || (m_pressure_equalizer && layer_to_print_idx == (layers_to_print.size() + 1))) { + fc.stop(); + return {}; + } else { + // Pressure equalizer need insert empty input. Because it returns one layer back. + // Insert NOP (no operation) layer; + ++layer_to_print_idx; + return LayerResult::make_nop_layer_result(); + } } else { const std::pair>& layer = layers_to_print[layer_to_print_idx++]; const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first); @@ -2612,13 +2610,23 @@ void GCode::process_layers( return this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1)); } }); - const auto spiral_mode = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&spiral_mode = *this->m_spiral_vase.get()](GCode::LayerResult in) -> GCode::LayerResult { + + const auto spiral_mode = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&spiral_mode = *this->m_spiral_vase.get()](LayerResult in) -> LayerResult { + if (in.nop_layer_result) + return in; + spiral_mode.enable(in.spiral_vase_enable); return { spiral_mode.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; }); - const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string { + const auto pressure_equalizer = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult { + return pressure_equalizer->process_layer(std::move(in)); + }); + const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&cooling_buffer = *this->m_cooling_buffer.get()](LayerResult in) -> std::string { + if (in.nop_layer_result) + return in.gcode; 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, @@ -2627,6 +2635,7 @@ void GCode::process_layers( const auto fan_mover = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [&fan_mover = this->m_fan_mover, &config = this->config(), &writer = this->m_writer](std::string in)->std::string { + CNumericLocalesSetter locales_setter; if (config.fan_speedup_time.value != 0 || config.fan_kickstart.value > 0) { @@ -2645,10 +2654,14 @@ void GCode::process_layers( }); // 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 & fan_mover & output); + if (m_spiral_vase && m_pressure_equalizer) + tbb::parallel_pipeline(12, generator & spiral_mode & pressure_equalizer & cooling & fan_mover & output); + else if (m_spiral_vase) + tbb::parallel_pipeline(12, generator & spiral_mode & cooling & fan_mover & output); + else if (m_pressure_equalizer) + tbb::parallel_pipeline(12, generator & pressure_equalizer & cooling & fan_mover & output); else - tbb::parallel_pipeline(12, generator & cooling & fan_mover & output); + tbb::parallel_pipeline(12, generator & cooling & fan_mover & output); } // Process all layers of a single object instance (sequential mode) with a parallel pipeline: @@ -2665,8 +2678,8 @@ void GCode::process_layers( { // 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 { + 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) -> LayerResult { if (layer_to_print_idx == layers_to_print.size()) { fc.stop(); return {}; @@ -2679,13 +2692,13 @@ void GCode::process_layers( 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); } }); - const auto spiral_mode = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&spiral_mode = *this->m_spiral_vase.get()](GCode::LayerResult in)->GCode::LayerResult { + const auto spiral_mode = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&spiral_mode = *this->m_spiral_vase.get()](LayerResult in)->LayerResult { spiral_mode.enable(in.spiral_vase_enable); return { spiral_mode.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; }); - const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in)->std::string { + const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&cooling_buffer = *this->m_cooling_buffer.get()](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, @@ -3216,7 +3229,7 @@ inline std::string get_instance_name(const PrintObject *object, const PrintInsta // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. // For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths // and performing the extruder specific extrusions together. -GCode::LayerResult GCode::process_layer( +LayerResult GCode::process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, @@ -3255,7 +3268,7 @@ GCode::LayerResult GCode::process_layer( else if (support_layer != nullptr) layer_ptr = support_layer; const Layer& layer = *layer_ptr; - GCode::LayerResult result { {}, layer.id(), false, last_layer }; + LayerResult result { {}, layer.id(), false, last_layer }; if (layer_tools.extruders.empty()) // Nothing to extrude. return result; @@ -3878,14 +3891,6 @@ GCode::LayerResult GCode::process_layer( // 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); -#ifdef HAS_PRESSURE_EQUALIZER - // Apply pressure equalization if enabled; - // printf("G-code before filter:\n%s\n", gcode.c_str()); - if (m_pressure_equalizer) - gcode = m_pressure_equalizer->process(gcode.c_str(), false); - // printf("G-code after filter:\n%s\n", out.c_str()); -#endif /* HAS_PRESSURE_EQUALIZER */ - file.write(gcode); #endif diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index c604d200ef..a276ebd7bd 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -22,15 +22,13 @@ #include "libslic3r/ObjectID.hpp" #include "GCode/ExtrusionProcessor.hpp" +#include "GCode/PressureEqualizer.hpp" + #include #include #include #include -#ifdef HAS_PRESSURE_EQUALIZER -#include "GCode/PressureEqualizer.hpp" -#endif /* HAS_PRESSURE_EQUALIZER */ - namespace Slic3r { // Forward declarations. @@ -138,6 +136,20 @@ public: static const std::vector& get() { return Colors; } }; +struct LayerResult { + std::string gcode; + size_t layer_id; + // Is spiral vase post processing enabled for this layer? + bool spiral_vase_enable { false }; + // Should the cooling buffer content be flushed at the end of this layer? + bool cooling_buffer_flush { false }; + // Is indicating if this LayerResult should be processed, or it is just inserted artificial LayerResult. + // It is used for the pressure equalizer because it needs to buffer one layer back. + bool nop_layer_result { false }; + + static LayerResult make_nop_layer_result() { return {"", std::numeric_limits::max(), false, false, true}; } +}; + class GCode { public: GCode() : @@ -285,14 +297,6 @@ private: static std::vector collect_layers_to_print(const PrintObject &object); static std::vector>> collect_layers_to_print(const Print &print); - struct LayerResult { - std::string gcode; - size_t layer_id; - // Is spiral vase post processing enabled for this layer? - bool spiral_vase_enable { false }; - // Should the cooling buffer content be flushed at the end of this layer? - bool cooling_buffer_flush { false }; - }; LayerResult process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. @@ -511,9 +515,9 @@ private: std::unique_ptr m_cooling_buffer; std::unique_ptr m_spiral_vase; -#ifdef HAS_PRESSURE_EQUALIZER + std::unique_ptr m_pressure_equalizer; -#endif /* HAS_PRESSURE_EQUALIZER */ + std::unique_ptr m_wipe_tower; // Heights (print_z) at which the skirt has already been extruded. @@ -572,6 +576,7 @@ private: friend class Wipe; friend class WipeTowerIntegration; + friend class PressureEqualizer; friend class Print; }; diff --git a/src/libslic3r/GCode/PressureEqualizer.cpp b/src/libslic3r/GCode/PressureEqualizer.cpp index 48a16a8d58..67e23c914e 100644 --- a/src/libslic3r/GCode/PressureEqualizer.cpp +++ b/src/libslic3r/GCode/PressureEqualizer.cpp @@ -1,139 +1,218 @@ +///|/ Copyright (c) Prusa Research 2016 - 2023 Vojtěch Bubník @bubnikv, Lukáš Hejl @hejllukas, Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena +///|/ Copyright (c) SuperSlicer 2023 Remi Durand @supermerill +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include -#include -#include +#include +#include +#include #include "../libslic3r.h" #include "../PrintConfig.hpp" #include "../LocalesUtils.hpp" +#include "../GCode.hpp" #include "PressureEqualizer.hpp" +#include "fast_float/fast_float.h" +#include "GCodeWriter.hpp" namespace Slic3r { -PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig *config) : - m_config(config) -{ - reset(); -} +static const std::string EXTRUSION_ROLE_TAG = ";_EXTRUSION_ROLE:"; +static const std::string EXTRUDE_END_TAG = ";_EXTRUDE_END"; +static const std::string EXTRUDE_SET_SPEED_TAG = ";_EXTRUDE_SET_SPEED"; +static const std::string EXTERNAL_PERIMETER_TAG = ";_EXTERNAL_PERIMETER"; -PressureEqualizer::~PressureEqualizer() -{ -} +// For how many GCode lines back will adjust a flow rate from the latest line. +// Bigger values affect the GCode export speed a lot, and smaller values could +// affect how distant will be propagated a flow rate adjustment. +static constexpr int max_look_back_limit = 128; -void PressureEqualizer::reset() -{ - circular_buffer_pos = 0; - circular_buffer_size = 100; - circular_buffer_items = 0; - circular_buffer.assign(circular_buffer_size, GCodeLine()); +// Max non-extruding XY distance (travel move) in mm between two continous extrusions where we pretend +// its all one continous extruded line. Above this distance we assume extruder pressure hits 0 +// This exists because often there's tiny travel moves between stuff like infill +// lines where some extruder pressure will remain (so we should equalize between these small travels) +static constexpr long max_ignored_gap_between_extruding_segments = 3; +PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig &config) : m_use_relative_e_distances(config.use_relative_e_distances.value) +{ // Preallocate some data, so that output_buffer.data() will return an empty string. output_buffer.assign(32, 0); - output_buffer_length = 0; + output_buffer_length = 0; + output_buffer_prev_length = 0; m_current_extruder = 0; // Zero the position of the XYZE axes + the current feed memset(m_current_pos, 0, sizeof(float) * 5); - m_current_extrusion_role = erNone; + m_current_extrusion_role = GCodeExtrusionRole::None; // Expect the first command to fill the nozzle (deretract). m_retracted = true; + + m_max_segment_length = 2.f; // Calculate filamet crossections for the multiple extruders. m_filament_crossections.clear(); - for (size_t i = 0; i < m_config->filament_diameter.values.size(); ++ i) { - double r = m_config->filament_diameter.values[i]; - double a = 0.25f*M_PI*r*r; + for (double r : config.filament_diameter.values) { + double a = 0.25f * M_PI * r * r; m_filament_crossections.push_back(float(a)); } - m_max_segment_length = 20.f; // Volumetric rate of a 0.45mm x 0.2mm extrusion at 60mm/s XY movement: 0.45*0.2*60*60=5.4*60 = 324 mm^3/min // Volumetric rate of a 0.45mm x 0.2mm extrusion at 20mm/s XY movement: 0.45*0.2*20*60=1.8*60 = 108 mm^3/min // Slope of the volumetric rate, changing from 20mm/s to 60mm/s over 2 seconds: (5.4-1.8)*60*60/2=60*60*1.8 = 6480 mm^3/min^2 = 1.8 mm^3/s^2 - m_max_volumetric_extrusion_rate_slope_positive = (m_config == NULL) ? 6480.f : - m_config->max_volumetric_extrusion_rate_slope_positive.value * 60.f * 60.f; - m_max_volumetric_extrusion_rate_slope_negative = (m_config == NULL) ? 6480.f : - m_config->max_volumetric_extrusion_rate_slope_negative.value * 60.f * 60.f; - - for (size_t i = 0; i < numExtrusionRoles; ++ i) { - m_max_volumetric_extrusion_rate_slopes[i].negative = m_max_volumetric_extrusion_rate_slope_negative; - m_max_volumetric_extrusion_rate_slopes[i].positive = m_max_volumetric_extrusion_rate_slope_positive; + + if(config.max_volumetric_extrusion_rate_slope.value > 0){ + m_max_volumetric_extrusion_rate_slope_positive = float(config.max_volumetric_extrusion_rate_slope.value) * 60.f * 60.f; + m_max_volumetric_extrusion_rate_slope_negative = float(config.max_volumetric_extrusion_rate_slope.value) * 60.f * 60.f; + m_max_segment_length = float(config.max_volumetric_extrusion_rate_slope_segment_length.value); } - // Don't regulate the pressure in infill. - m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].negative = 0; - m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].positive = 0; - // Don't regulate the pressure in gap fill. - m_max_volumetric_extrusion_rate_slopes[erGapFill].negative = 0; - m_max_volumetric_extrusion_rate_slopes[erGapFill].positive = 0; + for (ExtrusionRateSlope &extrusion_rate_slope : m_max_volumetric_extrusion_rate_slopes) { + extrusion_rate_slope.negative = m_max_volumetric_extrusion_rate_slope_negative; + extrusion_rate_slope.positive = m_max_volumetric_extrusion_rate_slope_positive; + } + + // Don't regulate the pressure before and after ironing. + for (const GCodeExtrusionRole er : {GCodeExtrusionRole::Ironing}) { + m_max_volumetric_extrusion_rate_slopes[size_t(er)].negative = 0; + m_max_volumetric_extrusion_rate_slopes[size_t(er)].positive = 0; + } + opened_extrude_set_speed_block = false; + +#ifdef PRESSURE_EQUALIZER_STATISTIC m_stat.reset(); +#endif + +#ifdef PRESSURE_EQUALIZER_DEBUG line_idx = 0; +#endif } -const char* PressureEqualizer::process(const char *szGCode, bool flush) +void PressureEqualizer::process_layer(const std::string &gcode) { - // Reset length of the output_buffer. - output_buffer_length = 0; - - if (szGCode != 0) { - const char *p = szGCode; - while (*p != 0) { + if (!gcode.empty()) { + const char *gcode_begin = gcode.c_str(); + while (*gcode_begin != 0) { // Find end of the line. - const char *endl = p; + const char *gcode_end = gcode_begin; // Slic3r always generates end of lines in a Unix style. - for (; *endl != 0 && *endl != '\n'; ++ endl) ; - if (circular_buffer_items == circular_buffer_size) - // Buffer is full. Push out the oldest line. - output_gcode_line(circular_buffer[circular_buffer_pos]); - else - ++ circular_buffer_items; - // Process a G-code line, store it into the provided GCodeLine object. - size_t idx_tail = circular_buffer_pos; - circular_buffer_pos = circular_buffer_idx_next(circular_buffer_pos); - if (! process_line(p, endl - p, circular_buffer[idx_tail])) { - // The line has to be forgotten. It contains comment marks, which shall be - // filtered out of the target g-code. - circular_buffer_pos = idx_tail; - -- circular_buffer_items; + for (; *gcode_end != 0 && *gcode_end != '\n'; ++gcode_end); + + m_gcode_lines.emplace_back(); + if (!this->process_line(gcode_begin, gcode_end, m_gcode_lines.back())) { + // The line has to be forgotten. It contains comment marks, which shall be filtered out of the target g-code. + m_gcode_lines.pop_back(); } - p = endl; - if (*p == '\n') - ++ p; + gcode_begin = gcode_end; + if (*gcode_begin == '\n') + ++gcode_begin; + } + assert(!this->opened_extrude_set_speed_block); + } + + // at this point, we have an entire layer of gcode lines loaded into m_gcode_lines + // now we will split the mix of travels and extrudes into segments of continous extrusion and process those + // We skip over large travels, and pretend small ones are part of a continous extrusion segment + long idx_end_current_extrusion = 0; + while (idx_end_current_extrusion < m_gcode_lines.size()) { + // find beginning of next extrusion segment from current pos + const long idx_begin_current_extrusion = find_if(m_gcode_lines.begin() + idx_end_current_extrusion, m_gcode_lines.end(), + [](GCodeLine line) { return line.extruding(); }) - m_gcode_lines.begin(); + // (extrusion begin idx = extrusion end idx) here because we start with extrusion length of zero + idx_end_current_extrusion = idx_begin_current_extrusion; + + // inner loop extends the extrusion segment over small travel moves + while (idx_end_current_extrusion < m_gcode_lines.size()) { + // find end of the current extrusion segment + const auto just_after_end_extrusion = find_if(m_gcode_lines.begin() + idx_end_current_extrusion, m_gcode_lines.end(), + [](GCodeLine line) { return !line.extruding(); }); + idx_end_current_extrusion = std::max(0,(just_after_end_extrusion - m_gcode_lines.begin()) - 1); + const long idx_begin_segment_continuation = advance_segment_beyond_small_gap(idx_end_current_extrusion); + if (idx_begin_segment_continuation > idx_end_current_extrusion) { + // extend the continous line over the small gap + idx_end_current_extrusion = idx_begin_segment_continuation; + continue; // keep going, loop again to find new end of extrusion segment + } else { + // gap to next extrude is too big, stop looking forward. We've found end of this segment + break; + } + } + + // now run the pressure equalizer across the segment like a streamroller + // it operates on a sliding window that moves forward across gcode line by line + for (int i = idx_begin_current_extrusion; i < idx_end_current_extrusion; ++i) { + // feed pressure equalizer past lines, going back to max_look_back_limit (or start of segment) + const auto start_idx = std::max(idx_begin_current_extrusion, i - max_look_back_limit); + adjust_volumetric_rate(start_idx, i); + } + // current extrusion is all done processing so advance beyond it for next loop + idx_end_current_extrusion++; + } +} + +long PressureEqualizer::advance_segment_beyond_small_gap(const long idx_orig) +{ + // this should only be run on the last extruding line before a gap + assert(m_gcode_lines[idx_cur_pos].extruding()); + double distance_traveled = 0.0; + // start at beginning of gap, advance till extrusion found or gap too big + for (auto idx_cur_pos = idx_orig + 1; idx_cur_pos < m_gcode_lines.size(); idx_cur_pos++) { + // started extruding again! return segment extension + if (m_gcode_lines[idx_cur_pos].extruding()) { + return idx_cur_pos; + } + + distance_traveled += m_gcode_lines[idx_cur_pos].dist_xy(); + // gap too big, dont extend segment + if (distance_traveled > max_ignored_gap_between_extruding_segments) { + return idx_orig; } } + // looped until end of layer and couldn't extend extrusion + return idx_orig; +} - if (flush) { - // Flush the remaining valid lines of the circular buffer. - for (size_t idx = circular_buffer_idx_head(); circular_buffer_items > 0; -- circular_buffer_items) { - output_gcode_line(circular_buffer[idx]); - if (++ idx == circular_buffer_size) - idx = 0; - } - // Reset the index pointer. - assert(circular_buffer_items == 0); - circular_buffer_pos = 0; +LayerResult PressureEqualizer::process_layer(LayerResult &&input) +{ + const bool is_first_layer = m_layer_results.empty(); + const size_t next_layer_first_idx = m_gcode_lines.size(); -#if 1 - printf("Statistics: \n"); - printf("Minimum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_min); - printf("Maximum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_max); - if (m_stat.extrusion_length > 0) - m_stat.volumetric_extrusion_rate_avg /= m_stat.extrusion_length; - printf("Average volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_avg); - m_stat.reset(); -#endif - } + if (!input.nop_layer_result) { + this->process_layer(input.gcode); + input.gcode.clear(); // GCode is already processed, so it isn't needed to store it. + m_layer_results.emplace(new LayerResult(input)); + } - return output_buffer.data(); + if (is_first_layer) // Buffer previous input result and output NOP. + return LayerResult::make_nop_layer_result(); + + // Export previous layer. + LayerResult *prev_layer_result = m_layer_results.front(); + m_layer_results.pop(); + + output_buffer_length = 0; + output_buffer_prev_length = 0; + for (size_t line_idx = 0; line_idx < next_layer_first_idx; ++line_idx) + output_gcode_line(line_idx); + m_gcode_lines.erase(m_gcode_lines.begin(), m_gcode_lines.begin() + int(next_layer_first_idx)); + + if (output_buffer_length > 0) + prev_layer_result->gcode = std::string(output_buffer.data()); + + assert(!input.nop_layer_result || m_layer_results.empty()); + LayerResult out = *prev_layer_result; + delete prev_layer_result; + return out; } // Is a white space? static inline bool is_ws(const char c) { return c == ' ' || c == '\t'; } // Is it an end of line? Consider a comment to be an end of line as well. -static inline bool is_eol(const char c) { return c == 0 || c == '\r' || c == '\n' || c == ';'; }; +static inline bool is_eol(const char c) { return c == 0 || c == '\r' || c == '\n' || c == ';'; } // Is it a white space or end of line? -static inline bool is_ws_or_eol(const char c) { return is_ws(c) || is_eol(c); }; +static inline bool is_ws_or_eol(const char c) { return is_ws(c) || is_eol(c); } // Eat whitespaces. static void eatws(const char *&line) @@ -146,35 +225,45 @@ static void eatws(const char *&line) // If succeeded, the line pointer is advanced. static inline int parse_int(const char *&line) { - char *endptr = NULL; + char *endptr = nullptr; long result = strtol(line, &endptr, 10); - if (endptr == NULL || !is_ws_or_eol(*endptr)) - throw Slic3r::RuntimeError("PressureEqualizer: Error parsing an int"); + if (endptr == nullptr || !is_ws_or_eol(*endptr)) + throw Slic3r::InvalidArgument("PressureEqualizer: Error parsing an int"); line = endptr; return int(result); -}; +} + +float string_to_float_decimal_point(const char *line, const size_t str_len, size_t* pos) +{ + float out; + size_t p = fast_float::from_chars(line, line + str_len, out).ptr - line; + if (pos) + *pos = p; + return out; +} // Parse an int starting at the current position of a line. // If succeeded, the line pointer is advanced. -static inline float parse_float(const char *&line) +static inline float parse_float(const char *&line, const size_t line_length) { - char *endptr = NULL; - float result = string_to_double_decimal_point(line, &endptr); - if (endptr == NULL || !is_ws_or_eol(*endptr)) + size_t endptr = 0; + auto result = string_to_float_decimal_point(line, line_length, &endptr); + if (endptr == 0 || !is_ws_or_eol(*(line + endptr))) throw Slic3r::RuntimeError("PressureEqualizer: Error parsing a float"); - line = endptr; + line = line + endptr; return result; -}; +} -bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLine &buf) +bool PressureEqualizer::process_line(const char *line, const char *line_end, GCodeLine &buf) { - static constexpr const char *EXTRUSION_ROLE_TAG = ";_EXTRUSION_ROLE:"; - - if (strncmp(line, EXTRUSION_ROLE_TAG, strlen(EXTRUSION_ROLE_TAG)) == 0) { - line += strlen(EXTRUSION_ROLE_TAG); + const size_t len = line_end - line; + if (strncmp(line, EXTRUSION_ROLE_TAG.data(), EXTRUSION_ROLE_TAG.length()) == 0) { + line += EXTRUSION_ROLE_TAG.length(); int role = atoi(line); - m_current_extrusion_role = ExtrusionRole(role); - ++ line_idx; + m_current_extrusion_role = GCodeExtrusionRole(role); +#ifdef PRESSURE_EQUALIZER_DEBUG + ++line_idx; +#endif return false; } @@ -199,21 +288,43 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi buf.max_volumetric_extrusion_rate_slope_negative = 0.f; buf.extrusion_role = m_current_extrusion_role; + std::string str_line(line, line_end); + const bool found_extrude_set_speed_tag = boost::contains(str_line, EXTRUDE_SET_SPEED_TAG); + const bool found_extrude_end_tag = boost::contains(str_line, EXTRUDE_END_TAG); + assert(!found_extrude_set_speed_tag || !found_extrude_end_tag); + + if (found_extrude_set_speed_tag) + this->opened_extrude_set_speed_block = true; + else if (found_extrude_end_tag) + this->opened_extrude_set_speed_block = false; + // Parse the G-code line, store the result into the buf. switch (toupper(*line ++)) { case 'G': { - int gcode = parse_int(line); + int gcode = -1; + try { + gcode = parse_int(line); + } catch (Slic3r::InvalidArgument &) { + // Ignore invalid GCodes. + eatws(line); + break; + } + + assert(gcode != -1); eatws(line); switch (gcode) { case 0: case 1: { // G0, G1: A FFF 3D printer does not make a difference between the two. + buf.adjustable_flow = this->opened_extrude_set_speed_block; + buf.extrude_set_speed_tag = found_extrude_set_speed_tag; + buf.extrude_end_tag = found_extrude_end_tag; float new_pos[5]; memcpy(new_pos, m_current_pos, sizeof(float)*5); bool changed[5] = { false, false, false, false, false }; while (!is_eol(*line)) { - char axis = toupper(*line++); + const char axis = toupper(*line++); int i = -1; switch (axis) { case 'X': @@ -228,16 +339,16 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi i = 4; break; default: - assert(false); + break; + } + if (i != -1) { + buf.pos_provided[i] = true; + new_pos[i] = parse_float(line, line_end - line); + if (i == 3 && m_use_relative_e_distances) + new_pos[i] += m_current_pos[i]; + changed[i] = new_pos[i] != m_current_pos[i]; + eatws(line); } - if (i == -1) - throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis); - buf.pos_provided[i] = true; - new_pos[i] = parse_float(line); - if (i == 3 && m_config->use_relative_e_distances.value) - new_pos[i] += m_current_pos[i]; - changed[i] = new_pos[i] != m_current_pos[i]; - eatws(line); } if (changed[3]) { // Extrusion, retract or unretract. @@ -263,15 +374,17 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi buf.volumetric_extrusion_rate = rate; buf.volumetric_extrusion_rate_start = rate; buf.volumetric_extrusion_rate_end = rate; + +#ifdef PRESSURE_EQUALIZER_STATISTIC m_stat.update(rate, sqrt(len2)); +#endif +#ifdef PRESSURE_EQUALIZER_DEBUG if (rate < 40.f) { - printf("Extremely low flow rate: %f. Line %d, Length: %f, extrusion: %f Old position: (%f, %f, %f), new position: (%f, %f, %f)\n", - rate, - int(line_idx), - sqrt(len2), sqrt((diff[3]*diff[3])/len2), - m_current_pos[0], m_current_pos[1], m_current_pos[2], - new_pos[0], new_pos[1], new_pos[2]); + printf("Extremely low flow rate: %f. Line %d, Length: %f, extrusion: %f Old position: (%f, %f, %f), new position: (%f, %f, %f)\n", + rate, int(line_idx), sqrt(len2), sqrt((diff[3] * diff[3]) / len2), m_current_pos[0], m_current_pos[1], m_current_pos[2], + new_pos[0], new_pos[1], new_pos[2]); } +#endif } } else if (changed[0] || changed[1] || changed[2]) { // Moving without extrusion. @@ -285,26 +398,22 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi // G92 : Set Position // Set a logical coordinate position to a new value without actually moving the machine motors. // Which axes to set? - bool set = false; while (!is_eol(*line)) { - char axis = toupper(*line++); + const char axis = toupper(*line++); switch (axis) { case 'X': case 'Y': case 'Z': - m_current_pos[axis - 'X'] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f; - set = true; + m_current_pos[axis - 'X'] = (!is_ws_or_eol(*line)) ? parse_float(line, line_end - line) : 0.f; break; case 'E': - m_current_pos[3] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f; - set = true; + m_current_pos[3] = (!is_ws_or_eol(*line)) ? parse_float(line, line_end - line) : 0.f; break; default: - throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis); + break; } eatws(line); } - assert(set); break; } case 10: @@ -326,20 +435,24 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi break; } case 'M': { - int mcode = parse_int(line); eatws(line); - switch (mcode) { - default: - // Ignore the rest of the M-codes. - break; - } + // Ignore the rest of the M-codes. break; } case 'T': { // Activate an extruder head. - int new_extruder = parse_int(line); - if (new_extruder != m_current_extruder) { + int new_extruder = -1; + try { + new_extruder = parse_int(line); + } catch (Slic3r::InvalidArgument &) { + // Ignore invalid GCodes starting with T. + eatws(line); + break; + } + assert(new_extruder != -1); + + if (new_extruder != int(m_current_extruder)) { m_current_extruder = new_extruder; m_retracted = true; buf.type = GCODELINETYPE_TOOL_CHANGE; @@ -352,15 +465,16 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi buf.extruder_id = m_current_extruder; memcpy(buf.pos_end, m_current_pos, sizeof(float)*5); - - adjust_volumetric_rate(); - ++ line_idx; - return true; +#ifdef PRESSURE_EQUALIZER_DEBUG + ++line_idx; +#endif + return true; } -void PressureEqualizer::output_gcode_line(GCodeLine &line) +void PressureEqualizer::output_gcode_line(const size_t line_idx) { - if (! line.modified) { + GCodeLine &line = m_gcode_lines[line_idx]; + if (!line.modified) { push_to_output(line.raw.data(), line.raw_length, true); return; } @@ -370,15 +484,12 @@ void PressureEqualizer::output_gcode_line(GCodeLine &line) const char *comment = line.raw.data(); while (*comment != ';' && *comment != 0) ++comment; if (*comment != ';') - comment = NULL; - + comment = nullptr; + // Emit the line with lowered extrusion rates. - float l2 = line.dist_xyz2(); - float l = sqrt(l2); - size_t nSegments = size_t(ceil(l / m_max_segment_length)); - if (nSegments == 1) { - // Just update this segment. - push_line_to_output(line, line.feedrate() * line.volumetric_correction_avg(), comment); + float l = line.dist_xyz(); + if (auto nSegments = size_t(ceil(l / m_max_segment_length)); nSegments == 1) { // Just update this segment. + push_line_to_output(line_idx, line.feedrate() * line.volumetric_correction_avg(), comment); } else { bool accelerating = line.volumetric_extrusion_rate_start < line.volumetric_extrusion_rate_end; // Update the initial and final feed rate values. @@ -386,8 +497,8 @@ void PressureEqualizer::output_gcode_line(GCodeLine &line) line.pos_end [4] = line.volumetric_extrusion_rate_end * line.pos_end[4] / line.volumetric_extrusion_rate; float feed_avg = 0.5f * (line.pos_start[4] + line.pos_end[4]); // Limiting volumetric extrusion rate slope for this segment. - float max_volumetric_extrusion_rate_slope = accelerating ? - line.max_volumetric_extrusion_rate_slope_positive : line.max_volumetric_extrusion_rate_slope_negative; + float max_volumetric_extrusion_rate_slope = accelerating ? line.max_volumetric_extrusion_rate_slope_positive : + line.max_volumetric_extrusion_rate_slope_negative; // Total time for the segment, corrected for the possibly lowered volumetric feed rate, // if accelerating / decelerating over the complete segment. float t_total = line.dist_xyz() / feed_avg; @@ -398,8 +509,8 @@ void PressureEqualizer::output_gcode_line(GCodeLine &line) float l_steady = 0.f; if (t_acc < t_total) { // One may achieve higher print speeds if part of the segment is not speed limited. - float l_acc = t_acc * feed_avg; - float l_steady = l - l_acc; + l_acc = t_acc * feed_avg; + l_steady = l - l_acc; if (l_steady < 0.5f * m_max_segment_length) { l_acc = l; l_steady = 0.f; @@ -407,10 +518,10 @@ void PressureEqualizer::output_gcode_line(GCodeLine &line) nSegments = size_t(ceil(l_acc / m_max_segment_length)); } float pos_start[5]; - float pos_end [5]; - float pos_end2 [4]; - memcpy(pos_start, line.pos_start, sizeof(float)*5); - memcpy(pos_end , line.pos_end , sizeof(float)*5); + float pos_end[5]; + float pos_end2[4]; + memcpy(pos_start, line.pos_start, sizeof(float) * 5); + memcpy(pos_end, line.pos_end, sizeof(float) * 5); if (l_steady > 0.f) { // There will be a steady feed segment emitted. if (accelerating) { @@ -428,10 +539,16 @@ void PressureEqualizer::output_gcode_line(GCodeLine &line) line.pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t; line.pos_provided[i] = true; } - push_line_to_output(line, pos_start[4], comment); - comment = NULL; + push_line_to_output(line_idx, pos_start[4], comment); + comment = nullptr; + + float new_pos_start_feedrate = pos_start[4]; + memcpy(line.pos_start, line.pos_end, sizeof(float)*5); memcpy(pos_start, line.pos_end, sizeof(float)*5); + + line.pos_start[4] = new_pos_start_feedrate; + pos_start[4] = new_pos_start_feedrate; } } // Split the segment into pieces. @@ -442,8 +559,8 @@ void PressureEqualizer::output_gcode_line(GCodeLine &line) line.pos_provided[j] = true; } // Interpolate the feed rate at the center of the segment. - push_line_to_output(line, pos_start[4] + (pos_end[4] - pos_start[4]) * (float(i) - 0.5f) / float(nSegments), comment); - comment = NULL; + push_line_to_output(line_idx, pos_start[4] + (pos_end[4] - pos_start[4]) * (float(i) - 0.5f) / float(nSegments), comment); + comment = nullptr; memcpy(line.pos_start, line.pos_end, sizeof(float)*5); } if (l_steady > 0.f && accelerating) { @@ -451,136 +568,161 @@ void PressureEqualizer::output_gcode_line(GCodeLine &line) line.pos_end[i] = pos_end2[i]; line.pos_provided[i] = true; } - push_line_to_output(line, pos_end[4], comment); + push_line_to_output(line_idx, pos_end[4], comment); + } else { + for (int i = 0; i < 4; ++ i) { + line.pos_end[i] = pos_end[i]; + line.pos_provided[i] = true; + } + push_line_to_output(line_idx, pos_end[4], comment); } } } -void PressureEqualizer::adjust_volumetric_rate() +void PressureEqualizer::adjust_volumetric_rate(const size_t fist_line_idx, const size_t last_line_idx) { - if (circular_buffer_items < 2) + // don't bother adjusting volumetric rate if there's no gcode to adjust + if (last_line_idx-fist_line_idx < 2) return; - // Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes. - const size_t idx_head = circular_buffer_idx_head(); - const size_t idx_tail = circular_buffer_idx_prev(circular_buffer_idx_tail()); - size_t idx = idx_tail; - if (idx == idx_head || ! circular_buffer[idx].extruding()) + size_t line_idx = last_line_idx; + if (line_idx == fist_line_idx || !m_gcode_lines[line_idx].extruding()) // Nothing to do, the last move is not extruding. return; - float feedrate_per_extrusion_role[numExtrusionRoles]; - for (size_t i = 0; i < numExtrusionRoles; ++ i) - feedrate_per_extrusion_role[i] = FLT_MAX; - feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_start; + std::array feedrate_per_extrusion_role{}; + feedrate_per_extrusion_role.fill(std::numeric_limits::max()); + feedrate_per_extrusion_role[int(m_gcode_lines[line_idx].extrusion_role)] = m_gcode_lines[line_idx].volumetric_extrusion_rate_start; - bool modified = true; - while (modified && idx != idx_head) { - size_t idx_prev = circular_buffer_idx_prev(idx); - for (; ! circular_buffer[idx_prev].extruding() && idx_prev != idx_head; idx_prev = circular_buffer_idx_prev(idx_prev)) ; - if (! circular_buffer[idx_prev].extruding()) - break; + while (line_idx != fist_line_idx) { + size_t idx_prev = line_idx - 1; + for (; !m_gcode_lines[idx_prev].extruding() && idx_prev != fist_line_idx; --idx_prev); + if (!m_gcode_lines[idx_prev].extruding()) + break; + // Don't decelerate before ironing. + if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing) { line_idx = idx_prev; + continue; + } // Volumetric extrusion rate at the start of the succeding segment. - float rate_succ = circular_buffer[idx].volumetric_extrusion_rate_start; + float rate_succ = m_gcode_lines[line_idx].volumetric_extrusion_rate_start; // What is the gradient of the extrusion rate between idx_prev and idx? - idx = idx_prev; - GCodeLine &line = circular_buffer[idx]; - for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) { - float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].negative; - if (rate_slope == 0) - // The negative rate is unlimited. - continue; + line_idx = idx_prev; + GCodeLine &line = m_gcode_lines[line_idx]; + + for (size_t iRole = 1; iRole < size_t(GCodeExtrusionRole::Count); ++ iRole) { + const float &rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].negative; + if (rate_slope == 0 || feedrate_per_extrusion_role[iRole] == std::numeric_limits::max()) + continue; // The negative rate is unlimited or the rate for GCodeExtrusionRole iRole is unlimited. + float rate_end = feedrate_per_extrusion_role[iRole]; - if (iRole == line.extrusion_role && rate_succ < rate_end) + if (iRole == size_t(line.extrusion_role) && rate_succ < rate_end) // Limit by the succeeding volumetric flow rate. rate_end = rate_succ; - if (line.volumetric_extrusion_rate_end > rate_end) { - line.volumetric_extrusion_rate_end = rate_end; - line.modified = true; - } else if (iRole == line.extrusion_role) { + + // don't alter the flow rate for these extrusion types + if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) { + rate_end = line.volumetric_extrusion_rate_end; + } else if (line.volumetric_extrusion_rate_end > rate_end) { + line.volumetric_extrusion_rate_end = rate_end; + line.max_volumetric_extrusion_rate_slope_negative = rate_slope; + line.modified = true; + } else if (iRole == size_t(line.extrusion_role)) { rate_end = line.volumetric_extrusion_rate_end; - } else if (rate_end == FLT_MAX) { - // The rate for ExtrusionRole iRole is unlimited. - continue; } else { // Use the original, 'floating' extrusion rate as a starting point for the limiter. } -// modified = false; - float rate_start = rate_end + rate_slope * line.time_corrected(); - if (rate_start < line.volumetric_extrusion_rate_start) { - // Limit the volumetric extrusion rate at the start of this segment due to a segment - // of ExtrusionType iRole, which will be extruded in the future. - line.volumetric_extrusion_rate_start = rate_start; - line.max_volumetric_extrusion_rate_slope_negative = rate_slope; - line.modified = true; -// modified = true; + + if (line.adjustable_flow) { + float rate_start = rate_end + rate_slope * line.time_corrected(); + if (rate_start < line.volumetric_extrusion_rate_start) { + // Limit the volumetric extrusion rate at the start of this segment due to a segment + // of ExtrusionType iRole, which will be extruded in the future. + line.volumetric_extrusion_rate_start = rate_start; + line.max_volumetric_extrusion_rate_slope_negative = rate_slope; + line.modified = true; + } } - feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_start : rate_start; +// feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_start : rate_start; + // Don't store feed rate for ironing + if (line.extrusion_role != GCodeExtrusionRole::Ironing) + feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_start; } } - // Go forward and adjust the feedrate to decrease the slope of the extrusion rate changes. - for (size_t i = 0; i < numExtrusionRoles; ++ i) - feedrate_per_extrusion_role[i] = FLT_MAX; - feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_end; + feedrate_per_extrusion_role.fill(std::numeric_limits::max()); + feedrate_per_extrusion_role[size_t(m_gcode_lines[line_idx].extrusion_role)] = m_gcode_lines[line_idx].volumetric_extrusion_rate_end; - assert(circular_buffer[idx].extruding()); - while (idx != idx_tail) { - size_t idx_next = circular_buffer_idx_next(idx); - for (; ! circular_buffer[idx_next].extruding() && idx_next != idx_tail; idx_next = circular_buffer_idx_next(idx_next)) ; - if (! circular_buffer[idx_next].extruding()) - break; - float rate_prec = circular_buffer[idx].volumetric_extrusion_rate_end; + assert(m_gcode_lines[line_idx].extruding()); + while (line_idx != last_line_idx) { + size_t idx_next = line_idx + 1; + for (; !m_gcode_lines[idx_next].extruding() && idx_next != last_line_idx; ++idx_next); + if (!m_gcode_lines[idx_next].extruding()) + break; + // Don't accelerate after ironing. + if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing) { + line_idx = idx_next; + continue; + } + float rate_prec = m_gcode_lines[line_idx].volumetric_extrusion_rate_end; // What is the gradient of the extrusion rate between idx_prev and idx? - idx = idx_next; - GCodeLine &line = circular_buffer[idx]; - for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) { - float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].positive; - if (rate_slope == 0) - // The positive rate is unlimited. - continue; + line_idx = idx_next; + GCodeLine &line = m_gcode_lines[line_idx]; + + for (size_t iRole = 1; iRole < size_t(GCodeExtrusionRole::Count); ++ iRole) { + const float &rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].positive; + if (rate_slope == 0 || feedrate_per_extrusion_role[iRole] == std::numeric_limits::max()) + continue; // The positive rate is unlimited or the rate for GCodeExtrusionRole iRole is unlimited. + float rate_start = feedrate_per_extrusion_role[iRole]; - if (iRole == line.extrusion_role && rate_prec < rate_start) + // don't alter the flow rate for these extrusion types + if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) { + rate_start = line.volumetric_extrusion_rate_start; + } else if (iRole == size_t(line.extrusion_role) && rate_prec < rate_start) rate_start = rate_prec; if (line.volumetric_extrusion_rate_start > rate_start) { line.volumetric_extrusion_rate_start = rate_start; + line.max_volumetric_extrusion_rate_slope_positive = rate_slope; line.modified = true; - } else if (iRole == line.extrusion_role) { + } else if (iRole == size_t(line.extrusion_role)) { rate_start = line.volumetric_extrusion_rate_start; - } else if (rate_start == FLT_MAX) { - // The rate for ExtrusionRole iRole is unlimited. - continue; } else { // Use the original, 'floating' extrusion rate as a starting point for the limiter. } - float rate_end = (rate_slope == 0) ? FLT_MAX : rate_start + rate_slope * line.time_corrected(); - if (rate_end < line.volumetric_extrusion_rate_end) { - // Limit the volumetric extrusion rate at the start of this segment due to a segment - // of ExtrusionType iRole, which was extruded before. - line.volumetric_extrusion_rate_end = rate_end; - line.max_volumetric_extrusion_rate_slope_positive = rate_slope; - line.modified = true; + + if (line.adjustable_flow) { + float rate_end = rate_start + rate_slope * line.time_corrected(); + if (rate_end < line.volumetric_extrusion_rate_end) { + // Limit the volumetric extrusion rate at the start of this segment due to a segment + // of ExtrusionType iRole, which was extruded before. + line.volumetric_extrusion_rate_end = rate_end; + line.max_volumetric_extrusion_rate_slope_positive = rate_slope; + line.modified = true; + } } - feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_end : rate_end; +// feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_end : rate_end; + // Don't store feed rate for ironing + if (line.extrusion_role != GCodeExtrusionRole::Ironing) + feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_end; } } } -void PressureEqualizer::push_axis_to_output(const char axis, const float value, bool add_eol) +inline void PressureEqualizer::push_to_output(GCodeG1Formatter &formatter) { - char buf[2048]; - int len = sprintf(buf, - (axis == 'E') ? " %c%.3f" : " %c%.5f", - axis, value); - push_to_output(buf, len, add_eol); + return this->push_to_output(formatter.string(), false); } -void PressureEqualizer::push_to_output(const char *text, const size_t len, bool add_eol) +inline void PressureEqualizer::push_to_output(const std::string &text, bool add_eol) +{ + return this->push_to_output(text.data(), text.size(), add_eol); +} + +inline void PressureEqualizer::push_to_output(const char *text, const size_t len, bool add_eol) { // New length of the output buffer content. size_t len_new = output_buffer_length + len + 1; if (add_eol) - ++ len_new; + ++len_new; // Resize the output buffer to a power of 2 higher than the required memory. if (output_buffer.size() < len_new) { @@ -600,24 +742,63 @@ void PressureEqualizer::push_to_output(const char *text, const size_t len, bool // Copy the text to the output. if (len != 0) { memcpy(output_buffer.data() + output_buffer_length, text, len); + this->output_buffer_prev_length = this->output_buffer_length; output_buffer_length += len; } if (add_eol) - output_buffer[output_buffer_length ++] = '\n'; + output_buffer[output_buffer_length++] = '\n'; output_buffer[output_buffer_length] = 0; } -void PressureEqualizer::push_line_to_output(const GCodeLine &line, const float new_feedrate, const char *comment) +inline bool is_just_line_with_extrude_set_speed_tag(const std::string &line) { - push_to_output("G1", 2, false); - for (char i = 0; i < 3; ++ i) - if (line.pos_provided[i]) - push_axis_to_output('X'+i, line.pos_end[i]); - push_axis_to_output('E', m_config->use_relative_e_distances.value ? (line.pos_end[3] - line.pos_start[3]) : line.pos_end[3]); -// if (line.pos_provided[4] || fabs(line.feedrate() - new_feedrate) > 1e-5) - push_axis_to_output('F', new_feedrate); - // output comment and EOL - push_to_output(comment, (comment == NULL) ? 0 : strlen(comment), true); -} + if (line.empty() && !boost::starts_with(line, "G1 ") && !boost::ends_with(line, EXTRUDE_SET_SPEED_TAG)) + return false; + + const char *p_line = line.data() + 3; + const char *const line_end = line.data() + line.length() - 1; + while (!is_eol(*p_line)) { + if (toupper(*p_line++) == 'F') + break; + else + return false; + } + parse_float(p_line, line_end - p_line); + eatws(p_line); + p_line += EXTRUDE_SET_SPEED_TAG.length(); + return p_line <= line_end && is_eol(*p_line); +} + +void PressureEqualizer::push_line_to_output(const size_t line_idx, const float new_feedrate, const char *comment) +{ + const GCodeLine &line = m_gcode_lines[line_idx]; + if (line_idx > 0 && output_buffer_length > 0) { + const std::string prev_line_str = std::string(output_buffer.begin() + int(this->output_buffer_prev_length), + output_buffer.begin() + int(this->output_buffer_length) + 1); + if (is_just_line_with_extrude_set_speed_tag(prev_line_str)) + this->output_buffer_length = this->output_buffer_prev_length; // Remove the last line because it only sets the speed for an empty block of g-code lines, so it is useless. + else + push_to_output(EXTRUDE_END_TAG.data(), EXTRUDE_END_TAG.length(), true); + } else + push_to_output(EXTRUDE_END_TAG.data(), EXTRUDE_END_TAG.length(), true); + + GCodeG1Formatter feedrate_formatter; + feedrate_formatter.emit_f(new_feedrate); + feedrate_formatter.emit_string(std::string(EXTRUDE_SET_SPEED_TAG.data(), EXTRUDE_SET_SPEED_TAG.length())); + if (line.extrusion_role == GCodeExtrusionRole::ExternalPerimeter) + feedrate_formatter.emit_string(std::string(EXTERNAL_PERIMETER_TAG.data(), EXTERNAL_PERIMETER_TAG.length())); + push_to_output(feedrate_formatter); + + GCodeG1Formatter extrusion_formatter; + for (size_t axis_idx = 0; axis_idx < 3; ++axis_idx) + if (line.pos_provided[axis_idx]) + extrusion_formatter.emit_axis(char('X' + axis_idx), line.pos_end[axis_idx], GCodeFormatter::XYZF_EXPORT_DIGITS); + extrusion_formatter.emit_axis('E', m_use_relative_e_distances ? (line.pos_end[3] - line.pos_start[3]) : line.pos_end[3], GCodeFormatter::E_EXPORT_DIGITS); + + if (comment != nullptr) + extrusion_formatter.emit_string(std::string(comment)); + + push_to_output(extrusion_formatter); +} } // namespace Slic3r diff --git a/src/libslic3r/GCode/PressureEqualizer.hpp b/src/libslic3r/GCode/PressureEqualizer.hpp index 13cdc94188..1e9f013396 100644 --- a/src/libslic3r/GCode/PressureEqualizer.hpp +++ b/src/libslic3r/GCode/PressureEqualizer.hpp @@ -1,41 +1,59 @@ +///|/ Copyright (c) Prusa Research 2016 - 2023 Vojtěch Bubník @bubnikv, Lukáš Hejl @hejllukas +///|/ Copyright (c) SuperSlicer 2023 Remi Durand @supermerill +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GCode_PressureEqualizer_hpp_ #define slic3r_GCode_PressureEqualizer_hpp_ #include "../libslic3r.h" #include "../PrintConfig.hpp" -#include "../ExtrusionEntity.hpp" +#include "../ExtrusionRole.hpp" + +#include namespace Slic3r { +struct LayerResult; + +class GCodeG1Formatter; + +//#define PRESSURE_EQUALIZER_STATISTIC +//#define PRESSURE_EQUALIZER_DEBUG + // Processes a G-code. Finds changes in the volumetric extrusion speed and adjusts the transitions // between these paths to limit fast changes in the volumetric extrusion speed. class PressureEqualizer { public: - PressureEqualizer(const Slic3r::GCodeConfig *config); - ~PressureEqualizer(); - - void reset(); - - // Process a next batch of G-code lines. Flush the internal buffers if asked for. - const char* process(const char *szGCode, bool flush); - - size_t get_output_buffer_length() const { return output_buffer_length; } + PressureEqualizer() = delete; + explicit PressureEqualizer(const Slic3r::GCodeConfig &config); + ~PressureEqualizer() = default; + // Process a next batch of G-code lines. + // The last LayerResult must be LayerResult::make_nop_layer_result() because it always returns GCode for the previous layer. + // When process_layer is called for the first layer, then LayerResult::make_nop_layer_result() is returned. + LayerResult process_layer(LayerResult &&input); private: + + void process_layer(const std::string &gcode); + +#ifdef PRESSURE_EQUALIZER_STATISTIC struct Statistics { - void reset() { - volumetric_extrusion_rate_min = std::numeric_limits::max(); + void reset() + { + volumetric_extrusion_rate_min = std::numeric_limits::max(); volumetric_extrusion_rate_max = 0.f; volumetric_extrusion_rate_avg = 0.f; - extrusion_length = 0.f; + extrusion_length = 0.f; } - void update(float volumetric_extrusion_rate, float length) { - volumetric_extrusion_rate_min = std::min(volumetric_extrusion_rate_min, volumetric_extrusion_rate); - volumetric_extrusion_rate_max = std::max(volumetric_extrusion_rate_max, volumetric_extrusion_rate); + void update(float volumetric_extrusion_rate, float length) + { + volumetric_extrusion_rate_min = std::min(volumetric_extrusion_rate_min, volumetric_extrusion_rate); + volumetric_extrusion_rate_max = std::max(volumetric_extrusion_rate_max, volumetric_extrusion_rate); volumetric_extrusion_rate_avg += volumetric_extrusion_rate * length; - extrusion_length += length; + extrusion_length += length; } float volumetric_extrusion_rate_min; float volumetric_extrusion_rate_max; @@ -44,9 +62,7 @@ private: }; struct Statistics m_stat; - - // Keeps the reference, does not own the config. - const Slic3r::GCodeConfig *m_config; +#endif // Private configuration values // How fast could the volumetric extrusion rate increase / decrase? mm^3/sec^2 @@ -54,12 +70,9 @@ private: float positive; float negative; }; - enum { numExtrusionRoles = erSupportMaterialInterface + 1 }; - ExtrusionRateSlope m_max_volumetric_extrusion_rate_slopes[numExtrusionRoles]; + ExtrusionRateSlope m_max_volumetric_extrusion_rate_slopes[size_t(GCodeExtrusionRole::Count)]; float m_max_volumetric_extrusion_rate_slope_positive; float m_max_volumetric_extrusion_rate_slope_negative; - // Maximum segment length to split a long segment, if the initial and the final flow rate differ. - float m_max_segment_length; // Configuration extracted from config. // Area of the crossestion of each filament. Necessary to calculate the volumetric flow rate. @@ -69,11 +82,19 @@ private: // X,Y,Z,E,F float m_current_pos[5]; size_t m_current_extruder; - ExtrusionRole m_current_extrusion_role; + GCodeExtrusionRole m_current_extrusion_role; bool m_retracted; + bool m_use_relative_e_distances; - enum GCodeLineType - { + // Maximum segment length to split a long segment if the initial and the final flow rate differ. + // Smaller value means a smoother transition between two different flow rates. + float m_max_segment_length; + + // Indicate if extrude set speed block was opened using the tag ";_EXTRUDE_SET_SPEED" + // or not (not opened, or it was closed using the tag ";_EXTRUDE_END"). + bool opened_extrude_set_speed_block = false; + + enum GCodeLineType { GCODELINETYPE_INVALID, GCODELINETYPE_NOOP, GCODELINETYPE_OTHER, @@ -128,18 +149,16 @@ private: // or maybe the line needs to be split into multiple lines. bool modified; - // float timeStart; - // float timeEnd; // X,Y,Z,E,F. Storing the state of the currently active extruder only. float pos_start[5]; float pos_end[5]; - // Was the axis found on the G-code line? X,Y,Z,F + // Was the axis found on the G-code line? X,Y,Z,E,F bool pos_provided[5]; // Index of the active extruder. size_t extruder_id; // Extrusion role of this segment. - ExtrusionRole extrusion_role; + GCodeExtrusionRole extrusion_role; // Current volumetric extrusion rate. float volumetric_extrusion_rate; @@ -152,59 +171,42 @@ private: // If set to zero, the slope is unlimited. float max_volumetric_extrusion_rate_slope_positive; float max_volumetric_extrusion_rate_slope_negative; - }; - // Circular buffer of GCode lines. The circular buffer size will be limited to circular_buffer_size. - std::vector circular_buffer; - // Current position of the circular buffer (index, where to write the next line to, the line has to be pushed out before it is overwritten). - size_t circular_buffer_pos; - // Circular buffer size, configuration value. - size_t circular_buffer_size; - // Number of valid lines in the circular buffer. Lower or equal to circular_buffer_size. - size_t circular_buffer_items; + bool adjustable_flow = false; + + bool extrude_set_speed_tag = false; + bool extrude_end_tag = false; + }; // Output buffer will only grow. It will not be reallocated over and over. std::vector output_buffer; size_t output_buffer_length; + size_t output_buffer_prev_length; +#ifdef PRESSURE_EQUALIZER_DEBUG // For debugging purposes. Index of the G-code line processed. size_t line_idx; +#endif - bool process_line(const char *line, const size_t len, GCodeLine &buf); - void output_gcode_line(GCodeLine &buf); + bool process_line(const char *line, const char *line_end, GCodeLine &buf); + long advance_segment_beyond_small_gap(long idx_cur_pos); + void output_gcode_line(size_t line_idx); // Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes. // Then go forward and adjust the feedrate to decrease the slope of the extrusion rate changes. - void adjust_volumetric_rate(); + void adjust_volumetric_rate(size_t first_line_idx, size_t last_line_idx); // Push the text to the end of the output_buffer. - void push_to_output(const char *text, const size_t len, bool add_eol = true); - // Push an axis assignment to the end of the output buffer. - void push_axis_to_output(const char axis, const float value, bool add_eol = false); - // Push a G-code line to the output, - void push_line_to_output(const GCodeLine &line, const float new_feedrate, const char *comment); + inline void push_to_output(GCodeG1Formatter &formatter); + inline void push_to_output(const std::string &text, bool add_eol); + inline void push_to_output(const char *text, size_t len, bool add_eol = true); + // Push a G-code line to the output. + void push_line_to_output(size_t line_idx, float new_feedrate, const char *comment); - size_t circular_buffer_idx_head() const { - size_t idx = circular_buffer_pos + circular_buffer_size - circular_buffer_items; - if (idx >= circular_buffer_size) - idx -= circular_buffer_size; - return idx; - } +public: + std::queue m_layer_results; - size_t circular_buffer_idx_tail() const { return circular_buffer_pos; } - - size_t circular_buffer_idx_prev(size_t idx) const { - idx += circular_buffer_size - 1; - if (idx >= circular_buffer_size) - idx -= circular_buffer_size; - return idx; - } - - size_t circular_buffer_idx_next(size_t idx) const { - if (++ idx >= circular_buffer_size) - idx -= circular_buffer_size; - return idx; - } + std::vector m_gcode_lines; }; } // namespace Slic3r diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index f640524ad9..7568a015f0 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -725,9 +725,7 @@ static std::vector s_Preset_print_options { "ironing_type", "ironing_pattern", "ironing_flow", "ironing_speed", "ironing_spacing", "max_travel_detour_distance", "fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_distance", -#ifdef HAS_PRESSURE_EQUALIZER - "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", -#endif /* HAS_PRESSURE_EQUALIZER */ + "max_volumetric_extrusion_rate_slope", "max_volumetric_extrusion_rate_slope_segment_length", "inner_wall_speed", "outer_wall_speed", "sparse_infill_speed", "internal_solid_infill_speed", "top_surface_speed", "support_speed", "support_object_xy_distance", "support_interface_speed", "bridge_speed", "internal_bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 96cf920f64..d44e348139 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -150,10 +150,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "fan_max_speed", "printable_height", "slow_down_min_speed", -#ifdef HAS_PRESSURE_EQUALIZER - "max_volumetric_extrusion_rate_slope_positive", - "max_volumetric_extrusion_rate_slope_negative", -#endif /* HAS_PRESSURE_EQUALIZER */ + "max_volumetric_extrusion_rate_slope", + "max_volumetric_extrusion_rate_slope_segment_length", "reduce_infill_retraction", "filename_format", "retraction_minimum_travel", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 2b2c8ba9bf..7f24f32175 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2470,29 +2470,37 @@ def = this->add("filament_loading_speed", coFloats); def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats { 0. }); -#ifdef HAS_PRESSURE_EQUALIZER - //def = this->add("max_volumetric_extrusion_rate_slope_positive", coFloat); - //def->label = L("Max volumetric slope positive"); - //def->tooltip = L("This experimental setting is used to limit the speed of change in extrusion rate. " - // "A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " - // "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) " - // "to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."); - //def->sidetext = L("mm³/s²"); - //def->min = 0; - //def->mode = comAdvanced; - //def->set_default_value(new ConfigOptionFloat(0)); - - //def = this->add("max_volumetric_extrusion_rate_slope_negative", coFloat); - //def->label = L("Max volumetric slope negative"); - //def->tooltip = L("This experimental setting is used to limit the speed of change in extrusion rate. " - // "A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " - // "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) " - // "to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."); - //def->sidetext = L("mm³/s²"); - //def->min = 0; - //def->mode = comAdvanced; - //def->set_default_value(new ConfigOptionFloat(0)); -#endif /* HAS_PRESSURE_EQUALIZER */ + def = this->add("max_volumetric_extrusion_rate_slope", coFloat); + def->label = L("Extrusion rate smoothing"); + def->tooltip = L("This parameter smooths out sudden extrusion rate changes that happen when " + "the printer transitions from printing a high flow (high speed/larger width) " + "extrusion to a lower flow (lower speed/smaller width) extrusion and vice versa.\n\n" + "It defines the maximum rate by which the extruded volumetric flow in mm3/sec can change over time. " + "Higher values mean higher extrusion rate changes are allowed, resulting in faster speed transitions.\n\n" + "A value of 0 disables the feature. \n\n" + "For a high speed, high flow direct drive printer (like the Bambu lab or Voron) this value is usually not needed. " + "However it can provide some marginal benefit in certain cases where feature speeds vary greatly. For example, " + "when there are aggressive slowdowns due to overhangs. In these cases a high value of around 300-350mm3/s2 is " + "recommended as this allows for just enough smoothing to assist pressure advance achieve a smoother flow transition.\n\n" + "For slower printers without pressure advance, the value should be set much lower. A value of 10-15mm3/s2 is a " + "good starting point for direct drive extruders and 5-10mm3/s2 for Bowden style. \n\n" + "This feature is known as Pressure Equalizer in Prusa slicer.\n\n" + "Note: this parameter disables arc fitting."); + def->sidetext = L("mm³/s²"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("max_volumetric_extrusion_rate_slope_segment_length", coInt); + def->label = L("Smoothing segment length"); + def->tooltip = L("A lower value results in smoother extrusion rate transitions. However, this results in a significantly larger gcode file " + "and more instructions for the printer to process. \n\n" + "Default value of 3 works well for most cases. If your printer is stuttering, increase this value to reduce the number of adjustments made\n\n" + "Allowed values: 1-5"); + def->min = 1; + def->max = 5; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(3)); def = this->add("fan_min_speed", coInts); def->label = L("Fan speed"); @@ -4962,9 +4970,6 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va "acceleration", "scale", "rotate", "duplicate", "duplicate_grid", "bed_size", "print_center", "g0", "wipe_tower_per_color_wipe" -#ifndef HAS_PRESSURE_EQUALIZER - , "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative" -#endif /* HAS_PRESSURE_EQUALIZER */ // BBS , "support_sharp_tails","support_remove_small_overhangs", "support_with_sheath", "tree_support_collision_resolution", "tree_support_with_infill", diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 3d518f625f..57b82efd47 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -27,8 +27,6 @@ #include #include -// #define HAS_PRESSURE_EQUALIZER - namespace Slic3r { enum GCodeFlavor : unsigned char { @@ -883,10 +881,10 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionEnum, gcode_flavor)) ((ConfigOptionString, layer_change_gcode)) -//#ifdef HAS_PRESSURE_EQUALIZER -// ((ConfigOptionFloat, max_volumetric_extrusion_rate_slope_positive)) -// ((ConfigOptionFloat, max_volumetric_extrusion_rate_slope_negative)) -//#endif + + ((ConfigOptionFloat, max_volumetric_extrusion_rate_slope)) + ((ConfigOptionInt, max_volumetric_extrusion_rate_slope_segment_length)) + ((ConfigOptionPercents, retract_before_wipe)) ((ConfigOptionFloats, retraction_length)) ((ConfigOptionFloats, retract_length_toolchange)) diff --git a/src/libslic3r/enum_bitmask.hpp b/src/libslic3r/enum_bitmask.hpp index 4c20763135..1ce6804853 100644 --- a/src/libslic3r/enum_bitmask.hpp +++ b/src/libslic3r/enum_bitmask.hpp @@ -31,14 +31,25 @@ public: constexpr enum_bitmask(option_type o) : m_bits(mask_value(o)) {} // Set the bit corresponding to the given option. - constexpr enum_bitmask operator|(option_type t) { return enum_bitmask(m_bits | mask_value(t)); } + constexpr enum_bitmask operator|(option_type t) const { return enum_bitmask(m_bits | mask_value(t)); } // Combine with another enum_bitmask of the same type. - constexpr enum_bitmask operator|(enum_bitmask t) { return enum_bitmask(m_bits | t.m_bits); } + constexpr enum_bitmask operator|(enum_bitmask t) const { return enum_bitmask(m_bits | t.m_bits); } + + // Set the bit corresponding to the given option. + constexpr void operator|=(option_type t) { m_bits = enum_bitmask(m_bits | mask_value(t)); } + + // Combine with another enum_bitmask of the same type. + constexpr void operator|=(enum_bitmask t) { m_bits = enum_bitmask(m_bits | t.m_bits); } // Get the value of the bit corresponding to the given option. - constexpr bool operator&(option_type t) { return m_bits & mask_value(t); } - constexpr bool has(option_type t) { return m_bits & mask_value(t); } + constexpr bool operator&(option_type t) const { return m_bits & mask_value(t); } + constexpr bool has(option_type t) const { return m_bits & mask_value(t); } + + constexpr bool operator==(const enum_bitmask r) const { return m_bits == r.m_bits; } + constexpr bool operator!=(const enum_bitmask r) const { return m_bits != r.m_bits; } + // For sorting by the enum values. + constexpr bool lower(const enum_bitmask r) const { return m_bits < r.m_bits; } private: underlying_type m_bits = 0; diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index e801e92bec..4a4b8f1b73 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -475,6 +475,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con apply(config, &new_conf); is_msg_dlg_already_exist = false; } + } void ConfigManipulation::apply_null_fff_config(DynamicPrintConfig *config, std::vector const &keys, std::map const &configs) @@ -508,6 +509,17 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co //SoftFever auto gcflavor = preset_bundle->printers.get_edited_preset().config.option>("gcode_flavor")->value; + bool have_volumetric_extrusion_rate_slope = config->option("max_volumetric_extrusion_rate_slope")->value > 0; + int have_volumetric_extrusion_rate_slope_segment_length = config->option("max_volumetric_extrusion_rate_slope_segment_length")->value; + toggle_field("enable_arc_fitting", !have_volumetric_extrusion_rate_slope); + toggle_line("max_volumetric_extrusion_rate_slope_segment_length", have_volumetric_extrusion_rate_slope); + if(have_volumetric_extrusion_rate_slope) config->set_key_value("enable_arc_fitting", new ConfigOptionBool(false)); + if(have_volumetric_extrusion_rate_slope_segment_length==0) { + DynamicPrintConfig new_conf = *config; + new_conf.set_key_value("max_volumetric_extrusion_rate_slope_segment_length", new ConfigOptionInt(1)); + apply(config, &new_conf); + } + bool have_perimeters = config->opt_int("wall_loops") > 0; for (auto el : { "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "detect_thin_wall", "detect_overhang_wall", "seam_position", "staggered_inner_seams", "wall_infill_order", "outer_wall_line_width", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 7d369a3fae..370fcf2bc9 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1995,11 +1995,10 @@ void TabPrint::build() optgroup->append_single_option_line("top_surface_jerk"); optgroup->append_single_option_line("initial_layer_jerk"); optgroup->append_single_option_line("travel_jerk"); - -#ifdef HAS_PRESSURE_EQUALIZER - optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_positive"); - optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_negative"); -#endif /* HAS_PRESSURE_EQUALIZER */ + + optgroup = page->new_optgroup(L("Advanced"), L"param_advanced", 15); + optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope"); + optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_segment_length"); page = add_options_page(L("Support"), "support"); optgroup = page->new_optgroup(L("Support"), L"param_support");