mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-07 06:57:36 -06:00
Enhancement: Adaptive Pressure advance (#5609)
* Adaptive Pressure advance options setup * Dynamic PA - PCHIP interpolator code and tests * Integrate dynamic PA with slicing code - emit new PA values per speed change * Link adaptive PA to role change instead of speed change * Adaptive PA - Alpha 2 Reduce the frequency of requested PA changes by introducing a "state" variable. Implement user toggle for adapting PA for external walls for overhangs * Hide adaptive PA for overhangs * Convert Adaptive PA to use volumetric flow model and start preparing for converting to Gcode post processor * Converted Dynamic PA to a post processing filter. Reverted changes in GCode cpp and created tagging mechanism to allow filter to apply PA changes. * Removed adaptive PA for overhangs * Foundations for two dimensional adaptive PA based on acceleration and volumetric flow speed * Minor code cleanup and updating of tooltips * Renaming files for better clarity and generate classes documentation * Update src/libslic3r/PrintConfig.cpp Co-authored-by: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> * Update src/libslic3r/PrintConfig.cpp Co-authored-by: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> * Update src/libslic3r/PrintConfig.cpp Co-authored-by: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> * Introduce average mm3_mm over the length of a multipath for adaptive PA * Updates for multipath handling part 2 * Introduce average mm3_mm over the length of a multipath for adaptive PA * Trigger PA evaluation more frequently to catch edge cases where speed changes across islands of the same feature type. * Updates for multipath handling part 2 * Adaptive PA: Implement average flow estimation on loops * Code formatting * Fix adaptive PA not adapting for small disconnected external wall line segments. * Updated to take max print speed of upcoming feature to calculate new PA value. This is to resolve issue of incorrect PA value used when starting a new feature at an overhang. * Code clean up * Performance tuning * Further performance tuning by reducing use of regex commands in the nested loops and fix bug preventing gcode line output * Further performance tuning and tweaks to stop searching for max speed after the first travel move. * Reduce debug information * Updated debug info * Fix an issue on seams on specific models when wipe before external perimeter was enabled. Also cleanup documentation and add new to-do's * Prepare for adaptive PA for overhangs, fix wipe bug & clean up code and comments * Initial commit for adapting PA when extruding fully overhanging perimeters * Ignore wipe command when identifying current print speed * Option to evaluate adaptive PA on overhang regions in preparation for Klipper experimental option testing * Update to issue PA changes for varying flow conditions within the same feature * Fix bug where adaptive PA was enabled erroneously for role changes and ignoring user's preference. * Refactored some code * More refactoring * Some bug fixes and enabled comments only when verbose g-code is enabled * Introduced dedicated PA option for bridges * Code refactoring to optimise initialisation of PA processor (making it faster). Fix a bug where PA was not always set after a toolchange. Improve general error handling and robustness. * Updates to adaptive PA tooltips * Bridging PA check with Epsilon instead of 0. * Adaptive PA: addressing comments --------- Co-authored-by: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com>
This commit is contained in:
parent
543a1e5c37
commit
529c44d8e3
15 changed files with 1067 additions and 15 deletions
285
src/libslic3r/GCode/AdaptivePAProcessor.cpp
Normal file
285
src/libslic3r/GCode/AdaptivePAProcessor.cpp
Normal file
|
@ -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 <sstream>
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
|
||||
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<unsigned int> &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<AdaptivePAInterpolator>();
|
||||
// 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
|
Loading…
Add table
Add a link
Reference in a new issue