diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 9897bdfe63..52b38af4f8 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -156,6 +156,12 @@ set(lisbslic3r_sources GCode/RetractWhenCrossingPerimeters.hpp GCode/SmallAreaInfillFlowCompensator.cpp GCode/SmallAreaInfillFlowCompensator.hpp + GCode/PchipInterpolatorHelper.cpp + GCode/PchipInterpolatorHelper.hpp + GCode/AdaptivePAInterpolator.cpp + GCode/AdaptivePAInterpolator.hpp + GCode/AdaptivePAProcessor.cpp + GCode/AdaptivePAProcessor.hpp GCode/SpiralVase.cpp GCode/SpiralVase.hpp GCode/SeamPlacer.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f81a1c9438..693fd36b25 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -668,6 +668,9 @@ static std::vector get_path_of_change_filament(const Print& print) // SoftFever: set new PA for new filament if (gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) { gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_extruder_id)); + // Orca: Adaptive PA + // Reset Adaptive PA processor last PA value + gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_extruder_id)); } // A phony move to the end position at the wipe tower. @@ -856,6 +859,9 @@ static std::vector get_path_of_change_filament(const Print& print) // SoftFever: set new PA for new filament if (new_extruder_id != -1 && gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) { gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_extruder_id)); + // Orca: Adaptive PA + // Reset Adaptive PA processor last PA value + gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_extruder_id)); } // A phony move to the end position at the wipe tower. @@ -1979,6 +1985,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato if (!print.config().small_area_infill_flow_compensation_model.empty()) m_small_area_infill_flow_compensator = make_unique(print.config()); + file.write_format("; HEADER_BLOCK_START\n"); // Write information on the generator. @@ -2227,6 +2234,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_cooling_buffer = make_unique(*this); m_cooling_buffer->set_current_extruder(initial_extruder_id); + + // Orca: Initialise AdaptivePA processor filter + m_pa_processor = std::make_unique(*this, tool_ordering.all_extruders()); // Emit machine envelope limits for the Marlin firmware. this->print_machine_envelope(file, print); @@ -2870,6 +2880,12 @@ void GCode::process_layers( return in.gcode; return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); }); + const auto pa_processor_filter = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&pa_processor = *this->m_pa_processor](std::string in) -> std::string { + return pa_processor.process_layer(std::move(in)); + } + ); + const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [&output_stream](std::string s) { output_stream.write(s); } ); @@ -2900,9 +2916,9 @@ void GCode::process_layers( 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); + tbb::parallel_pipeline(12, generator & pressure_equalizer & cooling & fan_mover & pa_processor_filter & output); else - tbb::parallel_pipeline(12, generator & cooling & fan_mover & output); + tbb::parallel_pipeline(12, generator & cooling & fan_mover & pa_processor_filter & output); } // Process all layers of a single object instance (sequential mode) with a parallel pipeline: @@ -4548,6 +4564,7 @@ static std::unique_ptr calculate_layer_edge_grid(const Layer& la std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, const ExtrusionEntitiesPtr& region_perimeters) { + // get a copy; don't modify the orientation of the original loop object otherwise // next copies (if any) would not detect the correct orientation @@ -4673,7 +4690,9 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou if(discoveredTouchingLines > 1){ // use extrude instead of travel_to_xy to trigger the unretract ExtrusionPath fake_path_wipe(Polyline{pt, current_point}, paths.front()); + fake_path_wipe.set_force_no_extrusion(true); fake_path_wipe.mm3_per_mm = 0; + //fake_path_wipe.set_extrusion_role(erExternalPerimeter); gcode += extrude_path(fake_path_wipe, "move inwards before retraction/seam", speed); } } @@ -4685,9 +4704,32 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou return is_small_peri ? small_peri_speed : speed; }; + + //Orca: Adaptive PA: calculate average mm3_per_mm value over the length of the loop. + //This is used for adaptive PA + m_multi_flow_segment_path_pa_set = false; // always emit PA on the first path of the loop + m_multi_flow_segment_path_average_mm3_per_mm = 0; + double weighted_sum_mm3_per_mm = 0.0; + double total_multipath_length = 0.0; + for (const ExtrusionPath& path : paths) { + if(!path.is_force_no_extrusion()){ + double path_length = unscale(path.length()); //path length in mm + weighted_sum_mm3_per_mm += path.mm3_per_mm * path_length; + total_multipath_length += path_length; + } + } + if (total_multipath_length > 0.0) + m_multi_flow_segment_path_average_mm3_per_mm = weighted_sum_mm3_per_mm / total_multipath_length; + // Orca: end of multipath average mm3_per_mm value calculation + if (!enable_seam_slope) { for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) { gcode += this->_extrude(*path, description, speed_for_path(*path)); + // Orca: Adaptive PA - dont adapt PA after the first pultipath extrusion is completed + // as we have already set the PA value to the average flow over the totality of the path + // in the first extrude move + // TODO: testing is needed with slope seams and adaptive PA. + m_multi_flow_segment_path_pa_set = true; } } else { // Create seam slope @@ -4719,6 +4761,10 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // Then extrude it for (const auto& p : new_loop.get_all_paths()) { gcode += this->_extrude(*p, description, speed_for_path(*p)); + // Orca: Adaptive PA - dont adapt PA after the first pultipath extrusion is completed + // as we have already set the PA value to the average flow over the totality of the path + // in the first extrude move + m_multi_flow_segment_path_pa_set = true; } // Fix path for wipe @@ -4790,8 +4836,31 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string { // extrude along the path std::string gcode; - for (ExtrusionPath path : multipath.paths) + + //Orca: calculate multipath average mm3_per_mm value over the length of the path. + //This is used for adaptive PA + m_multi_flow_segment_path_pa_set = false; // always emit PA on the first path of the multi-path + m_multi_flow_segment_path_average_mm3_per_mm = 0; + double weighted_sum_mm3_per_mm = 0.0; + double total_multipath_length = 0.0; + for (const ExtrusionPath& path : multipath.paths) { + if(!path.is_force_no_extrusion()){ + double path_length = unscale(path.length()); //path length in mm + weighted_sum_mm3_per_mm += path.mm3_per_mm * path_length; + total_multipath_length += path_length; + } + } + if (total_multipath_length > 0.0) + m_multi_flow_segment_path_average_mm3_per_mm = weighted_sum_mm3_per_mm / total_multipath_length; + // Orca: end of multipath average mm3_per_mm value calculation + + for (ExtrusionPath path : multipath.paths){ gcode += this->_extrude(path, description, speed); + // Orca: Adaptive PA - dont adapt PA after the first pultipath extrusion is completed + // as we have already set the PA value to the average flow over the totality of the path + // in the first extrude move. + m_multi_flow_segment_path_pa_set = true; + } // BBS if (m_wipe.enable) { @@ -4825,7 +4894,10 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed) { -// description += ExtrusionEntity::role_to_string(path.role()); + // Orca: Reset average multipath flow as this is a single line, single extrude volumetric speed path + m_multi_flow_segment_path_pa_set = false; + m_multi_flow_segment_path_average_mm3_per_mm = 0; + // description += ExtrusionEntity::role_to_string(path.role()); std::string gcode = this->_extrude(path, description, speed); if (m_wipe.enable) { m_wipe.path = std::move(path.polyline); @@ -5291,7 +5363,28 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } double F = speed * 60; // convert mm/sec to mm/min - + + // Orca: Dynamic PA + // If adaptive PA is enabled, by default evaluate PA on all extrusion moves + bool evaluate_adaptive_pa = false; + bool role_change = (m_last_extrusion_role != path.role()); + if(EXTRUDER_CONFIG(adaptive_pressure_advance) && EXTRUDER_CONFIG(enable_pressure_advance)){ + evaluate_adaptive_pa = true; + // If we have already emmited a PA change because the m_multi_flow_segment_path_pa_set is set + // skip re-issuing the PA change tag. + if (m_multi_flow_segment_path_pa_set && evaluate_adaptive_pa) + evaluate_adaptive_pa = false; + // TODO: Explore forcing evaluation of PA if a role change is happening mid extrusion. + // TODO: This would enable adapting PA for overhang perimeters as they are part of the current loop + // TODO: The issue with simply enabling PA evaluation on a role change is that the speed change + // TODO: is issued before the overhang perimeter role change is triggered + // TODO: because for some reason (maybe path segmentation upstream?) there is a short path extruded + // TODO: with the overhang speed and flow before the role change is flagged in the path.role() function. + if(role_change) + evaluate_adaptive_pa = true; + } + // Orca: End of dynamic PA trigger flag segment + //Orca: process custom gcode for extrusion role change if (path.role() != m_last_extrusion_role && !m_config.change_extrusion_role_gcode.value.empty()) { DynamicConfig config; @@ -5347,6 +5440,45 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, sprintf(buf, ";%s%g\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height).c_str(), m_last_height); gcode += buf; } + + // Orca: Dynamic PA + // Post processor flag generation code segment when option to emit only at role changes is enabled + // Variables published to the post processor: + // 1) Tag to trigger a PA evaluation (because a role change was identified and the user has requested dynamic PA adjustments) + // 2) Current extruder ID (to identify the PA model for the currently used extruder) + // 3) mm3_per_mm value (to then multiply by the final model print speed after slowdown for cooling is applied) + // 4) the current acceleration (to pass to the model for evaluation) + // 5) whether this is an external perimeter (for future use) + // 6) whether this segment is triggered because of a role change (to aid in calculation of average speed for the role) + // This tag simplifies the creation of the gcode post processor while also keeping the feature decoupled from other tags. + if (evaluate_adaptive_pa) { + bool isOverhangPerimeter = (path.role() == erOverhangPerimeter); + if (m_multi_flow_segment_path_average_mm3_per_mm > 0) { + sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), + m_writer.extruder()->id(), + m_multi_flow_segment_path_average_mm3_per_mm, + acceleration_i, + ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), + role_change, + isOverhangPerimeter); + gcode += buf; + } else if(_mm3_per_mm >0 ){ // Triggered when extruding a single segment path (like a line). + // Check if mm3_mm value is greater than zero as the wipe before external perimeter + // is a zero mm3_mm path to force de-retraction to happen and we dont want + // to issue a zero flow PA change command for this + sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), + m_writer.extruder()->id(), + _mm3_per_mm, + acceleration_i, + ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), + role_change, + isOverhangPerimeter); + gcode += buf; + } + } + auto overhang_fan_threshold = EXTRUDER_CONFIG(overhang_fan_threshold); auto enable_overhang_bridge_fan = EXTRUDER_CONFIG(enable_overhang_bridge_fan); @@ -5397,6 +5529,54 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if (!variable_speed) { // F is mm per minute. + if( (std::abs(writer().get_current_speed() - F) > EPSILON) || (std::abs(_mm3_per_mm - m_last_mm3_mm) > EPSILON) ){ + // ORCA: Adaptive PA code segment when adjusting PA within the same feature + // There is a speed change coming out of an overhang region + // or a flow change, so emit the flag to evaluate PA for the upcomming extrusion + // Emit tag before new speed is set so the post processor reads the next speed immediately and uses it. + // Dont emit tag if it has just already been emitted from a role change above + if(_mm3_per_mm >0 && + EXTRUDER_CONFIG(adaptive_pressure_advance) && + EXTRUDER_CONFIG(enable_pressure_advance) && + EXTRUDER_CONFIG(adaptive_pressure_advance_overhangs) && + !evaluate_adaptive_pa){ + if(writer().get_current_speed() > F){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion + if(m_config.gcode_comments){ + sprintf(buf, "; Ramp down-non-variable\n"); + gcode += buf; + } + sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), + m_writer.extruder()->id(), + _mm3_per_mm, + acceleration_i, + ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), + 1, // Force a dummy "role change" & "overhang perimeter" for the post processor, as, while technically it is not a role change, + // the properties of the extrusion in the overhang are different so it behaves similarly to a role + // change for the Adaptive PA post processor. + 1); + }else{ // Ramping up speed - use baseline logic where max speed is used between current and upcoming extrusion + if(m_config.gcode_comments){ + sprintf(buf, "; Ramp up-non-variable\n"); + gcode += buf; + } + sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), + m_writer.extruder()->id(), + _mm3_per_mm, + acceleration_i, + ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), + 1, // Force a dummy "role change" & "overhang perimeter" for the post processor, as, while technically it is not a role change, + // the properties of the extrusion in the overhang are different so it is technically similar to a role + // change for the Adaptive PA post processor. + 0); + } + gcode += buf; + m_last_mm3_mm = _mm3_per_mm; + } + // ORCA: End of adaptive PA code segment + } + gcode += m_writer.set_speed(F, "", comment); { if (m_enable_cooling_markers) { @@ -5591,6 +5771,52 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, continue; path_length += line_length; double new_speed = pre_processed_point.speed * 60.0; + + if ((std::abs(last_set_speed - new_speed) > EPSILON) || (std::abs(_mm3_per_mm - m_last_mm3_mm) > EPSILON)) { + // ORCA: Adaptive PA code segment when adjusting PA within the same feature + // There is a speed change or flow change so emit the flag to evaluate PA for the upcomming extrusion + // Emit tag before new speed is set so the post processor reads the next speed immediately and uses it. + if(_mm3_per_mm >0 && + EXTRUDER_CONFIG(adaptive_pressure_advance) && + EXTRUDER_CONFIG(enable_pressure_advance) && + EXTRUDER_CONFIG(adaptive_pressure_advance_overhangs) ){ + if(last_set_speed > new_speed){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion + if(m_config.gcode_comments) { + sprintf(buf, "; Ramp up-variable\n"); + gcode += buf; + } + sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), + m_writer.extruder()->id(), + _mm3_per_mm, + acceleration_i, + ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), + 1, // Force a dummy "role change" & "overhang perimeter" for the post processor, as, while technically it is not a role change, + // the properties of the extrusion in the overhang are different so it is technically similar to a role + // change for the Adaptive PA post processor. + 1); + }else{ // Ramping up speed - use baseline logic where max speed is used between current and upcoming extrusion + if(m_config.gcode_comments) { + sprintf(buf, "; Ramp down-variable\n"); + gcode += buf; + } + sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), + m_writer.extruder()->id(), + _mm3_per_mm, + acceleration_i, + ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), + 1, // Force a dummy "role change" & "overhang perimeter" for the post processor, as, while technically it is not a role change, + // the properties of the extrusion in the overhang are different so it is technically similar to a role + // change for the Adaptive PA post processor. + 0); + } + gcode += buf; + m_last_mm3_mm = _mm3_per_mm; + } + }// ORCA: End of adaptive PA code segment + + if (last_set_speed != new_speed) { gcode += m_writer.set_speed(new_speed, "", comment); last_set_speed = new_speed; @@ -6029,6 +6255,9 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b } if (m_config.enable_pressure_advance.get_at(extruder_id)) { gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(extruder_id)); + // Orca: Adaptive PA + // Reset Adaptive PA processor last PA value + m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(extruder_id)); } gcode += m_writer.toolchange(extruder_id); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 18ea653546..141ad2f366 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -24,6 +24,8 @@ #include "GCode/PressureEqualizer.hpp" #include "GCode/SmallAreaInfillFlowCompensator.hpp" +// ORCA: post processor below used for Dynamic Pressure advance +#include "GCode/AdaptivePAProcessor.hpp" #include #include @@ -358,6 +360,19 @@ private: std::string extrude_loop(ExtrusionLoop loop, std::string description, double speed = -1., const ExtrusionEntitiesPtr& region_perimeters = ExtrusionEntitiesPtr()); std::string extrude_multi_path(ExtrusionMultiPath multipath, std::string description = "", double speed = -1.); std::string extrude_path(ExtrusionPath path, std::string description = "", double speed = -1.); + + // Orca: Adaptive PA variables + // Used for adaptive PA when extruding paths with multiple, varying flow segments. + // This contains the sum of the mm3_per_mm values weighted by the length of each path segment. + // The m_multi_flow_segment_path_pa_set constrains the PA change request to the first extrusion segment. + // It sets the mm3_mm value for the adaptive PA post processor to be the average of that path + // as calculated and stored in the m_multi_segment_path_average_mm3_per_mm value + double m_multi_flow_segment_path_average_mm3_per_mm = 0; + bool m_multi_flow_segment_path_pa_set = false; + // Adaptive PA last set flow to enable issuing of PA change commands when adaptive PA for overhangs + // is enabled + double m_last_mm3_mm = 0; + // Orca: Adaptive PA code segment end // Extruding multiple objects with soluble / non-soluble / combined supports // on a multi-material printer, trying to minimize tool switches. @@ -540,11 +555,13 @@ private: std::unique_ptr m_spiral_vase; std::unique_ptr m_pressure_equalizer; + + std::unique_ptr m_pa_processor; std::unique_ptr m_wipe_tower; std::unique_ptr m_small_area_infill_flow_compensator; - + // Heights (print_z) at which the skirt has already been extruded. std::vector m_skirt_done; // Has the brim been extruded already? Brim is being extruded only for the first object of a multi-object print. diff --git a/src/libslic3r/GCode/AdaptivePAInterpolator.cpp b/src/libslic3r/GCode/AdaptivePAInterpolator.cpp new file mode 100644 index 0000000000..2681ab13b2 --- /dev/null +++ b/src/libslic3r/GCode/AdaptivePAInterpolator.cpp @@ -0,0 +1,114 @@ +// AdaptivePAInterpolator.cpp +// OrcaSlicer +// +// Implementation file for the AdaptivePAInterpolator class, providing methods to parse data and perform PA interpolation. + +#include "AdaptivePAInterpolator.hpp" +#include +#include +#include +#include + +/** + * @brief Parses the input data and sets up the interpolators. + * @param data A string containing the data in CSV format (PA, flow rate, acceleration). + * @return 0 on success, -1 on error. + */ +int AdaptivePAInterpolator::parseAndSetData(const std::string& data) { + flow_interpolators_.clear(); + accelerations_.clear(); + + try { + std::istringstream ss(data); + std::string line; + std::map>> acc_to_flow_pa; + + while (std::getline(ss, line)) { + std::istringstream lineStream(line); + std::string value; + double paValue, flowRate, acceleration; + paValue = flowRate = acceleration = 0.f; // initialize all to zero. + + // Parse PA value + if (std::getline(lineStream, value, ',')) { + paValue = std::stod(value); + } + + // Parse flow rate value + if (std::getline(lineStream, value, ',')) { + flowRate = std::stod(value); + } + + // Parse acceleration value + if (std::getline(lineStream, value, ',')) { + acceleration = std::stod(value); + } + + // Store the parsed values in a map with acceleration as the key + acc_to_flow_pa[acceleration].emplace_back(flowRate, paValue); + } + + // Iterate through the map to set up the interpolators + for (const auto& kv : acc_to_flow_pa) { + double acceleration = kv.first; + const auto& data = kv.second; + + std::vector flowRates; + std::vector paValues; + + for (const auto& pair : data) { + flowRates.push_back(pair.first); + paValues.push_back(pair.second); + } + + // Only set up the interpolator if there are enough data points + if (flowRates.size() > 1) { + PchipInterpolatorHelper interpolator(flowRates, paValues); + flow_interpolators_[acceleration] = interpolator; + accelerations_.push_back(acceleration); + } + } + } catch (const std::exception&) { + m_isInitialised = false; + return -1; // Error: Exception during parsing + } + m_isInitialised = true; + return 0; // Success +} + +/** + * @brief Interpolates the PA value for the given flow rate and acceleration. + * @param flow_rate The flow rate at which to interpolate. + * @param acceleration The acceleration at which to interpolate. + * @return The interpolated PA value, or -1 if interpolation fails. + */ +double AdaptivePAInterpolator::operator()(double flow_rate, double acceleration) { + std::vector pa_values; + std::vector acc_values; + + // Estimate PA value for every flow to PA model for the given flow rate + for (const auto& kv : flow_interpolators_) { + double pa_value = kv.second.interpolate(flow_rate); + + // Check if the interpolated PA value is valid + if (pa_value != -1) { + pa_values.push_back(pa_value); + acc_values.push_back(kv.first); + } + } + + // Check if there are enough acceleration values for interpolation + if (acc_values.size() < 2) { + // Special case: Only one acceleration value + if (acc_values.size() == 1) { + return std::round(pa_values[0] * 1000.0) / 1000.0; // Rounded to 3 decimal places + } + return -1; // Error: Not enough data points for interpolation + } + + // Create a new PchipInterpolatorHelper for PA-acceleration interpolation + // Use the estimated PA values from the for loop above and their corresponding accelerations to + // generate the new PCHIP model. Then run this model to interpolate the PA value for the given acceleration value. + PchipInterpolatorHelper pa_accel_interpolator(acc_values, pa_values); + return std::round(pa_accel_interpolator.interpolate(acceleration) * 1000.0) / 1000.0; // Rounded to 3 decimal places +} diff --git a/src/libslic3r/GCode/AdaptivePAInterpolator.hpp b/src/libslic3r/GCode/AdaptivePAInterpolator.hpp new file mode 100644 index 0000000000..8303af5490 --- /dev/null +++ b/src/libslic3r/GCode/AdaptivePAInterpolator.hpp @@ -0,0 +1,54 @@ +// AdaptivePAInterpolator.hpp +// OrcaSlicer +// +// Header file for the AdaptivePAInterpolator class, responsible for interpolating pressure advance (PA) values based on flow rate and acceleration using PCHIP interpolation. + +#ifndef ADAPTIVEPAINTERPOLATOR_HPP +#define ADAPTIVEPAINTERPOLATOR_HPP + +#include +#include +#include +#include "PchipInterpolatorHelper.hpp" + +/** + * @class AdaptivePAInterpolator + * @brief A class to interpolate pressure advance (PA) values based on flow rate and acceleration using Piecewise Cubic Hermite Interpolating Polynomial (PCHIP) interpolation. + */ +class AdaptivePAInterpolator { +public: + /** + * @brief Default constructor. + */ + AdaptivePAInterpolator() : m_isInitialised(false) {} + + /** + * @brief Parses the input data and sets up the interpolators. + * @param data A string containing the data in CSV format (PA, flow rate, acceleration). + * @return 0 on success, -1 on error. + */ + int parseAndSetData(const std::string& data); + + /** + * @brief Interpolates the PA value for the given flow rate and acceleration. + * @param flow_rate The flow rate at which to interpolate. + * @param acceleration The acceleration at which to interpolate. + * @return The interpolated PA value, or -1 if interpolation fails. + */ + double operator()(double flow_rate, double acceleration); + + /** + * @brief Returns the initialization status. + * @return The value of m_isInitialised. + */ + bool isInitialised() const { + return m_isInitialised; + } + +private: + std::map flow_interpolators_; ///< Map each acceleration to a flow-rate-to-PA interpolator. + std::vector accelerations_; ///< Store unique accelerations. + bool m_isInitialised; +}; + +#endif // ADAPTIVEPAINTERPOLATOR_HPP diff --git a/src/libslic3r/GCode/AdaptivePAProcessor.cpp b/src/libslic3r/GCode/AdaptivePAProcessor.cpp new file mode 100644 index 0000000000..3d87a660ad --- /dev/null +++ b/src/libslic3r/GCode/AdaptivePAProcessor.cpp @@ -0,0 +1,285 @@ +// AdaptivePAProcessor.cpp +// OrcaSlicer +// +// Implementation of the AdaptivePAProcessor class, responsible for processing G-code layers with adaptive pressure advance. + +#include "../GCode.hpp" +#include "AdaptivePAProcessor.hpp" +#include +#include +#include + +namespace Slic3r { + +/** + * @brief Constructor for AdaptivePAProcessor. + * + * This constructor initializes the AdaptivePAProcessor with a reference to a GCode object. + * It also initializes the configuration reference, pressure advance interpolation object, + * and regular expression patterns used for processing the G-code. + * + * @param gcodegen A reference to the GCode object that generates the G-code. + */ +AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen, const std::vector &tools_used) + : m_gcodegen(gcodegen), + m_config(gcodegen.config()), + m_last_predicted_pa(0.0), + m_max_next_feedrate(0.0), + m_next_feedrate(0.0), + m_current_feedrate(0.0), + m_last_extruder_id(-1), + m_pa_change_pattern(R"(; PA_CHANGE:T(\d+) MM3MM:([0-9]*\.[0-9]+) ACCEL:(\d+) BR:(\d+) RC:(\d+) OV:(\d+))"), + m_g1_f_pattern(R"(G1 F([0-9]+))") +{ + // Constructor body can be used for further initialization if necessary + for (unsigned int tool : tools_used) { + // Only enable model for the tool if both PA and adaptive PA options are enabled + if(m_config.adaptive_pressure_advance.get_at(tool) && m_config.enable_pressure_advance.get_at(tool)){ + auto interpolator = std::make_unique(); + // Get calibration values from extruder + std::string pa_calibration_values = m_config.adaptive_pressure_advance_model.get_at(tool); + // Setup the model and store it in the tool-interpolation model map + interpolator->parseAndSetData(pa_calibration_values); + m_AdaptivePAInterpolators[tool] = std::move(interpolator); + } + } +} + +// Method to get the interpolator for a specific tool ID +AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int tool_id) { + auto it = m_AdaptivePAInterpolators.find(tool_id); + if (it != m_AdaptivePAInterpolators.end()) { + return it->second.get(); + } + return nullptr; // Handle the case where the tool_id is not found +} + +/** + * @brief Processes a layer of G-code and applies adaptive pressure advance. + * + * This method processes the G-code for a single layer, identifying the appropriate + * pressure advance settings and applying them based on the current state and configurations. + * + * @param gcode A string containing the G-code for the layer. + * @return A string containing the processed G-code with adaptive pressure advance applied. + */ +std::string AdaptivePAProcessor::process_layer(std::string &&gcode) { + std::istringstream stream(gcode); + std::string line; + std::ostringstream output; + double mm3mm_value = 0.0; + unsigned int accel_value = 0; + std::string pa_change_line; + bool wipe_command = false; + + // Iterate through each line of the layer G-code + while (std::getline(stream, line)) { + + // If a wipe start command is found, ignore all speed changes till the wipe end part is found + if (line.find("WIPE_START") != std::string::npos) { + wipe_command = true; + } + + // Update current feed rate (this is preceding an extrude or wipe command only). Ignore any speed changes that are emitted during a wipe move. + // Travel feedrate is output as part of a G1 X Y (Z) F command + if ( (line.find("G1 F") == 0) && (!wipe_command) ) { // prune lines quickly before running pattern matching + std::size_t pos = line.find('F'); + if (pos != std::string::npos){ + m_current_feedrate = std::stod(line.substr(pos + 1)) / 60.0; // Convert from mm/min to mm/s + } + } + + // Wipe end found, continue searching for current feed rate. + if (line.find("WIPE_END") != std::string::npos) { + wipe_command = false; + } + + // Reset next feedrate to zero enable searching for the first encountered + // feedrate change command after the PA change tag. + m_next_feedrate = 0; + + // Check for PA_CHANGE pattern in the line + // We will only find this pattern for extruders where adaptive PA is enabled. + // If there is mixed extruders in the layer (i.e. with adaptive PA on and off + // this will only update the extruders where the adaptive PA is enabled + // as these are the only ones where the PA pattern is output + // For a mixed extruder layer with both adaptive PA enabled and disabled when the new tool is selected + // the PA for that material is set. As no tag below will be found for this extruder, the original PA is retained. + if (line.find("; PA_CHANGE") == 0) { // prune lines quickly before running regex check as regex is more expensive to run + if (std::regex_search(line, m_match, m_pa_change_pattern)) { + int extruder_id = std::stoi(m_match[1].str()); + mm3mm_value = std::stod(m_match[2].str()); + accel_value = std::stod(m_match[3].str()); + int isBridge = std::stoi(m_match[4].str()); + int roleChange = std::stoi(m_match[5].str()); + int isOverhang = std::stoi(m_match[6].str()); + + // Check if the extruder ID has changed + bool extruder_changed = (extruder_id != m_last_extruder_id); + m_last_extruder_id = extruder_id; + + // Save the PA_CHANGE line to output later after finding feedrate + pa_change_line = line; + + // Look ahead for feedrate before any line containing both G and E commands + std::streampos current_pos = stream.tellg(); + std::string next_line; + double temp_feed_rate = 0; + bool extrude_move_found = false; + int line_counter = 0; + + // Carry on searching on the layer gcode lines to find the print speed + // If a G1 Fxxxx pattern is found, the new speed is identified + // Carry on searching for feedrates to find the maximum print speed + // until a feature change pattern or a wipe command is detected + while (std::getline(stream, next_line)) { + line_counter++; + // Found an extrude move, set extrude move found flag and move to the next line + if ((!extrude_move_found) && next_line.find("G1 ") == 0 && + next_line.find('X') != std::string::npos && + next_line.find('Y') != std::string::npos && + next_line.find('E') != std::string::npos) { + // Pattern matched, break the loop + extrude_move_found = true; + continue; + } + + // Found a travel move after we've found at least one extrude move + // We now need to stop searching for speeds as we're done printing this island + if (next_line.find("G1 ") == 0 && + next_line.find('X') != std::string::npos && // X is present + next_line.find('Y') != std::string::npos && // Y is present + next_line.find('E') == std::string::npos && // no "E" present + extrude_move_found) { // An extrude move has happened already + // First travel move after extrude move found. Stop searching + break; + } + + // Found a WIPE command + // If we have a wipe command, usually the wipe speed is different (larger) than the max print speed + // for that feature. So stop searching if a wipe command is found because we do not want to overwrite the + // speed used for PA calculation by the Wipe speed. + if (next_line.find("WIPE") != std::string::npos) { + break; // Stop searching if wipe command is found + } + + // Found another PA_CHANGE pattern + // If RC = 1, it means we have a role change, so stop trying to find the max speed for the feature. + // This is possibly redundant as a new feature would always have a travel move preceding it + // but check anyway. However check last so to not invoke it without reason... + if (next_line.find("; PA_CHANGE") == 0) { // prune lines quickly before running pattern matching + std::size_t rc_pos = next_line.rfind("RC:"); + if (rc_pos != std::string::npos) { + int rc_value = std::stoi(next_line.substr(rc_pos + 3)); + if (rc_value == 1) { + break; // Role change found, stop searching + } + } + } + + // Found a Feedrate change command + // If the new feedrate is greater than any feedrate encountered so far after the PA change command, use that to calculate the PA value + // Also if this is the first feedrate we encounter, store it as the next feedrate. + if (next_line.find("G1 F") == 0) { // prune lines quickly before running pattern matching + std::size_t pos = next_line.find('F'); + if (pos != std::string::npos) { + double feedrate = std::stod(next_line.substr(pos + 1)) / 60.0; // Convert from mm/min to mm/s + if(line_counter==1){ // this is the first command after the PA change pattern, and hence before any extrusion has happened. Reset + // the current speed to this one + m_current_feedrate = feedrate; + } + if (temp_feed_rate < feedrate) { + temp_feed_rate = feedrate; + } + if(m_next_feedrate < EPSILON){ // This the first feedrate found after the PA Change command + m_next_feedrate = feedrate; + } + } + continue; + } + } + + // If we found a new maximum feedrate after the PA change command, use it + if (temp_feed_rate > 0) { + m_max_next_feedrate = temp_feed_rate; + } else // If we didnt find a new feedrate at all after the PA change command, use the current feedrate. + m_max_next_feedrate = m_current_feedrate; + + // Restore stream position + stream.clear(); + stream.seekg(current_pos); + + // Calculate the predicted PA using the upcomming feature maximum feedrate + // Get the interpolator for the active tool + AdaptivePAInterpolator* interpolator = getInterpolator(m_last_extruder_id); + + double predicted_pa = 0; + double adaptive_PA_speed = 0; + + if(!interpolator){ // Tool not found in the interpolator map + // Tool not found in the PA interpolator to tool map + predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0; + if(m_config.gcode_comments) output << "; APA: Tool doesnt have APA enabled\n"; + } else if (!interpolator->isInitialised() || (!m_config.adaptive_pressure_advance.get_at(m_last_extruder_id)) ) + // Check if the model is not initialised by the constructor for the active extruder + // Also check that adaptive PA is enabled for that extruder. This should not be needed + // as the PA change flag should not be set upstream (in the GCode.cpp file) if adaptive PA is disabled + // however check for robustness sake. + { + // Model failed or adaptive pressure advance not enabled - use default value from m_config + predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0; + if(m_config.gcode_comments) output << "; APA: Interpolator setup failed, using default pressure advance\n"; + } else { // Model setup succeeded + // Proceed to identify the print speed to use to calculate the adaptive PA value + if(isOverhang > 0){ // If we are in an overhang area, use the minimum between current print speed + // and any speed immediately after + // In most cases the current speed is the minimum one; + // however if slowdown for layer cooling is enabled, the overhang + // may be slowed down more than the current speed. + adaptive_PA_speed = (m_current_feedrate == 0 || m_next_feedrate == 0) ? + std::max(m_current_feedrate, m_next_feedrate) : + std::min(m_current_feedrate, m_next_feedrate); + }else{ // If this is not an overhang area, use the maximum speed from the current and + // upcomming speeds for the island. + adaptive_PA_speed = std::max(m_max_next_feedrate,m_current_feedrate); + } + + // Calculate the adaptive PA value + predicted_pa = (*interpolator)(mm3mm_value * adaptive_PA_speed, accel_value); + + // This is a bridge, use the dedicated PA setting. + if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id) > EPSILON) + predicted_pa = m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id); + + if (predicted_pa < 0) { // If extrapolation fails, fall back to the default PA for the extruder. + predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0; + if(m_config.gcode_comments) output << "; APA: Interpolation failed, using fallback pressure advance value\n"; + } + } + if(m_config.gcode_comments) { + // Output debug GCode comments + output << pa_change_line << '\n'; // Output PA change command tag + if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id) > EPSILON) + output << "; APA Model Override (bridge)\n"; + output << "; APA Current Speed: " << std::to_string(m_current_feedrate) << "\n"; + output << "; APA Next Speed: " << std::to_string(m_next_feedrate) << "\n"; + output << "; APA Max Next Speed: " << std::to_string(m_max_next_feedrate) << "\n"; + output << "; APA Speed Used: " << std::to_string(adaptive_PA_speed) << "\n"; + output << "; APA Flow rate: " << std::to_string(mm3mm_value * m_max_next_feedrate) << "\n"; + output << "; APA Prev PA: " << std::to_string(m_last_predicted_pa) << " New PA: " << std::to_string(predicted_pa) << "\n"; + } + if (extruder_changed || std::fabs(predicted_pa - m_last_predicted_pa) > EPSILON) { + output << m_gcodegen.writer().set_pressure_advance(predicted_pa); // Use m_writer to set pressure advance + m_last_predicted_pa = predicted_pa; // Update the last predicted PA value + } + } + }else { + // Output the current line as this isn't a PA change tag + output << line << '\n'; + } + } + + return output.str(); +} + +} // namespace Slic3r diff --git a/src/libslic3r/GCode/AdaptivePAProcessor.hpp b/src/libslic3r/GCode/AdaptivePAProcessor.hpp new file mode 100644 index 0000000000..b9d2ef15fb --- /dev/null +++ b/src/libslic3r/GCode/AdaptivePAProcessor.hpp @@ -0,0 +1,85 @@ +// AdaptivePAProcessor.hpp +// OrcaSlicer +// +// Header file for the AdaptivePAProcessor class, responsible for processing G-code layers for the purposes of applying adaptive pressure advance. + +#ifndef ADAPTIVEPAPROCESSOR_H +#define ADAPTIVEPAPROCESSOR_H + +#include +#include +#include +#include +#include +#include +#include "AdaptivePAInterpolator.hpp" + +namespace Slic3r { + +// Forward declaration of GCode class +class GCode; + +/** + * @brief Class for processing G-code layers with adaptive pressure advance. + */ +class AdaptivePAProcessor { +public: + /** + * @brief Constructor for AdaptivePAProcessor. + * + * This constructor initializes the AdaptivePAProcessor with a reference to a GCode object. + * It also initializes the configuration reference, pressure advance interpolation object, + * and regular expression patterns used for processing the G-code. + * + * @param gcodegen A reference to the GCode object that generates the G-code. + */ + AdaptivePAProcessor(GCode &gcodegen, const std::vector &tools_used); + + /** + * @brief Processes a layer of G-code and applies adaptive pressure advance. + * + * This method processes the G-code for a single layer, identifying the appropriate + * pressure advance settings and applying them based on the current state and configurations. + * + * @param gcode A string containing the G-code for the layer. + * @return A string containing the processed G-code with adaptive pressure advance applied. + */ + std::string process_layer(std::string &&gcode); + + /** + * @brief Manually sets adaptive PA internal value. + * + * This method manually sets the adaptive PA internally held value. + * Call this when changing tools or in any other case where the internally assumed last PA value may be incorrect + */ + void resetPreviousPA(double PA){ m_last_predicted_pa = PA; }; + +private: + GCode &m_gcodegen; ///< Reference to the GCode object. + std::unordered_map> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and tool ID's + const PrintConfig &m_config; ///< Reference to the print configuration. + double m_last_predicted_pa; ///< Last predicted pressure advance value. + double m_max_next_feedrate; ///< Maximum feed rate (speed) for the upcomming island. If no speed is found, the previous island speed is used. + double m_next_feedrate; ///< First feed rate (speed) for the upcomming island. + double m_current_feedrate; ///< Current, latest feedrate. + int m_last_extruder_id; ///< Last used extruder ID. + + std::regex m_pa_change_pattern; ///< Regular expression to detect PA_CHANGE pattern. + std::regex m_g1_f_pattern; ///< Regular expression to detect G1 F pattern. + std::smatch m_match; ///< Match results for regular expressions. + + /** + * @brief Get the PA interpolator attached to the specified tool ID. + * + * This method manually sets the adaptive PA internally held value. + * Call this when changing tools or in any other case where the internally assumed last PA value may be incorrect + * + * @param An integer with the tool ID for which the PA interpolation model is to be returned. + * @return The Adaptive PA Interpolator object corresponding to that tool. + */ + AdaptivePAInterpolator* getInterpolator(unsigned int tool_id); +}; + +} // namespace Slic3r + +#endif // ADAPTIVEPAPROCESSOR_H diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 648b570d82..69dbddfaa4 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -68,7 +68,8 @@ const std::vector GCodeProcessor::Reserved_Tags = { " MANUAL_TOOL_CHANGE ", "_DURING_PRINT_EXHAUST_FAN", " WIPE_TOWER_START", - " WIPE_TOWER_END" + " WIPE_TOWER_END", + " PA_CHANGE:" }; const std::vector GCodeProcessor::Reserved_Tags_compatible = { @@ -88,7 +89,8 @@ const std::vector GCodeProcessor::Reserved_Tags_compatible = { " MANUAL_TOOL_CHANGE ", "_DURING_PRINT_EXHAUST_FAN", " WIPE_TOWER_START", - " WIPE_TOWER_END" + " WIPE_TOWER_END", + " PA_CHANGE:" }; diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index a1fd5237e3..1a1970e924 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -291,6 +291,7 @@ namespace Slic3r { During_Print_Exhaust_Fan, Wipe_Tower_Start, Wipe_Tower_End, + PA_Change, }; static const std::string& reserved_tag(ETags tag) { return s_IsBBLPrinter ? Reserved_Tags[static_cast(tag)] : Reserved_Tags_compatible[static_cast(tag)]; } diff --git a/src/libslic3r/GCode/PchipInterpolatorHelper.cpp b/src/libslic3r/GCode/PchipInterpolatorHelper.cpp new file mode 100644 index 0000000000..44baa6cbf7 --- /dev/null +++ b/src/libslic3r/GCode/PchipInterpolatorHelper.cpp @@ -0,0 +1,100 @@ +// PchipInterpolatorHelper.cpp +// OrcaSlicer +// +// Implementation file for the PchipInterpolatorHelper class + +#include "PchipInterpolatorHelper.hpp" +#include +#include +#include + +/** + * @brief Constructs the PCHIP interpolator with given data points. + * @param x The x-coordinates of the data points. + * @param y The y-coordinates of the data points. + */ +PchipInterpolatorHelper::PchipInterpolatorHelper(const std::vector& x, const std::vector& y) { + setData(x, y); +} + +/** + * @brief Sets the data points for the interpolator. + * @param x The x-coordinates of the data points. + * @param y The y-coordinates of the data points. + * @throw std::invalid_argument if x and y have different sizes or if they contain fewer than two points. + */ +void PchipInterpolatorHelper::setData(const std::vector& x, const std::vector& y) { + if (x.size() != y.size() || x.size() < 2) { + throw std::invalid_argument("Input vectors must have the same size and contain at least two points."); + } + x_ = x; + y_ = y; + sortData(); + computePCHIP(); +} + +/** + * @brief Sorts the data points by x-coordinate. + */ +void PchipInterpolatorHelper::sortData() { + std::vector> data; + for (size_t i = 0; i < x_.size(); ++i) { + data.emplace_back(x_[i], y_[i]); + } + std::sort(data.begin(), data.end()); + + for (size_t i = 0; i < data.size(); ++i) { + x_[i] = data[i].first; + y_[i] = data[i].second; + } +} + +/** + * @brief Computes the PCHIP coefficients. + */ +void PchipInterpolatorHelper::computePCHIP() { + size_t n = x_.size() - 1; + h_.resize(n); + delta_.resize(n); + d_.resize(n+1); + + for (size_t i = 0; i < n; ++i) { + h_[i] = h(i); + delta_[i] = delta(i); + } + + d_[0] = delta_[0]; + d_[n] = delta_[n-1]; + for (size_t i = 1; i < n; ++i) { + if (delta_[i-1] * delta_[i] > 0) { + double w1 = 2 * h_[i] + h_[i-1]; + double w2 = h_[i] + 2 * h_[i-1]; + d_[i] = (w1 + w2) / (w1 / delta_[i-1] + w2 / delta_[i]); + } else { + d_[i] = 0; + } + } +} + +/** + * @brief Interpolates the value at a given point. + */ +double PchipInterpolatorHelper::interpolate(double xi) const { + if (xi <= x_.front()) return y_.front(); + if (xi >= x_.back()) return y_.back(); + + auto it = std::lower_bound(x_.begin(), x_.end(), xi); + size_t i = std::distance(x_.begin(), it) - 1; + + double h_i = h_[i]; + double t = (xi - x_[i]) / h_i; + double t2 = t * t; + double t3 = t2 * t; + + double h00 = 2 * t3 - 3 * t2 + 1; + double h10 = t3 - 2 * t2 + t; + double h01 = -2 * t3 + 3 * t2; + double h11 = t3 - t2; + + return h00 * y_[i] + h10 * h_i * d_[i] + h01 * y_[i+1] + h11 * h_i * d_[i+1]; +} diff --git a/src/libslic3r/GCode/PchipInterpolatorHelper.hpp b/src/libslic3r/GCode/PchipInterpolatorHelper.hpp new file mode 100644 index 0000000000..becfe28210 --- /dev/null +++ b/src/libslic3r/GCode/PchipInterpolatorHelper.hpp @@ -0,0 +1,76 @@ +// PchipInterpolatorHelper.hpp +// OrcaSlicer +// +// Header file for the PchipInterpolatorHelper class, responsible for performing Piecewise Cubic Hermite Interpolating Polynomial (PCHIP) interpolation on given data points. + +#ifndef PCHIPINTERPOLATORHELPER_HPP +#define PCHIPINTERPOLATORHELPER_HPP + +#include + +/** + * @class PchipInterpolatorHelper + * @brief A helper class to perform Piecewise Cubic Hermite Interpolating Polynomial (PCHIP) interpolation. + */ +class PchipInterpolatorHelper { +public: + /** + * @brief Default constructor. + */ + PchipInterpolatorHelper() = default; + + /** + * @brief Constructs the PCHIP interpolator with given data points. + * @param x The x-coordinates of the data points. + * @param y The y-coordinates of the data points. + */ + PchipInterpolatorHelper(const std::vector& x, const std::vector& y); + + /** + * @brief Sets the data points for the interpolator. + * @param x The x-coordinates of the data points. + * @param y The y-coordinates of the data points. + * @throw std::invalid_argument if x and y have different sizes or if they contain fewer than two points. + */ + void setData(const std::vector& x, const std::vector& y); + + /** + * @brief Interpolates the value at a given point. + * @param xi The x-coordinate at which to interpolate. + * @return The interpolated y-coordinate. + */ + double interpolate(double xi) const; + +private: + std::vector x_; ///< The x-coordinates of the data points. + std::vector y_; ///< The y-coordinates of the data points. + std::vector h_; ///< The differences between successive x-coordinates. + std::vector delta_; ///< The slopes of the segments between successive data points. + std::vector d_; ///< The derivatives at the data points. + + /** + * @brief Computes the PCHIP coefficients. + */ + void computePCHIP(); + + /** + * @brief Sorts the data points by x-coordinate. + */ + void sortData(); + + /** + * @brief Computes the difference between successive x-coordinates. + * @param i The index of the x-coordinate. + * @return The difference between x_[i+1] and x_[i]. + */ + double h(int i) const { return x_[i+1] - x_[i]; } + + /** + * @brief Computes the slope of the segment between successive data points. + * @param i The index of the segment. + * @return The slope of the segment between y_[i] and y_[i+1]. + */ + double delta(int i) const { return (y_[i+1] - y_[i]) / h(i); } +}; + +#endif // PCHIPINTERPOLATORHELPER_HPP diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 05c34446b6..59b0d1f9c1 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -840,7 +840,7 @@ static std::vector s_Preset_filament_options { "filament_wipe_distance", "additional_cooling_fan_speed", "nozzle_temperature_range_low", "nozzle_temperature_range_high", //SoftFever - "enable_pressure_advance", "pressure_advance","chamber_temperature", "filament_shrink", "support_material_interface_fan_speed", "filament_notes" /*,"filament_seam_gap"*/, + "enable_pressure_advance", "pressure_advance","adaptive_pressure_advance","adaptive_pressure_advance_model","adaptive_pressure_advance_overhangs", "adaptive_pressure_advance_bridges","chamber_temperature", "filament_shrink", "support_material_interface_fan_speed", "filament_notes" /*,"filament_seam_gap"*/, "filament_loading_speed", "filament_loading_speed_start", "filament_load_time", "filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ddcb4bcbf8..b43a91ae1f 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1681,6 +1681,60 @@ void PrintConfigDef::init_fff_params() def->max = 2; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats { 0.02 }); + + // Orca: Adaptive pressure advance option and calibration values + def = this->add("adaptive_pressure_advance", coBools); + def->label = L("Enable adaptive pressure advance (beta)"); + def->tooltip = L("With increasing print speeds (and hence increasing volumetric flow through the nozzle) and increasing accelerations, " + "it has been observed that the effective PA value typically decreases. " + "This means that a single PA value is not always 100% optimal for all features and a compromise value is usually used " + "that does not cause too much bulging on features with lower flow speed and accelerations while also not causing gaps on faster features.\n\n" + "This feature aims to address this limitation by modeling the response of your printer's extrusion system depending " + "on the volumetric flow speed and acceleration it is printing at. Internally, it generates a fitted model that can extrapolate the needed pressure " + "advance for any given volumetric flow speed and acceleration, which is then emmited to the printer depending on the current print conditions.\n\n" + "When enabled, the pressure advance value above is overriden. However, a reasonable default value above is " + "strongly recomended to act as a fallback and for when tool changing.\n\n"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBools{ false }); + + // Orca: Adaptive pressure advance option and calibration values + def = this->add("adaptive_pressure_advance_model", coStrings); + def->label = L("Adaptive pressure advance measurements (beta)"); + def->tooltip = L("Add sets of pressure advance (PA) values, the volumetric flow speeds and accelerations they were measured at, separated by a comma. " + "One set of values per line. For example\n" + "0.04,3.96,3000\n0.033,3.96,10000\n0.029,7.91,3000\n0.026,7.91,10000\n\n" + "How to calibrate:\n" + "1. Run the pressure advance test for at least 3 speeds per acceleration value. It is recommended that the test is run " + "for at least the speed of the external perimeters, the speed of the internal perimeters and the fastest feature " + "print speed in your profile (usually its the sparse or solid infill). Then run them for the same speeds for the slowest and fastest print accelerations," + "and no faster than the recommended maximum acceleration as given by the klipper input shaper.\n" + "2. Take note of the optimal PA value for each volumetric flow speed and acceleration. You can find the flow number by selecting " + "flow from the color scheme drop down and move the horizontal slider over the PA pattern lines. The number should be visible " + "at the bottom of the page. The ideal PA value should be decreasing the higher the volumetric flow is. If it is not, confirm that your extruder is functioning correctly." + "The slower and with less acceleration you print, the larger the range of acceptable PA values. If no difference is visible, use the PA value from the faster test." + "3. Enter the triplets of PA values, Flow and Accelerations in the text box here and save your filament profile\n\n" + ""); + def->mode = comAdvanced; + //def->gui_flags = "serialized"; + def->multiline = true; + def->full_width = true; + def->height = 15; + def->set_default_value(new ConfigOptionStrings{"0,0,0\n0,0,0"}); + + def = this->add("adaptive_pressure_advance_overhangs", coBools); + def->label = L("Enable adaptive pressure advance for overhangs (beta)"); + def->tooltip = L("Enable adaptive PA for overhangs as well as when flow changes within the same feature. This is an experimental option, " + "as if the PA profile is not set accurately, it will cause uniformity issues on the external surfaces before and after overhangs.\n"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBools{ false }); + + def = this->add("adaptive_pressure_advance_bridges", coFloats); + def->label = L("Pressure advance for bridges"); + def->tooltip = L("Pressure advance value for bridges. Set to 0 to disable. \n\n A lower PA value when printing bridges helps reduce the appearance of slight under extrusion " + "immediately after bridges. This is caused by the pressure drop in the nozzle when printing in the air and a lower PA helps counteract this."); + def->max = 2; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 0.0 }); def = this->add("line_width", coFloatOrPercent); def->label = L("Default"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 67cdfef999..9e31eb5bfb 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1031,6 +1031,12 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, filament_flow_ratio)) ((ConfigOptionBools, enable_pressure_advance)) ((ConfigOptionFloats, pressure_advance)) + // Orca: adaptive pressure advance and calibration model + ((ConfigOptionBools, adaptive_pressure_advance)) + ((ConfigOptionBools, adaptive_pressure_advance_overhangs)) + ((ConfigOptionStrings, adaptive_pressure_advance_model)) + ((ConfigOptionFloats, adaptive_pressure_advance_bridges)) + // ((ConfigOptionFloat, fan_kickstart)) ((ConfigOptionBool, fan_speedup_overhangs)) ((ConfigOptionFloat, fan_speedup_time)) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index b4f9959ebc..9fd2f823a0 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3223,11 +3223,6 @@ void TabFilament::build() optgroup->append_single_option_line("required_nozzle_HRC"); optgroup->append_single_option_line("default_filament_colour"); optgroup->append_single_option_line("filament_diameter"); - optgroup->append_single_option_line("pellet_flow_coefficient", "pellet-flow-coefficient"); - optgroup->append_single_option_line("filament_flow_ratio"); - - optgroup->append_single_option_line("enable_pressure_advance"); - optgroup->append_single_option_line("pressure_advance"); optgroup->append_single_option_line("filament_density"); optgroup->append_single_option_line("filament_shrink"); @@ -3249,6 +3244,25 @@ void TabFilament::build() on_value_change(opt_key, value); }; + // Orca: New section to focus on flow rate and PA to declutter general section + optgroup = page->new_optgroup(L("Flow ratio and Pressure Advance"), L"param_information"); + optgroup->append_single_option_line("pellet_flow_coefficient", "pellet-flow-coefficient"); + optgroup->append_single_option_line("filament_flow_ratio"); + + optgroup->append_single_option_line("enable_pressure_advance"); + optgroup->append_single_option_line("pressure_advance"); + + // Orca: adaptive pressure advance and calibration model + optgroup->append_single_option_line("adaptive_pressure_advance"); + optgroup->append_single_option_line("adaptive_pressure_advance_overhangs"); + optgroup->append_single_option_line("adaptive_pressure_advance_bridges"); + + option = optgroup->get_option("adaptive_pressure_advance_model"); + option.opt.full_width = true; + option.opt.is_code = true; + option.opt.height = 15; + optgroup->append_single_option_line(option); + // optgroup = page->new_optgroup(L("Print chamber temperature"), L"param_chamber_temp"); optgroup->append_single_option_line("chamber_temperature", "chamber-temperature"); @@ -3538,9 +3552,18 @@ void TabFilament::toggle_options() toggle_line("cool_plate_temp_initial_layer", support_multi_bed_types ); toggle_line("eng_plate_temp_initial_layer", support_multi_bed_types); toggle_line("textured_plate_temp_initial_layer", support_multi_bed_types); + + // Orca: adaptive pressure advance and calibration model + // If PA is not enabled, disable adaptive pressure advance and hide the model section + // If adaptive PA is not enabled, hide the adaptive PA model section + toggle_option("adaptive_pressure_advance", pa); + toggle_option("adaptive_pressure_advance_overhangs", pa); + bool has_adaptive_pa = m_config->opt_bool("adaptive_pressure_advance", 0); + toggle_line("adaptive_pressure_advance_overhangs", has_adaptive_pa && pa); + toggle_line("adaptive_pressure_advance_model", has_adaptive_pa && pa); + toggle_line("adaptive_pressure_advance_bridges", has_adaptive_pa && pa); bool is_pellet_printer = cfg.opt_bool("pellet_modded_printer"); - toggle_line("pellet_flow_coefficient", is_pellet_printer); toggle_line("filament_diameter", !is_pellet_printer); }