Files
OrcaSlicer/src/libslic3r/GCode/AdaptivePAProcessor.cpp
T
HanifKoh 4895bc03b4 Remove Unused Project Includes and Forward-Declare Where a Type Is Only Referenced (#16099)
* Remove Unused Project Includes and Forward-Declare Where a Type Is Only Referenced

Generated with include-what-you-use and applied conservatively. Only OrcaSlicer's own headers, the ones under src/ and tests/, are removed or forward-declared; standard-library and third-party includes are left alone. An include is removed only when both the Release and the Debug configuration leave it unused, never from inside a conditional block, and never from a file with platform-specific blocks, which only gain includes. Files whose only use of a header sits behind a feature or debug macro (libvgcode's OpenGL ES and marker code, the ARACHNE/TESTS_EXPORT_SVGS debug output) keep their includes.

clonable_ptr.hpp gains #pragma once; it had no include guard and was only safe while Config.hpp was its sole includer.

* Remove Unused Project Includes From Files With Platform-Specific Code

A Linux include-what-you-use run cannot see the code inside _WIN32, __APPLE__ or __linux__ blocks, so its verdict is only taken where nothing the removed header declares, directly or through what it includes, is named inside those blocks. Removals also have to hold in both the Release and Debug configuration and never touch a line inside a conditional block.

* Restore the libslic3r Precompiled Header and Direct Includes Lost in the Platform Pass

The platform-file pass treated pchheader.hpp as an ordinary header and
emptied it, and left GUI_Preview.hpp and 14 other files relying on
headers they no longer reached directly.

* Restore MainFrame.hpp in ParamsDialog.cpp for the Windows-Only Reparent Call

* Include Headers That Files Reached Through Ones the Cleanup Removed

* Drop Includes Duplicated by the Cleanup or by Main's Own Additions

* Leave PreciseSeam.cpp as Main Has It After the Precise Seam Rework
2026-10-05 16:47:17 +08:00

379 lines
20 KiB
C++

// AdaptivePAProcessor.cpp
// OrcaSlicer
//
// Implementation of the AdaptivePAProcessor class, responsible for processing G-code layers with adaptive pressure advance.
#include "../GCode.hpp"
#include "libslic3r/GCode/AdaptivePAInterpolator.hpp"
#include "libslic3r/libslic3r.h"
#include "AdaptivePAProcessor.hpp"
#include <memory>
#include <cstddef>
#include <regex>
#include <iosfwd>
#include <algorithm>
#include <exception>
#include <sstream>
#include <iostream>
#include <cmath>
#include <cctype>
#include <string>
#include <utility>
#include "libslic3r/Config.hpp"
#include "libslic3r/GCodeWriter.hpp"
#include "libslic3r/PrintConfig.hpp"
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)
: 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_config_index(-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]+))")
{
const size_t indices = std::max(m_config.adaptive_pressure_advance.size(), m_config.enable_pressure_advance.size());
for (size_t i = 0; i < indices && !m_enabled; ++i)
m_enabled = m_config.adaptive_pressure_advance.get_at(i) && m_config.enable_pressure_advance.get_at(i);
}
// Method to get the interpolator for a specific filament config index.
// The model is built the first time an index is requested, as the indices in use depend on
// the extruder variant each filament prints with.
AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int config_index) {
auto [it, inserted] = m_AdaptivePAInterpolators.try_emplace(config_index);
// Only enable model for the index if both PA and adaptive PA options are enabled
if (inserted && m_config.adaptive_pressure_advance.get_at(config_index) && m_config.enable_pressure_advance.get_at(config_index)) {
it->second = std::make_unique<AdaptivePAInterpolator>();
it->second->parseAndSetData(m_config.adaptive_pressure_advance_model.get_at(config_index));
}
return it->second.get();
}
/**
* @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) {
// Without PA_CHANGE tags the loop below would only terminate the layer's last line.
if (!m_enabled && gcode.find("; PA_CHANGE") == std::string::npos) {
if (!gcode.empty() && gcode.back() != '\n')
gcode += '\n';
return std::move(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)) {
// The tag carries the filament config index, which selects the PA settings of the filament's extruder variant
int config_index = 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 filament config index has changed
bool config_index_changed = (config_index != m_last_config_index);
m_last_config_index = config_index;
// 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_config_index);
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_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 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_config_index)) )
// 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_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 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_config_index) > EPSILON)
predicted_pa = m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index);
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_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 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_config_index) > 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 (config_index_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();
}
std::string AdaptivePAProcessor::validate_adaptive_pa_model(const std::string& model_str)
{
if (model_str.empty())
return {}; // Empty model is valid
std::istringstream model_stream(model_str);
std::string line;
int line_number = 0;
while (std::getline(model_stream, line)) {
++line_number;
// Trim whitespace
const auto first = line.find_first_not_of(" \t\r\n");
if (first == std::string::npos)
continue; // Skip empty lines
const auto last = line.find_last_not_of(" \t\r\n");
line = line.substr(first, last - first + 1);
// Only numbers, commas and dots are allowed (no letters or other characters)
for (char c : line) {
if (!std::isdigit(static_cast<unsigned char>(c)) && c != ',' && c != '.') {
return "Line " + std::to_string(line_number) +
": only numbers, commas and dots are allowed";
}
}
// Count commas to validate format (should be exactly 2 for 3 values)
int comma_count = 0;
for (char c : line) {
if (c == ',') comma_count++;
}
if (comma_count != 2) {
return "Line " + std::to_string(line_number) +
": must contain exactly 3 comma-separated values (PA, flow, acceleration)";
}
// Parse and validate the values
try {
std::istringstream line_stream(line);
std::string value;
// Parse PA
if (!std::getline(line_stream, value, ','))
return "Line " + std::to_string(line_number) + ": missing PA value";
double pa = std::stod(value);
// Parse flow
if (!std::getline(line_stream, value, ','))
return "Line " + std::to_string(line_number) + ": missing flow value";
double flow = std::stod(value);
// Parse acceleration
if (!std::getline(line_stream, value, ','))
return "Line " + std::to_string(line_number) + ": missing acceleration value";
double accel = std::stod(value);
// Validate constraints
if (pa >= 2.0) {
return "Line " + std::to_string(line_number) + ": PA value must be less than 2";
}
if (flow <= pa) {
return "Line " + std::to_string(line_number) + ": flow value must be greater than PA value";
}
if (accel <= flow) {
return "Line " + std::to_string(line_number) + ": acceleration value must be greater than flow value";
}
} catch (const std::exception&) {
return "Line " + std::to_string(line_number) + ": invalid numeric value";
}
}
return {}; // All validations passed
}
} // namespace Slic3r